#include "plcdocument.h" #include "plcitems.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include PLCDocument::PLCDocument(QWidget *parent) : BaseDocument(PLC, parent) , m_rows(DEFAULT_ROWS) , m_columns(DEFAULT_COLUMNS) , m_cellSize(DEFAULT_CELL_SIZE) { m_scene = new QGraphicsScene(this); m_scene->setSceneRect(0, 0, m_columns * m_cellSize, m_rows * m_cellSize); m_view = new QGraphicsView(m_scene, this); m_view->setRenderHint(QPainter::Antialiasing); m_view->setDragMode(QGraphicsView::RubberBandDrag); m_view->setMouseTracking(true); m_view->viewport()->installEventFilter(this); m_view->setAlignment(Qt::AlignLeft | Qt::AlignTop); QScrollArea *scrollArea = new QScrollArea(this); scrollArea->setWidget(m_view); scrollArea->setWidgetResizable(true); auto layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(scrollArea); setLayout(layout); m_view->setAcceptDrops(true); createRealTable(); connect(m_scene, &QGraphicsScene::selectionChanged, this, [this]() { auto items = m_scene->selectedItems(); if (items.size() == 1 && dynamic_cast(items.first())) { clearCurrentConnection(); } }); connect(m_scene, &QGraphicsScene::changed, this, &PLCDocument::handleSceneChanged); } PLCDocument::~PLCDocument() { qDeleteAll(m_connections); m_copiedItem = nullptr; } QString PLCDocument::title() const { return "PLC文档"; } void PLCDocument::createRealTable() { QRectF tableRect(0, 0, m_columns * m_cellSize, m_rows * m_cellSize); m_tableFrame = new QGraphicsRectItem(tableRect); m_tableFrame->setPen(QPen(Qt::black, 2)); m_tableFrame->setBrush(QBrush(QColor(228, 255, 202))); m_tableFrame->setZValue(-10); m_scene->addItem(m_tableFrame); for (int col = 0; col <= m_columns; col++) { int x = col * m_cellSize; QGraphicsLineItem* line = new QGraphicsLineItem(x, 0, x, m_rows * m_cellSize); line->setPen(QPen(Qt::darkGray, 1)); line->setZValue(-9); m_scene->addItem(line); m_verticalLines.append(line); } for (int row = 0; row <= m_rows; row++) { int y = row * m_cellSize; QGraphicsLineItem* line = new QGraphicsLineItem(0, y, m_columns * m_cellSize, y); line->setPen(QPen(Qt::darkGray, 1)); line->setZValue(-9); m_scene->addItem(line); m_horizontalLines.append(line); } for (int row = 0; row < m_rows; row++) { const int terminalSize = 15; QPointF terminalPos(0, row * m_cellSize + m_cellSize / 2.0 - terminalSize / 2); QGraphicsEllipseItem* terminal = new QGraphicsEllipseItem( terminalPos.x(), terminalPos.y(), terminalSize, terminalSize); terminal->setFlag(QGraphicsItem::ItemIsSelectable, true); terminal->setFlag(QGraphicsItem::ItemIsFocusable, true); terminal->setAcceptHoverEvents(true); terminal->setVisible(true); m_scene->addItem(terminal); m_rowTerminals.append(terminal); m_rowTerminalMap[terminal] = row; } } QPointF PLCDocument::snapToCellCenter(const QPointF& pos) const { int col = static_cast(std::floor(pos.x() / m_cellSize)); int row = static_cast(std::floor(pos.y() / m_cellSize)); col = qBound(0, col, m_columns - 1); row = qBound(0, row, m_rows - 1); return QPointF(col * m_cellSize + m_cellSize / 2.0, row * m_cellSize + m_cellSize / 2.0); } QPointF PLCDocument::constrainToTable(const QPointF &pos) const { int col = static_cast(std::floor(pos.x() / m_cellSize)); int row = static_cast(std::floor(pos.y() / m_cellSize)); col = qBound(0, col, m_columns - 1); row = qBound(0, row, m_rows - 1); return QPointF(col * m_cellSize + m_cellSize / 2.0, row * m_cellSize + m_cellSize / 2.0); } bool PLCDocument::eventFilter(QObject *obj, QEvent *event) { if (obj == m_view->viewport()) { if (event->type() == QEvent::DragEnter) { QDragEnterEvent *dragEvent = static_cast(event); if (dragEvent->mimeData()->hasText()) { dragEvent->acceptProposedAction(); return true; } } else if (event->type() == QEvent::DragMove) { QDragMoveEvent *dragEvent = static_cast(event); if(dragEvent->mimeData()->hasText()) { dragEvent->acceptProposedAction(); return true; } } else if (event->type() == QEvent::Drop) { QDropEvent *dropEvent = static_cast(event); const QMimeData *mimeData = dropEvent->mimeData(); if (mimeData->hasText()) { QPoint viewportPos = m_view->viewport()->mapFromParent(dropEvent->pos()); QPointF scenePos = m_view->mapToScene(viewportPos); QPointF cellCenter = snapToCellCenter(scenePos); QSet occupiedCells; for (QGraphicsItem* item : m_scene->items()) { if (PLCItem* plcItem = dynamic_cast(item)) { int col = static_cast(plcItem->x() / m_cellSize); int row = static_cast(plcItem->y() / m_cellSize); occupiedCells.insert(col * 1000 + row); } } int col = static_cast(cellCenter.x() / m_cellSize); int row = static_cast(cellCenter.y() / m_cellSize); int cellKey = col * 1000 + row; if (occupiedCells.contains(cellKey)) { bool found = false; for (int offset = 1; offset < m_columns; offset++) { int newCol = col + offset; if (newCol < m_columns) { int newKey = newCol * 1000 + row; if (!occupiedCells.contains(newKey)) { cellCenter = QPointF(newCol * m_cellSize + m_cellSize / 2.0, row * m_cellSize + m_cellSize / 2.0); found = true; break; } } } if (!found) { for (int offset = 1; offset < m_rows; offset++) { int newRow = row + offset; if (newRow < m_rows) { int newKey = col * 1000 + newRow; if (!occupiedCells.contains(newKey)) { cellCenter = QPointF(col * m_cellSize + m_cellSize / 2.0, newRow * m_cellSize + m_cellSize / 2.0); found = true; break; } } } } } QString toolType = mimeData->text(); createPLCItem(toolType, cellCenter); m_currentTool.clear(); dropEvent->acceptProposedAction(); return true; } } } if (obj != m_view->viewport()) { return QWidget::eventFilter(obj, event); } if (event->type() == QEvent::MouseMove && m_tempLine) { QMouseEvent *mouseEvent = static_cast(event); QPointF scenePos = m_view->mapToScene(mouseEvent->pos()); QPointF cellCenter = snapToCellCenter(scenePos); if (m_connectionSourceType != ConnectionNone) { if (m_connectionSourceType == ConnectionFromItem && m_connectionStartItem) { if (m_startTerminal == LeftTerminal) { QPointF startPos = m_connectionStartItem->leftTerminal(); QLineF line(startPos, cellCenter); m_tempLine->setLine(line); } else if (m_startTerminal == RightTerminal) { QPointF startPos = m_connectionStartItem->rightTerminal(); QLineF line(startPos, cellCenter); m_tempLine->setLine(line); } } else if (m_connectionSourceType == ConnectionFromRowTerminal) { QPointF startPos(0, (m_connectionStartRow + 0.5) * m_cellSize); QLineF line(startPos, cellCenter); m_tempLine->setLine(line); } } } if (event->type() == QEvent::MouseButtonPress) { QMouseEvent *mouseEvent = static_cast(event); if (mouseEvent->button() == Qt::RightButton) { createContextMenu(mouseEvent->globalPos()); return true; } if (mouseEvent->button() == Qt::LeftButton) { QPointF scenePos = m_view->mapToScene(mouseEvent->pos()); QGraphicsEllipseItem* clickedRowTerminal = nullptr; int rowTerminalIndex = -1; for (auto terminal : m_rowTerminals) { QPointF localPos = terminal->mapFromScene(scenePos); if (terminal->contains(localPos)) { clickedRowTerminal = terminal; rowTerminalIndex = m_rowTerminalMap[terminal]; break; } } if (clickedRowTerminal) { if (m_connectionSourceType != ConnectionNone) { if (tryEndConnection(nullptr, RowTerminal)) { return true; } } else { startConnectionFromRowTerminal(rowTerminalIndex); return true; } } QPointF cellCenter = snapToCellCenter(scenePos); QGraphicsItem *clickedItem = m_scene->itemAt(cellCenter, m_view->transform()); PLCItem *plcItem = dynamic_cast(clickedItem); if (!m_currentTool.isEmpty()) { QSet occupiedCells; for (QGraphicsItem* item : m_scene->items()) { if (PLCItem* existingItem = dynamic_cast(item)) { int col = static_cast(existingItem->x() / m_cellSize); int row = static_cast(existingItem->y() / m_cellSize); occupiedCells.insert(col * 1000 + row); } } int col = static_cast(cellCenter.x() / m_cellSize); int row = static_cast(cellCenter.y() / m_cellSize); int cellKey = col * 1000 + row; if (!occupiedCells.contains(cellKey)) { createPLCItem(m_currentTool, cellCenter); m_currentTool.clear(); return true; } else { QMessageBox::warning(this, "位置被占用", "此单元格已被占用,请选择其他位置。"); return true; } } if (plcItem) { TerminalType clickedTerminal = whichTerminal(scenePos, plcItem); if (m_connectionSourceType != ConnectionNone) { if (clickedTerminal != NoTerminal) { if (tryEndConnection(plcItem, clickedTerminal)) { return true; } } } else { if (clickedTerminal != NoTerminal && !isTerminalUsed(plcItem, clickedTerminal)) { startConnection(plcItem, clickedTerminal); return true; } } } } } return QWidget::eventFilter(obj, event); } void PLCDocument::startConnectionFromRowTerminal(int row) { clearCurrentConnection(); m_connectionSourceType = ConnectionFromRowTerminal; m_connectionStartRow = row; m_tempLine = new QGraphicsLineItem; m_tempLine->setPen(QPen(Qt::blue, 2, Qt::DashLine)); QPointF startPos(0, (row + 0.5) * m_cellSize); m_tempLine->setLine(QLineF(startPos, startPos)); m_scene->addItem(m_tempLine); } PLCDocument::TerminalType PLCDocument::whichTerminal(const QPointF& scenePos, PLCItem* item) const { const qreal terminalRadius = 20.0; QPointF leftTerminal = item->leftTerminal(); qreal dxLeft = scenePos.x() - leftTerminal.x(); qreal dyLeft = scenePos.y() - leftTerminal.y(); qreal distLeft = std::sqrt(dxLeft*dxLeft + dyLeft*dyLeft); if (distLeft <= terminalRadius) { return LeftTerminal; } QPointF rightTerminal = item->rightTerminal(); qreal dxRight = scenePos.x() - rightTerminal.x(); qreal dyRight = scenePos.y() - rightTerminal.y(); qreal distRight = std::sqrt(dxRight*dxRight + dyRight*dyRight); if (distRight <= terminalRadius) { return RightTerminal; } return NoTerminal; } bool PLCDocument::isTerminalUsed(PLCItem* item, TerminalType terminal) const { if (item->itemType() == PLCItem::Coil && terminal == RightTerminal) { return true; } for (ConnectionLine* line : m_connections) { if (line->startItem() == item && line->startTerminal() == terminal) { return true; } if (line->endItem() == item && line->endTerminal() == terminal) { return true; } } return false; } void PLCDocument::startConnection(PLCItem *startItem, TerminalType startTerminal) { clearCurrentConnection(); m_connectionSourceType = ConnectionFromItem; m_connectionStartItem = startItem; m_startTerminal = startTerminal; m_tempLine = new QGraphicsLineItem; m_tempLine->setPen(QPen(Qt::blue, 2, Qt::DashLine)); QPointF startPos; if (startTerminal == LeftTerminal) { startPos = startItem->leftTerminal(); } else if (startTerminal == RightTerminal) { startPos = startItem->rightTerminal(); } m_tempLine->setLine(QLineF(startPos, startPos)); m_scene->addItem(m_tempLine); } bool PLCDocument::tryEndConnection(PLCItem *endItem, TerminalType endTerminal) { if (m_connectionSourceType == ConnectionFromRowTerminal) { if (endTerminal != LeftTerminal) { QMessageBox::warning(this, "连线错误", "行触点只能连接到元件的左端子"); clearCurrentConnection(); return false; } int endRow = static_cast(endItem->y() / m_cellSize); if (endRow != m_connectionStartRow) { QMessageBox::warning(this, "连线错误", "行触点只能连接到同一行元件的左端子"); clearCurrentConnection(); return false; } if (isTerminalUsed(endItem, LeftTerminal)) { QMessageBox::warning(this, "连线错误", "该元件的左端子已被使用"); clearCurrentConnection(); return false; } ConnectionLine *connection = new ConnectionLine( nullptr, RowTerminal, endItem, LeftTerminal ); connection->setStartTerminalPoint(QPointF(0, (m_connectionStartRow + 0.5) * m_cellSize)); connection->setRowTerminalSourceRow(m_connectionStartRow); m_scene->addItem(connection); m_connections.append(connection); terminalConnections[endItem][LeftTerminal] = true; clearCurrentConnection(); setModified(true); return true; } if (endTerminal == RowTerminal) { if (m_startTerminal != LeftTerminal) { QMessageBox::warning(this, "连线错误", "行触点只能连接到元件的左端子"); clearCurrentConnection(); return false; } int startRow = static_cast(m_connectionStartItem->y() / m_cellSize); ConnectionLine *connection = new ConnectionLine( m_connectionStartItem, m_startTerminal, nullptr, RowTerminal ); connection->setEndTerminalPoint(QPointF(0, (startRow + 0.5) * m_cellSize)); connection->setRowTerminalTargetRow(startRow); m_scene->addItem(connection); m_connections.append(connection); terminalConnections[m_connectionStartItem][m_startTerminal] = true; clearCurrentConnection(); setModified(true); return true; } if (m_connectionSourceType == ConnectionFromItem && m_connectionStartItem) { if (endItem == m_connectionStartItem && endTerminal == m_startTerminal) { clearCurrentConnection(); return false; } int startCol = static_cast(m_connectionStartItem->pos().x() / m_cellSize); int endCol = static_cast(endItem->pos().x() / m_cellSize); if (m_startTerminal == LeftTerminal) { if (endTerminal != RightTerminal) { QMessageBox::warning(this, "连线错误", "左端子只能连接到其他元件的右端子"); clearCurrentConnection(); return false; } if (startCol <= endCol) { QMessageBox::warning(this, "连线错误", "左端子只能连接到左边元件的右端子"); clearCurrentConnection(); return false; } } else if (m_startTerminal == RightTerminal) { if (endTerminal != LeftTerminal) { QMessageBox::warning(this, "连线错误", "右端子只能连接到其他元件的左端子"); clearCurrentConnection(); return false; } if (startCol >= endCol) { QMessageBox::warning(this, "连线错误", "右端子只能连接到右边元件的左端子"); clearCurrentConnection(); return false; } } if (isTerminalUsed(endItem, endTerminal)) { QMessageBox::warning(this, "连线错误", "该端子已被使用"); clearCurrentConnection(); return false; } ConnectionLine *connection = new ConnectionLine( m_connectionStartItem, m_startTerminal, endItem, endTerminal ); m_scene->addItem(connection); m_connections.append(connection); terminalConnections[m_connectionStartItem][m_startTerminal] = true; terminalConnections[endItem][endTerminal] = true; clearCurrentConnection(); setModified(true); return true; } return false; } void PLCDocument::clearCurrentConnection() { if (m_tempLine) { m_scene->removeItem(m_tempLine); delete m_tempLine; m_tempLine = nullptr; } m_connectionSourceType = ConnectionNone; m_connectionStartItem = nullptr; m_startTerminal = NoTerminal; m_connectionStartRow = -1; } void PLCDocument::createPLCItem(const QString &type, const QPointF &pos) { PLCItem *item = nullptr; if (type == "常开") { item = new NormallyOpenItem; } else if (type == "常闭") { item = new NormallyClosedItem; } else if (type == "大于") { item = new GreaterThanItem; } else if (type == "大于等于") { item = new GreaterEqualItem; } else if (type == "小于") { item = new LessThanItem; } else if (type == "小于等于") { item = new LessEqualItem; } else if (type == "等于") { item = new EqualItem; } else if (type == "线圈") { item = new CoilItem; } if (item) { QPointF constrainedPos = constrainToTable(pos); item->setPos(constrainedPos); m_scene->addItem(item); setModified(true); terminalConnections[item][LeftTerminal] = false; terminalConnections[item][RightTerminal] = false; m_lastValidPositions[item] = constrainedPos; connect(item, &PLCItem::stateChanged, this, [this, item](bool active) { if (m_simulationRunning) { if (active) { m_activeItems.insert(item); } else { m_activeItems.remove(item); } updateConnections(); } }); } } void PLCDocument::handleSceneChanged() { for (auto* connection : qAsConst(m_connections)) { connection->updatePosition(); } if (m_tempLine && m_connectionSourceType != ConnectionNone) { QPointF cursorPos = m_view->mapFromGlobal(QCursor::pos()); QPointF scenePos = m_view->mapToScene(cursorPos.toPoint()); QLineF line = m_tempLine->line(); line.setP2(scenePos); m_tempLine->setLine(line); } for (QGraphicsItem *item : m_scene->items()) { if (PLCItem *plcItem = dynamic_cast(item)) { handleItemPositionChange(plcItem); } } } QList PLCDocument::allPLCItems() const { QList items; for (QGraphicsItem *item : m_scene->items()) { if (PLCItem *plcItem = dynamic_cast(item)) { items.append(plcItem); } } return items; } bool PLCDocument::isCellOccupied(int col, int row, PLCItem* excludeItem) const { for (PLCItem* item : allPLCItems()) { if (item == excludeItem) continue; int itemCol = static_cast(item->x() / m_cellSize); int itemRow = static_cast(item->y() / m_cellSize); if (itemCol == col && itemRow == row) { return true; } } return false; } // 计算元件在连线上的位置比例(0-1) qreal PLCDocument::getPositionRatioOnLine(PLCItem* item, ConnectionLine* line) const { if (!item || !line) return -1; QLineF lineSeg = line->line(); QPointF itemPos = item->pos(); // 计算投影比例 qreal dx = lineSeg.dx(); qreal dy = lineSeg.dy(); qreal lengthSquared = dx*dx + dy*dy; if (lengthSquared == 0) return 0; qreal t = ((itemPos.x() - lineSeg.p1().x()) * dx + (itemPos.y() - lineSeg.p1().y()) * dy) / lengthSquared; return qBound(0.0, t, 1.0); } void PLCDocument::handleItemPositionChange(PLCItem *item) { if (!item) return; if (m_loadingFile) { QPointF newPos = constrainToTable(item->pos()); item->setPos(newPos); m_lastValidPositions[item] = newPos; return; } if (m_processingItems.contains(item)) { return; } m_processingItems.insert(item); QPointF newPos = constrainToTable(item->pos()); int newCol = static_cast(newPos.x() / m_cellSize); int newRow = static_cast(newPos.y() / m_cellSize); if (isCellOccupied(newCol, newRow, item)) { if (m_lastValidPositions.contains(item)) { item->setPos(m_lastValidPositions[item]); QMessageBox::warning(this, "移动失败", "无法移动,该单元格已被占用"); } } else { int oldCol = static_cast(m_lastValidPositions[item].x() / m_cellSize); item->setPos(newPos); m_lastValidPositions[item] = newPos; if (newCol != oldCol) { removeInvalidConnectionsForItem(item); } // 获取要拆分的连线并排序 QList connectionsToSplit = findConnectionsUnderItem(item); // 按元件在连线上的位置排序,确保从左到右处理 std::sort(connectionsToSplit.begin(), connectionsToSplit.end(), [this, item](ConnectionLine* a, ConnectionLine* b) { return getPositionRatioOnLine(item, a) < getPositionRatioOnLine(item, b); }); // 处理每条连线 for (ConnectionLine* line : connectionsToSplit) { if (m_connections.contains(line) && !line->isProcessing()) { splitConnection(line, item); } } // 更新所有连接位置 for (ConnectionLine *line : m_connections) { if (line) { line->updatePosition(); } } } m_processingItems.remove(item); } //QList PLCDocument::findConnectionsUnderItem(PLCItem* item) const //{ // QList result; // if (!item) return result; // QRectF itemRect = item->boundingRect(); // itemRect.adjust(-2, -2, 2, 2); // QPolygonF itemPolygon = QPolygonF(itemRect).translated(item->pos()); // foreach (ConnectionLine* line, m_connections) { // if (!line || line->isProcessing()) continue; // QLineF lineGeometry = line->line(); // QRectF lineRect(lineGeometry.p1(), lineGeometry.p2()); // lineRect.adjust(-10, -10, 10, 10); // if (itemPolygon.intersects(lineRect)) { // result.append(line); // } // } // return result; //} // 在PLCDocument类的splitConnection函数中,修改逻辑以支持行触点连线的插入 bool PLCDocument::splitConnection(ConnectionLine* originalLine, PLCItem* insertItem) { if (!originalLine || !insertItem || originalLine->isProcessing()) { return false; } originalLine->setProcessing(true); PLCItem* originalStart = originalLine->startItem(); TerminalType originalStartTerminal = originalLine->startTerminal(); PLCItem* originalEnd = originalLine->endItem(); TerminalType originalEndTerminal = originalLine->endTerminal(); // 保存原始行触点信息 int sourceRow = originalLine->rowTerminalSourceRow(); int targetRow = originalLine->rowTerminalTargetRow(); PLCItem* logicalInput = originalStart; TerminalType inputTerminal = originalStartTerminal; PLCItem* logicalOutput = originalEnd; TerminalType outputTerminal = originalEndTerminal; // 标准化方向时同步交换行触点信息 if ((originalStartTerminal == LeftTerminal && originalEndTerminal == RightTerminal) || (originalStartTerminal == LeftTerminal && originalEndTerminal == RowTerminal)) { std::swap(logicalInput, logicalOutput); std::swap(inputTerminal, outputTerminal); std::swap(sourceRow, targetRow); // 同步交换行号 } // 位置有效性校验 int inputCol = (inputTerminal == RowTerminal) ? 0 : static_cast(logicalInput->x() / m_cellSize); int outputCol = (outputTerminal == RowTerminal) ? 0 : static_cast(logicalOutput->x() / m_cellSize); int insertCol = static_cast(insertItem->x() / m_cellSize); // 统一位置验证逻辑 bool isBetween = (insertCol > qMin(inputCol, outputCol)) && (insertCol < qMax(inputCol, outputCol)); if (!isBetween) { originalLine->setProcessing(false); return false; } // 检查端子可用性 if (isTerminalUsed(insertItem, LeftTerminal) || isTerminalUsed(insertItem, RightTerminal)) { originalLine->setProcessing(false); return false; } // 创建新连线 ConnectionLine* line1 = new ConnectionLine( logicalInput, inputTerminal, insertItem, LeftTerminal ); // 保留行触点源信息 if (inputTerminal == RowTerminal) { line1->setStartTerminalPoint(QPointF(0, (sourceRow + 0.5) * m_cellSize)); line1->setRowTerminalSourceRow(sourceRow); } ConnectionLine* line2 = new ConnectionLine( insertItem, RightTerminal, logicalOutput, outputTerminal ); // 保留行触点目标信息 if (outputTerminal == RowTerminal) { line2->setEndTerminalPoint(QPointF(0, (targetRow + 0.5) * m_cellSize)); line2->setRowTerminalTargetRow(targetRow); } // 添加新连线 m_scene->addItem(line1); m_scene->addItem(line2); m_connections.append(line1); m_connections.append(line2); // 更新端子状态 terminalConnections[insertItem][LeftTerminal] = true; terminalConnections[insertItem][RightTerminal] = true; // 移除原连线 m_connections.removeAll(originalLine); m_scene->removeItem(originalLine); delete originalLine; setModified(true); originalLine->setProcessing(false); return true; } // 补充:确保行触点连线能被检测为"需要拆分的连线"(扩展findConnectionsUnderItem) QList PLCDocument::findConnectionsUnderItem(PLCItem* item) const { QList result; if (!item) return result; // 元件的边界框(扩大检测范围,确保行触点连线也能被检测) QRectF itemRect = item->boundingRect(); itemRect.adjust(-5, -5, 5, 5); // 扩大边界框,提高检测灵敏度 QPolygonF itemPolygon = QPolygonF(itemRect).translated(item->pos()); foreach (ConnectionLine* line, m_connections) { if (!line || line->isProcessing()) continue; // 行触点连线的特殊处理:起点是(0, row),终点是元件左端子 QLineF lineGeometry = line->line(); QRectF lineRect = QRectF(lineGeometry.p1(), lineGeometry.p2()).normalized(); lineRect.adjust(-10, -10, 10, 10); // 扩大连线的检测范围 // 检查元件是否与连线相交(包括行触点连线) if (itemPolygon.intersects(lineRect)) { result.append(line); } } return result; } void PLCDocument::removeInvalidConnectionsForItem(PLCItem* movedItem) { if (!movedItem || m_connections.isEmpty()) return; if (m_cellSize <= 0) { qWarning() << "Invalid cell size!"; return; } int movedCol = static_cast(movedItem->x() / m_cellSize); QSet connectionsToRemove; bool shouldRemoveAll = false; for (ConnectionLine* line : m_connections) { if (!line) continue; PLCItem* otherItem = nullptr; TerminalType movedTerminal = NoTerminal; TerminalType otherTerminal = NoTerminal; if (line->startItem() == movedItem) { otherItem = line->endItem(); movedTerminal = line->startTerminal(); otherTerminal = line->endTerminal(); } else if (line->endItem() == movedItem) { otherItem = line->startItem(); movedTerminal = line->endTerminal(); otherTerminal = line->startTerminal(); } else { continue; } if (!otherItem) continue; int otherCol = static_cast(otherItem->x() / m_cellSize); if (movedTerminal == LeftTerminal && otherTerminal == RightTerminal) { if (movedCol <= otherCol) { shouldRemoveAll = true; break; } } else if (movedTerminal == RightTerminal && otherTerminal == LeftTerminal) { if (movedCol >= otherCol) { shouldRemoveAll = true; break; } } } if (shouldRemoveAll) { for (ConnectionLine* line : m_connections) { if (!line) continue; if (line->startItem() == movedItem || line->endItem() == movedItem) { connectionsToRemove.insert(line); } } } for (ConnectionLine* line : connectionsToRemove) { if (!line) continue; if (line->startItem()) { terminalConnections[line->startItem()].remove(line->startTerminal()); } if (line->endItem()) { terminalConnections[line->endItem()].remove(line->endTerminal()); } if (m_scene && line->scene() == m_scene) { m_scene->removeItem(line); } if (m_connections.contains(line)) { m_connections.removeAll(line); } delete line; } if (!connectionsToRemove.isEmpty()) { setModified(true); } } void PLCDocument::setSimulationRunning(bool running) { m_simulationRunning = running; if (!running) { resetSimulation(); } } void PLCDocument::resetSimulation() { m_activeItems.clear(); for (QGraphicsItem *item : m_scene->items()) { if (PLCItem *plcItem = dynamic_cast(item)) { plcItem->setState(false); } } terminalConnections.clear(); updateConnections(); } void PLCDocument::updateConnections() { for (ConnectionLine *line : m_connections) { PLCItem *start = line->startItem(); PLCItem *end = line->endItem(); if (line->endTerminal() == RowTerminal || line->startTerminal() == RowTerminal) { line->setPen(QPen(Qt::green, 2)); continue; } if (m_activeItems.contains(start) && m_activeItems.contains(end)) { line->setPen(QPen(Qt::green, 2)); } else { line->setPen(QPen(Qt::black, 1)); } } } void PLCDocument::deleteSelectedItems() { QList selected = m_scene->selectedItems(); if (selected.isEmpty()) return; QList itemsToDelete; QList linesToDelete; QSet connectionsToDelete; for (QGraphicsItem* item : selected) { if (PLCItem* plcItem = dynamic_cast(item)) { itemsToDelete.append(plcItem); } else if (ConnectionLine* line = dynamic_cast(item)) { linesToDelete.append(line); } } for (ConnectionLine* line : m_connections) { if (line->startItem() && itemsToDelete.contains(line->startItem())) { connectionsToDelete.insert(line); } if (line->endItem() && itemsToDelete.contains(line->endItem())) { connectionsToDelete.insert(line); } } for (ConnectionLine* line : linesToDelete) { connectionsToDelete.insert(line); } for (ConnectionLine* line : connectionsToDelete) { if (!line) continue; if (line->startItem()) { terminalConnections[line->startItem()].remove(line->startTerminal()); } if (line->endItem()) { terminalConnections[line->endItem()].remove(line->endTerminal()); } if (m_scene && m_scene->items().contains(line)) { m_scene->removeItem(line); } m_connections.removeAll(line); delete line; } for (PLCItem* item : itemsToDelete) { terminalConnections.remove(item); m_lastValidPositions.remove(item); m_scene->removeItem(item); delete item; } setModified(true); } void PLCDocument::copySelectedItem() { QList selected = m_scene->selectedItems(); if (selected.size() != 1) return; if (PLCItem* item = dynamic_cast(selected.first())) { m_copiedItem = item; } } void PLCDocument::pasteItem() { if (!m_copiedItem) return; QPointF pastePos = snapToCellCenter(m_view->mapToScene(m_view->viewport()->mapFromGlobal(QCursor::pos()))); pastePos.setX(pastePos.x() - 70); pastePos.setY(pastePos.y() - 70); int col = static_cast(pastePos.x() / m_cellSize); int row = static_cast(pastePos.y() / m_cellSize); if (isCellOccupied(col, row, nullptr)) { QMessageBox::warning(this, "粘贴失败", "目标单元格已被占用"); return; } PLCItem* newItem = createItemByType(m_copiedItem->itemType()); if (!newItem) return; newItem->setPos(pastePos); newItem->setName(m_copiedItem->name() + "_副本"); m_scene->addItem(newItem); terminalConnections[newItem][LeftTerminal] = false; terminalConnections[newItem][RightTerminal] = false; m_lastValidPositions[newItem] = pastePos; setModified(true); } void PLCDocument::keyPressEvent(QKeyEvent* event) { if (event->key() == Qt::Key_Delete) { deleteSelectedItems(); event->accept(); return; } BaseDocument::keyPressEvent(event); } void PLCDocument::createContextMenu(QPoint globalPos) { QMenu menu(this); QAction* deleteAction = menu.addAction("删除"); QAction* copyAction = menu.addAction("复制"); QAction* pasteAction = menu.addAction("粘贴"); QList selected = m_scene->selectedItems(); deleteAction->setEnabled(!selected.isEmpty()); copyAction->setEnabled(selected.size() == 1&&dynamic_cast(selected.first())); pasteAction->setEnabled(m_copiedItem != nullptr); connect(deleteAction, &QAction::triggered, this, &PLCDocument::deleteSelectedItems); connect(copyAction, &QAction::triggered, this, &PLCDocument::copySelectedItem); connect(pasteAction, &QAction::triggered, this, &PLCDocument::pasteItem); menu.addSeparator(); QAction *tableGridAction = menu.addAction("隐藏/显示网格"); connect(tableGridAction, &QAction::triggered, this, [this]() { for (auto line : m_horizontalLines) { line->setVisible(!line->isVisible()); } for (auto line : m_verticalLines) { line->setVisible(!line->isVisible()); } }); menu.exec(globalPos + QPoint(10, 10)); } void PLCDocument::onRowTerminalConnection() { QList selected = m_scene->selectedItems(); if (selected.size() != 1) return; PLCItem* item = dynamic_cast(selected.first()); if (!item) return; int itemRow = static_cast(item->y() / m_cellSize); int itemCol = static_cast(item->x() / m_cellSize); if (itemCol != 0) { QMessageBox::warning(this, "连接错误", "只有最左侧列的元件可以连接到行触点"); return; } if (isTerminalUsed(item, LeftTerminal)) { QMessageBox::warning(this, "连接失败", "该元件的左端子已被使用"); return; } ConnectionLine* connection = new ConnectionLine( item, LeftTerminal, nullptr, RowTerminal ); connection->setRowTerminalTargetRow(itemRow); connection->setEndTerminalPoint(QPointF(0, (itemRow + 0.5) * m_cellSize)); connection->updatePosition(); m_scene->addItem(connection); m_connections.append(connection); terminalConnections[item][LeftTerminal] = true; setModified(true); } PLCItem* PLCDocument::createItemByType(PLCItem::ItemType type) { switch (type) { case PLCItem::NormallyOpen: return new NormallyOpenItem; case PLCItem::NormallyClosed: return new NormallyClosedItem; case PLCItem::GreaterThan: return new GreaterThanItem; case PLCItem::GreaterEqual: return new GreaterEqualItem; case PLCItem::LessThan: return new LessThanItem; case PLCItem::LessEqual: return new LessEqualItem; case PLCItem::Equal: return new EqualItem; case PLCItem::Coil: return new CoilItem; default: qWarning() << "未知的PLC元件类型:" << type; return nullptr; } } bool PLCDocument::saveToFile(const QString &filePath) { QFile file(filePath); if (!file.open(QIODevice::WriteOnly)) { QMessageBox::warning(this, "保存失败", "无法打开文件进行写入:" + filePath); return false; } QJsonObject docObject; docObject["type"] = "PLCDocument"; docObject["table_rows"] = m_rows; docObject["table_columns"] = m_columns; docObject["table_cell_size"] = m_cellSize; QJsonArray itemsArray; for (QGraphicsItem *item : m_scene->items()) { if (PLCItem *plcItem = dynamic_cast(item)) { QJsonObject itemObj; itemObj["type"] = static_cast(plcItem->itemType()); int col = static_cast(plcItem->x() / m_cellSize); int row = static_cast(plcItem->y() / m_cellSize); itemObj["col"] = col; itemObj["row"] = row; itemObj["name"] = plcItem->name(); itemObj["active"] = plcItem->isActive(); itemsArray.append(itemObj); } } docObject["items"] = itemsArray; QJsonArray connectionsArray; for (ConnectionLine *line : m_connections) { QJsonObject connObj; if (line->startItem()) { int startCol = static_cast(line->startItem()->pos().x() / m_cellSize); int startRow = static_cast(line->startItem()->pos().y() / m_cellSize); connObj["startCol"] = startCol; connObj["startRow"] = startRow; } else if (line->startTerminal() == RowTerminal) { connObj["startRowTerminal"] = line->rowTerminalSourceRow(); } connObj["startTerminal"] = static_cast(line->startTerminal()); connObj["endTerminal"] = static_cast(line->endTerminal()); if (line->endTerminal() == RowTerminal) { connObj["endRowTerminal"] = line->rowTerminalTargetRow(); } else if (line->endItem()) { int endCol = static_cast(line->endItem()->pos().x() / m_cellSize); int endRow = static_cast(line->endItem()->pos().y() / m_cellSize); connObj["endCol"] = endCol; connObj["endRow"] = endRow; } connectionsArray.append(connObj); } docObject["connections"] = connectionsArray; QJsonDocument doc(docObject); file.write(doc.toJson()); file.close(); setFilePath(filePath); setModified(false); return true; } bool PLCDocument::loadFromFile(const QString &filePath) { QFile file(filePath); if (!file.open(QIODevice::ReadOnly)) { QMessageBox::warning(this, "打开失败", "无法打开文件:" + filePath); return false; } QByteArray data = file.readAll(); file.close(); QJsonParseError parseError; QJsonDocument doc = QJsonDocument::fromJson(data, &parseError); if (parseError.error != QJsonParseError::NoError) { QMessageBox::warning(this, "文件格式错误", "JSON解析错误: " + parseError.errorString()); return false; } if (doc.isNull() || !doc.isObject()) { QMessageBox::warning(this, "文件格式错误", "不是有效的PLC文档"); return false; } QJsonObject docObject = doc.object(); if (docObject["type"].toString() != "PLCDocument") { QMessageBox::warning(this, "文件类型错误", "不是有效的PLC文档"); return false; } m_rows = docObject["table_rows"].toInt(DEFAULT_ROWS); m_columns = docObject["table_columns"].toInt(DEFAULT_COLUMNS); m_cellSize = docObject["table_cell_size"].toInt(DEFAULT_CELL_SIZE); setSimulationRunning(false); m_scene->blockSignals(true); for (auto conn : m_connections) { m_scene->removeItem(conn); delete conn; } m_connections.clear(); QList allItems = m_scene->items(); for (QGraphicsItem* item : allItems) { if (dynamic_cast(item) || dynamic_cast(item)) { m_scene->removeItem(item); delete item; } } m_activeItems.clear(); terminalConnections.clear(); clearCurrentConnection(); createRealTable(); QMap, PLCItem*> itemMap; QJsonArray itemsArray = docObject["items"].toArray(); for (const QJsonValue &itemValue : itemsArray) { QJsonObject itemObj = itemValue.toObject(); PLCItem::ItemType type = static_cast(itemObj["type"].toInt()); int col = itemObj["col"].toInt(); int row = itemObj["row"].toInt(); QString name = itemObj["name"].toString(); bool active = itemObj["active"].toBool(); col = qBound(0, col, m_columns - 1); row = qBound(0, row, m_rows - 1); PLCItem *item = createItemByType(type); if (item) { QPointF pos(col * m_cellSize + m_cellSize / 2.0, row * m_cellSize + m_cellSize / 2.0); item->setPos(pos); item->setName(name); item->setState(active); m_scene->addItem(item); itemMap.insert(qMakePair(col, row), item); if (active) { m_activeItems.insert(item); } terminalConnections[item][LeftTerminal] = false; terminalConnections[item][RightTerminal] = false; connect(item, &PLCItem::stateChanged, this, [this, item](bool active) { if (m_simulationRunning) { if (active) m_activeItems.insert(item); else m_activeItems.remove(item); updateConnections(); } }); } } QJsonArray connectionsArray = docObject["connections"].toArray(); for (const QJsonValue &connValue : connectionsArray) { QJsonObject connObj = connValue.toObject(); TerminalType startTerminal = static_cast(connObj["startTerminal"].toInt()); TerminalType endTerminal = static_cast(connObj["endTerminal"].toInt()); PLCItem *startItem = nullptr; PLCItem *endItem = nullptr; if (connObj.contains("startRowTerminal")) { int startRow = connObj["startRowTerminal"].toInt(); ConnectionLine *line = new ConnectionLine( nullptr, RowTerminal, endItem, endTerminal ); line->setStartTerminalPoint(QPointF(0, (startRow + 0.5) * m_cellSize)); line->setRowTerminalSourceRow(startRow); m_scene->addItem(line); m_connections.append(line); } else if (connObj.contains("startCol") && connObj.contains("startRow")) { int startCol = connObj["startCol"].toInt(); int startRow = connObj["startRow"].toInt(); startItem = itemMap.value(qMakePair(startCol, startRow)); if (endTerminal == RowTerminal && connObj.contains("endRowTerminal")) { int endRow = connObj["endRowTerminal"].toInt(); ConnectionLine *line = new ConnectionLine( startItem, startTerminal, nullptr, RowTerminal ); line->setEndTerminalPoint(QPointF(0, (endRow + 0.5) * m_cellSize)); line->setRowTerminalTargetRow(endRow); m_scene->addItem(line); m_connections.append(line); } else if (connObj.contains("endCol") && connObj.contains("endRow")) { int endCol = connObj["endCol"].toInt(); int endRow = connObj["endRow"].toInt(); endItem = itemMap.value(qMakePair(endCol, endRow)); if (startItem && endItem) { ConnectionLine *line = new ConnectionLine( startItem, startTerminal, endItem, endTerminal ); m_scene->addItem(line); m_connections.append(line); } } } } for (ConnectionLine* line : m_connections) { if (line->startItem()) { terminalConnections[line->startItem()][line->startTerminal()] = true; } if (line->endItem()) { terminalConnections[line->endItem()][line->endTerminal()] = true; } } m_scene->blockSignals(false); handleSceneChanged(); setFilePath(filePath); setModified(false); return true; } ConnectionLine::ConnectionLine( PLCItem *startItem, PLCDocument::TerminalType startTerminal, PLCItem *endItem, PLCDocument::TerminalType endTerminal, QGraphicsItem *parent ) : QGraphicsLineItem(parent) , m_startItem(startItem) , m_startTerminal(startTerminal) , m_endItem(endItem) , m_endTerminal(endTerminal) , m_rowTerminalTargetRow(-1) , m_rowTerminalSourceRow(-1) , m_processing(false) { setFlag(QGraphicsItem::ItemIsSelectable, true); setPen(QPen(Qt::black, 1)); setZValue(-1); updatePosition(); } void ConnectionLine::updatePosition() { QPointF startPoint; if (m_startItem) { if (m_startTerminal == PLCDocument::LeftTerminal) { startPoint = m_startItem->leftTerminal(); } else if (m_startTerminal == PLCDocument::RightTerminal) { startPoint = m_startItem->rightTerminal(); } } else if (!m_startTerminalPoint.isNull()) { startPoint = m_startTerminalPoint; } QPointF endPoint; if (m_endItem) { if (m_endTerminal == PLCDocument::LeftTerminal) { endPoint = m_endItem->leftTerminal(); } else if (m_endTerminal == PLCDocument::RightTerminal) { endPoint = m_endItem->rightTerminal(); } } else if (!m_endTerminalPoint.isNull()) { endPoint = m_endTerminalPoint; } if (!startPoint.isNull() && !endPoint.isNull()) { setLine(QLineF(startPoint, endPoint)); } }