You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1492 lines
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(245, 245, 245)));
  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. if (endTerminal != RightTerminal) {
  423. QMessageBox::warning(this, "连线错误", "左端子只能连接到其他元件的右端子");
  424. clearCurrentConnection();
  425. return false;
  426. }
  427. if (startCol <= endCol) {
  428. QMessageBox::warning(this, "连线错误", "左端子只能连接到左边元件的右端子");
  429. clearCurrentConnection();
  430. return false;
  431. }
  432. } else if (m_startTerminal == RightTerminal) {
  433. if (endTerminal != LeftTerminal) {
  434. QMessageBox::warning(this, "连线错误", "右端子只能连接到其他元件的左端子");
  435. clearCurrentConnection();
  436. return false;
  437. }
  438. if (startCol >= endCol) {
  439. QMessageBox::warning(this, "连线错误", "右端子只能连接到右边元件的左端子");
  440. clearCurrentConnection();
  441. return false;
  442. }
  443. }
  444. if (isTerminalUsed(endItem, endTerminal)) {
  445. QMessageBox::warning(this, "连线错误", "该端子已被使用");
  446. clearCurrentConnection();
  447. return false;
  448. }
  449. ConnectionLine *connection = new ConnectionLine(
  450. m_connectionStartItem, m_startTerminal,
  451. endItem, endTerminal
  452. );
  453. m_scene->addItem(connection);
  454. m_connections.append(connection);
  455. terminalConnections[m_connectionStartItem][m_startTerminal] = true;
  456. terminalConnections[endItem][endTerminal] = true;
  457. clearCurrentConnection();
  458. setModified(true);
  459. return true;
  460. }
  461. return false;
  462. }
  463. void PLCDocument::clearCurrentConnection()
  464. {
  465. if (m_tempLine) {
  466. m_scene->removeItem(m_tempLine);
  467. delete m_tempLine;
  468. m_tempLine = nullptr;
  469. }
  470. m_connectionSourceType = ConnectionNone;
  471. m_connectionStartItem = nullptr;
  472. m_startTerminal = NoTerminal;
  473. m_connectionStartRow = -1;
  474. }
  475. void PLCDocument::createPLCItem(const QString &type, const QPointF &pos)
  476. {
  477. PLCItem *item = nullptr;
  478. if (type == "常开") {
  479. item = new NormallyOpenItem;
  480. } else if (type == "常闭") {
  481. item = new NormallyClosedItem;
  482. } else if (type == "大于") {
  483. item = new GreaterThanItem;
  484. } else if (type == "大于等于") {
  485. item = new GreaterEqualItem;
  486. } else if (type == "小于") {
  487. item = new LessThanItem;
  488. } else if (type == "小于等于") {
  489. item = new LessEqualItem;
  490. } else if (type == "等于") {
  491. item = new EqualItem;
  492. } else if (type == "线圈") {
  493. item = new CoilItem;
  494. }
  495. if (item) {
  496. QPointF constrainedPos = constrainToTable(pos);
  497. item->setPos(constrainedPos);
  498. m_scene->addItem(item);
  499. setModified(true);
  500. terminalConnections[item][LeftTerminal] = false;
  501. terminalConnections[item][RightTerminal] = false;
  502. m_lastValidPositions[item] = constrainedPos;
  503. connect(item, &PLCItem::stateChanged, this, [this, item](bool active) {
  504. if (m_simulationRunning) {
  505. if (active) {
  506. m_activeItems.insert(item);
  507. } else {
  508. m_activeItems.remove(item);
  509. }
  510. updateConnections();
  511. }
  512. });
  513. }
  514. }
  515. void PLCDocument::handleSceneChanged()
  516. {
  517. for (auto* connection : qAsConst(m_connections))
  518. {
  519. connection->updatePosition();
  520. }
  521. if (m_tempLine && m_connectionSourceType != ConnectionNone)
  522. {
  523. QPointF cursorPos = m_view->mapFromGlobal(QCursor::pos());
  524. QPointF scenePos = m_view->mapToScene(cursorPos.toPoint());
  525. QLineF line = m_tempLine->line();
  526. line.setP2(scenePos);
  527. m_tempLine->setLine(line);
  528. }
  529. for (QGraphicsItem *item : m_scene->items())
  530. {
  531. if (PLCItem *plcItem = dynamic_cast<PLCItem*>(item))
  532. {
  533. handleItemPositionChange(plcItem);
  534. }
  535. }
  536. }
  537. QList<PLCItem*> PLCDocument::allPLCItems() const
  538. {
  539. QList<PLCItem*> items;
  540. for (QGraphicsItem *item : m_scene->items()) {
  541. if (PLCItem *plcItem = dynamic_cast<PLCItem*>(item)) {
  542. items.append(plcItem);
  543. }
  544. }
  545. return items;
  546. }
  547. bool PLCDocument::isCellOccupied(int col, int row, PLCItem* excludeItem) const
  548. {
  549. for (PLCItem* item : allPLCItems()) {
  550. if (item == excludeItem) continue;
  551. int itemCol = static_cast<int>(item->x() / m_cellSize);
  552. int itemRow = static_cast<int>(item->y() / m_cellSize);
  553. if (itemCol == col && itemRow == row) {
  554. return true;
  555. }
  556. }
  557. return false;
  558. }
  559. // 计算元件在连线上的位置比例(0-1)
  560. qreal PLCDocument::getPositionRatioOnLine(PLCItem* item, ConnectionLine* line) const
  561. {
  562. if (!item || !line) return -1;
  563. QLineF lineSeg = line->line();
  564. QPointF itemPos = item->pos();
  565. // 计算投影比例
  566. qreal dx = lineSeg.dx();
  567. qreal dy = lineSeg.dy();
  568. qreal lengthSquared = dx*dx + dy*dy;
  569. if (lengthSquared == 0) return 0;
  570. qreal t = ((itemPos.x() - lineSeg.p1().x()) * dx +
  571. (itemPos.y() - lineSeg.p1().y()) * dy) / lengthSquared;
  572. return qBound(0.0, t, 1.0);
  573. }
  574. void PLCDocument::handleItemPositionChange(PLCItem *item)
  575. {
  576. if (!item) return;
  577. if (m_loadingFile) {
  578. QPointF newPos = constrainToTable(item->pos());
  579. item->setPos(newPos);
  580. m_lastValidPositions[item] = newPos;
  581. return;
  582. }
  583. if (m_processingItems.contains(item)) {
  584. return;
  585. }
  586. m_processingItems.insert(item);
  587. QPointF newPos = constrainToTable(item->pos());
  588. int newCol = static_cast<int>(newPos.x() / m_cellSize);
  589. int newRow = static_cast<int>(newPos.y() / m_cellSize);
  590. if (isCellOccupied(newCol, newRow, item))
  591. {
  592. if (m_lastValidPositions.contains(item))
  593. {
  594. item->setPos(m_lastValidPositions[item]);
  595. QMessageBox::warning(this, "移动失败", "无法移动,该单元格已被占用");
  596. }
  597. }
  598. else
  599. {
  600. int oldCol = static_cast<int>(m_lastValidPositions[item].x() / m_cellSize);
  601. item->setPos(newPos);
  602. m_lastValidPositions[item] = newPos;
  603. if (newCol != oldCol) {
  604. removeInvalidConnectionsForItem(item);
  605. }
  606. // 获取要拆分的连线并排序
  607. QList<ConnectionLine*> connectionsToSplit = findConnectionsUnderItem(item);
  608. // 按元件在连线上的位置排序,确保从左到右处理
  609. std::sort(connectionsToSplit.begin(), connectionsToSplit.end(),
  610. [this, item](ConnectionLine* a, ConnectionLine* b) {
  611. return getPositionRatioOnLine(item, a) < getPositionRatioOnLine(item, b);
  612. });
  613. // 处理每条连线
  614. for (ConnectionLine* line : connectionsToSplit) {
  615. if (m_connections.contains(line) && !line->isProcessing()) {
  616. splitConnection(line, item);
  617. }
  618. }
  619. // 更新所有连接位置
  620. for (ConnectionLine *line : m_connections) {
  621. if (line) {
  622. line->updatePosition();
  623. }
  624. }
  625. }
  626. m_processingItems.remove(item);
  627. }
  628. //QList<ConnectionLine*> PLCDocument::findConnectionsUnderItem(PLCItem* item) const
  629. //{
  630. // QList<ConnectionLine*> result;
  631. // if (!item) return result;
  632. // QRectF itemRect = item->boundingRect();
  633. // itemRect.adjust(-2, -2, 2, 2);
  634. // QPolygonF itemPolygon = QPolygonF(itemRect).translated(item->pos());
  635. // foreach (ConnectionLine* line, m_connections) {
  636. // if (!line || line->isProcessing()) continue;
  637. // QLineF lineGeometry = line->line();
  638. // QRectF lineRect(lineGeometry.p1(), lineGeometry.p2());
  639. // lineRect.adjust(-10, -10, 10, 10);
  640. // if (itemPolygon.intersects(lineRect)) {
  641. // result.append(line);
  642. // }
  643. // }
  644. // return result;
  645. //}
  646. // 在PLCDocument类的splitConnection函数中,修改逻辑以支持行触点连线的插入
  647. bool PLCDocument::splitConnection(ConnectionLine* originalLine, PLCItem* insertItem)
  648. {
  649. if (!originalLine || !insertItem || originalLine->isProcessing()) {
  650. return false;
  651. }
  652. originalLine->setProcessing(true);
  653. PLCItem* originalStart = originalLine->startItem();
  654. TerminalType originalStartTerminal = originalLine->startTerminal();
  655. PLCItem* originalEnd = originalLine->endItem();
  656. TerminalType originalEndTerminal = originalLine->endTerminal();
  657. // 保存原始行触点信息
  658. int sourceRow = originalLine->rowTerminalSourceRow();
  659. int targetRow = originalLine->rowTerminalTargetRow();
  660. PLCItem* logicalInput = originalStart;
  661. TerminalType inputTerminal = originalStartTerminal;
  662. PLCItem* logicalOutput = originalEnd;
  663. TerminalType outputTerminal = originalEndTerminal;
  664. // 标准化方向时同步交换行触点信息
  665. if ((originalStartTerminal == LeftTerminal && originalEndTerminal == RightTerminal) ||
  666. (originalStartTerminal == LeftTerminal && originalEndTerminal == RowTerminal))
  667. {
  668. std::swap(logicalInput, logicalOutput);
  669. std::swap(inputTerminal, outputTerminal);
  670. std::swap(sourceRow, targetRow); // 同步交换行号
  671. }
  672. // 位置有效性校验
  673. int inputCol = (inputTerminal == RowTerminal) ? 0 :
  674. static_cast<int>(logicalInput->x() / m_cellSize);
  675. int outputCol = (outputTerminal == RowTerminal) ? 0 :
  676. static_cast<int>(logicalOutput->x() / m_cellSize);
  677. int insertCol = static_cast<int>(insertItem->x() / m_cellSize);
  678. // 统一位置验证逻辑
  679. bool isBetween = (insertCol > qMin(inputCol, outputCol)) &&
  680. (insertCol < qMax(inputCol, outputCol));
  681. if (!isBetween) {
  682. originalLine->setProcessing(false);
  683. return false;
  684. }
  685. // 检查端子可用性
  686. if (isTerminalUsed(insertItem, LeftTerminal) ||
  687. isTerminalUsed(insertItem, RightTerminal))
  688. {
  689. originalLine->setProcessing(false);
  690. return false;
  691. }
  692. // 创建新连线
  693. ConnectionLine* line1 = new ConnectionLine(
  694. logicalInput, inputTerminal,
  695. insertItem, LeftTerminal
  696. );
  697. // 保留行触点源信息
  698. if (inputTerminal == RowTerminal) {
  699. line1->setStartTerminalPoint(QPointF(0, (sourceRow + 0.5) * m_cellSize));
  700. line1->setRowTerminalSourceRow(sourceRow);
  701. }
  702. ConnectionLine* line2 = new ConnectionLine(
  703. insertItem, RightTerminal,
  704. logicalOutput, outputTerminal
  705. );
  706. // 保留行触点目标信息
  707. if (outputTerminal == RowTerminal) {
  708. line2->setEndTerminalPoint(QPointF(0, (targetRow + 0.5) * m_cellSize));
  709. line2->setRowTerminalTargetRow(targetRow);
  710. }
  711. // 添加新连线
  712. m_scene->addItem(line1);
  713. m_scene->addItem(line2);
  714. m_connections.append(line1);
  715. m_connections.append(line2);
  716. // 更新端子状态
  717. terminalConnections[insertItem][LeftTerminal] = true;
  718. terminalConnections[insertItem][RightTerminal] = true;
  719. // 移除原连线
  720. m_connections.removeAll(originalLine);
  721. m_scene->removeItem(originalLine);
  722. delete originalLine;
  723. setModified(true);
  724. originalLine->setProcessing(false);
  725. return true;
  726. }
  727. // 补充:确保行触点连线能被检测为"需要拆分的连线"(扩展findConnectionsUnderItem)
  728. QList<ConnectionLine*> PLCDocument::findConnectionsUnderItem(PLCItem* item) const
  729. {
  730. QList<ConnectionLine*> result;
  731. if (!item) return result;
  732. // 元件的边界框(扩大检测范围,确保行触点连线也能被检测)
  733. QRectF itemRect = item->boundingRect();
  734. itemRect.adjust(-5, -5, 5, 5); // 扩大边界框,提高检测灵敏度
  735. QPolygonF itemPolygon = QPolygonF(itemRect).translated(item->pos());
  736. foreach (ConnectionLine* line, m_connections) {
  737. if (!line || line->isProcessing()) continue;
  738. // 行触点连线的特殊处理:起点是(0, row),终点是元件左端子
  739. QLineF lineGeometry = line->line();
  740. QRectF lineRect = QRectF(lineGeometry.p1(), lineGeometry.p2()).normalized();
  741. lineRect.adjust(-10, -10, 10, 10); // 扩大连线的检测范围
  742. // 检查元件是否与连线相交(包括行触点连线)
  743. if (itemPolygon.intersects(lineRect)) {
  744. result.append(line);
  745. }
  746. }
  747. return result;
  748. }
  749. void PLCDocument::removeInvalidConnectionsForItem(PLCItem* movedItem)
  750. {
  751. if (!movedItem || m_connections.isEmpty()) return;
  752. if (m_cellSize <= 0) {
  753. qWarning() << "Invalid cell size!";
  754. return;
  755. }
  756. int movedCol = static_cast<int>(movedItem->x() / m_cellSize);
  757. QSet<ConnectionLine*> connectionsToRemove;
  758. bool shouldRemoveAll = false;
  759. for (ConnectionLine* line : m_connections) {
  760. if (!line) continue;
  761. PLCItem* otherItem = nullptr;
  762. TerminalType movedTerminal = NoTerminal;
  763. TerminalType otherTerminal = NoTerminal;
  764. if (line->startItem() == movedItem) {
  765. otherItem = line->endItem();
  766. movedTerminal = line->startTerminal();
  767. otherTerminal = line->endTerminal();
  768. } else if (line->endItem() == movedItem) {
  769. otherItem = line->startItem();
  770. movedTerminal = line->endTerminal();
  771. otherTerminal = line->startTerminal();
  772. } else {
  773. continue;
  774. }
  775. if (!otherItem) continue;
  776. int otherCol = static_cast<int>(otherItem->x() / m_cellSize);
  777. if (movedTerminal == LeftTerminal && otherTerminal == RightTerminal) {
  778. if (movedCol <= otherCol) {
  779. shouldRemoveAll = true;
  780. break;
  781. }
  782. } else if (movedTerminal == RightTerminal && otherTerminal == LeftTerminal) {
  783. if (movedCol >= otherCol) {
  784. shouldRemoveAll = true;
  785. break;
  786. }
  787. }
  788. }
  789. if (shouldRemoveAll) {
  790. for (ConnectionLine* line : m_connections) {
  791. if (!line) continue;
  792. if (line->startItem() == movedItem || line->endItem() == movedItem) {
  793. connectionsToRemove.insert(line);
  794. }
  795. }
  796. }
  797. for (ConnectionLine* line : connectionsToRemove) {
  798. if (!line) continue;
  799. if (line->startItem()) {
  800. terminalConnections[line->startItem()].remove(line->startTerminal());
  801. }
  802. if (line->endItem()) {
  803. terminalConnections[line->endItem()].remove(line->endTerminal());
  804. }
  805. if (m_scene && line->scene() == m_scene) {
  806. m_scene->removeItem(line);
  807. }
  808. if (m_connections.contains(line)) {
  809. m_connections.removeAll(line);
  810. }
  811. delete line;
  812. }
  813. if (!connectionsToRemove.isEmpty()) {
  814. setModified(true);
  815. }
  816. }
  817. void PLCDocument::setSimulationRunning(bool running)
  818. {
  819. m_simulationRunning = running;
  820. if (!running) {
  821. resetSimulation();
  822. }
  823. }
  824. void PLCDocument::resetSimulation()
  825. {
  826. m_activeItems.clear();
  827. for (QGraphicsItem *item : m_scene->items()) {
  828. if (PLCItem *plcItem = dynamic_cast<PLCItem*>(item)) {
  829. plcItem->setState(false);
  830. }
  831. }
  832. terminalConnections.clear();
  833. updateConnections();
  834. }
  835. void PLCDocument::updateConnections()
  836. {
  837. for (ConnectionLine *line : m_connections) {
  838. PLCItem *start = line->startItem();
  839. PLCItem *end = line->endItem();
  840. if (line->endTerminal() == RowTerminal || line->startTerminal() == RowTerminal) {
  841. line->setPen(QPen(Qt::green, 2));
  842. continue;
  843. }
  844. if (m_activeItems.contains(start) && m_activeItems.contains(end)) {
  845. line->setPen(QPen(Qt::green, 2));
  846. } else {
  847. line->setPen(QPen(Qt::black, 1));
  848. }
  849. }
  850. }
  851. void PLCDocument::deleteSelectedItems()
  852. {
  853. QList<QGraphicsItem*> selected = m_scene->selectedItems();
  854. if (selected.isEmpty()) return;
  855. QList<PLCItem*> itemsToDelete;
  856. QList<ConnectionLine*> linesToDelete;
  857. QSet<ConnectionLine*> connectionsToDelete;
  858. for (QGraphicsItem* item : selected) {
  859. if (PLCItem* plcItem = dynamic_cast<PLCItem*>(item)) {
  860. itemsToDelete.append(plcItem);
  861. } else if (ConnectionLine* line = dynamic_cast<ConnectionLine*>(item)) {
  862. linesToDelete.append(line);
  863. }
  864. }
  865. for (ConnectionLine* line : m_connections) {
  866. if (line->startItem() && itemsToDelete.contains(line->startItem())) {
  867. connectionsToDelete.insert(line);
  868. }
  869. if (line->endItem() && itemsToDelete.contains(line->endItem())) {
  870. connectionsToDelete.insert(line);
  871. }
  872. }
  873. for (ConnectionLine* line : linesToDelete) {
  874. connectionsToDelete.insert(line);
  875. }
  876. for (ConnectionLine* line : connectionsToDelete) {
  877. if (!line) continue;
  878. if (line->startItem()) {
  879. terminalConnections[line->startItem()].remove(line->startTerminal());
  880. }
  881. if (line->endItem()) {
  882. terminalConnections[line->endItem()].remove(line->endTerminal());
  883. }
  884. if (m_scene && m_scene->items().contains(line)) {
  885. m_scene->removeItem(line);
  886. }
  887. m_connections.removeAll(line);
  888. delete line;
  889. }
  890. for (PLCItem* item : itemsToDelete) {
  891. terminalConnections.remove(item);
  892. m_lastValidPositions.remove(item);
  893. m_scene->removeItem(item);
  894. delete item;
  895. }
  896. setModified(true);
  897. }
  898. void PLCDocument::copySelectedItem()
  899. {
  900. QList<QGraphicsItem*> selected = m_scene->selectedItems();
  901. if (selected.size() != 1) return;
  902. if (PLCItem* item = dynamic_cast<PLCItem*>(selected.first())) {
  903. m_copiedItem = item;
  904. }
  905. }
  906. void PLCDocument::pasteItem()
  907. {
  908. if (!m_copiedItem) return;
  909. QPointF pastePos = snapToCellCenter(m_view->mapToScene(m_view->viewport()->mapFromGlobal(QCursor::pos())));
  910. pastePos.setX(pastePos.x() - 70);
  911. pastePos.setY(pastePos.y() - 70);
  912. int col = static_cast<int>(pastePos.x() / m_cellSize);
  913. int row = static_cast<int>(pastePos.y() / m_cellSize);
  914. if (isCellOccupied(col, row, nullptr))
  915. {
  916. QMessageBox::warning(this, "粘贴失败", "目标单元格已被占用");
  917. return;
  918. }
  919. PLCItem* newItem = createItemByType(m_copiedItem->itemType());
  920. if (!newItem) return;
  921. newItem->setPos(pastePos);
  922. newItem->setName(m_copiedItem->name() + "_副本");
  923. m_scene->addItem(newItem);
  924. terminalConnections[newItem][LeftTerminal] = false;
  925. terminalConnections[newItem][RightTerminal] = false;
  926. m_lastValidPositions[newItem] = pastePos;
  927. setModified(true);
  928. }
  929. void PLCDocument::keyPressEvent(QKeyEvent* event)
  930. {
  931. if (event->key() == Qt::Key_Delete)
  932. {
  933. deleteSelectedItems();
  934. event->accept();
  935. return;
  936. }
  937. BaseDocument::keyPressEvent(event);
  938. }
  939. void PLCDocument::createContextMenu(QPoint globalPos)
  940. {
  941. QMenu menu(this);
  942. QAction* deleteAction = menu.addAction("删除");
  943. QAction* copyAction = menu.addAction("复制");
  944. QAction* pasteAction = menu.addAction("粘贴");
  945. QList<QGraphicsItem*> selected = m_scene->selectedItems();
  946. deleteAction->setEnabled(!selected.isEmpty());
  947. copyAction->setEnabled(selected.size() == 1&&dynamic_cast<PLCItem*>(selected.first()));
  948. pasteAction->setEnabled(m_copiedItem != nullptr);
  949. connect(deleteAction, &QAction::triggered, this, &PLCDocument::deleteSelectedItems);
  950. connect(copyAction, &QAction::triggered, this, &PLCDocument::copySelectedItem);
  951. connect(pasteAction, &QAction::triggered, this, &PLCDocument::pasteItem);
  952. menu.addSeparator();
  953. QAction *tableGridAction = menu.addAction("隐藏/显示网格");
  954. connect(tableGridAction, &QAction::triggered, this, [this]() {
  955. for (auto line : m_horizontalLines)
  956. {
  957. line->setVisible(!line->isVisible());
  958. }
  959. for (auto line : m_verticalLines)
  960. {
  961. line->setVisible(!line->isVisible());
  962. }
  963. });
  964. menu.exec(globalPos + QPoint(10, 10));
  965. }
  966. void PLCDocument::onRowTerminalConnection()
  967. {
  968. QList<QGraphicsItem*> selected = m_scene->selectedItems();
  969. if (selected.size() != 1) return;
  970. PLCItem* item = dynamic_cast<PLCItem*>(selected.first());
  971. if (!item) return;
  972. int itemRow = static_cast<int>(item->y() / m_cellSize);
  973. int itemCol = static_cast<int>(item->x() / m_cellSize);
  974. if (itemCol != 0) {
  975. QMessageBox::warning(this, "连接错误", "只有最左侧列的元件可以连接到行触点");
  976. return;
  977. }
  978. if (isTerminalUsed(item, LeftTerminal)) {
  979. QMessageBox::warning(this, "连接失败", "该元件的左端子已被使用");
  980. return;
  981. }
  982. ConnectionLine* connection = new ConnectionLine(
  983. item, LeftTerminal,
  984. nullptr, RowTerminal
  985. );
  986. connection->setRowTerminalTargetRow(itemRow);
  987. connection->setEndTerminalPoint(QPointF(0, (itemRow + 0.5) * m_cellSize));
  988. connection->updatePosition();
  989. m_scene->addItem(connection);
  990. m_connections.append(connection);
  991. terminalConnections[item][LeftTerminal] = true;
  992. setModified(true);
  993. }
  994. PLCItem* PLCDocument::createItemByType(PLCItem::ItemType type)
  995. {
  996. switch (type) {
  997. case PLCItem::NormallyOpen: return new NormallyOpenItem;
  998. case PLCItem::NormallyClosed: return new NormallyClosedItem;
  999. case PLCItem::GreaterThan: return new GreaterThanItem;
  1000. case PLCItem::GreaterEqual: return new GreaterEqualItem;
  1001. case PLCItem::LessThan: return new LessThanItem;
  1002. case PLCItem::LessEqual: return new LessEqualItem;
  1003. case PLCItem::Equal: return new EqualItem;
  1004. case PLCItem::Coil: return new CoilItem;
  1005. default:
  1006. qWarning() << "未知的PLC元件类型:" << type;
  1007. return nullptr;
  1008. }
  1009. }
  1010. bool PLCDocument::saveToFile(const QString &filePath)
  1011. {
  1012. QFile file(filePath);
  1013. if (!file.open(QIODevice::WriteOnly)) {
  1014. QMessageBox::warning(this, "保存失败", "无法打开文件进行写入:" + filePath);
  1015. return false;
  1016. }
  1017. QJsonObject docObject;
  1018. docObject["type"] = "PLCDocument";
  1019. docObject["table_rows"] = m_rows;
  1020. docObject["table_columns"] = m_columns;
  1021. docObject["table_cell_size"] = m_cellSize;
  1022. QJsonArray itemsArray;
  1023. for (QGraphicsItem *item : m_scene->items()) {
  1024. if (PLCItem *plcItem = dynamic_cast<PLCItem*>(item)) {
  1025. QJsonObject itemObj;
  1026. itemObj["type"] = static_cast<int>(plcItem->itemType());
  1027. int col = static_cast<int>(plcItem->x() / m_cellSize);
  1028. int row = static_cast<int>(plcItem->y() / m_cellSize);
  1029. itemObj["col"] = col;
  1030. itemObj["row"] = row;
  1031. itemObj["name"] = plcItem->name();
  1032. itemObj["active"] = plcItem->isActive();
  1033. itemsArray.append(itemObj);
  1034. }
  1035. }
  1036. docObject["items"] = itemsArray;
  1037. QJsonArray connectionsArray;
  1038. for (ConnectionLine *line : m_connections) {
  1039. QJsonObject connObj;
  1040. if (line->startItem()) {
  1041. int startCol = static_cast<int>(line->startItem()->pos().x() / m_cellSize);
  1042. int startRow = static_cast<int>(line->startItem()->pos().y() / m_cellSize);
  1043. connObj["startCol"] = startCol;
  1044. connObj["startRow"] = startRow;
  1045. } else if (line->startTerminal() == RowTerminal) {
  1046. connObj["startRowTerminal"] = line->rowTerminalSourceRow();
  1047. }
  1048. connObj["startTerminal"] = static_cast<int>(line->startTerminal());
  1049. connObj["endTerminal"] = static_cast<int>(line->endTerminal());
  1050. if (line->endTerminal() == RowTerminal) {
  1051. connObj["endRowTerminal"] = line->rowTerminalTargetRow();
  1052. } else if (line->endItem()) {
  1053. int endCol = static_cast<int>(line->endItem()->pos().x() / m_cellSize);
  1054. int endRow = static_cast<int>(line->endItem()->pos().y() / m_cellSize);
  1055. connObj["endCol"] = endCol;
  1056. connObj["endRow"] = endRow;
  1057. }
  1058. connectionsArray.append(connObj);
  1059. }
  1060. docObject["connections"] = connectionsArray;
  1061. QJsonDocument doc(docObject);
  1062. file.write(doc.toJson());
  1063. file.close();
  1064. setFilePath(filePath);
  1065. setModified(false);
  1066. return true;
  1067. }
  1068. bool PLCDocument::loadFromFile(const QString &filePath)
  1069. {
  1070. QFile file(filePath);
  1071. if (!file.open(QIODevice::ReadOnly)) {
  1072. QMessageBox::warning(this, "打开失败", "无法打开文件:" + filePath);
  1073. return false;
  1074. }
  1075. QByteArray data = file.readAll();
  1076. file.close();
  1077. QJsonParseError parseError;
  1078. QJsonDocument doc = QJsonDocument::fromJson(data, &parseError);
  1079. if (parseError.error != QJsonParseError::NoError) {
  1080. QMessageBox::warning(this, "文件格式错误", "JSON解析错误: " + parseError.errorString());
  1081. return false;
  1082. }
  1083. if (doc.isNull() || !doc.isObject()) {
  1084. QMessageBox::warning(this, "文件格式错误", "不是有效的PLC文档");
  1085. return false;
  1086. }
  1087. QJsonObject docObject = doc.object();
  1088. if (docObject["type"].toString() != "PLCDocument") {
  1089. QMessageBox::warning(this, "文件类型错误", "不是有效的PLC文档");
  1090. return false;
  1091. }
  1092. m_rows = docObject["table_rows"].toInt(DEFAULT_ROWS);
  1093. m_columns = docObject["table_columns"].toInt(DEFAULT_COLUMNS);
  1094. m_cellSize = docObject["table_cell_size"].toInt(DEFAULT_CELL_SIZE);
  1095. setSimulationRunning(false);
  1096. m_scene->blockSignals(true);
  1097. for (auto conn : m_connections) {
  1098. m_scene->removeItem(conn);
  1099. delete conn;
  1100. }
  1101. m_connections.clear();
  1102. QList<QGraphicsItem*> allItems = m_scene->items();
  1103. for (QGraphicsItem* item : allItems) {
  1104. if (dynamic_cast<PLCItem*>(item) || dynamic_cast<ConnectionLine*>(item)) {
  1105. m_scene->removeItem(item);
  1106. delete item;
  1107. }
  1108. }
  1109. m_activeItems.clear();
  1110. terminalConnections.clear();
  1111. clearCurrentConnection();
  1112. createRealTable();
  1113. QMap<QPair<int, int>, PLCItem*> itemMap;
  1114. QJsonArray itemsArray = docObject["items"].toArray();
  1115. for (const QJsonValue &itemValue : itemsArray) {
  1116. QJsonObject itemObj = itemValue.toObject();
  1117. PLCItem::ItemType type = static_cast<PLCItem::ItemType>(itemObj["type"].toInt());
  1118. int col = itemObj["col"].toInt();
  1119. int row = itemObj["row"].toInt();
  1120. QString name = itemObj["name"].toString();
  1121. bool active = itemObj["active"].toBool();
  1122. col = qBound(0, col, m_columns - 1);
  1123. row = qBound(0, row, m_rows - 1);
  1124. PLCItem *item = createItemByType(type);
  1125. if (item) {
  1126. QPointF pos(col * m_cellSize + m_cellSize / 2.0,
  1127. row * m_cellSize + m_cellSize / 2.0);
  1128. item->setPos(pos);
  1129. item->setName(name);
  1130. item->setState(active);
  1131. m_scene->addItem(item);
  1132. itemMap.insert(qMakePair(col, row), item);
  1133. if (active) {
  1134. m_activeItems.insert(item);
  1135. }
  1136. terminalConnections[item][LeftTerminal] = false;
  1137. terminalConnections[item][RightTerminal] = false;
  1138. connect(item, &PLCItem::stateChanged, this, [this, item](bool active) {
  1139. if (m_simulationRunning) {
  1140. if (active) m_activeItems.insert(item);
  1141. else m_activeItems.remove(item);
  1142. updateConnections();
  1143. }
  1144. });
  1145. }
  1146. }
  1147. QJsonArray connectionsArray = docObject["connections"].toArray();
  1148. for (const QJsonValue &connValue : connectionsArray) {
  1149. QJsonObject connObj = connValue.toObject();
  1150. TerminalType startTerminal = static_cast<TerminalType>(connObj["startTerminal"].toInt());
  1151. TerminalType endTerminal = static_cast<TerminalType>(connObj["endTerminal"].toInt());
  1152. PLCItem *startItem = nullptr;
  1153. PLCItem *endItem = nullptr;
  1154. if (connObj.contains("startRowTerminal")) {
  1155. int startRow = connObj["startRowTerminal"].toInt();
  1156. ConnectionLine *line = new ConnectionLine(
  1157. nullptr, RowTerminal,
  1158. endItem, endTerminal
  1159. );
  1160. line->setStartTerminalPoint(QPointF(0, (startRow + 0.5) * m_cellSize));
  1161. line->setRowTerminalSourceRow(startRow);
  1162. m_scene->addItem(line);
  1163. m_connections.append(line);
  1164. }
  1165. else if (connObj.contains("startCol") && connObj.contains("startRow")) {
  1166. int startCol = connObj["startCol"].toInt();
  1167. int startRow = connObj["startRow"].toInt();
  1168. startItem = itemMap.value(qMakePair(startCol, startRow));
  1169. if (endTerminal == RowTerminal && connObj.contains("endRowTerminal")) {
  1170. int endRow = connObj["endRowTerminal"].toInt();
  1171. ConnectionLine *line = new ConnectionLine(
  1172. startItem, startTerminal,
  1173. nullptr, RowTerminal
  1174. );
  1175. line->setEndTerminalPoint(QPointF(0, (endRow + 0.5) * m_cellSize));
  1176. line->setRowTerminalTargetRow(endRow);
  1177. m_scene->addItem(line);
  1178. m_connections.append(line);
  1179. }
  1180. else if (connObj.contains("endCol") && connObj.contains("endRow")) {
  1181. int endCol = connObj["endCol"].toInt();
  1182. int endRow = connObj["endRow"].toInt();
  1183. endItem = itemMap.value(qMakePair(endCol, endRow));
  1184. if (startItem && endItem) {
  1185. ConnectionLine *line = new ConnectionLine(
  1186. startItem, startTerminal,
  1187. endItem, endTerminal
  1188. );
  1189. m_scene->addItem(line);
  1190. m_connections.append(line);
  1191. }
  1192. }
  1193. }
  1194. }
  1195. for (ConnectionLine* line : m_connections) {
  1196. if (line->startItem()) {
  1197. terminalConnections[line->startItem()][line->startTerminal()] = true;
  1198. }
  1199. if (line->endItem()) {
  1200. terminalConnections[line->endItem()][line->endTerminal()] = true;
  1201. }
  1202. }
  1203. m_scene->blockSignals(false);
  1204. handleSceneChanged();
  1205. setFilePath(filePath);
  1206. setModified(false);
  1207. return true;
  1208. }
  1209. ConnectionLine::ConnectionLine(
  1210. PLCItem *startItem, PLCDocument::TerminalType startTerminal,
  1211. PLCItem *endItem, PLCDocument::TerminalType endTerminal,
  1212. QGraphicsItem *parent
  1213. ) : QGraphicsLineItem(parent)
  1214. , m_startItem(startItem)
  1215. , m_startTerminal(startTerminal)
  1216. , m_endItem(endItem)
  1217. , m_endTerminal(endTerminal)
  1218. , m_rowTerminalTargetRow(-1)
  1219. , m_rowTerminalSourceRow(-1)
  1220. , m_processing(false)
  1221. {
  1222. setFlag(QGraphicsItem::ItemIsSelectable, true);
  1223. setPen(QPen(Qt::black, 1));
  1224. setZValue(-1);
  1225. updatePosition();
  1226. }
  1227. void ConnectionLine::updatePosition()
  1228. {
  1229. QPointF startPoint;
  1230. if (m_startItem) {
  1231. if (m_startTerminal == PLCDocument::LeftTerminal) {
  1232. startPoint = m_startItem->leftTerminal();
  1233. } else if (m_startTerminal == PLCDocument::RightTerminal) {
  1234. startPoint = m_startItem->rightTerminal();
  1235. }
  1236. } else if (!m_startTerminalPoint.isNull()) {
  1237. startPoint = m_startTerminalPoint;
  1238. }
  1239. QPointF endPoint;
  1240. if (m_endItem) {
  1241. if (m_endTerminal == PLCDocument::LeftTerminal) {
  1242. endPoint = m_endItem->leftTerminal();
  1243. } else if (m_endTerminal == PLCDocument::RightTerminal) {
  1244. endPoint = m_endItem->rightTerminal();
  1245. }
  1246. } else if (!m_endTerminalPoint.isNull()) {
  1247. endPoint = m_endTerminalPoint;
  1248. }
  1249. if (!startPoint.isNull() && !endPoint.isNull()) {
  1250. setLine(QLineF(startPoint, endPoint));
  1251. }
  1252. }