Przeglądaj źródła

复制粘贴删除插入实现

main
email 1 dzień temu
rodzic
commit
f988e94e75
4 zmienionych plików z 361 dodań i 158 usunięć
  1. +1
    -1
      untitled/hmidocument.cpp
  2. +11
    -2
      untitled/mainwindow.cpp
  3. +325
    -150
      untitled/plcdocument.cpp
  4. +24
    -5
      untitled/plcdocument.h

+ 1
- 1
untitled/hmidocument.cpp Wyświetl plik

@@ -37,7 +37,7 @@ HMIDocument::HMIDocument(QWidget *parent)
// 创建视图 - 关键修改:使用滚动区域包装
m_scrollArea = new QScrollArea(this);
m_scrollArea->setWidgetResizable(false); // 禁止内容自动调整大小
m_scrollArea->setFrameShape(QFrame::NoFrame); // 无边框
//m_scrollArea->setFrameShape(QFrame::NoFrame); // 无边框

// 创建真正的视图部件
m_view = new QGraphicsView(m_scene, this);


+ 11
- 2
untitled/mainwindow.cpp Wyświetl plik

@@ -146,25 +146,32 @@ void MainWindow::createMenus()
m_saveAsAction->setFont(itemFont);
connect(m_saveAsAction, &QAction::triggered, this, &MainWindow::onSaveAs);
fileMenu->addAction(m_saveAsAction);

// 操作菜单 - 添加复制、粘贴、删除功能
QAction *copyAction = new QAction("复制(&C)", this);
copyAction->setShortcut(QKeySequence::Copy);
copyAction->setStatusTip("复制选中的项目");
copyAction->setFont(itemFont);
connect(copyAction, &QAction::triggered, this, [this]() {
if (auto hmiDoc = dynamic_cast<HMIDocument*>(m_tabWidget->currentWidget())) {
if (auto hmiDoc = dynamic_cast<HMIDocument*>(m_tabWidget->currentWidget()))
{
hmiDoc->copySelectedItems();
}
else if (auto plcDoc = dynamic_cast<PLCDocument*>(m_tabWidget->currentWidget()))
{
plcDoc->copySelectedItem();//添加 PLC 文档支持
}
});

QAction *pasteAction = new QAction("粘贴(&V)", this);
pasteAction->setShortcut(QKeySequence::Paste);
pasteAction->setStatusTip("粘贴复制的项目");

pasteAction->setFont(itemFont);
connect(pasteAction, &QAction::triggered, this, [this]() {
if (auto hmiDoc = dynamic_cast<HMIDocument*>(m_tabWidget->currentWidget())) {
hmiDoc->pasteItems();
} else if (auto plcDoc = dynamic_cast<PLCDocument*>(m_tabWidget->currentWidget())) {
plcDoc->pasteItem(); // 添加 PLC 文档支持
}
});

@@ -175,6 +182,8 @@ void MainWindow::createMenus()
connect(deleteAction, &QAction::triggered, this, [this]() {
if (auto hmiDoc = dynamic_cast<HMIDocument*>(m_tabWidget->currentWidget())) {
hmiDoc->deleteSelectedItems();
} else if (auto plcDoc = dynamic_cast<PLCDocument*>(m_tabWidget->currentWidget())) {
plcDoc->deleteSelectedItems(); // 添加 PLC 文档支持
}
});



+ 325
- 150
untitled/plcdocument.cpp Wyświetl plik

@@ -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();


+ 24
- 5
untitled/plcdocument.h Wyświetl plik

@@ -18,7 +18,7 @@
#include <QPair>

const int DEFAULT_ROWS = 11;
const int DEFAULT_COLUMNS = 15;
const int DEFAULT_COLUMNS = 24;
const int DEFAULT_CELL_SIZE = 70;

class PLCItem;
@@ -28,14 +28,16 @@ class PLCDocument : public BaseDocument
{
Q_OBJECT
public:
enum TerminalType {
enum TerminalType
{
NoTerminal = 0,
LeftTerminal,
RightTerminal,
RowTerminal
};

enum ConnectionSourceType {
enum ConnectionSourceType
{
ConnectionNone, // 无连接
ConnectionFromItem, // 起点是元件的端子
ConnectionFromRowTerminal // 起点是行触点
@@ -69,17 +71,24 @@ public:

void setTitle(const QString &title) { m_title = title; }

void deleteSelectedItems();
void copySelectedItem();
void pasteItem();

protected:
bool eventFilter(QObject *obj, QEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
void keyPressEvent(QKeyEvent* event) override;

private slots:
void handleSceneChanged();
void onRowTerminalConnection();

private:
QList<ConnectionLine*> findConnectionsUnderItem(PLCItem* item) const;
bool lineIntersectsRect(const QLineF& line, const QRectF& rect) const;
bool splitConnection(ConnectionLine* originalLine, PLCItem* insertItem);
bool isPointOnLineSegment(const QPointF& point, const QLineF& line) const;
void createRealTable();
//void clearTable();
TerminalType whichTerminal(const QPointF& scenePos, PLCItem* item) const;
bool isTerminalUsed(PLCItem* item, TerminalType terminal) const;
void handleItemPositionChange(PLCItem *item);
@@ -90,6 +99,9 @@ private:
void removeInvalidConnectionsForItem(PLCItem* movedItem);
QList<PLCItem*> allPLCItems() const;

// 新增:获取元件在连线上的位置比例(0-1)
qreal getPositionRatioOnLine(PLCItem* item, ConnectionLine* line) const;

QGraphicsScene *m_scene;
QGraphicsView *m_view;
QString m_currentTool;
@@ -114,6 +126,8 @@ private:
QMap<PLCItem*, QPointF> m_lastValidPositions;
QSet<PLCItem*> m_processingItems;
bool m_loadingFile = false;

PLCItem* m_copiedItem = nullptr;
};

class ConnectionLine : public QGraphicsLineItem
@@ -138,6 +152,10 @@ public:
void setStartTerminalPoint(const QPointF& point) { m_startTerminalPoint = point; }
void setEndTerminalPoint(const QPointF& point) { m_endTerminalPoint = point; }

// 新增:设置这条连线是否正在被处理
void setProcessing(bool processing) { m_processing = processing; }
bool isProcessing() const { return m_processing; }

private:
PLCItem *m_startItem;
PLCDocument::TerminalType m_startTerminal;
@@ -147,6 +165,7 @@ private:
QPointF m_endTerminalPoint;
int m_rowTerminalTargetRow = -1;
int m_rowTerminalSourceRow = -1;
bool m_processing = false; // 用于防止递归处理
};

#endif // PLCDOCUMENT_H

Ładowanie…
Anuluj
Zapisz