Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

782 строки
26 KiB

  1. // PLCDocument.cpp 的完整修改版
  2. #include "plcdocument.h"
  3. #include "plcitems.h"
  4. #include <QPainter>
  5. #include <QPen>
  6. #include <QBrush>
  7. #include <QMenu>
  8. #include <QAction>
  9. #include <QGraphicsSceneMouseEvent>
  10. #include <QJsonDocument>
  11. #include <QJsonObject>
  12. #include <QJsonArray>
  13. #include <QFile>
  14. #include <QFileInfo>
  15. #include <QVBoxLayout>
  16. #include <QMessageBox>
  17. #include <QInputDialog>
  18. #include <QDragEnterEvent>
  19. #include <QDragMoveEvent>
  20. #include <QCursor>
  21. #include <QDropEvent>
  22. #include <QMimeData>
  23. #include <cmath>
  24. #include <QResizeEvent>
  25. #include <QDebug>
  26. #include <QGraphicsItem>
  27. #include <QGraphicsSceneMouseEvent>
  28. #include <QSet>
  29. // 固定表格尺寸的常量
  30. const int DEFAULT_ROWS = 20;
  31. const int DEFAULT_COLUMNS = 25;
  32. const int DEFAULT_CELL_SIZE = 50;
  33. PLCDocument::PLCDocument(QWidget *parent)
  34. : BaseDocument(PLC, parent)
  35. , m_rows(DEFAULT_ROWS)
  36. , m_columns(DEFAULT_COLUMNS)
  37. ,m_cellSize(DEFAULT_CELL_SIZE)
  38. {
  39. // 创建绘图场景
  40. m_scene = new QGraphicsScene(this);
  41. m_scene->setSceneRect(0, 0, m_columns * m_cellSize, m_rows * m_cellSize);
  42. // 创建视图
  43. m_view = new QGraphicsView(m_scene, this);
  44. m_view->setRenderHint(QPainter::Antialiasing);
  45. m_view->setDragMode(QGraphicsView::RubberBandDrag);
  46. m_view->setMouseTracking(true);
  47. m_view->viewport()->installEventFilter(this);
  48. m_view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
  49. m_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
  50. m_view->setAlignment(Qt::AlignLeft | Qt::AlignTop);
  51. m_view->setFixedSize(m_columns * m_cellSize, m_rows * m_cellSize);
  52. setAcceptDrops(true);
  53. m_view->setAcceptDrops(true);
  54. m_view->viewport()->setAcceptDrops(true);
  55. // 布局(让视图占满文档区域)
  56. auto layout = new QVBoxLayout(this);
  57. layout->setContentsMargins(0, 0, 0, 0);
  58. layout->addWidget(m_view);
  59. setLayout(layout);
  60. // 创建固定大小的表格
  61. createRealTable();
  62. // 连接场景信号
  63. connect(m_scene, &QGraphicsScene::selectionChanged, this, [this]() {
  64. auto items = m_scene->selectedItems();
  65. if (items.size() == 1 && dynamic_cast<PLCItem*>(items.first())) {
  66. PLCItem *item = static_cast<PLCItem*>(items.first());
  67. if (m_connectionStart) {
  68. endConnection(item);
  69. }
  70. }
  71. });
  72. // 连接场景变化信号(用于更新连接线位置)
  73. connect(m_scene, &QGraphicsScene::changed, this, &PLCDocument::handleSceneChanged);
  74. }
  75. PLCDocument::~PLCDocument()
  76. {
  77. qDeleteAll(m_connections);
  78. clearTable();
  79. }
  80. QString PLCDocument::title() const {
  81. return "PLC文档";
  82. }
  83. void PLCDocument::createRealTable()
  84. {
  85. clearTable();
  86. // 表格整体区域(覆盖整个场景)
  87. QRectF tableRect(0, 0, m_columns * m_cellSize, m_rows * m_cellSize);
  88. m_tableFrame = new QGraphicsRectItem(tableRect);
  89. m_tableFrame->setPen(QPen(Qt::black, 2));
  90. m_tableFrame->setBrush(QBrush(QColor(245, 245, 245))); // 浅灰背景区分表格区域
  91. m_tableFrame->setZValue(-10);
  92. m_scene->addItem(m_tableFrame);
  93. // 添加垂直线(单元格分隔线)
  94. for (int col = 0; col <= m_columns; col++) {
  95. int x = col * m_cellSize;
  96. QGraphicsLineItem* line = new QGraphicsLineItem(x, 0, x, m_rows * m_cellSize);
  97. line->setPen(QPen(Qt::darkGray, 1));
  98. line->setZValue(-9);
  99. m_scene->addItem(line);
  100. m_verticalLines.append(line);
  101. }
  102. // 添加水平线(单元格分隔线)
  103. for (int row = 0; row <= m_rows; row++) {
  104. int y = row * m_cellSize;
  105. QGraphicsLineItem* line = new QGraphicsLineItem(0, y, m_columns * m_cellSize, y);
  106. line->setPen(QPen(Qt::darkGray, 1));
  107. line->setZValue(-9);
  108. m_scene->addItem(line);
  109. m_horizontalLines.append(line);
  110. }
  111. }
  112. void PLCDocument::clearTable()
  113. {
  114. if (m_tableFrame) {
  115. m_scene->removeItem(m_tableFrame);
  116. delete m_tableFrame;
  117. m_tableFrame = nullptr;
  118. }
  119. for (auto line : m_horizontalLines) {
  120. m_scene->removeItem(line);
  121. delete line;
  122. }
  123. m_horizontalLines.clear();
  124. for (auto line : m_verticalLines) {
  125. m_scene->removeItem(line);
  126. delete line;
  127. }
  128. m_verticalLines.clear();
  129. }
  130. QPointF PLCDocument::snapToCellCenter(const QPointF& pos) const
  131. {
  132. // 计算所在单元格列号(0~m_columns-1)
  133. int col = static_cast<int>(std::floor(pos.x() / m_cellSize));
  134. // 计算所在单元格行号(0~m_rows-1)
  135. int row = static_cast<int>(std::floor(pos.y() / m_cellSize));
  136. // 限制在表格范围内
  137. col = qBound(0, col, m_columns - 1);
  138. row = qBound(0, row, m_rows - 1);
  139. return QPointF(col * m_cellSize + m_cellSize / 2.0,
  140. row * m_cellSize + m_cellSize / 2.0);
  141. }
  142. QPointF PLCDocument::constrainToTable(const QPointF &pos) const
  143. {
  144. int col = static_cast<int>(std::floor(pos.x() / m_cellSize));
  145. int row = static_cast<int>(std::floor(pos.y() / m_cellSize));
  146. col = qBound(0, col, m_columns - 1);
  147. row = qBound(0, row, m_rows - 1);
  148. return QPointF(col * m_cellSize + m_cellSize / 2.0,
  149. row * m_cellSize + m_cellSize / 2.0);
  150. }
  151. void PLCDocument::resizeEvent(QResizeEvent *event)
  152. {
  153. QWidget::resizeEvent(event);
  154. // 不再调整表格大小,只更新视图尺寸
  155. m_view->setFixedSize(m_columns * m_cellSize, m_rows * m_cellSize);
  156. }
  157. bool PLCDocument::eventFilter(QObject *obj, QEvent *event)
  158. {
  159. // 仅处理viewport的事件
  160. if (obj == m_view->viewport())
  161. {
  162. // 处理拖拽进入事件
  163. if (event->type() == QEvent::DragEnter) {
  164. QDragEnterEvent *dragEvent = static_cast<QDragEnterEvent*>(event);
  165. if (dragEvent->mimeData()->hasText()) {
  166. dragEvent->acceptProposedAction();
  167. return true;
  168. }
  169. }
  170. // 处理拖拽移动事件
  171. else if (event->type() == QEvent::DragMove) {
  172. QDragMoveEvent *dragEvent = static_cast<QDragMoveEvent*>(event);
  173. if(dragEvent->mimeData()->hasText()) {
  174. dragEvent->acceptProposedAction();
  175. return true;
  176. }
  177. }
  178. // 处理拖拽释放事件(使用单元格中心对齐)
  179. else if (event->type() == QEvent::Drop) {
  180. QDropEvent *dropEvent = static_cast<QDropEvent*>(event);
  181. const QMimeData *mimeData = dropEvent->mimeData();
  182. if (mimeData->hasText()) {
  183. QPoint viewportPos = m_view->viewport()->mapFromParent(dropEvent->pos());
  184. QPointF scenePos = m_view->mapToScene(viewportPos);
  185. // 对齐到单元格中心
  186. QPointF cellCenter = snapToCellCenter(scenePos);
  187. // 检查单元格是否已被占用
  188. QSet<int> occupiedCells;
  189. for (QGraphicsItem* item : m_scene->items()) {
  190. if (PLCItem* plcItem = dynamic_cast<PLCItem*>(item)) {
  191. int col = static_cast<int>(plcItem->x() / m_cellSize);
  192. int row = static_cast<int>(plcItem->y() / m_cellSize);
  193. occupiedCells.insert(col * 1000 + row); // 简单的行列唯一键
  194. }
  195. }
  196. int col = static_cast<int>(cellCenter.x() / m_cellSize);
  197. int row = static_cast<int>(cellCenter.y() / m_cellSize);
  198. int cellKey = col * 1000 + row;
  199. // 如果单元格被占用,尝试移动到最近的空单元格
  200. if (occupiedCells.contains(cellKey)) {
  201. bool found = false;
  202. // 尝试向右移动
  203. for (int offset = 1; offset < m_columns; offset++) {
  204. int newCol = col + offset;
  205. if (newCol < m_columns) {
  206. int newKey = newCol * 1000 + row;
  207. if (!occupiedCells.contains(newKey)) {
  208. cellCenter = QPointF(newCol * m_cellSize + m_cellSize / 2.0,
  209. row * m_cellSize + m_cellSize / 2.0);
  210. found = true;
  211. break;
  212. }
  213. }
  214. }
  215. if (!found) {
  216. // 尝试向下移动
  217. for (int offset = 1; offset < m_rows; offset++) {
  218. int newRow = row + offset;
  219. if (newRow < m_rows) {
  220. int newKey = col * 1000 + newRow;
  221. if (!occupiedCells.contains(newKey)) {
  222. cellCenter = QPointF(col * m_cellSize + m_cellSize / 2.0,
  223. newRow * m_cellSize + m_cellSize / 2.0);
  224. found = true;
  225. break;
  226. }
  227. }
  228. }
  229. }
  230. }
  231. // 创建PLC控件
  232. QString toolType = mimeData->text();
  233. createPLCItem(toolType, cellCenter);
  234. m_currentTool.clear();
  235. dropEvent->acceptProposedAction();
  236. return true;
  237. }
  238. }
  239. }
  240. if (obj != m_view->viewport()) {
  241. return QWidget::eventFilter(obj, event);
  242. }
  243. // 鼠标移动事件(更新临时连线)- 基于单元格中心
  244. if (event->type() == QEvent::MouseMove && m_tempLine) {
  245. QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
  246. QPointF scenePos = m_view->mapToScene(mouseEvent->pos());
  247. QPointF cellCenter = snapToCellCenter(scenePos); // 使用单元格中心
  248. QLineF line(m_connectionStart->rightTerminal(), cellCenter);
  249. m_tempLine->setLine(line);
  250. }
  251. // 鼠标按下事件
  252. if (event->type() == QEvent::MouseButtonPress) {
  253. QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
  254. // 右键点击 - 显示上下文菜单
  255. if (mouseEvent->button() == Qt::RightButton) {
  256. createContextMenu(mouseEvent->globalPos());
  257. return true;
  258. }
  259. // 左键点击
  260. if (mouseEvent->button() == Qt::LeftButton) {
  261. QPointF scenePos = m_view->mapToScene(mouseEvent->pos());
  262. QPointF cellCenter = snapToCellCenter(scenePos); // 单元格中心
  263. // 使用当前工具创建控件
  264. if (!m_currentTool.isEmpty()) {
  265. // 检查单元格是否已被占用
  266. QSet<int> occupiedCells;
  267. for (QGraphicsItem* item : m_scene->items()) {
  268. if (PLCItem* plcItem = dynamic_cast<PLCItem*>(item)) {
  269. int col = static_cast<int>(plcItem->x() / m_cellSize);
  270. int row = static_cast<int>(plcItem->y() / m_cellSize);
  271. occupiedCells.insert(col * 1000 + row); // 简单的行列唯一键
  272. }
  273. }
  274. int col = static_cast<int>(cellCenter.x() / m_cellSize);
  275. int row = static_cast<int>(cellCenter.y() / m_cellSize);
  276. int cellKey = col * 1000 + row;
  277. // 如果单元格被占用,显示警告
  278. if (occupiedCells.contains(cellKey)) {
  279. QMessageBox::warning(this, "位置被占用", "此单元格已被占用,请选择其他位置。");
  280. return true;
  281. }
  282. createPLCItem(m_currentTool, cellCenter);
  283. m_currentTool.clear();
  284. return true;
  285. }
  286. // 连线模式处理
  287. if (m_connectionStart) {
  288. QGraphicsItem *item = m_scene->itemAt(cellCenter, m_view->transform());
  289. if (PLCItem *plcItem = dynamic_cast<PLCItem*>(item)) {
  290. endConnection(plcItem);
  291. return true;
  292. }
  293. }
  294. // 开始连线
  295. QGraphicsItem *item = m_scene->itemAt(cellCenter, m_view->transform());
  296. if (PLCItem *plcItem = dynamic_cast<PLCItem*>(item)) {
  297. startConnection(plcItem);
  298. return true;
  299. }
  300. }
  301. }
  302. return QWidget::eventFilter(obj, event);
  303. }
  304. void PLCDocument::createPLCItem(const QString &type, const QPointF &pos)
  305. {
  306. PLCItem *item = nullptr;
  307. if (type == "常开") {
  308. item = new NormallyOpenItem;
  309. } else if (type == "常闭") {
  310. item = new NormallyClosedItem;
  311. } else if (type == "大于") {
  312. item = new GreaterThanItem;
  313. } else if (type == "大于等于") {
  314. item = new GreaterEqualItem;
  315. } else if (type == "小于") {
  316. item = new LessThanItem;
  317. } else if (type == "小于等于") {
  318. item = new LessEqualItem;
  319. } else if (type == "等于") {
  320. item = new EqualItem;
  321. } else if (type == "线圈") {
  322. item = new CoilItem;
  323. }
  324. if (item) {
  325. // 确保位置在表格内并居中
  326. QPointF constrainedPos = constrainToTable(pos);
  327. item->setPos(constrainedPos);
  328. m_scene->addItem(item);
  329. setModified(true);
  330. // 连接状态变化信号
  331. connect(item, &PLCItem::stateChanged, this, [this, item](bool active) {
  332. if (m_simulationRunning) {
  333. if (active) {
  334. m_activeItems.insert(item);
  335. } else {
  336. m_activeItems.remove(item);
  337. }
  338. updateConnections();
  339. }
  340. });
  341. }
  342. }
  343. void PLCDocument::handleSceneChanged()
  344. {
  345. // 遍历所有项目,处理位置变化
  346. for (QGraphicsItem *item : m_scene->items()) {
  347. if (PLCItem *plcItem = dynamic_cast<PLCItem*>(item)) {
  348. handleItemPositionChange(plcItem);
  349. }
  350. }
  351. }
  352. void PLCDocument::handleItemPositionChange(PLCItem *item)
  353. {
  354. // 限制项目位置在表格内
  355. QPointF newPos = constrainToTable(item->pos());
  356. if (newPos != item->pos()) {
  357. item->setPos(newPos);
  358. }
  359. // 更新所有与该项目相关的连接
  360. for (ConnectionLine *line : m_connections) {
  361. if (line->startItem() == item || line->endItem() == item) {
  362. line->updatePosition();
  363. }
  364. }
  365. }
  366. void PLCDocument::startConnection(PLCItem *startItem)
  367. {
  368. if (m_connectionStart) {
  369. clearCurrentConnection();
  370. }
  371. m_connectionStart = startItem;
  372. m_tempLine = new QGraphicsLineItem;
  373. m_tempLine->setPen(QPen(Qt::blue, 2, Qt::DashLine));
  374. m_tempLine->setLine(QLineF(startItem->rightTerminal(), startItem->rightTerminal()));
  375. m_scene->addItem(m_tempLine);
  376. }
  377. void PLCDocument::endConnection(PLCItem *endItem)
  378. {
  379. if (!m_connectionStart || m_connectionStart == endItem) {
  380. clearCurrentConnection();
  381. return;
  382. }
  383. // 创建永久连接线
  384. ConnectionLine *connection = new ConnectionLine(m_connectionStart, endItem);
  385. m_scene->addItem(connection);
  386. m_connections.append(connection);
  387. clearCurrentConnection();
  388. setModified(true);
  389. }
  390. void PLCDocument::clearCurrentConnection()
  391. {
  392. if (m_tempLine) {
  393. m_scene->removeItem(m_tempLine);
  394. delete m_tempLine;
  395. m_tempLine = nullptr;
  396. }
  397. m_connectionStart = nullptr;
  398. }
  399. void PLCDocument::updateConnections()
  400. {
  401. for (ConnectionLine *line : m_connections) {
  402. PLCItem *start = line->startItem();
  403. PLCItem *end = line->endItem();
  404. if (m_activeItems.contains(start) && m_activeItems.contains(end)) {
  405. line->setPen(QPen(Qt::green, 2));
  406. } else {
  407. line->setPen(QPen(Qt::black, 1));
  408. }
  409. }
  410. }
  411. void PLCDocument::setSimulationRunning(bool running)
  412. {
  413. m_simulationRunning = running;
  414. if (!running) {
  415. resetSimulation();
  416. }
  417. }
  418. void PLCDocument::resetSimulation()
  419. {
  420. m_activeItems.clear();
  421. for (QGraphicsItem *item : m_scene->items()) {
  422. if (PLCItem *plcItem = dynamic_cast<PLCItem*>(item)) {
  423. plcItem->setState(false);
  424. }
  425. }
  426. updateConnections();
  427. }
  428. void PLCDocument::createContextMenu(QPoint globalPos)
  429. {
  430. QMenu menu(this);
  431. QAction *runAction = menu.addAction("运行模拟");
  432. QAction *stopAction = menu.addAction("停止模拟");
  433. QAction *resetAction = menu.addAction("重置模拟");
  434. menu.addSeparator();
  435. QAction *nameAction = menu.addAction("重命名");
  436. menu.addSeparator();
  437. QAction *tableSizeAction = menu.addAction("调整表格尺寸");
  438. QAction *tableGridAction = menu.addAction("隐藏/显示网格");
  439. connect(runAction, &QAction::triggered, this, [this]() {
  440. setSimulationRunning(true);
  441. });
  442. connect(stopAction, &QAction::triggered, this, [this]() {
  443. setSimulationRunning(false);
  444. });
  445. connect(resetAction, &QAction::triggered, this, &PLCDocument::resetSimulation);
  446. connect(nameAction, &QAction::triggered, this, [this]() {
  447. QList<QGraphicsItem*> selected = m_scene->selectedItems();
  448. if (selected.size() == 1) {
  449. if (PLCItem *item = dynamic_cast<PLCItem*>(selected.first())) {
  450. QString name = QInputDialog::getText(this, "重命名", "输入名称:",
  451. QLineEdit::Normal, item->name());
  452. if (!name.isEmpty()) {
  453. item->setName(name);
  454. setModified(true);
  455. }
  456. }
  457. }
  458. });
  459. connect(tableSizeAction, &QAction::triggered, this, [this]() {
  460. bool ok;
  461. int cellSize = QInputDialog::getInt(this, "单元格大小", "输入单元格大小(像素):",
  462. m_cellSize, 10, 100, 10, &ok);
  463. if (ok && cellSize != m_cellSize) {
  464. m_cellSize = cellSize;
  465. m_view->setFixedSize(m_columns * m_cellSize, m_rows * m_cellSize);
  466. createRealTable();
  467. setModified(true);
  468. }
  469. });
  470. connect(tableGridAction, &QAction::triggered, this, [this]() {
  471. for (auto line : m_horizontalLines) {
  472. line->setVisible(!line->isVisible());
  473. }
  474. for (auto line : m_verticalLines) {
  475. line->setVisible(!line->isVisible());
  476. }
  477. });
  478. menu.exec(globalPos);
  479. }
  480. bool PLCDocument::saveToFile(const QString &filePath)
  481. {
  482. QFile file(filePath);
  483. if (!file.open(QIODevice::WriteOnly)) {
  484. QMessageBox::warning(this, "保存失败", "无法打开文件进行写入:" + filePath);
  485. return false;
  486. }
  487. // 创建JSON文档结构
  488. QJsonObject docObject;
  489. docObject["type"] = "PLCDocument";
  490. // 保存表格参数
  491. docObject["table_rows"] = m_rows;
  492. docObject["table_columns"] = m_columns;
  493. docObject["table_cell_size"] = m_cellSize;
  494. // 序列化所有PLC元件 - 改为保存单元格坐标
  495. QJsonArray itemsArray;
  496. for (QGraphicsItem *item : m_scene->items()) {
  497. if (PLCItem *plcItem = dynamic_cast<PLCItem*>(item)) {
  498. QJsonObject itemObj;
  499. itemObj["type"] = plcItem->itemType();
  500. int col = static_cast<int>(plcItem->x() / m_cellSize);
  501. int row = static_cast<int>(plcItem->y() / m_cellSize);
  502. itemObj["col"] = col; // 保存列索引
  503. itemObj["row"] = row; // 保存行索引
  504. itemObj["name"] = plcItem->name();
  505. itemObj["active"] = plcItem->isActive();
  506. itemsArray.append(itemObj);
  507. }
  508. }
  509. docObject["items"] = itemsArray;
  510. // 序列化所有连接 - 改为保存起始和终止元件的坐标
  511. QJsonArray connectionsArray;
  512. for (ConnectionLine *line : m_connections) {
  513. QJsonObject connObj;
  514. // 通过坐标查找起始和终止元件
  515. int startCol = static_cast<int>(line->startItem()->x() / m_cellSize);
  516. int startRow = static_cast<int>(line->startItem()->y() / m_cellSize);
  517. int endCol = static_cast<int>(line->endItem()->x() / m_cellSize);
  518. int endRow = static_cast<int>(line->endItem()->y() / m_cellSize);
  519. connObj["startCol"] = startCol;
  520. connObj["startRow"] = startRow;
  521. connObj["endCol"] = endCol;
  522. connObj["endRow"] = endRow;
  523. connectionsArray.append(connObj);
  524. }
  525. docObject["connections"] = connectionsArray;
  526. // 写入文件
  527. QJsonDocument doc(docObject);
  528. file.write(doc.toJson());
  529. file.close();
  530. // 更新文档状态
  531. setFilePath(filePath);
  532. setModified(false);
  533. return true;
  534. }
  535. bool PLCDocument::loadFromFile(const QString &filePath)
  536. {
  537. QFile file(filePath);
  538. if (!file.open(QIODevice::ReadOnly)) {
  539. QMessageBox::warning(this, "打开失败", "无法打开文件:" + filePath);
  540. return false;
  541. }
  542. // 读取并解析JSON
  543. QByteArray data = file.readAll();
  544. file.close();
  545. QJsonParseError parseError;
  546. QJsonDocument doc = QJsonDocument::fromJson(data, &parseError);
  547. if (parseError.error != QJsonParseError::NoError) {
  548. QMessageBox::warning(this, "文件格式错误", "JSON解析错误: " + parseError.errorString());
  549. return false;
  550. }
  551. if (doc.isNull() || !doc.isObject()) {
  552. QMessageBox::warning(this, "文件格式错误", "不是有效的PLC文档");
  553. return false;
  554. }
  555. // 解析文档
  556. QJsonObject docObject = doc.object();
  557. if (docObject["type"].toString() != "PLCDocument") {
  558. QMessageBox::warning(this, "文件类型错误", "不是有效的PLC文档");
  559. return false;
  560. }
  561. // 获取表格参数
  562. m_rows = docObject["table_rows"].toInt(DEFAULT_ROWS);
  563. m_columns = docObject["table_columns"].toInt(DEFAULT_COLUMNS);
  564. m_cellSize = docObject["table_cell_size"].toInt(DEFAULT_CELL_SIZE);
  565. // 停止任何模拟
  566. setSimulationRunning(false);
  567. // 安全地重置场景
  568. m_scene->blockSignals(true); // 防止在加载过程中发送变化信号
  569. // 清理现有内容 - 更安全的方式
  570. for (auto conn : m_connections) {
  571. m_scene->removeItem(conn);
  572. delete conn;
  573. }
  574. m_connections.clear();
  575. QList<QGraphicsItem*> allItems = m_scene->items();
  576. for (QGraphicsItem* item : allItems) {
  577. // 保留表格项,只删除PLC元件
  578. if (dynamic_cast<PLCItem*>(item) || dynamic_cast<ConnectionLine*>(item)) {
  579. m_scene->removeItem(item);
  580. delete item;
  581. }
  582. }
  583. m_activeItems.clear();
  584. m_connectionStart = nullptr;
  585. delete m_tempLine;
  586. m_tempLine = nullptr;
  587. // 创建表格
  588. createRealTable(); // 重新创建表格以确保正确
  589. // 加载PLC元件
  590. QMap<QPair<int, int>, PLCItem*> itemMap; // (列, 行) -> PLCItem
  591. QJsonArray itemsArray = docObject["items"].toArray();
  592. for (const QJsonValue &itemValue : itemsArray) {
  593. QJsonObject itemObj = itemValue.toObject();
  594. PLCItem::ItemType type = static_cast<PLCItem::ItemType>(itemObj["type"].toInt());
  595. int col = itemObj["col"].toInt();
  596. int row = itemObj["row"].toInt();
  597. QString name = itemObj["name"].toString();
  598. bool active = itemObj["active"].toBool();
  599. // 确保位置在表格范围内
  600. col = qBound(0, col, m_columns - 1);
  601. row = qBound(0, row, m_rows - 1);
  602. PLCItem *item = createItemByType(type);
  603. if (item) {
  604. QPointF pos(col * m_cellSize + m_cellSize / 2.0,
  605. row * m_cellSize + m_cellSize / 2.0);
  606. item->setPos(pos);
  607. item->setName(name);
  608. item->setState(active);
  609. m_scene->addItem(item);
  610. // 添加到映射表
  611. itemMap.insert(qMakePair(col, row), item);
  612. if (active) {
  613. m_activeItems.insert(item);
  614. }
  615. connect(item, &PLCItem::stateChanged, this, [this, item](bool active) {
  616. if (m_simulationRunning) {
  617. if (active) m_activeItems.insert(item);
  618. else m_activeItems.remove(item);
  619. updateConnections();
  620. }
  621. });
  622. }
  623. }
  624. // 加载连接
  625. QJsonArray connectionsArray = docObject["connections"].toArray();
  626. for (const QJsonValue &connValue : connectionsArray) {
  627. QJsonObject connObj = connValue.toObject();
  628. int startCol = connObj["startCol"].toInt();
  629. int startRow = connObj["startRow"].toInt();
  630. int endCol = connObj["endCol"].toInt();
  631. int endRow = connObj["endRow"].toInt();
  632. PLCItem *startItem = itemMap.value(qMakePair(startCol, startRow));
  633. PLCItem *endItem = itemMap.value(qMakePair(endCol, endRow));
  634. if (startItem && endItem && startItem != endItem) {
  635. ConnectionLine *line = new ConnectionLine(startItem, endItem);
  636. m_scene->addItem(line);
  637. m_connections.append(line);
  638. }
  639. }
  640. // 完成加载
  641. m_scene->blockSignals(false);
  642. handleSceneChanged(); // 手动触发位置更新
  643. // 更新UI状态
  644. setFilePath(filePath);
  645. setModified(false);
  646. qDebug() << "成功加载文件:" << filePath << "项目数:" << itemMap.size()
  647. << "连接数:" << m_connections.size();
  648. return true;
  649. }
  650. PLCItem* PLCDocument::createItemByType(PLCItem::ItemType type)
  651. {
  652. switch (type) {
  653. case PLCItem::NormallyOpen: return new NormallyOpenItem;
  654. case PLCItem::NormallyClosed: return new NormallyClosedItem;
  655. case PLCItem::GreaterThan: return new GreaterThanItem;
  656. case PLCItem::GreaterEqual: return new GreaterEqualItem;
  657. case PLCItem::LessThan: return new LessThanItem;
  658. case PLCItem::LessEqual: return new LessEqualItem;
  659. case PLCItem::Equal: return new EqualItem;
  660. case PLCItem::Coil: return new CoilItem;
  661. default:
  662. qWarning() << "未知的PLC元件类型:" << type;
  663. return nullptr;
  664. }
  665. }
  666. // ConnectionLine 实现
  667. ConnectionLine::ConnectionLine(PLCItem *start, PLCItem *end, QGraphicsItem *parent)
  668. : QGraphicsLineItem(parent), m_startItem(start), m_endItem(end)
  669. {
  670. setPen(QPen(Qt::black, 1));
  671. setZValue(-1);
  672. updatePosition();
  673. }
  674. void ConnectionLine::updatePosition()
  675. {
  676. if (m_startItem && m_endItem) {
  677. // 基于控件端子位置更新连线
  678. QLineF line(m_startItem->rightTerminal(), m_endItem->leftTerminal());
  679. setLine(line);
  680. }
  681. }