@@ -23,10 +23,28 @@ public: | |||||
virtual bool saveToFile(const QString &filePath) = 0; | virtual bool saveToFile(const QString &filePath) = 0; | ||||
virtual bool loadFromFile(const QString &filePath) = 0; | virtual bool loadFromFile(const QString &filePath) = 0; | ||||
// 设置变换矩阵 | |||||
virtual void setTransform(const QTransform& transform) { | |||||
m_transform = transform; | |||||
update(); | |||||
} | |||||
// 获取文档内容尺寸(子类需要重写) | |||||
virtual QSize getContentSize() const { return QSize(800, 600); } // 默认尺寸 | |||||
// 重写paintEvent应用变换 | |||||
// virtual void paintEvent(QPaintEvent *event) override { | |||||
// QPainter painter(this); | |||||
// painter.setTransform(m_transform); | |||||
// // 子类应调用父类的paintEvent? 或者自行绘制 | |||||
// } | |||||
protected: | protected: | ||||
DocumentType m_type; | DocumentType m_type; | ||||
QString m_filePath; | QString m_filePath; | ||||
bool m_modified = false; | bool m_modified = false; | ||||
QTransform m_transform; | |||||
}; | }; | ||||
#endif // BASEDOCUMENT_H | #endif // BASEDOCUMENT_H |
@@ -29,22 +29,38 @@ HMIDocument::HMIDocument(QWidget *parent) | |||||
m_canDrawRectangle(false), | m_canDrawRectangle(false), | ||||
m_lastPastePos(0, 0) | m_lastPastePos(0, 0) | ||||
{ | { | ||||
// 创建绘图场景 | |||||
// 创建绘图场景 - 固定大小 | |||||
m_scene = new QGraphicsScene(this); | m_scene = new QGraphicsScene(this); | ||||
m_scene->setSceneRect(0, 0, 800, 600); | |||||
m_scene->setSceneRect(0, 0, 1000, 600); // 固定画布尺寸 | |||||
m_scene->setBackgroundBrush(QBrush(Qt::lightGray)); | m_scene->setBackgroundBrush(QBrush(Qt::lightGray)); | ||||
// 创建视图 | |||||
// 创建视图 - 关键修改:使用滚动区域包装 | |||||
m_scrollArea = new QScrollArea(this); | |||||
m_scrollArea->setWidgetResizable(false); // 禁止内容自动调整大小 | |||||
m_scrollArea->setFrameShape(QFrame::NoFrame); // 无边框 | |||||
// 创建真正的视图部件 | |||||
m_view = new QGraphicsView(m_scene, this); | m_view = new QGraphicsView(m_scene, this); | ||||
m_view->setRenderHint(QPainter::Antialiasing); | m_view->setRenderHint(QPainter::Antialiasing); | ||||
m_view->setDragMode(QGraphicsView::RubberBandDrag); | m_view->setDragMode(QGraphicsView::RubberBandDrag); | ||||
m_view->setAcceptDrops(true); | m_view->setAcceptDrops(true); | ||||
m_view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // 隐藏自身滚动条 | |||||
m_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); | |||||
// 关键:视图固定大小与场景匹配 | |||||
m_view->setMinimumSize(1000, 600); | |||||
m_view->setMaximumSize(1000, 600); | |||||
m_view->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); | |||||
m_view->viewport()->installEventFilter(this); | m_view->viewport()->installEventFilter(this); | ||||
// 布局(让视图占满文档区域) | |||||
// 添加视图到滚动区域 | |||||
m_scrollArea->setWidget(m_view); | |||||
m_scrollArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); | |||||
// 设置主布局 | |||||
auto layout = new QVBoxLayout(this); | auto layout = new QVBoxLayout(this); | ||||
layout->setContentsMargins(0, 0, 0, 0); | layout->setContentsMargins(0, 0, 0, 0); | ||||
layout->addWidget(m_view); | |||||
layout->addWidget(m_scrollArea); | |||||
setLayout(layout); | setLayout(layout); | ||||
} | } | ||||
@@ -40,14 +40,12 @@ public: | |||||
bool loadFromFile(const QString &filePath) override; | bool loadFromFile(const QString &filePath) override; | ||||
void markModified(); | void markModified(); | ||||
signals: | signals: | ||||
void titleChanged(const QString &title); | void titleChanged(const QString &title); | ||||
protected: | protected: | ||||
bool eventFilter(QObject *obj, QEvent *event) override; | bool eventFilter(QObject *obj, QEvent *event) override; | ||||
private: | |||||
private: | |||||
// 辅助方法 | // 辅助方法 | ||||
void createShape(const QString& type, const QPointF &pos); | void createShape(const QString& type, const QPointF &pos); | ||||
void resetDrawFlags(); | void resetDrawFlags(); | ||||
@@ -63,6 +61,7 @@ private: | |||||
QString m_title; | QString m_title; | ||||
QGraphicsScene *m_scene; | QGraphicsScene *m_scene; | ||||
QGraphicsView *m_view; | QGraphicsView *m_view; | ||||
QScrollArea *m_scrollArea; | |||||
bool m_canDrawEllipse; | bool m_canDrawEllipse; | ||||
bool m_canDrawRectangle; | bool m_canDrawRectangle; | ||||
@@ -17,7 +17,7 @@ | |||||
#include <QFileDialog> | #include <QFileDialog> | ||||
#include <QMessageBox> | #include <QMessageBox> | ||||
#include <QTextEdit> | #include <QTextEdit> | ||||
#include<QDockWidget> | |||||
MainWindow::MainWindow(QWidget *parent) | MainWindow::MainWindow(QWidget *parent) | ||||
: QMainWindow(parent) | : QMainWindow(parent) | ||||
{ | { | ||||
@@ -32,11 +32,64 @@ MainWindow::MainWindow(QWidget *parent) | |||||
connect(m_tabWidget, &QTabWidget::tabCloseRequested, this, &MainWindow::onCloseTab); | connect(m_tabWidget, &QTabWidget::tabCloseRequested, this, &MainWindow::onCloseTab); | ||||
createMenus();// 创建菜单和工具栏 | createMenus();// 创建菜单和工具栏 | ||||
createToolbars(); | createToolbars(); | ||||
createLogPanel(); // 创建日志面板 | |||||
} | } | ||||
MainWindow::~MainWindow() | MainWindow::~MainWindow() | ||||
{ | { | ||||
} | } | ||||
void MainWindow::createLogPanel() | |||||
{ | |||||
// 创建可停靠窗口 | |||||
m_logDock = new QDockWidget("消息输出", this); | |||||
m_logDock->setAllowedAreas(Qt::BottomDockWidgetArea); | |||||
m_logDock->setFeatures(QDockWidget::DockWidgetClosable | | |||||
QDockWidget::DockWidgetMovable | | |||||
QDockWidget::DockWidgetFloatable); // 添加垂直标题栏 | |||||
// 设置高度范围而非固定高度 | |||||
m_logDock->setMinimumHeight(150); | |||||
m_logDock->setMaximumHeight(400); | |||||
// 创建主内容部件 | |||||
QWidget* logWidget = new QWidget; | |||||
QVBoxLayout* logLayout = new QVBoxLayout; | |||||
logLayout->setContentsMargins(2, 2, 2, 2); | |||||
// 创建文本编辑框用于显示日志 | |||||
m_logEdit = new QTextEdit; | |||||
m_logEdit->setReadOnly(true); | |||||
m_logEdit->setFont(QFont("Consolas", 10)); | |||||
m_logEdit->setStyleSheet("background-color: #f8f8f8; border: 1px solid #d9d9d9;"); | |||||
m_logEdit->setMinimumHeight(100); | |||||
// 创建控制按钮 | |||||
QHBoxLayout* btnLayout = new QHBoxLayout; | |||||
m_clearLogBtn = new QPushButton("清空日志"); | |||||
btnLayout->addWidget(m_clearLogBtn); | |||||
btnLayout->addStretch(); | |||||
// 组合布局 | |||||
logLayout->addLayout(btnLayout); | |||||
logLayout->addWidget(m_logEdit); | |||||
logWidget->setLayout(logLayout); | |||||
m_logDock->setWidget(logWidget); | |||||
addDockWidget(Qt::BottomDockWidgetArea, m_logDock); | |||||
// 初始显示状态 | |||||
m_logDock->show(); | |||||
// 连接信号 | |||||
connect(m_clearLogBtn, &QPushButton::clicked, this, &MainWindow::onClearLogButtonClicked); | |||||
} | |||||
// 槽函数实现 | |||||
void MainWindow::onClearLogButtonClicked() | |||||
{ | |||||
m_logEdit->clear(); | |||||
m_logEdit->append("日志已清空"); | |||||
} | |||||
// 创建菜单栏 | // 创建菜单栏 | ||||
void MainWindow::createMenus() | void MainWindow::createMenus() | ||||
@@ -7,7 +7,7 @@ | |||||
#include <QAction> | #include <QAction> | ||||
#include<QTextEdit> | #include<QTextEdit> | ||||
#include "basedocument.h" | #include "basedocument.h" | ||||
#include<QPushButton> | |||||
QT_BEGIN_NAMESPACE | QT_BEGIN_NAMESPACE | ||||
namespace Ui { class MainWindow; } | namespace Ui { class MainWindow; } | ||||
QT_END_NAMESPACE | QT_END_NAMESPACE | ||||
@@ -31,9 +31,14 @@ private slots: | |||||
void onSaveAs(); // 另存为文档 | void onSaveAs(); // 另存为文档 | ||||
void onOpen(); // 打开文档 | void onOpen(); // 打开文档 | ||||
void onCloseTab(int index); // 关闭标签页 | void onCloseTab(int index); // 关闭标签页 | ||||
void onClearLogButtonClicked(); | |||||
private: | private: | ||||
void createMenus(); // 创建菜单栏 | void createMenus(); // 创建菜单栏 | ||||
QDockWidget* m_logDock; | |||||
QTextEdit* m_logEdit; | |||||
QPushButton* m_clearLogBtn; | |||||
void createLogPanel(); | |||||
void createToolbars(); // 创建工具栏(左侧) | void createToolbars(); // 创建工具栏(左侧) | ||||
void updateToolBar(BaseDocument *doc); // 根据文档类型更新工具栏 | void updateToolBar(BaseDocument *doc); // 根据文档类型更新工具栏 | ||||
void saveDocument(BaseDocument *doc); // 保存文档 | void saveDocument(BaseDocument *doc); // 保存文档 | ||||
@@ -1,13 +1,13 @@ | |||||
// plcdocument.h | |||||
#ifndef PLCDOCUMENT_H | #ifndef PLCDOCUMENT_H | ||||
#define PLCDOCUMENT_H | #define PLCDOCUMENT_H | ||||
#include "basedocument.h" | #include "basedocument.h" | ||||
#include "plcitems.h" // 确保包含PLCItem的定义 | |||||
#include "plcitems.h" | |||||
#include <QGraphicsScene> | #include <QGraphicsScene> | ||||
#include <QGraphicsView> | #include <QGraphicsView> | ||||
#include <QGraphicsLineItem> | #include <QGraphicsLineItem> | ||||
#include <QGraphicsRectItem> | #include <QGraphicsRectItem> | ||||
#include <QGraphicsEllipseItem> | |||||
#include <QMap> | #include <QMap> | ||||
#include <QSet> | #include <QSet> | ||||
#include <QVector> | #include <QVector> | ||||
@@ -17,6 +17,10 @@ | |||||
#include <QMimeData> | #include <QMimeData> | ||||
#include <QPair> | #include <QPair> | ||||
const int DEFAULT_ROWS = 11; | |||||
const int DEFAULT_COLUMNS = 15; | |||||
const int DEFAULT_CELL_SIZE = 70; | |||||
class PLCItem; | class PLCItem; | ||||
class ConnectionLine; | class ConnectionLine; | ||||
@@ -24,6 +28,19 @@ class PLCDocument : public BaseDocument | |||||
{ | { | ||||
Q_OBJECT | Q_OBJECT | ||||
public: | public: | ||||
enum TerminalType { | |||||
NoTerminal = 0, | |||||
LeftTerminal, | |||||
RightTerminal, | |||||
RowTerminal | |||||
}; | |||||
enum ConnectionSourceType { | |||||
ConnectionNone, // 无连接 | |||||
ConnectionFromItem, // 起点是元件的端子 | |||||
ConnectionFromRowTerminal // 起点是行触点 | |||||
}; | |||||
explicit PLCDocument(QWidget *parent = nullptr); | explicit PLCDocument(QWidget *parent = nullptr); | ||||
~PLCDocument() override; | ~PLCDocument() override; | ||||
@@ -34,28 +51,20 @@ public: | |||||
bool saveToFile(const QString &filePath) override; | bool saveToFile(const QString &filePath) override; | ||||
bool loadFromFile(const QString &filePath) override; | bool loadFromFile(const QString &filePath) override; | ||||
// 创建PLC元件 | |||||
void createPLCItem(const QString &type, const QPointF &pos); | void createPLCItem(const QString &type, const QPointF &pos); | ||||
// 连接管理 | |||||
void startConnection(PLCItem *startItem); | |||||
void endConnection(PLCItem *endItem); | |||||
void startConnection(PLCItem *startItem, TerminalType startTerminal); | |||||
void startConnectionFromRowTerminal(int row); | |||||
void clearCurrentConnection(); | void clearCurrentConnection(); | ||||
// 运行状态控制 | |||||
bool tryEndConnection(PLCItem *endItem, TerminalType endTerminal); | |||||
void setSimulationRunning(bool running); | void setSimulationRunning(bool running); | ||||
void resetSimulation(); | void resetSimulation(); | ||||
void setCurrentTool(const QString &tool) { m_currentTool = tool; } | void setCurrentTool(const QString &tool) { m_currentTool = tool; } | ||||
// 获取表格参数 | |||||
int rowCount() const { return m_rows; } | int rowCount() const { return m_rows; } | ||||
int columnCount() const { return m_columns; } | int columnCount() const { return m_columns; } | ||||
int cellSize() const { return m_cellSize; } | int cellSize() const { return m_cellSize; } | ||||
// 计算单元格中心位置 | |||||
QPointF snapToCellCenter(const QPointF &pos) const; | QPointF snapToCellCenter(const QPointF &pos) const; | ||||
// 检查并修正位置,确保在表格内 | |||||
QPointF constrainToTable(const QPointF &pos) const; | QPointF constrainToTable(const QPointF &pos) const; | ||||
void setTitle(const QString &title) { m_title = title; } | void setTitle(const QString &title) { m_title = title; } | ||||
@@ -65,63 +74,79 @@ protected: | |||||
void resizeEvent(QResizeEvent *event) override; | void resizeEvent(QResizeEvent *event) override; | ||||
private slots: | private slots: | ||||
// 处理场景变化(用于更新连接线) | |||||
void handleSceneChanged(); | void handleSceneChanged(); | ||||
void onRowTerminalConnection(); | |||||
private: | private: | ||||
// 表格管理 | |||||
void createRealTable(); | void createRealTable(); | ||||
void clearTable(); | |||||
void adjustTableToFitView(); // 调整表格以适应视图大小 | |||||
// 项目位置改变处理 | |||||
//void clearTable(); | |||||
TerminalType whichTerminal(const QPointF& scenePos, PLCItem* item) const; | |||||
bool isTerminalUsed(PLCItem* item, TerminalType terminal) const; | |||||
void handleItemPositionChange(PLCItem *item); | void handleItemPositionChange(PLCItem *item); | ||||
// 仿真与连接管理 | |||||
bool isCellOccupied(int col, int row, PLCItem* excludeItem) const; | |||||
void updateConnections(); | void updateConnections(); | ||||
void createContextMenu(QPoint globalPos); | void createContextMenu(QPoint globalPos); | ||||
// 根据类型创建PLC项 - 新增的辅助函数 | |||||
PLCItem* createItemByType(PLCItem::ItemType type); | PLCItem* createItemByType(PLCItem::ItemType type); | ||||
void removeInvalidConnectionsForItem(PLCItem* movedItem); | |||||
QList<PLCItem*> allPLCItems() const; | |||||
QGraphicsScene *m_scene; | QGraphicsScene *m_scene; | ||||
QGraphicsView *m_view; | QGraphicsView *m_view; | ||||
QString m_currentTool; // 当前选择的工具 | |||||
// 连接管理 | |||||
PLCItem *m_connectionStart = nullptr; | |||||
QString m_currentTool; | |||||
ConnectionSourceType m_connectionSourceType = ConnectionNone; | |||||
PLCItem *m_connectionStartItem = nullptr; | |||||
TerminalType m_startTerminal = NoTerminal; | |||||
int m_connectionStartRow = -1; | |||||
QGraphicsLineItem *m_tempLine = nullptr; | QGraphicsLineItem *m_tempLine = nullptr; | ||||
QList<ConnectionLine*> m_connections; | QList<ConnectionLine*> m_connections; | ||||
// 运行状态 | |||||
QMap<PLCItem*, QMap<TerminalType, bool>> terminalConnections; | |||||
bool m_simulationRunning = false; | bool m_simulationRunning = false; | ||||
QSet<PLCItem*> m_activeItems; | QSet<PLCItem*> m_activeItems; | ||||
// 表格参数 - 使用常量定义默认值 | |||||
int m_rows = 15; // 表格行数 | |||||
int m_columns = 20; // 表格列数 | |||||
int m_cellSize = 50; // 单元格大小(像素) | |||||
// 表格图形项 | |||||
int m_rows = DEFAULT_ROWS; | |||||
int m_columns = DEFAULT_COLUMNS; | |||||
int m_cellSize = DEFAULT_CELL_SIZE; | |||||
QGraphicsRectItem* m_tableFrame = nullptr; | QGraphicsRectItem* m_tableFrame = nullptr; | ||||
QVector<QGraphicsLineItem*> m_horizontalLines; | QVector<QGraphicsLineItem*> m_horizontalLines; | ||||
QVector<QGraphicsLineItem*> m_verticalLines; | QVector<QGraphicsLineItem*> m_verticalLines; | ||||
QString m_title; // 添加标题成员变量 | |||||
QString m_title; | |||||
QVector<QGraphicsEllipseItem*> m_rowTerminals; | |||||
QMap<QGraphicsEllipseItem*, int> m_rowTerminalMap; | |||||
QMap<PLCItem*, QPointF> m_lastValidPositions; | |||||
QSet<PLCItem*> m_processingItems; | |||||
bool m_loadingFile = false; | |||||
}; | }; | ||||
// 连接线类 | |||||
class ConnectionLine : public QGraphicsLineItem | class ConnectionLine : public QGraphicsLineItem | ||||
{ | { | ||||
public: | public: | ||||
ConnectionLine(PLCItem *start, PLCItem *end, QGraphicsItem *parent = nullptr); | |||||
ConnectionLine( | |||||
PLCItem *startItem, PLCDocument::TerminalType startTerminal, | |||||
PLCItem *endItem, PLCDocument::TerminalType endTerminal, | |||||
QGraphicsItem *parent = nullptr | |||||
); | |||||
void updatePosition(); | void updatePosition(); | ||||
PLCItem* startItem() const { return m_startItem; } | PLCItem* startItem() const { return m_startItem; } | ||||
PLCItem* endItem() const { return m_endItem; } | PLCItem* endItem() const { return m_endItem; } | ||||
PLCDocument::TerminalType startTerminal() const { return m_startTerminal; } | |||||
PLCDocument::TerminalType endTerminal() const { return m_endTerminal; } | |||||
void setRowTerminalTargetRow(int row) { m_rowTerminalTargetRow = row; } | |||||
int rowTerminalTargetRow() const { return m_rowTerminalTargetRow; } | |||||
void setRowTerminalSourceRow(int row) { m_rowTerminalSourceRow = row; } | |||||
int rowTerminalSourceRow() const { return m_rowTerminalSourceRow; } | |||||
void setStartTerminalPoint(const QPointF& point) { m_startTerminalPoint = point; } | |||||
void setEndTerminalPoint(const QPointF& point) { m_endTerminalPoint = point; } | |||||
private: | private: | ||||
PLCItem *m_startItem; | PLCItem *m_startItem; | ||||
PLCDocument::TerminalType m_startTerminal; | |||||
PLCItem *m_endItem; | PLCItem *m_endItem; | ||||
PLCDocument::TerminalType m_endTerminal; | |||||
QPointF m_startTerminalPoint; | |||||
QPointF m_endTerminalPoint; | |||||
int m_rowTerminalTargetRow = -1; | |||||
int m_rowTerminalSourceRow = -1; | |||||
}; | }; | ||||
#endif // PLCDOCUMENT_H | #endif // PLCDOCUMENT_H |
@@ -11,7 +11,7 @@ PLCItem::PLCItem(ItemType type, QGraphicsItem *parent) | |||||
QRectF PLCItem::boundingRect() const | QRectF PLCItem::boundingRect() const | ||||
{ | { | ||||
return m_boundingRect; // 返回我们设置的边界矩形 | |||||
return m_boundingRect; //返回我们设置的边界矩形 | |||||
} | } | ||||
void PLCItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) | void PLCItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) | ||||
@@ -104,7 +104,7 @@ void PLCItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, Q | |||||
painter->drawLine(QPointF(-25, 0), QPointF(-15, 0)); // 左侧横线 | painter->drawLine(QPointF(-25, 0), QPointF(-15, 0)); // 左侧横线 | ||||
painter->drawArc(QRectF(-15, -15, 15, 30), 90 * 16, 180 * 16);// 绘制左括号 "(" | painter->drawArc(QRectF(-15, -15, 15, 30), 90 * 16, 180 * 16);// 绘制左括号 "(" | ||||
painter->drawArc(QRectF(0, -15, 15, 30), 270 * 16, 180 * 16);// 绘制右括号 ")" | painter->drawArc(QRectF(0, -15, 15, 30), 270 * 16, 180 * 16);// 绘制右括号 ")" | ||||
painter->drawLine(QPointF(15, 0), QPointF(25, 0));// 右侧横线 | |||||
//painter->drawLine(QPointF(15, 0), QPointF(25, 0));// 右侧横线 | |||||
painter->setPen(QPen(Qt::black, 4));// 调整线条宽度使括号更清晰 | painter->setPen(QPen(Qt::black, 4));// 调整线条宽度使括号更清晰 | ||||
break; | break; | ||||
} | } | ||||
@@ -1,6 +1,6 @@ | |||||
<?xml version="1.0" encoding="UTF-8"?> | <?xml version="1.0" encoding="UTF-8"?> | ||||
<!DOCTYPE QtCreatorProject> | <!DOCTYPE QtCreatorProject> | ||||
<!-- Written by QtCreator 4.11.1, 2025-08-09T17:07:02. --> | |||||
<!-- Written by QtCreator 4.11.1, 2025-08-11T21:25:16. --> | |||||
<qtcreator> | <qtcreator> | ||||
<data> | <data> | ||||
<variable>EnvironmentId</variable> | <variable>EnvironmentId</variable> | ||||