|
|
@@ -27,6 +27,7 @@ |
|
|
|
#include <QMap> |
|
|
|
#include <QPointer> |
|
|
|
#include <QScrollArea> |
|
|
|
#include <QKeyEvent> |
|
|
|
|
|
|
|
PLCDocument::PLCDocument(QWidget *parent) |
|
|
|
: BaseDocument(PLC, parent) |
|
|
@@ -34,36 +35,28 @@ PLCDocument::PLCDocument(QWidget *parent) |
|
|
|
, 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->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); |
|
|
|
m_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); |
|
|
|
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(); |
|
|
@@ -73,14 +66,13 @@ PLCDocument::PLCDocument(QWidget *parent) |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
// 连接场景变化信号 |
|
|
|
connect(m_scene, &QGraphicsScene::changed, this, &PLCDocument::handleSceneChanged); |
|
|
|
} |
|
|
|
|
|
|
|
PLCDocument::~PLCDocument() |
|
|
|
{ |
|
|
|
qDeleteAll(m_connections); |
|
|
|
clearTable(); |
|
|
|
m_copiedItem = nullptr; |
|
|
|
} |
|
|
|
|
|
|
|
QString PLCDocument::title() const { |
|
|
@@ -89,9 +81,6 @@ QString PLCDocument::title() const { |
|
|
|
|
|
|
|
void PLCDocument::createRealTable() |
|
|
|
{ |
|
|
|
//clearTable(); |
|
|
|
|
|
|
|
// 表格整体区域 |
|
|
|
QRectF tableRect(0, 0, m_columns * m_cellSize, m_rows * m_cellSize); |
|
|
|
m_tableFrame = new QGraphicsRectItem(tableRect); |
|
|
|
m_tableFrame->setPen(QPen(Qt::black, 2)); |
|
|
@@ -99,7 +88,6 @@ void PLCDocument::createRealTable() |
|
|
|
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); |
|
|
@@ -109,7 +97,6 @@ void PLCDocument::createRealTable() |
|
|
|
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); |
|
|
@@ -119,7 +106,6 @@ void PLCDocument::createRealTable() |
|
|
|
m_horizontalLines.append(line); |
|
|
|
} |
|
|
|
|
|
|
|
// 添加行触点 |
|
|
|
for (int row = 0; row < m_rows; row++) |
|
|
|
{ |
|
|
|
const int terminalSize = 15; |
|
|
@@ -131,7 +117,7 @@ void PLCDocument::createRealTable() |
|
|
|
terminal->setFlag(QGraphicsItem::ItemIsSelectable, true); |
|
|
|
terminal->setFlag(QGraphicsItem::ItemIsFocusable, true); |
|
|
|
terminal->setAcceptHoverEvents(true); |
|
|
|
terminal->setVisible(false); |
|
|
|
terminal->setVisible(true); |
|
|
|
|
|
|
|
m_scene->addItem(terminal); |
|
|
|
m_rowTerminals.append(terminal); |
|
|
@@ -139,35 +125,6 @@ void PLCDocument::createRealTable() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
//void PLCDocument::clearTable() |
|
|
|
//{ |
|
|
|
// if (m_tableFrame) { |
|
|
|
// m_scene->removeItem(m_tableFrame); |
|
|
|
// delete m_tableFrame; |
|
|
|
// m_tableFrame = nullptr; |
|
|
|
// } |
|
|
|
|
|
|
|
// for (auto line : m_horizontalLines) { |
|
|
|
// m_scene->removeItem(line); |
|
|
|
// delete line; |
|
|
|
// } |
|
|
|
// m_horizontalLines.clear(); |
|
|
|
|
|
|
|
// for (auto line : m_verticalLines) { |
|
|
|
// m_scene->removeItem(line); |
|
|
|
// delete line; |
|
|
|
// } |
|
|
|
// m_verticalLines.clear(); |
|
|
|
|
|
|
|
// // 清理行端子 |
|
|
|
// for (auto terminal : m_rowTerminals) { |
|
|
|
// m_scene->removeItem(terminal); |
|
|
|
// delete terminal; |
|
|
|
// } |
|
|
|
// m_rowTerminals.clear(); |
|
|
|
// m_rowTerminalMap.clear(); |
|
|
|
//} |
|
|
|
|
|
|
|
QPointF PLCDocument::snapToCellCenter(const QPointF& pos) const |
|
|
|
{ |
|
|
|
int col = static_cast<int>(std::floor(pos.x() / m_cellSize)); |
|
|
@@ -192,12 +149,6 @@ QPointF PLCDocument::constrainToTable(const QPointF &pos) const |
|
|
|
row * m_cellSize + m_cellSize / 2.0); |
|
|
|
} |
|
|
|
|
|
|
|
void PLCDocument::resizeEvent(QResizeEvent *event) |
|
|
|
{ |
|
|
|
QWidget::resizeEvent(event); |
|
|
|
m_view->setFixedSize(m_columns * m_cellSize, m_rows * m_cellSize); |
|
|
|
} |
|
|
|
|
|
|
|
bool PLCDocument::eventFilter(QObject *obj, QEvent *event) |
|
|
|
{ |
|
|
|
if (obj == m_view->viewport()) |
|
|
@@ -314,7 +265,6 @@ bool PLCDocument::eventFilter(QObject *obj, QEvent *event) |
|
|
|
if (mouseEvent->button() == Qt::LeftButton) { |
|
|
|
QPointF scenePos = m_view->mapToScene(mouseEvent->pos()); |
|
|
|
|
|
|
|
// 检查是否点击到行触点 |
|
|
|
QGraphicsEllipseItem* clickedRowTerminal = nullptr; |
|
|
|
int rowTerminalIndex = -1; |
|
|
|
for (auto terminal : m_rowTerminals) { |
|
|
@@ -327,14 +277,11 @@ bool PLCDocument::eventFilter(QObject *obj, QEvent *event) |
|
|
|
} |
|
|
|
|
|
|
|
if (clickedRowTerminal) { |
|
|
|
// 行触点被点击 |
|
|
|
if (m_connectionSourceType != ConnectionNone) { |
|
|
|
// 已有连接起点,尝试作为终点 |
|
|
|
if (tryEndConnection(nullptr, RowTerminal)) { |
|
|
|
return true; |
|
|
|
} |
|
|
|
} else { |
|
|
|
// 没有连接起点,开始新连接(行触点作为起点) |
|
|
|
startConnectionFromRowTerminal(rowTerminalIndex); |
|
|
|
return true; |
|
|
|
} |
|
|
@@ -435,7 +382,7 @@ bool PLCDocument::isTerminalUsed(PLCItem* item, TerminalType terminal) const |
|
|
|
{ |
|
|
|
if (item->itemType() == PLCItem::Coil && terminal == RightTerminal) |
|
|
|
{ |
|
|
|
return true; // 这一行是关键修改 |
|
|
|
return true; |
|
|
|
} |
|
|
|
for (ConnectionLine* line : m_connections) { |
|
|
|
if (line->startItem() == item && line->startTerminal() == terminal) { |
|
|
@@ -473,16 +420,13 @@ void PLCDocument::startConnection(PLCItem *startItem, TerminalType startTerminal |
|
|
|
|
|
|
|
bool PLCDocument::tryEndConnection(PLCItem *endItem, TerminalType endTerminal) |
|
|
|
{ |
|
|
|
// 情况1:行触点作为起点,连接到元件左端子 |
|
|
|
if (m_connectionSourceType == ConnectionFromRowTerminal) { |
|
|
|
// 行触点只能连接元件的左端子 |
|
|
|
if (endTerminal != LeftTerminal) { |
|
|
|
QMessageBox::warning(this, "连线错误", "行触点只能连接到元件的左端子"); |
|
|
|
clearCurrentConnection(); |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
// 检查是否在同一行 |
|
|
|
int endRow = static_cast<int>(endItem->y() / m_cellSize); |
|
|
|
if (endRow != m_connectionStartRow) { |
|
|
|
QMessageBox::warning(this, "连线错误", "行触点只能连接到同一行元件的左端子"); |
|
|
@@ -490,7 +434,6 @@ bool PLCDocument::tryEndConnection(PLCItem *endItem, TerminalType endTerminal) |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
// 检查端子是否已被使用 |
|
|
|
if (isTerminalUsed(endItem, LeftTerminal)) { |
|
|
|
QMessageBox::warning(this, "连线错误", "该元件的左端子已被使用"); |
|
|
|
clearCurrentConnection(); |
|
|
@@ -514,9 +457,7 @@ bool PLCDocument::tryEndConnection(PLCItem *endItem, TerminalType endTerminal) |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
// 情况2:元件作为起点,连接到行触点(终点) |
|
|
|
if (endTerminal == RowTerminal) { |
|
|
|
// 行触点只能被元件的左端子连接 |
|
|
|
if (m_startTerminal != LeftTerminal) { |
|
|
|
QMessageBox::warning(this, "连线错误", "行触点只能连接到元件的左端子"); |
|
|
|
clearCurrentConnection(); |
|
|
@@ -541,7 +482,6 @@ bool PLCDocument::tryEndConnection(PLCItem *endItem, TerminalType endTerminal) |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
// 情况3:元件连接到元件(标准连接) |
|
|
|
if (m_connectionSourceType == ConnectionFromItem && m_connectionStartItem) { |
|
|
|
if (endItem == m_connectionStartItem && endTerminal == m_startTerminal) { |
|
|
|
clearCurrentConnection(); |
|
|
@@ -641,11 +581,9 @@ void PLCDocument::createPLCItem(const QString &type, const QPointF &pos) |
|
|
|
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) { |
|
|
@@ -712,8 +650,31 @@ bool PLCDocument::isCellOccupied(int col, int row, PLCItem* excludeItem) const |
|
|
|
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); |
|
|
@@ -732,27 +693,41 @@ void PLCDocument::handleItemPositionChange(PLCItem *item) |
|
|
|
|
|
|
|
if (isCellOccupied(newCol, newRow, item)) |
|
|
|
{ |
|
|
|
if (m_lastValidPositions.contains(item)) { |
|
|
|
if (m_lastValidPositions.contains(item)) |
|
|
|
{ |
|
|
|
item->setPos(m_lastValidPositions[item]); |
|
|
|
QMessageBox::warning(this, "移动失败", "无法移动,该单元格已被占用"); |
|
|
|
} |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
// 获取移动前的列位置 |
|
|
|
int oldCol = static_cast<int>(m_lastValidPositions[item].x() / m_cellSize); |
|
|
|
|
|
|
|
item->setPos(newPos); |
|
|
|
m_lastValidPositions[item] = newPos; |
|
|
|
|
|
|
|
// 检查连接是否因移动而无效 |
|
|
|
if (newCol != oldCol) { |
|
|
|
removeInvalidConnectionsForItem(item); |
|
|
|
} |
|
|
|
|
|
|
|
for (ConnectionLine *line : m_connections) |
|
|
|
{ |
|
|
|
if (line->startItem() == item || line->endItem() == item) { |
|
|
|
// 获取要拆分的连线并排序 |
|
|
|
QList<ConnectionLine*> 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(); |
|
|
|
} |
|
|
|
} |
|
|
@@ -761,9 +736,160 @@ void PLCDocument::handleItemPositionChange(PLCItem *item) |
|
|
|
m_processingItems.remove(item); |
|
|
|
} |
|
|
|
|
|
|
|
//QList<ConnectionLine*> PLCDocument::findConnectionsUnderItem(PLCItem* item) const |
|
|
|
//{ |
|
|
|
// QList<ConnectionLine*> 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<int>(logicalInput->x() / m_cellSize); |
|
|
|
|
|
|
|
int outputCol = (outputTerminal == RowTerminal) ? 0 : |
|
|
|
static_cast<int>(logicalOutput->x() / m_cellSize); |
|
|
|
|
|
|
|
int insertCol = static_cast<int>(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<ConnectionLine*> PLCDocument::findConnectionsUnderItem(PLCItem* item) const |
|
|
|
{ |
|
|
|
QList<ConnectionLine*> 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) |
|
|
|
{ |
|
|
|
// 1. 基础防护 |
|
|
|
if (!movedItem || m_connections.isEmpty()) return; |
|
|
|
if (m_cellSize <= 0) { |
|
|
|
qWarning() << "Invalid cell size!"; |
|
|
@@ -774,15 +900,13 @@ void PLCDocument::removeInvalidConnectionsForItem(PLCItem* movedItem) |
|
|
|
QSet<ConnectionLine*> connectionsToRemove; |
|
|
|
bool shouldRemoveAll = false; |
|
|
|
|
|
|
|
// 2. 第一遍遍历:检查位置条件 |
|
|
|
for (ConnectionLine* line : m_connections) { |
|
|
|
if (!line) continue; |
|
|
|
|
|
|
|
PLCItem* otherItem = nullptr; |
|
|
|
TerminalType movedTerminal = NoTerminal; // 使用已定义的枚举值 |
|
|
|
TerminalType movedTerminal = NoTerminal; |
|
|
|
TerminalType otherTerminal = NoTerminal; |
|
|
|
|
|
|
|
// 确定连接的另一个元件 |
|
|
|
if (line->startItem() == movedItem) { |
|
|
|
otherItem = line->endItem(); |
|
|
|
movedTerminal = line->startTerminal(); |
|
|
@@ -792,22 +916,19 @@ void PLCDocument::removeInvalidConnectionsForItem(PLCItem* movedItem) |
|
|
|
movedTerminal = line->endTerminal(); |
|
|
|
otherTerminal = line->startTerminal(); |
|
|
|
} else { |
|
|
|
continue; // 不是与移动元件相关的连接 |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
if (!otherItem) continue; |
|
|
|
|
|
|
|
int otherCol = static_cast<int>(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; |
|
|
@@ -815,7 +936,6 @@ void PLCDocument::removeInvalidConnectionsForItem(PLCItem* movedItem) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 3. 如果需要删除,收集所有相关连接 |
|
|
|
if (shouldRemoveAll) { |
|
|
|
for (ConnectionLine* line : m_connections) { |
|
|
|
if (!line) continue; |
|
|
@@ -825,11 +945,9 @@ void PLCDocument::removeInvalidConnectionsForItem(PLCItem* movedItem) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 4. 执行删除 |
|
|
|
for (ConnectionLine* line : connectionsToRemove) { |
|
|
|
if (!line) continue; |
|
|
|
|
|
|
|
// 从终端连接表移除 |
|
|
|
if (line->startItem()) { |
|
|
|
terminalConnections[line->startItem()].remove(line->startTerminal()); |
|
|
|
} |
|
|
@@ -837,17 +955,16 @@ void PLCDocument::removeInvalidConnectionsForItem(PLCItem* movedItem) |
|
|
|
terminalConnections[line->endItem()].remove(line->endTerminal()); |
|
|
|
} |
|
|
|
|
|
|
|
// 从场景中移除 |
|
|
|
if (m_scene && m_scene->items().contains(line)) { |
|
|
|
if (m_scene && line->scene() == m_scene) { |
|
|
|
m_scene->removeItem(line); |
|
|
|
} |
|
|
|
|
|
|
|
// 从连接列表中移除并删除对象 |
|
|
|
m_connections.removeAll(line); |
|
|
|
if (m_connections.contains(line)) { |
|
|
|
m_connections.removeAll(line); |
|
|
|
} |
|
|
|
delete line; |
|
|
|
} |
|
|
|
|
|
|
|
// 5. 标记文档修改 |
|
|
|
if (!connectionsToRemove.isEmpty()) { |
|
|
|
setModified(true); |
|
|
|
} |
|
|
@@ -879,7 +996,6 @@ void PLCDocument::updateConnections() |
|
|
|
PLCItem *start = line->startItem(); |
|
|
|
PLCItem *end = line->endItem(); |
|
|
|
|
|
|
|
// 行触点连接的线路特殊处理(总是激活) |
|
|
|
if (line->endTerminal() == RowTerminal || line->startTerminal() == RowTerminal) { |
|
|
|
line->setPen(QPen(Qt::green, 2)); |
|
|
|
continue; |
|
|
@@ -893,66 +1009,132 @@ void PLCDocument::updateConnections() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void PLCDocument::createContextMenu(QPoint globalPos) |
|
|
|
void PLCDocument::deleteSelectedItems() |
|
|
|
{ |
|
|
|
QMenu menu(this); |
|
|
|
|
|
|
|
QAction *runAction = menu.addAction("运行模拟"); |
|
|
|
QAction *stopAction = menu.addAction("停止模拟"); |
|
|
|
QAction *resetAction = menu.addAction("重置模拟"); |
|
|
|
menu.addSeparator(); |
|
|
|
QAction *nameAction = menu.addAction("重命名"); |
|
|
|
menu.addSeparator(); |
|
|
|
QAction *tableSizeAction = menu.addAction("调整表格尺寸"); |
|
|
|
QAction *tableGridAction = menu.addAction("隐藏/显示网格"); |
|
|
|
|
|
|
|
QList<QGraphicsItem*> selected = m_scene->selectedItems(); |
|
|
|
if (selected.isEmpty()) return; |
|
|
|
|
|
|
|
QAction *rowTerminalAction = nullptr; |
|
|
|
if (selected.size() == 1) { |
|
|
|
if (PLCItem *item = dynamic_cast<PLCItem*>(selected.first())) { |
|
|
|
int col = static_cast<int>(item->x() / m_cellSize); |
|
|
|
if (col == 0) { |
|
|
|
rowTerminalAction = menu.addAction("连接到行触点"); |
|
|
|
} |
|
|
|
QList<PLCItem*> itemsToDelete; |
|
|
|
QList<ConnectionLine*> linesToDelete; |
|
|
|
QSet<ConnectionLine*> connectionsToDelete; |
|
|
|
|
|
|
|
for (QGraphicsItem* item : selected) { |
|
|
|
if (PLCItem* plcItem = dynamic_cast<PLCItem*>(item)) { |
|
|
|
itemsToDelete.append(plcItem); |
|
|
|
} else if (ConnectionLine* line = dynamic_cast<ConnectionLine*>(item)) { |
|
|
|
linesToDelete.append(line); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
connect(runAction, &QAction::triggered, this, [this]() { |
|
|
|
setSimulationRunning(true); |
|
|
|
}); |
|
|
|
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); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
connect(stopAction, &QAction::triggered, this, [this]() { |
|
|
|
setSimulationRunning(false); |
|
|
|
}); |
|
|
|
for (ConnectionLine* line : linesToDelete) { |
|
|
|
connectionsToDelete.insert(line); |
|
|
|
} |
|
|
|
|
|
|
|
connect(resetAction, &QAction::triggered, this, &PLCDocument::resetSimulation); |
|
|
|
for (ConnectionLine* line : connectionsToDelete) { |
|
|
|
if (!line) continue; |
|
|
|
|
|
|
|
connect(nameAction, &QAction::triggered, this, [this, selected]() { |
|
|
|
if (selected.size() == 1) { |
|
|
|
if (PLCItem *item = dynamic_cast<PLCItem*>(selected.first())) { |
|
|
|
QString name = QInputDialog::getText(this, "重命名", "输入名称:", |
|
|
|
QLineEdit::Normal, item->name()); |
|
|
|
if (!name.isEmpty()) { |
|
|
|
item->setName(name); |
|
|
|
setModified(true); |
|
|
|
} |
|
|
|
} |
|
|
|
if (line->startItem()) { |
|
|
|
terminalConnections[line->startItem()].remove(line->startTerminal()); |
|
|
|
} |
|
|
|
if (line->endItem()) { |
|
|
|
terminalConnections[line->endItem()].remove(line->endTerminal()); |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
connect(tableSizeAction, &QAction::triggered, this, [this]() { |
|
|
|
bool ok; |
|
|
|
int cellSize = QInputDialog::getInt(this, "单元格大小", "输入单元格大小(像素):", |
|
|
|
m_cellSize, 10, 100, 10, &ok); |
|
|
|
if (ok && cellSize != m_cellSize) { |
|
|
|
m_cellSize = cellSize; |
|
|
|
m_view->setFixedSize(m_columns * m_cellSize, m_rows * m_cellSize); |
|
|
|
createRealTable(); |
|
|
|
setModified(true); |
|
|
|
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<QGraphicsItem*> selected = m_scene->selectedItems(); |
|
|
|
if (selected.size() != 1) return; |
|
|
|
|
|
|
|
if (PLCItem* item = dynamic_cast<PLCItem*>(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<int>(pastePos.x() / m_cellSize); |
|
|
|
int row = static_cast<int>(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<QGraphicsItem*> selected = m_scene->selectedItems(); |
|
|
|
deleteAction->setEnabled(!selected.isEmpty()); |
|
|
|
copyAction->setEnabled(selected.size() == 1&&dynamic_cast<PLCItem*>(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) |
|
|
|
{ |
|
|
@@ -964,11 +1146,7 @@ void PLCDocument::createContextMenu(QPoint globalPos) |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
if (rowTerminalAction) { |
|
|
|
connect(rowTerminalAction, &QAction::triggered, this, &PLCDocument::onRowTerminalConnection); |
|
|
|
} |
|
|
|
|
|
|
|
menu.exec(globalPos+QPoint(10, 10)); |
|
|
|
menu.exec(globalPos + QPoint(10, 10)); |
|
|
|
} |
|
|
|
|
|
|
|
void PLCDocument::onRowTerminalConnection() |
|
|
@@ -1060,7 +1238,6 @@ bool PLCDocument::saveToFile(const QString &filePath) |
|
|
|
for (ConnectionLine *line : m_connections) { |
|
|
|
QJsonObject connObj; |
|
|
|
|
|
|
|
// 起点信息 |
|
|
|
if (line->startItem()) { |
|
|
|
int startCol = static_cast<int>(line->startItem()->pos().x() / m_cellSize); |
|
|
|
int startRow = static_cast<int>(line->startItem()->pos().y() / m_cellSize); |
|
|
@@ -1071,7 +1248,6 @@ bool PLCDocument::saveToFile(const QString &filePath) |
|
|
|
} |
|
|
|
connObj["startTerminal"] = static_cast<int>(line->startTerminal()); |
|
|
|
|
|
|
|
// 终点信息 |
|
|
|
connObj["endTerminal"] = static_cast<int>(line->endTerminal()); |
|
|
|
if (line->endTerminal() == RowTerminal) { |
|
|
|
connObj["endRowTerminal"] = line->rowTerminalTargetRow(); |
|
|
@@ -1180,7 +1356,6 @@ bool PLCDocument::loadFromFile(const QString &filePath) |
|
|
|
m_activeItems.insert(item); |
|
|
|
} |
|
|
|
|
|
|
|
// 初始化端子状态 |
|
|
|
terminalConnections[item][LeftTerminal] = false; |
|
|
|
terminalConnections[item][RightTerminal] = false; |
|
|
|
|
|
|
@@ -1204,9 +1379,7 @@ bool PLCDocument::loadFromFile(const QString &filePath) |
|
|
|
PLCItem *startItem = nullptr; |
|
|
|
PLCItem *endItem = nullptr; |
|
|
|
|
|
|
|
// 处理起点 |
|
|
|
if (connObj.contains("startRowTerminal")) { |
|
|
|
// 起点是行触点 |
|
|
|
int startRow = connObj["startRowTerminal"].toInt(); |
|
|
|
ConnectionLine *line = new ConnectionLine( |
|
|
|
nullptr, RowTerminal, |
|
|
@@ -1222,9 +1395,7 @@ bool PLCDocument::loadFromFile(const QString &filePath) |
|
|
|
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, |
|
|
@@ -1246,13 +1417,13 @@ bool PLCDocument::loadFromFile(const QString &filePath) |
|
|
|
endItem, endTerminal |
|
|
|
); |
|
|
|
m_scene->addItem(line); |
|
|
|
|
|
|
|
m_connections.append(line); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 更新连接状态 |
|
|
|
for (ConnectionLine* line : m_connections) { |
|
|
|
if (line->startItem()) { |
|
|
|
terminalConnections[line->startItem()][line->startTerminal()] = true; |
|
|
@@ -1280,7 +1451,11 @@ ConnectionLine::ConnectionLine( |
|
|
|
, 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(); |
|
|
|