Selaa lähdekoodia

PLC进行了绘图的初步编写,完成了空间的图标绘制和基于表格的拖拽点击生成

main
email 1 päivä sitten
vanhempi
commit
7c633ad47a
9 muutettua tiedostoa jossa 1162 lisäystä ja 80 poistoa
  1. +4
    -5
      IntegratedPlatform/mainwindow.cpp
  2. +7
    -3
      untitled/hmidocument.cpp
  3. +58
    -32
      untitled/mainwindow.cpp
  4. +726
    -33
      untitled/plcdocument.cpp
  5. +96
    -4
      untitled/plcdocument.h
  6. +146
    -0
      untitled/plcitems.cpp
  7. +120
    -0
      untitled/plcitems.h
  8. +4
    -2
      untitled/untitled.pro
  9. +1
    -1
      untitled/untitled.pro.user

+ 4
- 5
IntegratedPlatform/mainwindow.cpp Näytä tiedosto

@@ -85,10 +85,10 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
QPointF scenePos = ui->graphicsView_2->mapToScene(mouseEvent->pos());
QGraphicsItem *item = scene->itemAt(scenePos, ui->graphicsView_2->transform());
if (item && !item->isSelected())// 如果有项在点击位置,选中它并清除其他选择
if (item && !item->isSelected())//如果有项在点击位置,选中它并清除其他选择
{
scene->clearSelection(); // 清除所有当前选择
item->setSelected(true); // 选中当前项
scene->clearSelection();//清除所有当前选择
item->setSelected(true);//选中当前项
}
QPoint globalPos = mouseEvent->globalPos();
showContextMenu(globalPos);
@@ -123,8 +123,7 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *event)
return true;
}
}
//拖拽生成
if (event->type() == QEvent::DragEnter)
if (event->type() == QEvent::DragEnter)//拖拽生成
{
QDragEnterEvent *dragEvent = static_cast<QDragEnterEvent*>(event);
if (dragEvent->source() == ui->treeWidget_2)//检查拖拽源是否为树控件,并接受拖拽


+ 7
- 3
untitled/hmidocument.cpp Näytä tiedosto

@@ -55,7 +55,8 @@ HMIDocument::~HMIDocument()
// 事件过滤器(处理绘图、拖拽等事件)
bool HMIDocument::eventFilter(QObject *obj, QEvent *event)
{
if (obj != m_view->viewport()) {
if (obj != m_view->viewport())
{
return QWidget::eventFilter(obj, event);
}

@@ -123,14 +124,17 @@ bool HMIDocument::eventFilter(QObject *obj, QEvent *event)

void HMIDocument::createShape(const QString& type, const QPointF &pos)
{
if (type == "指示灯" || type == "ellipse") {
m_scene->clearSelection();
if (type == "指示灯" || type == "ellipse")
{
ResizableEllipse *ellipse = new ResizableEllipse(pos.x(), pos.y(), 50, 50);
ellipse->setBrush(QBrush(Qt::red));
ellipse->setPen(QPen(Qt::black, 1));
m_scene->addItem(ellipse);
ellipse->setSelected(true);
}
else if (type == "按钮" || type == "rectangle") {
else if (type == "按钮" || type == "rectangle")
{
ResizableRectangle *rect = new ResizableRectangle(pos.x(), pos.y(), 100, 50);
rect->setBrush(QBrush(Qt::yellow));
rect->setPen(QPen(Qt::black, 1));


+ 58
- 32
untitled/mainwindow.cpp Näytä tiedosto

@@ -373,7 +373,7 @@ void MainWindow::updateToolBar(BaseDocument *doc)
coilBtn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
coilBtn->setIcon(QIcon("../two/untitled/images/添加线圈.png")); // 替换为实际图标
coilBtn->installEventFilter(this);
coilBtn->setProperty("toolType", "小于等于");
coilBtn->setProperty("toolType", "线圈");
m_leftToolBar->addWidget(coilBtn);
coilBtn->setStyleSheet(R"(
QToolButton {
@@ -390,6 +390,15 @@ void MainWindow::updateToolBar(BaseDocument *doc)
background-color: #e0e0e0;
}
)");

normallyOpenBtn->installEventFilter(this);
normallyClosedBtn->installEventFilter(this);
greaterThanBtn->installEventFilter(this);
greaterThanEqualBtn->installEventFilter(this);
lessThanBtn->installEventFilter(this);
lessThanEqualBtn->installEventFilter(this);
equalBtn->installEventFilter(this);
coilBtn->installEventFilter(this);
}
}

@@ -406,29 +415,39 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *event)
if (!toolType.isEmpty())
{
BaseDocument* currentDoc = dynamic_cast<BaseDocument*>(m_tabWidget->currentWidget());
if (currentDoc && currentDoc->type() == BaseDocument::HMI) {
if (currentDoc && currentDoc->type() == BaseDocument::PLC)//PLC文档处理
{
PLCDocument* plcDoc = static_cast<PLCDocument*>(currentDoc);
// 设置当前工具
plcDoc->setCurrentTool(toolType);
// 创建拖拽对象
QMimeData *mime = new QMimeData;
mime->setText(toolType);
QDrag *drag = new QDrag(toolBtn);
drag->setMimeData(mime);
drag->exec(Qt::CopyAction);
return true;
}
// HMI文档处理(原有代码)
if (currentDoc && currentDoc->type() == BaseDocument::HMI)
{
HMIDocument* hmiDoc = static_cast<HMIDocument*>(currentDoc);

// 设置对应的绘制模式
if (toolType == "指示灯")
{
hmiDoc->startDrawingEllipse();

hmiDoc->startDrawingEllipse();//点击生成标志
}
else if (toolType == "按钮")
{
hmiDoc->startDrawingRectangle();
hmiDoc->startDrawingRectangle();//点击生成标志
}
// 新增拖拽逻辑(与PLC一致)
QMimeData *mime = new QMimeData;
mime->setText(toolType);//传递工具类型("指示灯"或"按钮")
QDrag *drag = new QDrag(toolBtn);
drag->setMimeData(mime);
drag->exec(Qt::CopyAction);//启动拖拽
return true;
}

// 创建拖拽对象
QMimeData *mime = new QMimeData;
mime->setText(toolType);

QDrag *drag = new QDrag(toolBtn);
drag->setMimeData(mime);
drag->exec(Qt::CopyAction);
return true;
}
}
}
@@ -455,7 +474,6 @@ void MainWindow::onNewPLC()
m_tabWidget->setCurrentWidget(doc);
updateToolBar(doc); // 更新工具栏为PLC工具
}

// 标签页切换时更新工具栏
void MainWindow::onTabChanged(int idx)
{
@@ -467,7 +485,6 @@ void MainWindow::onTabChanged(int idx)
BaseDocument *doc = dynamic_cast<BaseDocument*>(m_tabWidget->widget(idx));
updateToolBar(doc);
}

// 保存文档
void MainWindow::onSave()
{
@@ -480,7 +497,6 @@ void MainWindow::onSave()
saveDocument(doc);
}
}

// 另存为文档
void MainWindow::onSaveAs()
{
@@ -489,7 +505,6 @@ void MainWindow::onSaveAs()
saveDocumentAs(doc);
}
}

// 打开文档
void MainWindow::onOpen()
{
@@ -523,7 +538,6 @@ void MainWindow::onOpen()
delete doc;
}
}

// 关闭标签页
void MainWindow::onCloseTab(int index)
{
@@ -549,35 +563,47 @@ void MainWindow::onCloseTab(int index)
m_tabWidget->removeTab(index);
delete doc;
}

// 保存文档
void MainWindow::saveDocument(BaseDocument *doc)
{
if (doc->saveToFile(doc->filePath()))
// 如果文件路径为空(新文件),执行“另存为”
if (doc->filePath().isEmpty())
{
doc->setModified(false); // 清除修改状态
QMessageBox::information(this, "保存文档", "文档保存成功");
} else {
QMessageBox::critical(this, "保存文档", "无法保存文档");
// 直接调用另存为函数
saveDocumentAs(doc);
}
else
{
// 如果已有文件路径,直接保存
if (doc->saveToFile(doc->filePath()))
{
doc->setModified(false); // 清除修改状态
QMessageBox::information(this, "保存文档", "文档保存成功");
}
else
{
QMessageBox::critical(this, "保存文档", "无法保存文档");
}
}
}

// 另存为文档
void MainWindow::saveDocumentAs(BaseDocument *doc)
{
QString filePath = QFileDialog::getSaveFileName(
this,
"另存为",
"存为",
doc->filePath().isEmpty() ? doc->title() : doc->filePath(),
doc->type() == BaseDocument::HMI ?"HMI文档 (*.hmi)" :"PLC文档 (*.plc)"
);

if (filePath.isEmpty()) return;
if (doc->saveToFile(filePath)) {
if (doc->saveToFile(filePath))
{
doc->setModified(false); // 清除修改状态
QMessageBox::information(this, "保存文档", "文档保存成功");
} else {
}
else
{
QMessageBox::critical(this, "保存文档", "无法保存文档");
}
}

+ 726
- 33
untitled/plcdocument.cpp Näytä tiedosto

@@ -1,88 +1,781 @@
// PLCDocument.cpp 的完整修改版
#include "plcdocument.h"
#include <QGraphicsScene>
#include <QGraphicsView>
#include "plcitems.h"
#include <QPainter>
#include <QBrush>
#include <QPen>
#include <QFile>
#include <QBrush>
#include <QMenu>
#include <QAction>
#include <QGraphicsSceneMouseEvent>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QFile>
#include <QFileInfo>
#include <QVBoxLayout>
#include <QMessageBox>
#include <QInputDialog>
#include <QDragEnterEvent>
#include <QDragMoveEvent>
#include <QCursor>
#include <QDropEvent>
#include <QMimeData>
#include <cmath>
#include <QResizeEvent>
#include <QDebug>
#include <QGraphicsItem>
#include <QGraphicsSceneMouseEvent>
#include <QSet>

// 固定表格尺寸的常量
const int DEFAULT_ROWS = 20;
const int DEFAULT_COLUMNS = 25;
const int DEFAULT_CELL_SIZE = 50;

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, 800, 600);

// 创建网格背景
createGridBackground();
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);
m_view->setFixedSize(m_columns * m_cellSize, m_rows * m_cellSize);

setAcceptDrops(true);
m_view->setAcceptDrops(true);
m_view->viewport()->setAcceptDrops(true);

// 布局(让视图占满文档区域)
auto layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(m_view);
setLayout(layout);

// 创建固定大小的表格
createRealTable();

// 连接场景信号
connect(m_scene, &QGraphicsScene::selectionChanged, this, [this]() {
auto items = m_scene->selectedItems();
if (items.size() == 1 && dynamic_cast<PLCItem*>(items.first())) {
PLCItem *item = static_cast<PLCItem*>(items.first());
if (m_connectionStart) {
endConnection(item);
}
}
});

// 连接场景变化信号(用于更新连接线位置)
connect(m_scene, &QGraphicsScene::changed, this, &PLCDocument::handleSceneChanged);
}

PLCDocument::~PLCDocument()
{
// 场景和视图由Qt自动销毁
qDeleteAll(m_connections);
clearTable();
}

QString PLCDocument::title() const {
return "PLC文档";
}

void PLCDocument::createGridBackground()
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));
m_tableFrame->setBrush(QBrush(QColor(245, 245, 245))); // 浅灰背景区分表格区域
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);
}
}

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

QPointF PLCDocument::snapToCellCenter(const QPointF& pos) const
{
// 计算所在单元格列号(0~m_columns-1)
int col = static_cast<int>(std::floor(pos.x() / m_cellSize));
// 计算所在单元格行号(0~m_rows-1)
int row = static_cast<int>(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<int>(std::floor(pos.x() / m_cellSize));
int row = static_cast<int>(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);
}

void PLCDocument::resizeEvent(QResizeEvent *event)
{
// 创建网格图案
createGridPattern();
QWidget::resizeEvent(event);
// 不再调整表格大小,只更新视图尺寸
m_view->setFixedSize(m_columns * m_cellSize, m_rows * m_cellSize);
}

bool PLCDocument::eventFilter(QObject *obj, QEvent *event)
{
// 仅处理viewport的事件
if (obj == m_view->viewport())
{
// 处理拖拽进入事件
if (event->type() == QEvent::DragEnter) {
QDragEnterEvent *dragEvent = static_cast<QDragEnterEvent*>(event);
if (dragEvent->mimeData()->hasText()) {
dragEvent->acceptProposedAction();
return true;
}
}
// 处理拖拽移动事件
else if (event->type() == QEvent::DragMove) {
QDragMoveEvent *dragEvent = static_cast<QDragMoveEvent*>(event);
if(dragEvent->mimeData()->hasText()) {
dragEvent->acceptProposedAction();
return true;
}
}
// 处理拖拽释放事件(使用单元格中心对齐)
else if (event->type() == QEvent::Drop) {
QDropEvent *dropEvent = static_cast<QDropEvent*>(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<int> occupiedCells;
for (QGraphicsItem* item : m_scene->items()) {
if (PLCItem* plcItem = dynamic_cast<PLCItem*>(item)) {
int col = static_cast<int>(plcItem->x() / m_cellSize);
int row = static_cast<int>(plcItem->y() / m_cellSize);
occupiedCells.insert(col * 1000 + row); // 简单的行列唯一键
}
}

int col = static_cast<int>(cellCenter.x() / m_cellSize);
int row = static_cast<int>(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;
}
}
}
}
}

// 创建PLC控件
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<QMouseEvent*>(event);
QPointF scenePos = m_view->mapToScene(mouseEvent->pos());
QPointF cellCenter = snapToCellCenter(scenePos); // 使用单元格中心
QLineF line(m_connectionStart->rightTerminal(), cellCenter);
m_tempLine->setLine(line);
}

// 鼠标按下事件
if (event->type() == QEvent::MouseButtonPress) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);

// 右键点击 - 显示上下文菜单
if (mouseEvent->button() == Qt::RightButton) {
createContextMenu(mouseEvent->globalPos());
return true;
}

// 左键点击
if (mouseEvent->button() == Qt::LeftButton) {
QPointF scenePos = m_view->mapToScene(mouseEvent->pos());
QPointF cellCenter = snapToCellCenter(scenePos); // 单元格中心

// 使用当前工具创建控件
if (!m_currentTool.isEmpty()) {
// 检查单元格是否已被占用
QSet<int> occupiedCells;
for (QGraphicsItem* item : m_scene->items()) {
if (PLCItem* plcItem = dynamic_cast<PLCItem*>(item)) {
int col = static_cast<int>(plcItem->x() / m_cellSize);
int row = static_cast<int>(plcItem->y() / m_cellSize);
occupiedCells.insert(col * 1000 + row); // 简单的行列唯一键
}
}

int col = static_cast<int>(cellCenter.x() / m_cellSize);
int row = static_cast<int>(cellCenter.y() / m_cellSize);
int cellKey = col * 1000 + row;

// 如果单元格被占用,显示警告
if (occupiedCells.contains(cellKey)) {
QMessageBox::warning(this, "位置被占用", "此单元格已被占用,请选择其他位置。");
return true;
}

createPLCItem(m_currentTool, cellCenter);
m_currentTool.clear();
return true;
}

// 设置场景背景
QBrush gridBrush(m_gridPattern);
m_scene->setBackgroundBrush(gridBrush);
// 连线模式处理
if (m_connectionStart) {
QGraphicsItem *item = m_scene->itemAt(cellCenter, m_view->transform());
if (PLCItem *plcItem = dynamic_cast<PLCItem*>(item)) {
endConnection(plcItem);
return true;
}
}

// 开始连线
QGraphicsItem *item = m_scene->itemAt(cellCenter, m_view->transform());
if (PLCItem *plcItem = dynamic_cast<PLCItem*>(item)) {
startConnection(plcItem);
return true;
}
}
}

return QWidget::eventFilter(obj, event);
}

void PLCDocument::createGridPattern()
void PLCDocument::createPLCItem(const QString &type, const QPointF &pos)
{
// 网格大小
const int size = m_gridSize;
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);

// 创建网格图案
m_gridPattern = QPixmap(size * 2, size * 2);
m_gridPattern.fill(Qt::white);
// 连接状态变化信号
connect(item, &PLCItem::stateChanged, this, [this, item](bool active) {
if (m_simulationRunning) {
if (active) {
m_activeItems.insert(item);
} else {
m_activeItems.remove(item);
}
updateConnections();
}
});
}
}

QPainter painter(&m_gridPattern);
painter.setPen(QPen(QColor(220, 220, 220), 1));
void PLCDocument::handleSceneChanged()
{
// 遍历所有项目,处理位置变化
for (QGraphicsItem *item : m_scene->items()) {
if (PLCItem *plcItem = dynamic_cast<PLCItem*>(item)) {
handleItemPositionChange(plcItem);
}
}
}

// 绘制网格线
painter.drawLine(0, size, size * 2, size); // 水平线
painter.drawLine(size, 0, size, size * 2); // 垂直线
void PLCDocument::handleItemPositionChange(PLCItem *item)
{
// 限制项目位置在表格内
QPointF newPos = constrainToTable(item->pos());
if (newPos != item->pos()) {
item->setPos(newPos);
}

// 绘制网格交点
painter.setPen(QPen(QColor(180, 180, 180), 1));
painter.drawPoint(0, 0);
painter.drawPoint(size, size);
painter.drawPoint(0, size * 2);
painter.drawPoint(size * 2, 0);
painter.drawPoint(size * 2, size * 2);
// 更新所有与该项目相关的连接
for (ConnectionLine *line : m_connections) {
if (line->startItem() == item || line->endItem() == item) {
line->updatePosition();
}
}
}

void PLCDocument::startConnection(PLCItem *startItem)
{
if (m_connectionStart) {
clearCurrentConnection();
}

m_connectionStart = startItem;
m_tempLine = new QGraphicsLineItem;
m_tempLine->setPen(QPen(Qt::blue, 2, Qt::DashLine));
m_tempLine->setLine(QLineF(startItem->rightTerminal(), startItem->rightTerminal()));
m_scene->addItem(m_tempLine);
}

void PLCDocument::endConnection(PLCItem *endItem)
{
if (!m_connectionStart || m_connectionStart == endItem) {
clearCurrentConnection();
return;
}

// 创建永久连接线
ConnectionLine *connection = new ConnectionLine(m_connectionStart, endItem);
m_scene->addItem(connection);
m_connections.append(connection);

clearCurrentConnection();
setModified(true);
}

void PLCDocument::clearCurrentConnection()
{
if (m_tempLine) {
m_scene->removeItem(m_tempLine);
delete m_tempLine;
m_tempLine = nullptr;
}
m_connectionStart = nullptr;
}

void PLCDocument::updateConnections()
{
for (ConnectionLine *line : m_connections) {
PLCItem *start = line->startItem();
PLCItem *end = line->endItem();

if (m_activeItems.contains(start) && m_activeItems.contains(end)) {
line->setPen(QPen(Qt::green, 2));
} else {
line->setPen(QPen(Qt::black, 1));
}
}
}

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<PLCItem*>(item)) {
plcItem->setState(false);
}
}
updateConnections();
}

void PLCDocument::createContextMenu(QPoint globalPos)
{
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("隐藏/显示网格");

connect(runAction, &QAction::triggered, this, [this]() {
setSimulationRunning(true);
});

connect(stopAction, &QAction::triggered, this, [this]() {
setSimulationRunning(false);
});

connect(resetAction, &QAction::triggered, this, &PLCDocument::resetSimulation);

connect(nameAction, &QAction::triggered, this, [this]() {
QList<QGraphicsItem*> selected = m_scene->selectedItems();
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);
}
}
}
});

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);
}
});

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);
}

bool PLCDocument::saveToFile(const QString &filePath)
{
QFile file(filePath);
if (!file.open(QIODevice::WriteOnly)) {
QMessageBox::warning(this, "保存失败", "无法打开文件进行写入:" + filePath);
return false;
}

// 创建JSON文档结构
QJsonObject docObject;
docObject["type"] = "PLCDocument";

// 保存表格参数
docObject["table_rows"] = m_rows;
docObject["table_columns"] = m_columns;
docObject["table_cell_size"] = m_cellSize;

// 序列化所有PLC元件 - 改为保存单元格坐标
QJsonArray itemsArray;
for (QGraphicsItem *item : m_scene->items()) {
if (PLCItem *plcItem = dynamic_cast<PLCItem*>(item)) {
QJsonObject itemObj;
itemObj["type"] = plcItem->itemType();
int col = static_cast<int>(plcItem->x() / m_cellSize);
int row = static_cast<int>(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;
// 通过坐标查找起始和终止元件
int startCol = static_cast<int>(line->startItem()->x() / m_cellSize);
int startRow = static_cast<int>(line->startItem()->y() / m_cellSize);
int endCol = static_cast<int>(line->endItem()->x() / m_cellSize);
int endRow = static_cast<int>(line->endItem()->y() / m_cellSize);
connObj["startCol"] = startCol;
connObj["startRow"] = startRow;
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;
}

// 读取并解析JSON
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<QGraphicsItem*> allItems = m_scene->items();
for (QGraphicsItem* item : allItems) {
// 保留表格项,只删除PLC元件
if (dynamic_cast<PLCItem*>(item) || dynamic_cast<ConnectionLine*>(item)) {
m_scene->removeItem(item);
delete item;
}
}

m_activeItems.clear();
m_connectionStart = nullptr;
delete m_tempLine;
m_tempLine = nullptr;

// 创建表格
createRealTable(); // 重新创建表格以确保正确

// 加载PLC元件
QMap<QPair<int, int>, PLCItem*> itemMap; // (列, 行) -> PLCItem
QJsonArray itemsArray = docObject["items"].toArray();
for (const QJsonValue &itemValue : itemsArray) {
QJsonObject itemObj = itemValue.toObject();
PLCItem::ItemType type = static_cast<PLCItem::ItemType>(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);
}

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();
int startCol = connObj["startCol"].toInt();
int startRow = connObj["startRow"].toInt();
int endCol = connObj["endCol"].toInt();
int endRow = connObj["endRow"].toInt();

PLCItem *startItem = itemMap.value(qMakePair(startCol, startRow));
PLCItem *endItem = itemMap.value(qMakePair(endCol, endRow));

if (startItem && endItem && startItem != endItem) {
ConnectionLine *line = new ConnectionLine(startItem, endItem);
m_scene->addItem(line);
m_connections.append(line);
}
}

// 完成加载
m_scene->blockSignals(false);
handleSceneChanged(); // 手动触发位置更新

// 更新UI状态
setFilePath(filePath);
setModified(false);

qDebug() << "成功加载文件:" << filePath << "项目数:" << itemMap.size()
<< "连接数:" << m_connections.size();

return 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;
}
}
// ConnectionLine 实现
ConnectionLine::ConnectionLine(PLCItem *start, PLCItem *end, QGraphicsItem *parent)
: QGraphicsLineItem(parent), m_startItem(start), m_endItem(end)
{
setPen(QPen(Qt::black, 1));
setZValue(-1);
updatePosition();
}

void ConnectionLine::updatePosition()
{
if (m_startItem && m_endItem) {
// 基于控件端子位置更新连线
QLineF line(m_startItem->rightTerminal(), m_endItem->leftTerminal());
setLine(line);
}
}

+ 96
- 4
untitled/plcdocument.h Näytä tiedosto

@@ -1,9 +1,24 @@
// plcdocument.h
#ifndef PLCDOCUMENT_H
#define PLCDOCUMENT_H

#include "basedocument.h"
#include "plcitems.h" // 确保包含PLCItem的定义
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsLineItem>
#include <QGraphicsRectItem>
#include <QMap>
#include <QSet>
#include <QVector>
#include <QDragEnterEvent>
#include <QDragMoveEvent>
#include <QDropEvent>
#include <QMimeData>
#include <QPair>

class PLCItem;
class ConnectionLine;

class PLCDocument : public BaseDocument
{
@@ -19,14 +34,91 @@ public:
bool saveToFile(const QString &filePath) override;
bool loadFromFile(const QString &filePath) override;

// 创建PLC元件
void createPLCItem(const QString &type, const QPointF &pos);

// 连接管理
void startConnection(PLCItem *startItem);
void endConnection(PLCItem *endItem);
void clearCurrentConnection();

// 运行状态控制
void setSimulationRunning(bool running);
void resetSimulation();
void setCurrentTool(const QString &tool) { m_currentTool = tool; }

// 获取表格参数
int rowCount() const { return m_rows; }
int columnCount() const { return m_columns; }
int cellSize() const { return m_cellSize; }

// 计算单元格中心位置
QPointF snapToCellCenter(const QPointF &pos) const;

// 检查并修正位置,确保在表格内
QPointF constrainToTable(const QPointF &pos) const;

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

private slots:
// 处理场景变化(用于更新连接线)
void handleSceneChanged();

private:
void createGridBackground();
void createGridPattern();
// 表格管理
void createRealTable();
void clearTable();
void adjustTableToFitView(); // 调整表格以适应视图大小

// 项目位置改变处理
void handleItemPositionChange(PLCItem *item);

// 仿真与连接管理
void updateConnections();
void createContextMenu(QPoint globalPos);

// 根据类型创建PLC项 - 新增的辅助函数
PLCItem* createItemByType(PLCItem::ItemType type);

QGraphicsScene *m_scene;
QGraphicsView *m_view;
QPixmap m_gridPattern; // 网格图案缓存
int m_gridSize = 20; // 网格大小(像素)
QString m_currentTool; // 当前选择的工具

// 连接管理
PLCItem *m_connectionStart = nullptr;
QGraphicsLineItem *m_tempLine = nullptr;
QList<ConnectionLine*> m_connections;

// 运行状态
bool m_simulationRunning = false;
QSet<PLCItem*> m_activeItems;

// 表格参数 - 使用常量定义默认值
int m_rows = 15; // 表格行数
int m_columns = 20; // 表格列数
int m_cellSize = 50; // 单元格大小(像素)

// 表格图形项
QGraphicsRectItem* m_tableFrame = nullptr;
QVector<QGraphicsLineItem*> m_horizontalLines;
QVector<QGraphicsLineItem*> m_verticalLines;
};

// 连接线类
class ConnectionLine : public QGraphicsLineItem
{
public:
ConnectionLine(PLCItem *start, PLCItem *end, QGraphicsItem *parent = nullptr);

void updatePosition();
PLCItem* startItem() const { return m_startItem; }
PLCItem* endItem() const { return m_endItem; }

private:
PLCItem *m_startItem;
PLCItem *m_endItem;
};

#endif // PLCDOCUMENT_H

+ 146
- 0
untitled/plcitems.cpp Näytä tiedosto

@@ -0,0 +1,146 @@
#include "plcitems.h"

PLCItem::PLCItem(ItemType type, QGraphicsItem *parent)
: QGraphicsObject(parent), m_type(type)
{
setFlag(QGraphicsItem::ItemIsMovable, true);
setFlag(QGraphicsItem::ItemIsSelectable, true);
setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
m_boundingRect = QRectF(-50, -30, 100, 60);
}

QRectF PLCItem::boundingRect() const
{
return m_boundingRect; // 返回我们设置的边界矩形
}

void PLCItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
Q_UNUSED(option);
Q_UNUSED(widget);

painter->setRenderHint(QPainter::Antialiasing);
QPen pen(Qt::black, 1);
painter->setPen(pen);

// 根据状态设置填充颜色
QBrush brush(m_active ? Qt::green : Qt::white);
painter->setBrush(brush);

switch (m_type)
{
case NormallyOpen:
// 绘制常开触点
painter->drawLine(QPointF(-25, 0), QPointF(-10, 0)); // 左横线
painter->drawLine(QPointF(10, 0), QPointF(25, 0)); // 右横线
painter->drawLine(QPointF(-10, -20), QPointF(-10, 20)); // 左竖线
painter->drawLine(QPointF(10, -20), QPointF(10, 20)); // 右竖线
break;
case NormallyClosed:
// 绘制常闭触点
painter->drawLine(QPointF(-25, 0), QPointF(-10, 0)); // 左横线
painter->drawLine(QPointF(10, 0), QPointF(25, 0)); // 右横线
painter->drawLine(QPointF(-10, -20), QPointF(-10, 20)); // 左竖线
painter->drawLine(QPointF(10, -20), QPointF(10, 20)); // 右竖线
painter->drawLine(QPointF(10, -20), QPointF(-10, 20)); // 斜线
break;
case GreaterThan:
painter->drawLine(QPointF(-25, 0), QPointF(-15, 0)); // 左横线
painter->drawLine(QPointF(-15, -20), QPointF(-15, 20)); // 左竖线
painter->drawLine(QPointF(15, -20), QPointF(15, 20)); // 右竖线
painter->drawLine(QPointF(15, 0), QPointF(25, 0)); // 右横线
// 绘制大于号
painter->drawLine(QPointF(-8, -8), QPointF(10, 0)); // 上斜线
painter->drawLine(QPointF(-8, 8), QPointF(10, 0)); // 下斜线
break;

case GreaterEqual:
// 绘制大于等于指令
painter->drawLine(QPointF(-25, 0), QPointF(-15, 0)); // 左横线
painter->drawLine(QPointF(-15, -20), QPointF(-15, 20)); // 左竖线
painter->drawLine(QPointF(15, -20), QPointF(15, 20)); // 右竖线
painter->drawLine(QPointF(15, 0), QPointF(25, 0)); // 右横线
// 绘制大于号
painter->drawLine(QPointF(-8, -8), QPointF(10, 0)); // 上斜线
painter->drawLine(QPointF(-8, 8), QPointF(10, 0)); // 下斜线
painter->drawLine(QPointF(-8, 13), QPointF(10, 5)); // 等于线
break;

case LessThan:
// 绘制小于指令
painter->drawLine(QPointF(-25, 0), QPointF(-15, 0)); // 左横线
painter->drawLine(QPointF(-15, -20), QPointF(-15, 20)); // 左竖线
painter->drawLine(QPointF(15, -20), QPointF(15, 20)); // 右竖线
painter->drawLine(QPointF(15, 0), QPointF(25, 0)); // 右横线
// 绘制小于号
painter->drawLine(QPointF(-10,0), QPointF(8, 8)); // 上斜线
painter->drawLine(QPointF(-10,0), QPointF(8, -8)); // 下斜线
break;

case LessEqual:
// 绘制小于等于指令
painter->drawLine(QPointF(-25, 0), QPointF(-15, 0)); // 左横线
painter->drawLine(QPointF(-15, -20), QPointF(-15, 20)); // 左竖线
painter->drawLine(QPointF(15, -20), QPointF(15, 20)); // 右竖线
painter->drawLine(QPointF(15, 0), QPointF(25, 0)); // 右横线
// 绘制小于号
painter->drawLine(QPointF(-10,0), QPointF(8, 8)); // 上斜线
painter->drawLine(QPointF(-10,0), QPointF(8, -8)); // 下斜线
painter->drawLine(QPointF(-10,5), QPointF(8, 13)); // 等于线
break;

case Equal:
// 绘制等于指令
painter->drawLine(QPointF(-25, 0), QPointF(-15, 0)); // 左横线
painter->drawLine(QPointF(-15, -20), QPointF(-15, 20)); // 左竖线
painter->drawLine(QPointF(15, -20), QPointF(15, 20)); // 右竖线
painter->drawLine(QPointF(15, 0), QPointF(25, 0)); // 右横线
// 绘制等于号
painter->drawLine(QPointF(-10,0-5), QPointF(10, -5)); // 上横线
painter->drawLine(QPointF(-10,5), QPointF(10, 5)); // 下横线
break;

case Coil:
painter->drawLine(QPointF(-25, 0), QPointF(-15, 0)); // 左侧横线
painter->drawArc(QRectF(-15, -15, 15, 30), 90 * 16, 180 * 16);// 绘制左括号 "("
painter->drawArc(QRectF(0, -15, 15, 30), 270 * 16, 180 * 16);// 绘制右括号 ")"
painter->drawLine(QPointF(15, 0), QPointF(25, 0));// 右侧横线
painter->setPen(QPen(Qt::black, 4));// 调整线条宽度使括号更清晰
break;
}
}

// 位置变化时强制更新,避免拖影
QVariant PLCItem::itemChange(GraphicsItemChange change, const QVariant &value)
{
if (change == ItemPositionHasChanged) {
update(); // 位置变化时刷新绘制
}
return QGraphicsItem::itemChange(change, value);
}

void PLCItem::setState(bool active)
{
if (m_active != active)
{
m_active = active;
update();
emit stateChanged(active);
}
}

void PLCItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
{
Q_UNUSED(event);
setState(!m_active);
}

QPointF PLCItem::leftTerminal() const
{
return mapToScene(QPointF(-30, 0));
}

QPointF PLCItem::rightTerminal() const
{
return mapToScene(QPointF(30, 0));
}

+ 120
- 0
untitled/plcitems.h Näytä tiedosto

@@ -0,0 +1,120 @@
#ifndef PLCITEMS_H
#define PLCITEMS_H

#include <QGraphicsObject>
#include <QPen>
#include <QBrush>
#include <QFont>
#include <QPainter>
#include <QPointF>
#include <QDebug>

// PLC图形项基类
class PLCItem : public QGraphicsObject
{
Q_OBJECT
public:
enum ItemType { NormallyOpen, NormallyClosed, GreaterThan, GreaterEqual,
LessThan, LessEqual, Equal, Coil };

PLCItem(ItemType type, QGraphicsItem *parent = nullptr);

QRectF boundingRect() const override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;

ItemType itemType() const { return m_type; }
QString name() const { return m_name; }
void setName(const QString &name) { m_name = name; }

void setState(bool active);
bool isActive() const { return m_active; }

QPointF leftTerminal() const;
QPointF rightTerminal() const;

protected:
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;

signals:
void stateChanged(bool active);

private:
ItemType m_type;
QString m_name;
bool m_active = false;
QRectF m_boundingRect;
QGraphicsRectItem* m_tableFrame = nullptr;
QVector<QGraphicsLineItem*> m_horizontalLines;
QVector<QGraphicsLineItem*> m_verticalLines;
int m_rows = 15; // 表格行数
int m_columns = 20; // 表格列数
int m_cellSize = 50; // 单元格大小(像素)

};

// 常开触点
class NormallyOpenItem : public PLCItem
{
public:
NormallyOpenItem(QGraphicsItem *parent = nullptr)
: PLCItem(NormallyOpen, parent) {}
};

// 常闭触点
class NormallyClosedItem : public PLCItem
{
public:
NormallyClosedItem(QGraphicsItem *parent = nullptr)
: PLCItem(NormallyClosed, parent) {}
};

// 大于指令
class GreaterThanItem : public PLCItem
{
public:
GreaterThanItem(QGraphicsItem *parent = nullptr)
: PLCItem(GreaterThan, parent) {}
};

// 大于等于指令
class GreaterEqualItem : public PLCItem
{
public:
GreaterEqualItem(QGraphicsItem *parent = nullptr)
: PLCItem(GreaterEqual, parent) {}
};

// 小于指令
class LessThanItem : public PLCItem
{
public:
LessThanItem(QGraphicsItem *parent = nullptr)
: PLCItem(LessThan, parent) {}
};

// 小于等于指令
class LessEqualItem : public PLCItem
{
public:
LessEqualItem(QGraphicsItem *parent = nullptr)
: PLCItem(LessEqual, parent) {}
};

// 等于指令
class EqualItem : public PLCItem
{
public:
EqualItem(QGraphicsItem *parent = nullptr)
: PLCItem(Equal, parent) {}
};

// 线圈
class CoilItem : public PLCItem
{
public:
CoilItem(QGraphicsItem *parent = nullptr)
: PLCItem(Coil, parent) {}
};

#endif // PLCITEMS_H

+ 4
- 2
untitled/untitled.pro Näytä tiedosto

@@ -21,14 +21,16 @@ SOURCES += \
hmidocument.cpp \
main.cpp \
mainwindow.cpp \
plcdocument.cpp
plcdocument.cpp \
plcitems.cpp

HEADERS += \
basedocument.h \
graphicsitems.h \
hmidocument.h \
mainwindow.h \
plcdocument.h
plcdocument.h \
plcitems.h

FORMS += \
mainwindow.ui


+ 1
- 1
untitled/untitled.pro.user Näytä tiedosto

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 4.11.1, 2025-08-08T20:16:09. -->
<!-- Written by QtCreator 4.11.1, 2025-08-08T22:08:39. -->
<qtcreator>
<data>
<variable>EnvironmentId</variable>


Ladataan…
Peruuta
Tallenna