選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

1495 行
48 KiB

  1. #include "plcdocument.h"
  2. #include "plcitems.h"
  3. #include <QPainter>
  4. #include <QPen>
  5. #include <QBrush>
  6. #include <QMenu>
  7. #include <QAction>
  8. #include <QGraphicsSceneMouseEvent>
  9. #include <QJsonDocument>
  10. #include <QJsonObject>
  11. #include <QJsonArray>
  12. #include <QFile>
  13. #include <QFileInfo>
  14. #include <QVBoxLayout>
  15. #include <QMessageBox>
  16. #include <QInputDialog>
  17. #include <QDragEnterEvent>
  18. #include <QDragMoveEvent>
  19. #include <QCursor>
  20. #include <QDropEvent>
  21. #include <QMimeData>
  22. #include <cmath>
  23. #include <QResizeEvent>
  24. #include <QDebug>
  25. #include <QGraphicsItem>
  26. #include <QSet>
  27. #include <QMap>
  28. #include <QPointer>
  29. #include <QScrollArea>
  30. #include <QKeyEvent>
  31. PLCDocument::PLCDocument(QWidget *parent)
  32. : BaseDocument(PLC, parent)
  33. , m_rows(DEFAULT_ROWS)
  34. , m_columns(DEFAULT_COLUMNS)
  35. , m_cellSize(DEFAULT_CELL_SIZE)
  36. {
  37. m_scene = new QGraphicsScene(this);
  38. m_scene->setSceneRect(0, 0, m_columns * m_cellSize, m_rows * m_cellSize);
  39. m_view = new QGraphicsView(m_scene, this);
  40. m_view->setRenderHint(QPainter::Antialiasing);
  41. m_view->setDragMode(QGraphicsView::RubberBandDrag);
  42. m_view->setMouseTracking(true);
  43. m_view->viewport()->installEventFilter(this);
  44. m_view->setAlignment(Qt::AlignLeft | Qt::AlignTop);
  45. QScrollArea *scrollArea = new QScrollArea(this);
  46. scrollArea->setWidget(m_view);
  47. scrollArea->setWidgetResizable(true);
  48. auto layout = new QVBoxLayout(this);
  49. layout->setContentsMargins(0, 0, 0, 0);
  50. layout->addWidget(scrollArea);
  51. setLayout(layout);
  52. m_view->setAcceptDrops(true);
  53. createRealTable();
  54. connect(m_scene, &QGraphicsScene::selectionChanged, this, [this]()
  55. {
  56. auto items = m_scene->selectedItems();
  57. if (items.size() == 1 && dynamic_cast<PLCItem*>(items.first()))
  58. {
  59. clearCurrentConnection();
  60. }
  61. });
  62. connect(m_scene, &QGraphicsScene::changed, this, &PLCDocument::handleSceneChanged);
  63. }
  64. PLCDocument::~PLCDocument()
  65. {
  66. qDeleteAll(m_connections);
  67. m_copiedItem = nullptr;
  68. }
  69. QString PLCDocument::title() const {
  70. return "PLC文档";
  71. }
  72. void PLCDocument::createRealTable()
  73. {
  74. QRectF tableRect(0, 0, m_columns * m_cellSize, m_rows * m_cellSize);
  75. m_tableFrame = new QGraphicsRectItem(tableRect);
  76. m_tableFrame->setPen(QPen(Qt::black, 2));
  77. m_tableFrame->setBrush(QBrush(QColor(228, 255, 202)));
  78. m_tableFrame->setZValue(-10);
  79. m_scene->addItem(m_tableFrame);
  80. for (int col = 0; col <= m_columns; col++) {
  81. int x = col * m_cellSize;
  82. QGraphicsLineItem* line = new QGraphicsLineItem(x, 0, x, m_rows * m_cellSize);
  83. line->setPen(QPen(Qt::darkGray, 1));
  84. line->setZValue(-9);
  85. m_scene->addItem(line);
  86. m_verticalLines.append(line);
  87. }
  88. for (int row = 0; row <= m_rows; row++) {
  89. int y = row * m_cellSize;
  90. QGraphicsLineItem* line = new QGraphicsLineItem(0, y, m_columns * m_cellSize, y);
  91. line->setPen(QPen(Qt::darkGray, 1));
  92. line->setZValue(-9);
  93. m_scene->addItem(line);
  94. m_horizontalLines.append(line);
  95. }
  96. for (int row = 0; row < m_rows; row++)
  97. {
  98. const int terminalSize = 15;
  99. QPointF terminalPos(0, row * m_cellSize + m_cellSize / 2.0 - terminalSize / 2);
  100. QGraphicsEllipseItem* terminal = new QGraphicsEllipseItem(
  101. terminalPos.x(), terminalPos.y(), terminalSize, terminalSize);
  102. terminal->setFlag(QGraphicsItem::ItemIsSelectable, true);
  103. terminal->setFlag(QGraphicsItem::ItemIsFocusable, true);
  104. terminal->setAcceptHoverEvents(true);
  105. terminal->setVisible(true);
  106. m_scene->addItem(terminal);
  107. m_rowTerminals.append(terminal);
  108. m_rowTerminalMap[terminal] = row;
  109. }
  110. }
  111. QPointF PLCDocument::snapToCellCenter(const QPointF& pos) const
  112. {
  113. int col = static_cast<int>(std::floor(pos.x() / m_cellSize));
  114. int row = static_cast<int>(std::floor(pos.y() / m_cellSize));
  115. col = qBound(0, col, m_columns - 1);
  116. row = qBound(0, row, m_rows - 1);
  117. return QPointF(col * m_cellSize + m_cellSize / 2.0,
  118. row * m_cellSize + m_cellSize / 2.0);
  119. }
  120. QPointF PLCDocument::constrainToTable(const QPointF &pos) const
  121. {
  122. int col = static_cast<int>(std::floor(pos.x() / m_cellSize));
  123. int row = static_cast<int>(std::floor(pos.y() / m_cellSize));
  124. col = qBound(0, col, m_columns - 1);
  125. row = qBound(0, row, m_rows - 1);
  126. return QPointF(col * m_cellSize + m_cellSize / 2.0,
  127. row * m_cellSize + m_cellSize / 2.0);
  128. }
  129. bool PLCDocument::eventFilter(QObject *obj, QEvent *event)
  130. {
  131. if (obj == m_view->viewport())
  132. {
  133. if (event->type() == QEvent::DragEnter) {
  134. QDragEnterEvent *dragEvent = static_cast<QDragEnterEvent*>(event);
  135. if (dragEvent->mimeData()->hasText()) {
  136. dragEvent->acceptProposedAction();
  137. return true;
  138. }
  139. }
  140. else if (event->type() == QEvent::DragMove) {
  141. QDragMoveEvent *dragEvent = static_cast<QDragMoveEvent*>(event);
  142. if(dragEvent->mimeData()->hasText()) {
  143. dragEvent->acceptProposedAction();
  144. return true;
  145. }
  146. }
  147. else if (event->type() == QEvent::Drop) {
  148. QDropEvent *dropEvent = static_cast<QDropEvent*>(event);
  149. const QMimeData *mimeData = dropEvent->mimeData();
  150. if (mimeData->hasText()) {
  151. QPoint viewportPos = m_view->viewport()->mapFromParent(dropEvent->pos());
  152. QPointF scenePos = m_view->mapToScene(viewportPos);
  153. QPointF cellCenter = snapToCellCenter(scenePos);
  154. QSet<int> occupiedCells;
  155. for (QGraphicsItem* item : m_scene->items()) {
  156. if (PLCItem* plcItem = dynamic_cast<PLCItem*>(item)) {
  157. int col = static_cast<int>(plcItem->x() / m_cellSize);
  158. int row = static_cast<int>(plcItem->y() / m_cellSize);
  159. occupiedCells.insert(col * 1000 + row);
  160. }
  161. }
  162. int col = static_cast<int>(cellCenter.x() / m_cellSize);
  163. int row = static_cast<int>(cellCenter.y() / m_cellSize);
  164. int cellKey = col * 1000 + row;
  165. if (occupiedCells.contains(cellKey)) {
  166. bool found = false;
  167. for (int offset = 1; offset < m_columns; offset++) {
  168. int newCol = col + offset;
  169. if (newCol < m_columns) {
  170. int newKey = newCol * 1000 + row;
  171. if (!occupiedCells.contains(newKey)) {
  172. cellCenter = QPointF(newCol * m_cellSize + m_cellSize / 2.0,
  173. row * m_cellSize + m_cellSize / 2.0);
  174. found = true;
  175. break;
  176. }
  177. }
  178. }
  179. if (!found) {
  180. for (int offset = 1; offset < m_rows; offset++) {
  181. int newRow = row + offset;
  182. if (newRow < m_rows) {
  183. int newKey = col * 1000 + newRow;
  184. if (!occupiedCells.contains(newKey)) {
  185. cellCenter = QPointF(col * m_cellSize + m_cellSize / 2.0,
  186. newRow * m_cellSize + m_cellSize / 2.0);
  187. found = true;
  188. break;
  189. }
  190. }
  191. }
  192. }
  193. }
  194. QString toolType = mimeData->text();
  195. createPLCItem(toolType, cellCenter);
  196. m_currentTool.clear();
  197. dropEvent->acceptProposedAction();
  198. return true;
  199. }
  200. }
  201. }
  202. if (obj != m_view->viewport()) {
  203. return QWidget::eventFilter(obj, event);
  204. }
  205. if (event->type() == QEvent::MouseMove && m_tempLine) {
  206. QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
  207. QPointF scenePos = m_view->mapToScene(mouseEvent->pos());
  208. QPointF cellCenter = snapToCellCenter(scenePos);
  209. if (m_connectionSourceType != ConnectionNone) {
  210. if (m_connectionSourceType == ConnectionFromItem && m_connectionStartItem) {
  211. if (m_startTerminal == LeftTerminal) {
  212. QPointF startPos = m_connectionStartItem->leftTerminal();
  213. QLineF line(startPos, cellCenter);
  214. m_tempLine->setLine(line);
  215. } else if (m_startTerminal == RightTerminal) {
  216. QPointF startPos = m_connectionStartItem->rightTerminal();
  217. QLineF line(startPos, cellCenter);
  218. m_tempLine->setLine(line);
  219. }
  220. } else if (m_connectionSourceType == ConnectionFromRowTerminal) {
  221. QPointF startPos(0, (m_connectionStartRow + 0.5) * m_cellSize);
  222. QLineF line(startPos, cellCenter);
  223. m_tempLine->setLine(line);
  224. }
  225. }
  226. }
  227. if (event->type() == QEvent::MouseButtonPress) {
  228. QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
  229. if (mouseEvent->button() == Qt::RightButton) {
  230. createContextMenu(mouseEvent->globalPos());
  231. return true;
  232. }
  233. if (mouseEvent->button() == Qt::LeftButton) {
  234. QPointF scenePos = m_view->mapToScene(mouseEvent->pos());
  235. QGraphicsEllipseItem* clickedRowTerminal = nullptr;
  236. int rowTerminalIndex = -1;
  237. for (auto terminal : m_rowTerminals) {
  238. QPointF localPos = terminal->mapFromScene(scenePos);
  239. if (terminal->contains(localPos)) {
  240. clickedRowTerminal = terminal;
  241. rowTerminalIndex = m_rowTerminalMap[terminal];
  242. break;
  243. }
  244. }
  245. if (clickedRowTerminal) {
  246. if (m_connectionSourceType != ConnectionNone) {
  247. if (tryEndConnection(nullptr, RowTerminal)) {
  248. return true;
  249. }
  250. } else {
  251. startConnectionFromRowTerminal(rowTerminalIndex);
  252. return true;
  253. }
  254. }
  255. QPointF cellCenter = snapToCellCenter(scenePos);
  256. QGraphicsItem *clickedItem = m_scene->itemAt(cellCenter, m_view->transform());
  257. PLCItem *plcItem = dynamic_cast<PLCItem*>(clickedItem);
  258. if (!m_currentTool.isEmpty()) {
  259. QSet<int> occupiedCells;
  260. for (QGraphicsItem* item : m_scene->items()) {
  261. if (PLCItem* existingItem = dynamic_cast<PLCItem*>(item)) {
  262. int col = static_cast<int>(existingItem->x() / m_cellSize);
  263. int row = static_cast<int>(existingItem->y() / m_cellSize);
  264. occupiedCells.insert(col * 1000 + row);
  265. }
  266. }
  267. int col = static_cast<int>(cellCenter.x() / m_cellSize);
  268. int row = static_cast<int>(cellCenter.y() / m_cellSize);
  269. int cellKey = col * 1000 + row;
  270. if (!occupiedCells.contains(cellKey)) {
  271. createPLCItem(m_currentTool, cellCenter);
  272. m_currentTool.clear();
  273. return true;
  274. } else {
  275. QMessageBox::warning(this, "位置被占用", "此单元格已被占用,请选择其他位置。");
  276. return true;
  277. }
  278. }
  279. if (plcItem) {
  280. TerminalType clickedTerminal = whichTerminal(scenePos, plcItem);
  281. if (m_connectionSourceType != ConnectionNone) {
  282. if (clickedTerminal != NoTerminal) {
  283. if (tryEndConnection(plcItem, clickedTerminal)) {
  284. return true;
  285. }
  286. }
  287. } else {
  288. if (clickedTerminal != NoTerminal && !isTerminalUsed(plcItem, clickedTerminal))
  289. {
  290. startConnection(plcItem, clickedTerminal);
  291. return true;
  292. }
  293. }
  294. }
  295. }
  296. }
  297. return QWidget::eventFilter(obj, event);
  298. }
  299. void PLCDocument::startConnectionFromRowTerminal(int row)
  300. {
  301. clearCurrentConnection();
  302. m_connectionSourceType = ConnectionFromRowTerminal;
  303. m_connectionStartRow = row;
  304. m_tempLine = new QGraphicsLineItem;
  305. m_tempLine->setPen(QPen(Qt::blue, 2, Qt::DashLine));
  306. QPointF startPos(0, (row + 0.5) * m_cellSize);
  307. m_tempLine->setLine(QLineF(startPos, startPos));
  308. m_scene->addItem(m_tempLine);
  309. }
  310. PLCDocument::TerminalType PLCDocument::whichTerminal(const QPointF& scenePos, PLCItem* item) const
  311. {
  312. const qreal terminalRadius = 20.0;
  313. QPointF leftTerminal = item->leftTerminal();
  314. qreal dxLeft = scenePos.x() - leftTerminal.x();
  315. qreal dyLeft = scenePos.y() - leftTerminal.y();
  316. qreal distLeft = std::sqrt(dxLeft*dxLeft + dyLeft*dyLeft);
  317. if (distLeft <= terminalRadius) {
  318. return LeftTerminal;
  319. }
  320. QPointF rightTerminal = item->rightTerminal();
  321. qreal dxRight = scenePos.x() - rightTerminal.x();
  322. qreal dyRight = scenePos.y() - rightTerminal.y();
  323. qreal distRight = std::sqrt(dxRight*dxRight + dyRight*dyRight);
  324. if (distRight <= terminalRadius) {
  325. return RightTerminal;
  326. }
  327. return NoTerminal;
  328. }
  329. bool PLCDocument::isTerminalUsed(PLCItem* item, TerminalType terminal) const
  330. {
  331. if (item->itemType() == PLCItem::Coil && terminal == RightTerminal)
  332. {
  333. return true;
  334. }
  335. for (ConnectionLine* line : m_connections) {
  336. if (line->startItem() == item && line->startTerminal() == terminal) {
  337. return true;
  338. }
  339. if (line->endItem() == item && line->endTerminal() == terminal) {
  340. return true;
  341. }
  342. }
  343. return false;
  344. }
  345. void PLCDocument::startConnection(PLCItem *startItem, TerminalType startTerminal)
  346. {
  347. clearCurrentConnection();
  348. m_connectionSourceType = ConnectionFromItem;
  349. m_connectionStartItem = startItem;
  350. m_startTerminal = startTerminal;
  351. m_tempLine = new QGraphicsLineItem;
  352. m_tempLine->setPen(QPen(Qt::blue, 2, Qt::DashLine));
  353. QPointF startPos;
  354. if (startTerminal == LeftTerminal) {
  355. startPos = startItem->leftTerminal();
  356. } else if (startTerminal == RightTerminal) {
  357. startPos = startItem->rightTerminal();
  358. }
  359. m_tempLine->setLine(QLineF(startPos, startPos));
  360. m_scene->addItem(m_tempLine);
  361. }
  362. bool PLCDocument::tryEndConnection(PLCItem *endItem, TerminalType endTerminal)
  363. {
  364. if (m_connectionSourceType == ConnectionFromRowTerminal) {
  365. if (endTerminal != LeftTerminal) {
  366. QMessageBox::warning(this, "连线错误", "行触点只能连接到元件的左端子");
  367. clearCurrentConnection();
  368. return false;
  369. }
  370. int endRow = static_cast<int>(endItem->y() / m_cellSize);
  371. if (endRow != m_connectionStartRow) {
  372. QMessageBox::warning(this, "连线错误", "行触点只能连接到同一行元件的左端子");
  373. clearCurrentConnection();
  374. return false;
  375. }
  376. if (isTerminalUsed(endItem, LeftTerminal)) {
  377. QMessageBox::warning(this, "连线错误", "该元件的左端子已被使用");
  378. clearCurrentConnection();
  379. return false;
  380. }
  381. ConnectionLine *connection = new ConnectionLine(
  382. nullptr, RowTerminal,
  383. endItem, LeftTerminal
  384. );
  385. connection->setStartTerminalPoint(QPointF(0, (m_connectionStartRow + 0.5) * m_cellSize));
  386. connection->setRowTerminalSourceRow(m_connectionStartRow);
  387. m_scene->addItem(connection);
  388. m_connections.append(connection);
  389. terminalConnections[endItem][LeftTerminal] = true;
  390. clearCurrentConnection();
  391. setModified(true);
  392. return true;
  393. }
  394. if (endTerminal == RowTerminal) {
  395. if (m_startTerminal != LeftTerminal) {
  396. QMessageBox::warning(this, "连线错误", "行触点只能连接到元件的左端子");
  397. clearCurrentConnection();
  398. return false;
  399. }
  400. int startRow = static_cast<int>(m_connectionStartItem->y() / m_cellSize);
  401. ConnectionLine *connection = new ConnectionLine(
  402. m_connectionStartItem, m_startTerminal,
  403. nullptr, RowTerminal
  404. );
  405. connection->setEndTerminalPoint(QPointF(0, (startRow + 0.5) * m_cellSize));
  406. connection->setRowTerminalTargetRow(startRow);
  407. m_scene->addItem(connection);
  408. m_connections.append(connection);
  409. terminalConnections[m_connectionStartItem][m_startTerminal] = true;
  410. clearCurrentConnection();
  411. setModified(true);
  412. return true;
  413. }
  414. if (m_connectionSourceType == ConnectionFromItem && m_connectionStartItem) {
  415. if (endItem == m_connectionStartItem && endTerminal == m_startTerminal) {
  416. clearCurrentConnection();
  417. return false;
  418. }
  419. int startCol = static_cast<int>(m_connectionStartItem->pos().x() / m_cellSize);
  420. int endCol = static_cast<int>(endItem->pos().x() / m_cellSize);
  421. if (m_startTerminal == LeftTerminal)
  422. {
  423. if (endTerminal != RightTerminal)
  424. {
  425. QMessageBox::warning(this, "连线错误", "左端子只能连接到其他元件的右端子");
  426. clearCurrentConnection();
  427. return false;
  428. }
  429. if (startCol <= endCol)
  430. {
  431. QMessageBox::warning(this, "连线错误", "左端子只能连接到左边元件的右端子");
  432. clearCurrentConnection();
  433. return false;
  434. }
  435. } else if (m_startTerminal == RightTerminal)
  436. {
  437. if (endTerminal != LeftTerminal) {
  438. QMessageBox::warning(this, "连线错误", "右端子只能连接到其他元件的左端子");
  439. clearCurrentConnection();
  440. return false;
  441. }
  442. if (startCol >= endCol) {
  443. QMessageBox::warning(this, "连线错误", "右端子只能连接到右边元件的左端子");
  444. clearCurrentConnection();
  445. return false;
  446. }
  447. }
  448. if (isTerminalUsed(endItem, endTerminal)) {
  449. QMessageBox::warning(this, "连线错误", "该端子已被使用");
  450. clearCurrentConnection();
  451. return false;
  452. }
  453. ConnectionLine *connection = new ConnectionLine(
  454. m_connectionStartItem, m_startTerminal,
  455. endItem, endTerminal
  456. );
  457. m_scene->addItem(connection);
  458. m_connections.append(connection);
  459. terminalConnections[m_connectionStartItem][m_startTerminal] = true;
  460. terminalConnections[endItem][endTerminal] = true;
  461. clearCurrentConnection();
  462. setModified(true);
  463. return true;
  464. }
  465. return false;
  466. }
  467. void PLCDocument::clearCurrentConnection()
  468. {
  469. if (m_tempLine) {
  470. m_scene->removeItem(m_tempLine);
  471. delete m_tempLine;
  472. m_tempLine = nullptr;
  473. }
  474. m_connectionSourceType = ConnectionNone;
  475. m_connectionStartItem = nullptr;
  476. m_startTerminal = NoTerminal;
  477. m_connectionStartRow = -1;
  478. }
  479. void PLCDocument::createPLCItem(const QString &type, const QPointF &pos)
  480. {
  481. PLCItem *item = nullptr;
  482. if (type == "常开") {
  483. item = new NormallyOpenItem;
  484. } else if (type == "常闭") {
  485. item = new NormallyClosedItem;
  486. } else if (type == "大于") {
  487. item = new GreaterThanItem;
  488. } else if (type == "大于等于") {
  489. item = new GreaterEqualItem;
  490. } else if (type == "小于") {
  491. item = new LessThanItem;
  492. } else if (type == "小于等于") {
  493. item = new LessEqualItem;
  494. } else if (type == "等于") {
  495. item = new EqualItem;
  496. } else if (type == "线圈") {
  497. item = new CoilItem;
  498. }
  499. if (item) {
  500. QPointF constrainedPos = constrainToTable(pos);
  501. item->setPos(constrainedPos);
  502. m_scene->addItem(item);
  503. setModified(true);
  504. terminalConnections[item][LeftTerminal] = false;
  505. terminalConnections[item][RightTerminal] = false;
  506. m_lastValidPositions[item] = constrainedPos;
  507. connect(item, &PLCItem::stateChanged, this, [this, item](bool active) {
  508. if (m_simulationRunning) {
  509. if (active) {
  510. m_activeItems.insert(item);
  511. } else {
  512. m_activeItems.remove(item);
  513. }
  514. updateConnections();
  515. }
  516. });
  517. }
  518. }
  519. void PLCDocument::handleSceneChanged()
  520. {
  521. for (auto* connection : qAsConst(m_connections))
  522. {
  523. connection->updatePosition();
  524. }
  525. if (m_tempLine && m_connectionSourceType != ConnectionNone)
  526. {
  527. QPointF cursorPos = m_view->mapFromGlobal(QCursor::pos());
  528. QPointF scenePos = m_view->mapToScene(cursorPos.toPoint());
  529. QLineF line = m_tempLine->line();
  530. line.setP2(scenePos);
  531. m_tempLine->setLine(line);
  532. }
  533. for (QGraphicsItem *item : m_scene->items())
  534. {
  535. if (PLCItem *plcItem = dynamic_cast<PLCItem*>(item))
  536. {
  537. handleItemPositionChange(plcItem);
  538. }
  539. }
  540. }
  541. QList<PLCItem*> PLCDocument::allPLCItems() const
  542. {
  543. QList<PLCItem*> items;
  544. for (QGraphicsItem *item : m_scene->items()) {
  545. if (PLCItem *plcItem = dynamic_cast<PLCItem*>(item)) {
  546. items.append(plcItem);
  547. }
  548. }
  549. return items;
  550. }
  551. bool PLCDocument::isCellOccupied(int col, int row, PLCItem* excludeItem) const
  552. {
  553. for (PLCItem* item : allPLCItems()) {
  554. if (item == excludeItem) continue;
  555. int itemCol = static_cast<int>(item->x() / m_cellSize);
  556. int itemRow = static_cast<int>(item->y() / m_cellSize);
  557. if (itemCol == col && itemRow == row) {
  558. return true;
  559. }
  560. }
  561. return false;
  562. }
  563. // 计算元件在连线上的位置比例(0-1)
  564. qreal PLCDocument::getPositionRatioOnLine(PLCItem* item, ConnectionLine* line) const
  565. {
  566. if (!item || !line) return -1;
  567. QLineF lineSeg = line->line();
  568. QPointF itemPos = item->pos();
  569. // 计算投影比例
  570. qreal dx = lineSeg.dx();
  571. qreal dy = lineSeg.dy();
  572. qreal lengthSquared = dx*dx + dy*dy;
  573. if (lengthSquared == 0) return 0;
  574. qreal t = ((itemPos.x() - lineSeg.p1().x()) * dx +
  575. (itemPos.y() - lineSeg.p1().y()) * dy) / lengthSquared;
  576. return qBound(0.0, t, 1.0);
  577. }
  578. void PLCDocument::handleItemPositionChange(PLCItem *item)
  579. {
  580. if (!item) return;
  581. if (m_loadingFile) {
  582. QPointF newPos = constrainToTable(item->pos());
  583. item->setPos(newPos);
  584. m_lastValidPositions[item] = newPos;
  585. return;
  586. }
  587. if (m_processingItems.contains(item)) {
  588. return;
  589. }
  590. m_processingItems.insert(item);
  591. QPointF newPos = constrainToTable(item->pos());
  592. int newCol = static_cast<int>(newPos.x() / m_cellSize);
  593. int newRow = static_cast<int>(newPos.y() / m_cellSize);
  594. if (isCellOccupied(newCol, newRow, item))
  595. {
  596. if (m_lastValidPositions.contains(item))
  597. {
  598. item->setPos(m_lastValidPositions[item]);
  599. QMessageBox::warning(this, "移动失败", "无法移动,该单元格已被占用");
  600. }
  601. }
  602. else
  603. {
  604. int oldCol = static_cast<int>(m_lastValidPositions[item].x() / m_cellSize);
  605. item->setPos(newPos);
  606. m_lastValidPositions[item] = newPos;
  607. if (newCol != oldCol) {
  608. removeInvalidConnectionsForItem(item);
  609. }
  610. // 获取要拆分的连线并排序
  611. QList<ConnectionLine*> connectionsToSplit = findConnectionsUnderItem(item);
  612. // 按元件在连线上的位置排序,确保从左到右处理
  613. std::sort(connectionsToSplit.begin(), connectionsToSplit.end(),
  614. [this, item](ConnectionLine* a, ConnectionLine* b) {
  615. return getPositionRatioOnLine(item, a) < getPositionRatioOnLine(item, b);
  616. });
  617. // 处理每条连线
  618. for (ConnectionLine* line : connectionsToSplit) {
  619. if (m_connections.contains(line) && !line->isProcessing()) {
  620. splitConnection(line, item);
  621. }
  622. }
  623. // 更新所有连接位置
  624. for (ConnectionLine *line : m_connections) {
  625. if (line) {
  626. line->updatePosition();
  627. }
  628. }
  629. }
  630. m_processingItems.remove(item);
  631. }
  632. //QList<ConnectionLine*> PLCDocument::findConnectionsUnderItem(PLCItem* item) const
  633. //{
  634. // QList<ConnectionLine*> result;
  635. // if (!item) return result;
  636. // QRectF itemRect = item->boundingRect();
  637. // itemRect.adjust(-2, -2, 2, 2);
  638. // QPolygonF itemPolygon = QPolygonF(itemRect).translated(item->pos());
  639. // foreach (ConnectionLine* line, m_connections) {
  640. // if (!line || line->isProcessing()) continue;
  641. // QLineF lineGeometry = line->line();
  642. // QRectF lineRect(lineGeometry.p1(), lineGeometry.p2());
  643. // lineRect.adjust(-10, -10, 10, 10);
  644. // if (itemPolygon.intersects(lineRect)) {
  645. // result.append(line);
  646. // }
  647. // }
  648. // return result;
  649. //}
  650. // 在PLCDocument类的splitConnection函数中,修改逻辑以支持行触点连线的插入
  651. bool PLCDocument::splitConnection(ConnectionLine* originalLine, PLCItem* insertItem)
  652. {
  653. if (!originalLine || !insertItem || originalLine->isProcessing()) {
  654. return false;
  655. }
  656. originalLine->setProcessing(true);
  657. PLCItem* originalStart = originalLine->startItem();
  658. TerminalType originalStartTerminal = originalLine->startTerminal();
  659. PLCItem* originalEnd = originalLine->endItem();
  660. TerminalType originalEndTerminal = originalLine->endTerminal();
  661. // 保存原始行触点信息
  662. int sourceRow = originalLine->rowTerminalSourceRow();
  663. int targetRow = originalLine->rowTerminalTargetRow();
  664. PLCItem* logicalInput = originalStart;
  665. TerminalType inputTerminal = originalStartTerminal;
  666. PLCItem* logicalOutput = originalEnd;
  667. TerminalType outputTerminal = originalEndTerminal;
  668. // 标准化方向时同步交换行触点信息
  669. if ((originalStartTerminal == LeftTerminal && originalEndTerminal == RightTerminal) ||
  670. (originalStartTerminal == LeftTerminal && originalEndTerminal == RowTerminal))
  671. {
  672. std::swap(logicalInput, logicalOutput);
  673. std::swap(inputTerminal, outputTerminal);
  674. std::swap(sourceRow, targetRow); // 同步交换行号
  675. }
  676. // 位置有效性校验
  677. int inputCol = (inputTerminal == RowTerminal) ? 0 :
  678. static_cast<int>(logicalInput->x() / m_cellSize);
  679. int outputCol = (outputTerminal == RowTerminal) ? 0 :
  680. static_cast<int>(logicalOutput->x() / m_cellSize);
  681. int insertCol = static_cast<int>(insertItem->x() / m_cellSize);
  682. // 统一位置验证逻辑
  683. bool isBetween = (insertCol > qMin(inputCol, outputCol)) &&
  684. (insertCol < qMax(inputCol, outputCol));
  685. if (!isBetween) {
  686. originalLine->setProcessing(false);
  687. return false;
  688. }
  689. // 检查端子可用性
  690. if (isTerminalUsed(insertItem, LeftTerminal) ||
  691. isTerminalUsed(insertItem, RightTerminal))
  692. {
  693. originalLine->setProcessing(false);
  694. return false;
  695. }
  696. // 创建新连线
  697. ConnectionLine* line1 = new ConnectionLine(
  698. logicalInput, inputTerminal,
  699. insertItem, LeftTerminal
  700. );
  701. // 保留行触点源信息
  702. if (inputTerminal == RowTerminal) {
  703. line1->setStartTerminalPoint(QPointF(0, (sourceRow + 0.5) * m_cellSize));
  704. line1->setRowTerminalSourceRow(sourceRow);
  705. }
  706. ConnectionLine* line2 = new ConnectionLine(
  707. insertItem, RightTerminal,
  708. logicalOutput, outputTerminal
  709. );
  710. // 保留行触点目标信息
  711. if (outputTerminal == RowTerminal) {
  712. line2->setEndTerminalPoint(QPointF(0, (targetRow + 0.5) * m_cellSize));
  713. line2->setRowTerminalTargetRow(targetRow);
  714. }
  715. // 添加新连线
  716. m_scene->addItem(line1);
  717. m_scene->addItem(line2);
  718. m_connections.append(line1);
  719. m_connections.append(line2);
  720. // 更新端子状态
  721. terminalConnections[insertItem][LeftTerminal] = true;
  722. terminalConnections[insertItem][RightTerminal] = true;
  723. // 移除原连线
  724. m_connections.removeAll(originalLine);
  725. m_scene->removeItem(originalLine);
  726. delete originalLine;
  727. setModified(true);
  728. originalLine->setProcessing(false);
  729. return true;
  730. }
  731. // 补充:确保行触点连线能被检测为"需要拆分的连线"(扩展findConnectionsUnderItem)
  732. QList<ConnectionLine*> PLCDocument::findConnectionsUnderItem(PLCItem* item) const
  733. {
  734. QList<ConnectionLine*> result;
  735. if (!item) return result;
  736. // 元件的边界框(扩大检测范围,确保行触点连线也能被检测)
  737. QRectF itemRect = item->boundingRect();
  738. itemRect.adjust(-5, -5, 5, 5); // 扩大边界框,提高检测灵敏度
  739. QPolygonF itemPolygon = QPolygonF(itemRect).translated(item->pos());
  740. foreach (ConnectionLine* line, m_connections) {
  741. if (!line || line->isProcessing()) continue;
  742. // 行触点连线的特殊处理:起点是(0, row),终点是元件左端子
  743. QLineF lineGeometry = line->line();
  744. QRectF lineRect = QRectF(lineGeometry.p1(), lineGeometry.p2()).normalized();
  745. lineRect.adjust(-10, -10, 10, 10); // 扩大连线的检测范围
  746. // 检查元件是否与连线相交(包括行触点连线)
  747. if (itemPolygon.intersects(lineRect)) {
  748. result.append(line);
  749. }
  750. }
  751. return result;
  752. }
  753. void PLCDocument::removeInvalidConnectionsForItem(PLCItem* movedItem)
  754. {
  755. if (!movedItem || m_connections.isEmpty()) return;
  756. if (m_cellSize <= 0) {
  757. qWarning() << "Invalid cell size!";
  758. return;
  759. }
  760. int movedCol = static_cast<int>(movedItem->x() / m_cellSize);
  761. QSet<ConnectionLine*> connectionsToRemove;
  762. bool shouldRemoveAll = false;
  763. for (ConnectionLine* line : m_connections) {
  764. if (!line) continue;
  765. PLCItem* otherItem = nullptr;
  766. TerminalType movedTerminal = NoTerminal;
  767. TerminalType otherTerminal = NoTerminal;
  768. if (line->startItem() == movedItem) {
  769. otherItem = line->endItem();
  770. movedTerminal = line->startTerminal();
  771. otherTerminal = line->endTerminal();
  772. } else if (line->endItem() == movedItem) {
  773. otherItem = line->startItem();
  774. movedTerminal = line->endTerminal();
  775. otherTerminal = line->startTerminal();
  776. } else {
  777. continue;
  778. }
  779. if (!otherItem) continue;
  780. int otherCol = static_cast<int>(otherItem->x() / m_cellSize);
  781. if (movedTerminal == LeftTerminal && otherTerminal == RightTerminal) {
  782. if (movedCol <= otherCol) {
  783. shouldRemoveAll = true;
  784. break;
  785. }
  786. } else if (movedTerminal == RightTerminal && otherTerminal == LeftTerminal) {
  787. if (movedCol >= otherCol) {
  788. shouldRemoveAll = true;
  789. break;
  790. }
  791. }
  792. }
  793. if (shouldRemoveAll) {
  794. for (ConnectionLine* line : m_connections) {
  795. if (!line) continue;
  796. if (line->startItem() == movedItem || line->endItem() == movedItem) {
  797. connectionsToRemove.insert(line);
  798. }
  799. }
  800. }
  801. for (ConnectionLine* line : connectionsToRemove) {
  802. if (!line) continue;
  803. if (line->startItem()) {
  804. terminalConnections[line->startItem()].remove(line->startTerminal());
  805. }
  806. if (line->endItem()) {
  807. terminalConnections[line->endItem()].remove(line->endTerminal());
  808. }
  809. if (m_scene && line->scene() == m_scene) {
  810. m_scene->removeItem(line);
  811. }
  812. if (m_connections.contains(line)) {
  813. m_connections.removeAll(line);
  814. }
  815. delete line;
  816. }
  817. if (!connectionsToRemove.isEmpty()) {
  818. setModified(true);
  819. }
  820. }
  821. void PLCDocument::setSimulationRunning(bool running)
  822. {
  823. m_simulationRunning = running;
  824. if (!running) {
  825. resetSimulation();
  826. }
  827. }
  828. void PLCDocument::resetSimulation()
  829. {
  830. m_activeItems.clear();
  831. for (QGraphicsItem *item : m_scene->items()) {
  832. if (PLCItem *plcItem = dynamic_cast<PLCItem*>(item)) {
  833. plcItem->setState(false);
  834. }
  835. }
  836. terminalConnections.clear();
  837. updateConnections();
  838. }
  839. void PLCDocument::updateConnections()
  840. {
  841. for (ConnectionLine *line : m_connections) {
  842. PLCItem *start = line->startItem();
  843. PLCItem *end = line->endItem();
  844. if (line->endTerminal() == RowTerminal || line->startTerminal() == RowTerminal) {
  845. line->setPen(QPen(Qt::green, 2));
  846. continue;
  847. }
  848. if (m_activeItems.contains(start) && m_activeItems.contains(end)) {
  849. line->setPen(QPen(Qt::green, 2));
  850. } else {
  851. line->setPen(QPen(Qt::black, 1));
  852. }
  853. }
  854. }
  855. void PLCDocument::deleteSelectedItems()
  856. {
  857. QList<QGraphicsItem*> selected = m_scene->selectedItems();
  858. if (selected.isEmpty()) return;
  859. QList<PLCItem*> itemsToDelete;
  860. QList<ConnectionLine*> linesToDelete;
  861. QSet<ConnectionLine*> connectionsToDelete;
  862. for (QGraphicsItem* item : selected) {
  863. if (PLCItem* plcItem = dynamic_cast<PLCItem*>(item)) {
  864. itemsToDelete.append(plcItem);
  865. } else if (ConnectionLine* line = dynamic_cast<ConnectionLine*>(item)) {
  866. linesToDelete.append(line);
  867. }
  868. }
  869. for (ConnectionLine* line : m_connections) {
  870. if (line->startItem() && itemsToDelete.contains(line->startItem())) {
  871. connectionsToDelete.insert(line);
  872. }
  873. if (line->endItem() && itemsToDelete.contains(line->endItem())) {
  874. connectionsToDelete.insert(line);
  875. }
  876. }
  877. for (ConnectionLine* line : linesToDelete) {
  878. connectionsToDelete.insert(line);
  879. }
  880. for (ConnectionLine* line : connectionsToDelete) {
  881. if (!line) continue;
  882. if (line->startItem()) {
  883. terminalConnections[line->startItem()].remove(line->startTerminal());
  884. }
  885. if (line->endItem()) {
  886. terminalConnections[line->endItem()].remove(line->endTerminal());
  887. }
  888. if (m_scene && m_scene->items().contains(line)) {
  889. m_scene->removeItem(line);
  890. }
  891. m_connections.removeAll(line);
  892. delete line;
  893. }
  894. for (PLCItem* item : itemsToDelete) {
  895. terminalConnections.remove(item);
  896. m_lastValidPositions.remove(item);
  897. m_scene->removeItem(item);
  898. delete item;
  899. }
  900. setModified(true);
  901. }
  902. void PLCDocument::copySelectedItem()
  903. {
  904. QList<QGraphicsItem*> selected = m_scene->selectedItems();
  905. if (selected.size() != 1) return;
  906. if (PLCItem* item = dynamic_cast<PLCItem*>(selected.first())) {
  907. m_copiedItem = item;
  908. }
  909. }
  910. void PLCDocument::pasteItem()
  911. {
  912. if (!m_copiedItem) return;
  913. QPointF pastePos = snapToCellCenter(m_view->mapToScene(m_view->viewport()->mapFromGlobal(QCursor::pos())));
  914. pastePos.setX(pastePos.x() - 70);
  915. pastePos.setY(pastePos.y() - 70);
  916. int col = static_cast<int>(pastePos.x() / m_cellSize);
  917. int row = static_cast<int>(pastePos.y() / m_cellSize);
  918. if (isCellOccupied(col, row, nullptr))
  919. {
  920. QMessageBox::warning(this, "粘贴失败", "目标单元格已被占用");
  921. return;
  922. }
  923. PLCItem* newItem = createItemByType(m_copiedItem->itemType());
  924. if (!newItem) return;
  925. newItem->setPos(pastePos);
  926. newItem->setName(m_copiedItem->name() + "_副本");
  927. m_scene->addItem(newItem);
  928. terminalConnections[newItem][LeftTerminal] = false;
  929. terminalConnections[newItem][RightTerminal] = false;
  930. m_lastValidPositions[newItem] = pastePos;
  931. setModified(true);
  932. }
  933. void PLCDocument::keyPressEvent(QKeyEvent* event)
  934. {
  935. if (event->key() == Qt::Key_Delete)
  936. {
  937. deleteSelectedItems();
  938. event->accept();
  939. return;
  940. }
  941. BaseDocument::keyPressEvent(event);
  942. }
  943. void PLCDocument::createContextMenu(QPoint globalPos)
  944. {
  945. QMenu menu(this);
  946. QAction* deleteAction = menu.addAction("删除");
  947. QAction* copyAction = menu.addAction("复制");
  948. QAction* pasteAction = menu.addAction("粘贴");
  949. QList<QGraphicsItem*> selected = m_scene->selectedItems();
  950. deleteAction->setEnabled(!selected.isEmpty());
  951. copyAction->setEnabled(selected.size() == 1&&dynamic_cast<PLCItem*>(selected.first()));
  952. pasteAction->setEnabled(m_copiedItem != nullptr);
  953. connect(deleteAction, &QAction::triggered, this, &PLCDocument::deleteSelectedItems);
  954. connect(copyAction, &QAction::triggered, this, &PLCDocument::copySelectedItem);
  955. connect(pasteAction, &QAction::triggered, this, &PLCDocument::pasteItem);
  956. menu.addSeparator();
  957. QAction *tableGridAction = menu.addAction("隐藏/显示网格");
  958. connect(tableGridAction, &QAction::triggered, this, [this]() {
  959. for (auto line : m_horizontalLines)
  960. {
  961. line->setVisible(!line->isVisible());
  962. }
  963. for (auto line : m_verticalLines)
  964. {
  965. line->setVisible(!line->isVisible());
  966. }
  967. });
  968. menu.exec(globalPos + QPoint(10, 10));
  969. }
  970. void PLCDocument::onRowTerminalConnection()
  971. {
  972. QList<QGraphicsItem*> selected = m_scene->selectedItems();
  973. if (selected.size() != 1) return;
  974. PLCItem* item = dynamic_cast<PLCItem*>(selected.first());
  975. if (!item) return;
  976. int itemRow = static_cast<int>(item->y() / m_cellSize);
  977. int itemCol = static_cast<int>(item->x() / m_cellSize);
  978. if (itemCol != 0) {
  979. QMessageBox::warning(this, "连接错误", "只有最左侧列的元件可以连接到行触点");
  980. return;
  981. }
  982. if (isTerminalUsed(item, LeftTerminal)) {
  983. QMessageBox::warning(this, "连接失败", "该元件的左端子已被使用");
  984. return;
  985. }
  986. ConnectionLine* connection = new ConnectionLine(
  987. item, LeftTerminal,
  988. nullptr, RowTerminal
  989. );
  990. connection->setRowTerminalTargetRow(itemRow);
  991. connection->setEndTerminalPoint(QPointF(0, (itemRow + 0.5) * m_cellSize));
  992. connection->updatePosition();
  993. m_scene->addItem(connection);
  994. m_connections.append(connection);
  995. terminalConnections[item][LeftTerminal] = true;
  996. setModified(true);
  997. }
  998. PLCItem* PLCDocument::createItemByType(PLCItem::ItemType type)
  999. {
  1000. switch (type) {
  1001. case PLCItem::NormallyOpen: return new NormallyOpenItem;
  1002. case PLCItem::NormallyClosed: return new NormallyClosedItem;
  1003. case PLCItem::GreaterThan: return new GreaterThanItem;
  1004. case PLCItem::GreaterEqual: return new GreaterEqualItem;
  1005. case PLCItem::LessThan: return new LessThanItem;
  1006. case PLCItem::LessEqual: return new LessEqualItem;
  1007. case PLCItem::Equal: return new EqualItem;
  1008. case PLCItem::Coil: return new CoilItem;
  1009. default:
  1010. qWarning() << "未知的PLC元件类型:" << type;
  1011. return nullptr;
  1012. }
  1013. }
  1014. bool PLCDocument::saveToFile(const QString &filePath)
  1015. {
  1016. QFile file(filePath);
  1017. if (!file.open(QIODevice::WriteOnly)) {
  1018. QMessageBox::warning(this, "保存失败", "无法打开文件进行写入:" + filePath);
  1019. return false;
  1020. }
  1021. QJsonObject docObject;
  1022. docObject["type"] = "PLCDocument";
  1023. docObject["table_rows"] = m_rows;
  1024. docObject["table_columns"] = m_columns;
  1025. docObject["table_cell_size"] = m_cellSize;
  1026. QJsonArray itemsArray;
  1027. for (QGraphicsItem *item : m_scene->items()) {
  1028. if (PLCItem *plcItem = dynamic_cast<PLCItem*>(item)) {
  1029. QJsonObject itemObj;
  1030. itemObj["type"] = static_cast<int>(plcItem->itemType());
  1031. int col = static_cast<int>(plcItem->x() / m_cellSize);
  1032. int row = static_cast<int>(plcItem->y() / m_cellSize);
  1033. itemObj["col"] = col;
  1034. itemObj["row"] = row;
  1035. itemObj["name"] = plcItem->name();
  1036. itemObj["active"] = plcItem->isActive();
  1037. itemsArray.append(itemObj);
  1038. }
  1039. }
  1040. docObject["items"] = itemsArray;
  1041. QJsonArray connectionsArray;
  1042. for (ConnectionLine *line : m_connections) {
  1043. QJsonObject connObj;
  1044. if (line->startItem()) {
  1045. int startCol = static_cast<int>(line->startItem()->pos().x() / m_cellSize);
  1046. int startRow = static_cast<int>(line->startItem()->pos().y() / m_cellSize);
  1047. connObj["startCol"] = startCol;
  1048. connObj["startRow"] = startRow;
  1049. } else if (line->startTerminal() == RowTerminal) {
  1050. connObj["startRowTerminal"] = line->rowTerminalSourceRow();
  1051. }
  1052. connObj["startTerminal"] = static_cast<int>(line->startTerminal());
  1053. connObj["endTerminal"] = static_cast<int>(line->endTerminal());
  1054. if (line->endTerminal() == RowTerminal) {
  1055. connObj["endRowTerminal"] = line->rowTerminalTargetRow();
  1056. } else if (line->endItem()) {
  1057. int endCol = static_cast<int>(line->endItem()->pos().x() / m_cellSize);
  1058. int endRow = static_cast<int>(line->endItem()->pos().y() / m_cellSize);
  1059. connObj["endCol"] = endCol;
  1060. connObj["endRow"] = endRow;
  1061. }
  1062. connectionsArray.append(connObj);
  1063. }
  1064. docObject["connections"] = connectionsArray;
  1065. QJsonDocument doc(docObject);
  1066. file.write(doc.toJson());
  1067. file.close();
  1068. setFilePath(filePath);
  1069. setModified(false);
  1070. return true;
  1071. }
  1072. bool PLCDocument::loadFromFile(const QString &filePath)
  1073. {
  1074. QFile file(filePath);
  1075. if (!file.open(QIODevice::ReadOnly)) {
  1076. QMessageBox::warning(this, "打开失败", "无法打开文件:" + filePath);
  1077. return false;
  1078. }
  1079. QByteArray data = file.readAll();
  1080. file.close();
  1081. QJsonParseError parseError;
  1082. QJsonDocument doc = QJsonDocument::fromJson(data, &parseError);
  1083. if (parseError.error != QJsonParseError::NoError) {
  1084. QMessageBox::warning(this, "文件格式错误", "JSON解析错误: " + parseError.errorString());
  1085. return false;
  1086. }
  1087. if (doc.isNull() || !doc.isObject()) {
  1088. QMessageBox::warning(this, "文件格式错误", "不是有效的PLC文档");
  1089. return false;
  1090. }
  1091. QJsonObject docObject = doc.object();
  1092. if (docObject["type"].toString() != "PLCDocument") {
  1093. QMessageBox::warning(this, "文件类型错误", "不是有效的PLC文档");
  1094. return false;
  1095. }
  1096. m_rows = docObject["table_rows"].toInt(DEFAULT_ROWS);
  1097. m_columns = docObject["table_columns"].toInt(DEFAULT_COLUMNS);
  1098. m_cellSize = docObject["table_cell_size"].toInt(DEFAULT_CELL_SIZE);
  1099. setSimulationRunning(false);
  1100. m_scene->blockSignals(true);
  1101. for (auto conn : m_connections) {
  1102. m_scene->removeItem(conn);
  1103. delete conn;
  1104. }
  1105. m_connections.clear();
  1106. QList<QGraphicsItem*> allItems = m_scene->items();
  1107. for (QGraphicsItem* item : allItems) {
  1108. if (dynamic_cast<PLCItem*>(item) || dynamic_cast<ConnectionLine*>(item)) {
  1109. m_scene->removeItem(item);
  1110. delete item;
  1111. }
  1112. }
  1113. m_activeItems.clear();
  1114. terminalConnections.clear();
  1115. clearCurrentConnection();
  1116. createRealTable();
  1117. QMap<QPair<int, int>, PLCItem*> itemMap;
  1118. QJsonArray itemsArray = docObject["items"].toArray();
  1119. for (const QJsonValue &itemValue : itemsArray) {
  1120. QJsonObject itemObj = itemValue.toObject();
  1121. PLCItem::ItemType type = static_cast<PLCItem::ItemType>(itemObj["type"].toInt());
  1122. int col = itemObj["col"].toInt();
  1123. int row = itemObj["row"].toInt();
  1124. QString name = itemObj["name"].toString();
  1125. bool active = itemObj["active"].toBool();
  1126. col = qBound(0, col, m_columns - 1);
  1127. row = qBound(0, row, m_rows - 1);
  1128. PLCItem *item = createItemByType(type);
  1129. if (item) {
  1130. QPointF pos(col * m_cellSize + m_cellSize / 2.0,
  1131. row * m_cellSize + m_cellSize / 2.0);
  1132. item->setPos(pos);
  1133. item->setName(name);
  1134. item->setState(active);
  1135. m_scene->addItem(item);
  1136. itemMap.insert(qMakePair(col, row), item);
  1137. if (active) {
  1138. m_activeItems.insert(item);
  1139. }
  1140. terminalConnections[item][LeftTerminal] = false;
  1141. terminalConnections[item][RightTerminal] = false;
  1142. connect(item, &PLCItem::stateChanged, this, [this, item](bool active) {
  1143. if (m_simulationRunning) {
  1144. if (active) m_activeItems.insert(item);
  1145. else m_activeItems.remove(item);
  1146. updateConnections();
  1147. }
  1148. });
  1149. }
  1150. }
  1151. QJsonArray connectionsArray = docObject["connections"].toArray();
  1152. for (const QJsonValue &connValue : connectionsArray) {
  1153. QJsonObject connObj = connValue.toObject();
  1154. TerminalType startTerminal = static_cast<TerminalType>(connObj["startTerminal"].toInt());
  1155. TerminalType endTerminal = static_cast<TerminalType>(connObj["endTerminal"].toInt());
  1156. PLCItem *startItem = nullptr;
  1157. PLCItem *endItem = nullptr;
  1158. if (connObj.contains("startRowTerminal")) {
  1159. int startRow = connObj["startRowTerminal"].toInt();
  1160. ConnectionLine *line = new ConnectionLine(
  1161. nullptr, RowTerminal,
  1162. endItem, endTerminal
  1163. );
  1164. line->setStartTerminalPoint(QPointF(0, (startRow + 0.5) * m_cellSize));
  1165. line->setRowTerminalSourceRow(startRow);
  1166. m_scene->addItem(line);
  1167. m_connections.append(line);
  1168. }
  1169. else if (connObj.contains("startCol") && connObj.contains("startRow")) {
  1170. int startCol = connObj["startCol"].toInt();
  1171. int startRow = connObj["startRow"].toInt();
  1172. startItem = itemMap.value(qMakePair(startCol, startRow));
  1173. if (endTerminal == RowTerminal && connObj.contains("endRowTerminal")) {
  1174. int endRow = connObj["endRowTerminal"].toInt();
  1175. ConnectionLine *line = new ConnectionLine(
  1176. startItem, startTerminal,
  1177. nullptr, RowTerminal
  1178. );
  1179. line->setEndTerminalPoint(QPointF(0, (endRow + 0.5) * m_cellSize));
  1180. line->setRowTerminalTargetRow(endRow);
  1181. m_scene->addItem(line);
  1182. m_connections.append(line);
  1183. }
  1184. else if (connObj.contains("endCol") && connObj.contains("endRow")) {
  1185. int endCol = connObj["endCol"].toInt();
  1186. int endRow = connObj["endRow"].toInt();
  1187. endItem = itemMap.value(qMakePair(endCol, endRow));
  1188. if (startItem && endItem) {
  1189. ConnectionLine *line = new ConnectionLine(
  1190. startItem, startTerminal,
  1191. endItem, endTerminal
  1192. );
  1193. m_scene->addItem(line);
  1194. m_connections.append(line);
  1195. }
  1196. }
  1197. }
  1198. }
  1199. for (ConnectionLine* line : m_connections) {
  1200. if (line->startItem()) {
  1201. terminalConnections[line->startItem()][line->startTerminal()] = true;
  1202. }
  1203. if (line->endItem()) {
  1204. terminalConnections[line->endItem()][line->endTerminal()] = true;
  1205. }
  1206. }
  1207. m_scene->blockSignals(false);
  1208. handleSceneChanged();
  1209. setFilePath(filePath);
  1210. setModified(false);
  1211. return true;
  1212. }
  1213. ConnectionLine::ConnectionLine(
  1214. PLCItem *startItem, PLCDocument::TerminalType startTerminal,
  1215. PLCItem *endItem, PLCDocument::TerminalType endTerminal,
  1216. QGraphicsItem *parent
  1217. ) : QGraphicsLineItem(parent)
  1218. , m_startItem(startItem)
  1219. , m_startTerminal(startTerminal)
  1220. , m_endItem(endItem)
  1221. , m_endTerminal(endTerminal)
  1222. , m_rowTerminalTargetRow(-1)
  1223. , m_rowTerminalSourceRow(-1)
  1224. , m_processing(false)
  1225. {
  1226. setFlag(QGraphicsItem::ItemIsSelectable, true);
  1227. setPen(QPen(Qt::black, 1));
  1228. setZValue(-1);
  1229. updatePosition();
  1230. }
  1231. void ConnectionLine::updatePosition()
  1232. {
  1233. QPointF startPoint;
  1234. if (m_startItem) {
  1235. if (m_startTerminal == PLCDocument::LeftTerminal) {
  1236. startPoint = m_startItem->leftTerminal();
  1237. } else if (m_startTerminal == PLCDocument::RightTerminal) {
  1238. startPoint = m_startItem->rightTerminal();
  1239. }
  1240. } else if (!m_startTerminalPoint.isNull()) {
  1241. startPoint = m_startTerminalPoint;
  1242. }
  1243. QPointF endPoint;
  1244. if (m_endItem) {
  1245. if (m_endTerminal == PLCDocument::LeftTerminal) {
  1246. endPoint = m_endItem->leftTerminal();
  1247. } else if (m_endTerminal == PLCDocument::RightTerminal) {
  1248. endPoint = m_endItem->rightTerminal();
  1249. }
  1250. } else if (!m_endTerminalPoint.isNull()) {
  1251. endPoint = m_endTerminalPoint;
  1252. }
  1253. if (!startPoint.isNull() && !endPoint.isNull()) {
  1254. setLine(QLineF(startPoint, endPoint));
  1255. }
  1256. }