From 7c633ad47ae5d58042b49e92ec06850d83cc65a2 Mon Sep 17 00:00:00 2001 From: email <15737449156@163.com> Date: Sat, 9 Aug 2025 15:42:49 +0800 Subject: [PATCH] =?UTF-8?q?PLC=E8=BF=9B=E8=A1=8C=E4=BA=86=E7=BB=98?= =?UTF-8?q?=E5=9B=BE=E7=9A=84=E5=88=9D=E6=AD=A5=E7=BC=96=E5=86=99=EF=BC=8C?= =?UTF-8?q?=E5=AE=8C=E6=88=90=E4=BA=86=E7=A9=BA=E9=97=B4=E7=9A=84=E5=9B=BE?= =?UTF-8?q?=E6=A0=87=E7=BB=98=E5=88=B6=E5=92=8C=E5=9F=BA=E4=BA=8E=E8=A1=A8?= =?UTF-8?q?=E6=A0=BC=E7=9A=84=E6=8B=96=E6=8B=BD=E7=82=B9=E5=87=BB=E7=94=9F?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IntegratedPlatform/mainwindow.cpp | 9 +- untitled/hmidocument.cpp | 10 +- untitled/mainwindow.cpp | 90 ++-- untitled/plcdocument.cpp | 759 ++++++++++++++++++++++++++++-- untitled/plcdocument.h | 100 +++- untitled/plcitems.cpp | 146 ++++++ untitled/plcitems.h | 120 +++++ untitled/untitled.pro | 6 +- untitled/untitled.pro.user | 2 +- 9 files changed, 1162 insertions(+), 80 deletions(-) create mode 100644 untitled/plcitems.cpp create mode 100644 untitled/plcitems.h diff --git a/IntegratedPlatform/mainwindow.cpp b/IntegratedPlatform/mainwindow.cpp index c3b5c0e..f7cdf1d 100644 --- a/IntegratedPlatform/mainwindow.cpp +++ b/IntegratedPlatform/mainwindow.cpp @@ -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(event); if (dragEvent->source() == ui->treeWidget_2)//检查拖拽源是否为树控件,并接受拖拽 diff --git a/untitled/hmidocument.cpp b/untitled/hmidocument.cpp index fd8fd84..09d4138 100644 --- a/untitled/hmidocument.cpp +++ b/untitled/hmidocument.cpp @@ -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)); diff --git a/untitled/mainwindow.cpp b/untitled/mainwindow.cpp index 5f35d9f..420ec85 100644 --- a/untitled/mainwindow.cpp +++ b/untitled/mainwindow.cpp @@ -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(m_tabWidget->currentWidget()); - if (currentDoc && currentDoc->type() == BaseDocument::HMI) { + if (currentDoc && currentDoc->type() == BaseDocument::PLC)//PLC文档处理 + { + PLCDocument* plcDoc = static_cast(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(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(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, "保存文档", "无法保存文档"); } } diff --git a/untitled/plcdocument.cpp b/untitled/plcdocument.cpp index d6362f3..31a64f7 100644 --- a/untitled/plcdocument.cpp +++ b/untitled/plcdocument.cpp @@ -1,88 +1,781 @@ +// PLCDocument.cpp 的完整修改版 #include "plcdocument.h" -#include -#include +#include "plcitems.h" #include -#include #include -#include +#include +#include +#include +#include #include #include #include +#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// 固定表格尺寸的常量 +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(items.first())) { + PLCItem *item = static_cast(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(std::floor(pos.x() / m_cellSize)); + // 计算所在单元格行号(0~m_rows-1) + int row = static_cast(std::floor(pos.y() / m_cellSize)); + + // 限制在表格范围内 + col = qBound(0, col, m_columns - 1); + row = qBound(0, row, m_rows - 1); + + return QPointF(col * m_cellSize + m_cellSize / 2.0, + row * m_cellSize + m_cellSize / 2.0); +} + +QPointF PLCDocument::constrainToTable(const QPointF &pos) const +{ + int col = static_cast(std::floor(pos.x() / m_cellSize)); + int row = static_cast(std::floor(pos.y() / m_cellSize)); + + col = qBound(0, col, m_columns - 1); + row = qBound(0, row, m_rows - 1); + + return QPointF(col * m_cellSize + m_cellSize / 2.0, + row * m_cellSize + m_cellSize / 2.0); +} + +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(event); + if (dragEvent->mimeData()->hasText()) { + dragEvent->acceptProposedAction(); + return true; + } + } + // 处理拖拽移动事件 + else if (event->type() == QEvent::DragMove) { + QDragMoveEvent *dragEvent = static_cast(event); + if(dragEvent->mimeData()->hasText()) { + dragEvent->acceptProposedAction(); + return true; + } + } + // 处理拖拽释放事件(使用单元格中心对齐) + else if (event->type() == QEvent::Drop) { + QDropEvent *dropEvent = static_cast(event); + const QMimeData *mimeData = dropEvent->mimeData(); + if (mimeData->hasText()) { + QPoint viewportPos = m_view->viewport()->mapFromParent(dropEvent->pos()); + QPointF scenePos = m_view->mapToScene(viewportPos); + // 对齐到单元格中心 + QPointF cellCenter = snapToCellCenter(scenePos); + // 检查单元格是否已被占用 + QSet occupiedCells; + for (QGraphicsItem* item : m_scene->items()) { + if (PLCItem* plcItem = dynamic_cast(item)) { + int col = static_cast(plcItem->x() / m_cellSize); + int row = static_cast(plcItem->y() / m_cellSize); + occupiedCells.insert(col * 1000 + row); // 简单的行列唯一键 + } + } + + int col = static_cast(cellCenter.x() / m_cellSize); + int row = static_cast(cellCenter.y() / m_cellSize); + int cellKey = col * 1000 + row; + + // 如果单元格被占用,尝试移动到最近的空单元格 + if (occupiedCells.contains(cellKey)) { + bool found = false; + // 尝试向右移动 + for (int offset = 1; offset < m_columns; offset++) { + int newCol = col + offset; + if (newCol < m_columns) { + int newKey = newCol * 1000 + row; + if (!occupiedCells.contains(newKey)) { + cellCenter = QPointF(newCol * m_cellSize + m_cellSize / 2.0, + row * m_cellSize + m_cellSize / 2.0); + found = true; + break; + } + } + } + if (!found) { + // 尝试向下移动 + for (int offset = 1; offset < m_rows; offset++) { + int newRow = row + offset; + if (newRow < m_rows) { + int newKey = col * 1000 + newRow; + if (!occupiedCells.contains(newKey)) { + cellCenter = QPointF(col * m_cellSize + m_cellSize / 2.0, + newRow * m_cellSize + m_cellSize / 2.0); + found = true; + break; + } + } + } + } + } + + // 创建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(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(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 occupiedCells; + for (QGraphicsItem* item : m_scene->items()) { + if (PLCItem* plcItem = dynamic_cast(item)) { + int col = static_cast(plcItem->x() / m_cellSize); + int row = static_cast(plcItem->y() / m_cellSize); + occupiedCells.insert(col * 1000 + row); // 简单的行列唯一键 + } + } + + int col = static_cast(cellCenter.x() / m_cellSize); + int row = static_cast(cellCenter.y() / m_cellSize); + int cellKey = col * 1000 + row; + + // 如果单元格被占用,显示警告 + if (occupiedCells.contains(cellKey)) { + 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(item)) { + endConnection(plcItem); + return true; + } + } + + // 开始连线 + QGraphicsItem *item = m_scene->itemAt(cellCenter, m_view->transform()); + if (PLCItem *plcItem = dynamic_cast(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(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(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 selected = m_scene->selectedItems(); + if (selected.size() == 1) { + if (PLCItem *item = dynamic_cast(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(item)) { + QJsonObject itemObj; + itemObj["type"] = plcItem->itemType(); + int col = static_cast(plcItem->x() / m_cellSize); + int row = static_cast(plcItem->y() / m_cellSize); + itemObj["col"] = col; // 保存列索引 + itemObj["row"] = row; // 保存行索引 + itemObj["name"] = plcItem->name(); + itemObj["active"] = plcItem->isActive(); + itemsArray.append(itemObj); + } + } + docObject["items"] = itemsArray; + + // 序列化所有连接 - 改为保存起始和终止元件的坐标 + QJsonArray connectionsArray; + for (ConnectionLine *line : m_connections) { + QJsonObject connObj; + // 通过坐标查找起始和终止元件 + int startCol = static_cast(line->startItem()->x() / m_cellSize); + int startRow = static_cast(line->startItem()->y() / m_cellSize); + int endCol = static_cast(line->endItem()->x() / m_cellSize); + int endRow = static_cast(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 allItems = m_scene->items(); + for (QGraphicsItem* item : allItems) { + // 保留表格项,只删除PLC元件 + if (dynamic_cast(item) || dynamic_cast(item)) { + m_scene->removeItem(item); + delete item; + } + } + + m_activeItems.clear(); + m_connectionStart = nullptr; + delete m_tempLine; + m_tempLine = nullptr; + + // 创建表格 + createRealTable(); // 重新创建表格以确保正确 + + // 加载PLC元件 + QMap, PLCItem*> itemMap; // (列, 行) -> PLCItem + QJsonArray itemsArray = docObject["items"].toArray(); + for (const QJsonValue &itemValue : itemsArray) { + QJsonObject itemObj = itemValue.toObject(); + PLCItem::ItemType type = static_cast(itemObj["type"].toInt()); + int col = itemObj["col"].toInt(); + int row = itemObj["row"].toInt(); + QString name = itemObj["name"].toString(); + bool active = itemObj["active"].toBool(); + + // 确保位置在表格范围内 + col = qBound(0, col, m_columns - 1); + row = qBound(0, row, m_rows - 1); + + PLCItem *item = createItemByType(type); + if (item) { + QPointF pos(col * m_cellSize + m_cellSize / 2.0, + row * m_cellSize + m_cellSize / 2.0); + item->setPos(pos); + item->setName(name); + item->setState(active); + m_scene->addItem(item); + + // 添加到映射表 + itemMap.insert(qMakePair(col, row), item); + + if (active) { + m_activeItems.insert(item); + } + + 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); + } } diff --git a/untitled/plcdocument.h b/untitled/plcdocument.h index 2388e78..25938a5 100644 --- a/untitled/plcdocument.h +++ b/untitled/plcdocument.h @@ -1,9 +1,24 @@ +// plcdocument.h #ifndef PLCDOCUMENT_H #define PLCDOCUMENT_H #include "basedocument.h" +#include "plcitems.h" // 确保包含PLCItem的定义 #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 m_connections; + + // 运行状态 + bool m_simulationRunning = false; + QSet m_activeItems; + + // 表格参数 - 使用常量定义默认值 + int m_rows = 15; // 表格行数 + int m_columns = 20; // 表格列数 + int m_cellSize = 50; // 单元格大小(像素) + + // 表格图形项 + QGraphicsRectItem* m_tableFrame = nullptr; + QVector m_horizontalLines; + QVector 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 diff --git a/untitled/plcitems.cpp b/untitled/plcitems.cpp new file mode 100644 index 0000000..197757a --- /dev/null +++ b/untitled/plcitems.cpp @@ -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)); +} diff --git a/untitled/plcitems.h b/untitled/plcitems.h new file mode 100644 index 0000000..c7709e8 --- /dev/null +++ b/untitled/plcitems.h @@ -0,0 +1,120 @@ +#ifndef PLCITEMS_H +#define PLCITEMS_H + +#include +#include +#include +#include +#include +#include +#include + +// 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 m_horizontalLines; + QVector 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 diff --git a/untitled/untitled.pro b/untitled/untitled.pro index bfde661..eaebac4 100644 --- a/untitled/untitled.pro +++ b/untitled/untitled.pro @@ -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 diff --git a/untitled/untitled.pro.user b/untitled/untitled.pro.user index cff81f3..2af24c3 100644 --- a/untitled/untitled.pro.user +++ b/untitled/untitled.pro.user @@ -1,6 +1,6 @@ - + EnvironmentId