@@ -11,13 +11,15 @@ ResizableShape<BaseShape>::ResizableShape(qreal x, qreal y, qreal w, qreal h) | |||||
this->setFlag(QGraphicsItem::ItemIsSelectable, true); | this->setFlag(QGraphicsItem::ItemIsSelectable, true); | ||||
this->setAcceptHoverEvents(true); | this->setAcceptHoverEvents(true); | ||||
} | } | ||||
template <typename BaseShape> | template <typename BaseShape> | ||||
void ResizableShape<BaseShape>::hoverMoveEvent(QGraphicsSceneHoverEvent *event) | void ResizableShape<BaseShape>::hoverMoveEvent(QGraphicsSceneHoverEvent *event) | ||||
{ | { | ||||
if (isInResizeArea(event->pos())) { | |||||
if (isInResizeArea(event->pos())) | |||||
{ | |||||
this->setCursor(Qt::SizeFDiagCursor); | this->setCursor(Qt::SizeFDiagCursor); | ||||
} else { | |||||
} | |||||
else | |||||
{ | |||||
this->setCursor(Qt::SizeAllCursor); | this->setCursor(Qt::SizeAllCursor); | ||||
} | } | ||||
BaseShape::hoverMoveEvent(event); | BaseShape::hoverMoveEvent(event); | ||||
@@ -34,10 +36,13 @@ bool ResizableShape<BaseShape>::isInResizeArea(const QPointF &pos) const | |||||
template <typename BaseShape> | template <typename BaseShape> | ||||
void ResizableShape<BaseShape>::mousePressEvent(QGraphicsSceneMouseEvent *event) | void ResizableShape<BaseShape>::mousePressEvent(QGraphicsSceneMouseEvent *event) | ||||
{ | { | ||||
if (isInResizeArea(event->pos())) { | |||||
if (isInResizeArea(event->pos())) | |||||
{ | |||||
resizing = true; | resizing = true; | ||||
this->setCursor(Qt::SizeFDiagCursor); | this->setCursor(Qt::SizeFDiagCursor); | ||||
} else { | |||||
} | |||||
else | |||||
{ | |||||
resizing = false; | resizing = false; | ||||
this->setCursor(Qt::SizeAllCursor); | this->setCursor(Qt::SizeAllCursor); | ||||
BaseShape::mousePressEvent(event); | BaseShape::mousePressEvent(event); | ||||
@@ -47,11 +52,14 @@ void ResizableShape<BaseShape>::mousePressEvent(QGraphicsSceneMouseEvent *event) | |||||
template <typename BaseShape> | template <typename BaseShape> | ||||
void ResizableShape<BaseShape>::mouseMoveEvent(QGraphicsSceneMouseEvent *event) | void ResizableShape<BaseShape>::mouseMoveEvent(QGraphicsSceneMouseEvent *event) | ||||
{ | { | ||||
if (resizing) { | |||||
if (resizing) | |||||
{ | |||||
QRectF newRect = this->rect(); | QRectF newRect = this->rect(); | ||||
newRect.setBottomRight(event->pos()); | newRect.setBottomRight(event->pos()); | ||||
this->setRect(newRect); | this->setRect(newRect); | ||||
} else { | |||||
} | |||||
else | |||||
{ | |||||
BaseShape::mouseMoveEvent(event); | BaseShape::mouseMoveEvent(event); | ||||
} | } | ||||
} | } | ||||
@@ -0,0 +1,24 @@ | |||||
#include "basedocument.h" | |||||
BaseDocument::BaseDocument(DocumentType type, QWidget *parent) | |||||
: QWidget(parent), m_type(type) {} | |||||
BaseDocument::DocumentType BaseDocument::type() const { | |||||
return m_type; | |||||
} | |||||
QString BaseDocument::filePath() const { | |||||
return m_filePath; | |||||
} | |||||
void BaseDocument::setFilePath(const QString &path) { | |||||
m_filePath = path; | |||||
} | |||||
bool BaseDocument::isModified() const { | |||||
return m_modified; | |||||
} | |||||
void BaseDocument::setModified(bool modified) { | |||||
m_modified = modified; | |||||
} |
@@ -0,0 +1,32 @@ | |||||
#ifndef BASEDOCUMENT_H | |||||
#define BASEDOCUMENT_H | |||||
#include <QWidget> | |||||
#include <QString> | |||||
class BaseDocument : public QWidget | |||||
{ | |||||
Q_OBJECT | |||||
public: | |||||
enum DocumentType { HMI, PLC }; | |||||
BaseDocument(DocumentType type, QWidget *parent = nullptr); | |||||
virtual ~BaseDocument() = default; | |||||
DocumentType type() const; | |||||
virtual QString title() const = 0; | |||||
virtual QString filePath() const; | |||||
virtual void setFilePath(const QString &path); | |||||
virtual bool isModified() const; | |||||
virtual void setModified(bool modified); | |||||
// 保存/加载接口 | |||||
virtual bool saveToFile(const QString &filePath) = 0; | |||||
virtual bool loadFromFile(const QString &filePath) = 0; | |||||
protected: | |||||
DocumentType m_type; | |||||
QString m_filePath; | |||||
bool m_modified = false; | |||||
}; | |||||
#endif // BASEDOCUMENT_H |
@@ -16,7 +16,7 @@ | |||||
#include <QDragEnterEvent> | #include <QDragEnterEvent> | ||||
#include <QDropEvent> | #include <QDropEvent> | ||||
#include <QDebug> | #include <QDebug> | ||||
#include<QFileInfo> | |||||
HMIDocument::HMIDocument(QWidget *parent) | HMIDocument::HMIDocument(QWidget *parent) | ||||
: BaseDocument(HMI, parent), | : BaseDocument(HMI, parent), | ||||
m_title("未命名HMI"), | m_title("未命名HMI"), | ||||
@@ -28,6 +28,24 @@ HMIDocument::HMIDocument(QWidget *parent) | |||||
m_scene = new QGraphicsScene(this); | m_scene = new QGraphicsScene(this); | ||||
m_scene->setSceneRect(0, 0, 800, 600); | m_scene->setSceneRect(0, 0, 800, 600); | ||||
m_scene->setBackgroundBrush(QBrush(Qt::lightGray)); | m_scene->setBackgroundBrush(QBrush(Qt::lightGray)); | ||||
// 更粗的网格线示例 | |||||
// QImage gridImage(40, 40, QImage::Format_ARGB32); | |||||
// gridImage.fill(Qt::white); | |||||
// QPainter painter(&gridImage); | |||||
// painter.setPen(QPen(QColor(200, 200, 200), 1)); // 浅灰色 | |||||
// // 绘制水平线 | |||||
// painter.drawLine(0, 39, 39, 39); | |||||
// // 绘制垂直线 | |||||
// painter.drawLine(39, 0, 39, 39); | |||||
// // 每隔4个小网格绘制一条稍粗的线 | |||||
// painter.setPen(QPen(QColor(180, 180, 180), 1.5)); | |||||
// painter.drawLine(0, 39, 39, 39); | |||||
// painter.drawLine(39, 0, 39, 39); | |||||
// m_scene->setBackgroundBrush(QBrush(gridImage)); | |||||
// 创建视图 | // 创建视图 | ||||
m_view = new QGraphicsView(m_scene, this); | m_view = new QGraphicsView(m_scene, this); | ||||
@@ -53,16 +71,14 @@ bool HMIDocument::eventFilter(QObject *obj, QEvent *event) | |||||
if (obj != m_view->viewport()) { | if (obj != m_view->viewport()) { | ||||
return QWidget::eventFilter(obj, event); | return QWidget::eventFilter(obj, event); | ||||
} | } | ||||
// 鼠标按下事件(绘制图形) | // 鼠标按下事件(绘制图形) | ||||
if (event->type() == QEvent::MouseButtonPress) | if (event->type() == QEvent::MouseButtonPress) | ||||
{ | { | ||||
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event); | QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event); | ||||
setModified(true); | |||||
if (mouseEvent->button() == Qt::RightButton) | if (mouseEvent->button() == Qt::RightButton) | ||||
{ | { | ||||
// 右键菜单 | |||||
showContextMenu(mouseEvent->globalPos()); | |||||
showContextMenu(mouseEvent->globalPos());// 右键菜单 | |||||
return true; | return true; | ||||
} | } | ||||
if (mouseEvent->button() == Qt::LeftButton) | if (mouseEvent->button() == Qt::LeftButton) | ||||
@@ -72,42 +88,43 @@ bool HMIDocument::eventFilter(QObject *obj, QEvent *event) | |||||
// 如果点击空白区域且有绘制标志,创建图形 | // 如果点击空白区域且有绘制标志,创建图形 | ||||
if (!item) { | if (!item) { | ||||
if (m_canDrawEllipse) { | |||||
createEllipse(scenePos); | |||||
if (m_canDrawEllipse) | |||||
{ | |||||
createShape("ellipse", scenePos); | |||||
resetDrawFlags(); | resetDrawFlags(); | ||||
} else if (m_canDrawRectangle) { | |||||
createRectangle(scenePos); | |||||
} | |||||
else if (m_canDrawRectangle) | |||||
{ | |||||
createShape("rectangle", scenePos); | |||||
resetDrawFlags(); | resetDrawFlags(); | ||||
} | } | ||||
m_scene->clearSelection(); // 清除选择 | |||||
return true; | |||||
} | } | ||||
} | } | ||||
} | } | ||||
// 拖拽事件(从工具栏拖拽绘制) | // 拖拽事件(从工具栏拖拽绘制) | ||||
if (event->type() == QEvent::DragEnter) { | |||||
if (event->type() == QEvent::DragEnter) | |||||
{ | |||||
QDragEnterEvent *dragEvent = static_cast<QDragEnterEvent*>(event); | QDragEnterEvent *dragEvent = static_cast<QDragEnterEvent*>(event); | ||||
dragEvent->acceptProposedAction(); | dragEvent->acceptProposedAction(); | ||||
return true; | return true; | ||||
} | } | ||||
if (event->type() == QEvent::DragMove) { | |||||
if (event->type() == QEvent::DragMove) | |||||
{ | |||||
static_cast<QDragMoveEvent*>(event)->acceptProposedAction(); | static_cast<QDragMoveEvent*>(event)->acceptProposedAction(); | ||||
return true; | return true; | ||||
} | } | ||||
if (event->type() == QEvent::Drop) { | |||||
if (event->type() == QEvent::Drop) | |||||
{ | |||||
QDropEvent *dropEvent = static_cast<QDropEvent*>(event); | QDropEvent *dropEvent = static_cast<QDropEvent*>(event); | ||||
const QMimeData *mimeData = dropEvent->mimeData(); // 显式获取mimeData | |||||
const QMimeData *mimeData = dropEvent->mimeData(); | |||||
QPointF scenePos = m_view->mapToScene(dropEvent->pos()); | QPointF scenePos = m_view->mapToScene(dropEvent->pos()); | ||||
// 使用QMimeData | // 使用QMimeData | ||||
if (mimeData && mimeData->hasText()) { | |||||
QString type = mimeData->text(); | |||||
if (type == "指示灯") { | |||||
createEllipse(scenePos); | |||||
} else if (type == "按钮") { | |||||
createRectangle(scenePos); | |||||
} | |||||
if (mimeData && mimeData->hasText()) | |||||
{ | |||||
createShape(mimeData->text(), scenePos); | |||||
resetDrawFlags();//重置防止拖拽后再次点击生成 | |||||
} | } | ||||
dropEvent->acceptProposedAction(); | dropEvent->acceptProposedAction(); | ||||
return true; | return true; | ||||
@@ -116,26 +133,24 @@ bool HMIDocument::eventFilter(QObject *obj, QEvent *event) | |||||
return QWidget::eventFilter(obj, event); | return QWidget::eventFilter(obj, event); | ||||
} | } | ||||
// 创建椭圆(指示灯) | |||||
void HMIDocument::createEllipse(const QPointF &pos) | |||||
{ | |||||
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); | |||||
} | |||||
// 创建矩形(按钮) | |||||
void HMIDocument::createRectangle(const QPointF &pos) | |||||
void HMIDocument::createShape(const QString& type, const QPointF &pos) | |||||
{ | { | ||||
ResizableRectangle *rect = new ResizableRectangle(pos.x(), pos.y(), 100, 50); | |||||
rect->setBrush(QBrush(Qt::yellow)); | |||||
rect->setPen(QPen(Qt::black, 1)); | |||||
m_scene->addItem(rect); | |||||
rect->setSelected(true); | |||||
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") { | |||||
ResizableRectangle *rect = new ResizableRectangle(pos.x(), pos.y(), 100, 50); | |||||
rect->setBrush(QBrush(Qt::yellow)); | |||||
rect->setPen(QPen(Qt::black, 1)); | |||||
m_scene->addItem(rect); | |||||
rect->setSelected(true); | |||||
} | |||||
setModified(true); | |||||
} | } | ||||
// 重置绘制标志 | // 重置绘制标志 | ||||
void HMIDocument::resetDrawFlags() | void HMIDocument::resetDrawFlags() | ||||
{ | { | ||||
@@ -152,12 +167,15 @@ void HMIDocument::showContextMenu(QPoint globalPos) | |||||
QMenu menu(this); | QMenu menu(this); | ||||
if (!clickedItem) { | |||||
if (!clickedItem) | |||||
{ | |||||
// 空白区域:仅显示粘贴 | // 空白区域:仅显示粘贴 | ||||
QAction *pasteAction = menu.addAction("粘贴"); | QAction *pasteAction = menu.addAction("粘贴"); | ||||
pasteAction->setEnabled(!m_copiedItemsData.isEmpty()); | pasteAction->setEnabled(!m_copiedItemsData.isEmpty()); | ||||
connect(pasteAction, &QAction::triggered, this, &HMIDocument::pasteItems); | connect(pasteAction, &QAction::triggered, this, &HMIDocument::pasteItems); | ||||
} else { | |||||
} | |||||
else | |||||
{ | |||||
// 选中图形:显示完整菜单 | // 选中图形:显示完整菜单 | ||||
QAction *propAction = menu.addAction("属性"); | QAction *propAction = menu.addAction("属性"); | ||||
QAction *copyAction = menu.addAction("复制"); | QAction *copyAction = menu.addAction("复制"); | ||||
@@ -200,10 +218,12 @@ void HMIDocument::copySelectedItems() | |||||
QList<QGraphicsItem*> selectedItems = m_scene->selectedItems(); | QList<QGraphicsItem*> selectedItems = m_scene->selectedItems(); | ||||
if (selectedItems.isEmpty()) return; | if (selectedItems.isEmpty()) return; | ||||
foreach (QGraphicsItem *item, selectedItems) { | |||||
foreach (QGraphicsItem *item, selectedItems) | |||||
{ | |||||
m_copiedItemsData.append(serializeItem(item)); | m_copiedItemsData.append(serializeItem(item)); | ||||
} | } | ||||
m_lastPastePos = selectedItems.first()->pos(); | m_lastPastePos = selectedItems.first()->pos(); | ||||
setModified(true); | |||||
} | } | ||||
// 粘贴项 | // 粘贴项 | ||||
@@ -215,14 +235,17 @@ void HMIDocument::pasteItems() | |||||
m_lastPastePos += offset; | m_lastPastePos += offset; | ||||
m_scene->clearSelection(); | m_scene->clearSelection(); | ||||
foreach (const QByteArray &itemData, m_copiedItemsData) { | |||||
foreach (const QByteArray &itemData, m_copiedItemsData) | |||||
{ | |||||
QGraphicsItem *newItem = deserializeItem(itemData); | QGraphicsItem *newItem = deserializeItem(itemData); | ||||
if (newItem) { | |||||
if (newItem) | |||||
{ | |||||
m_scene->addItem(newItem); | m_scene->addItem(newItem); | ||||
newItem->setPos(m_lastPastePos); | newItem->setPos(m_lastPastePos); | ||||
newItem->setSelected(true); | newItem->setSelected(true); | ||||
} | } | ||||
} | } | ||||
setModified(true); | |||||
} | } | ||||
// 删除选中项 | // 删除选中项 | ||||
@@ -233,6 +256,7 @@ void HMIDocument::deleteSelectedItems() | |||||
m_scene->removeItem(item); | m_scene->removeItem(item); | ||||
delete item; | delete item; | ||||
} | } | ||||
setModified(true); | |||||
} | } | ||||
// 显示属性对话框 | // 显示属性对话框 | ||||
@@ -291,7 +315,7 @@ void HMIDocument::showItemProperties() | |||||
}); | }); | ||||
// 布局组装 | // 布局组装 | ||||
form->addRow("名称:", nameEdit); | |||||
form->addRow("对象:", nameEdit); | |||||
QDialogButtonBox *btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); | QDialogButtonBox *btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); | ||||
form->addRow(btnBox); | form->addRow(btnBox); | ||||
connect(btnBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); | connect(btnBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); | ||||
@@ -299,6 +323,7 @@ void HMIDocument::showItemProperties() | |||||
// 应用属性 | // 应用属性 | ||||
if (dialog.exec() == QDialog::Accepted) { | if (dialog.exec() == QDialog::Accepted) { | ||||
setModified(true); | |||||
// 使用NamedItem接口设置名称 | // 使用NamedItem接口设置名称 | ||||
namedItem->setName(nameEdit->text()); | namedItem->setName(nameEdit->text()); | ||||
@@ -325,27 +350,27 @@ QByteArray HMIDocument::serializeItem(QGraphicsItem *item) | |||||
stream << item->flags(); | stream << item->flags(); | ||||
stream << item->transform(); | stream << item->transform(); | ||||
stream << item->isVisible(); | stream << item->isVisible(); | ||||
// 形状信息 | // 形状信息 | ||||
if (auto shape = dynamic_cast<QAbstractGraphicsShapeItem*>(item)) { | |||||
if (auto shape = dynamic_cast<QAbstractGraphicsShapeItem*>(item)) | |||||
{ | |||||
stream << shape->pen(); | stream << shape->pen(); | ||||
stream << shape->brush(); | stream << shape->brush(); | ||||
stream << shape->boundingRect(); | stream << shape->boundingRect(); | ||||
} | } | ||||
// 具体图形信息 | // 具体图形信息 | ||||
if (auto rect = dynamic_cast<ResizableRectangle*>(item)) { | |||||
if (auto rect = dynamic_cast<ResizableRectangle*>(item)) | |||||
{ | |||||
stream << rect->rect(); | stream << rect->rect(); | ||||
stream << rect->pressedColor(); | stream << rect->pressedColor(); | ||||
stream << rect->releasedColor(); | stream << rect->releasedColor(); | ||||
stream << rect->name(); | stream << rect->name(); | ||||
} else if (auto ellipse = dynamic_cast<ResizableEllipse*>(item)) { | |||||
} else if (auto ellipse = dynamic_cast<ResizableEllipse*>(item)) | |||||
{ | |||||
stream << ellipse->rect(); | stream << ellipse->rect(); | ||||
stream << ellipse->onColor(); | stream << ellipse->onColor(); | ||||
stream << ellipse->offColor(); | stream << ellipse->offColor(); | ||||
stream << ellipse->name(); | stream << ellipse->name(); | ||||
} | } | ||||
return data; | return data; | ||||
} | } | ||||
@@ -406,3 +431,223 @@ QGraphicsItem *HMIDocument::deserializeItem(const QByteArray &data) | |||||
return item; | return item; | ||||
} | } | ||||
void HMIDocument::startDrawingEllipse() | |||||
{ | |||||
m_canDrawEllipse = true; | |||||
m_canDrawRectangle = false; | |||||
} | |||||
void HMIDocument::startDrawingRectangle() | |||||
{ | |||||
m_canDrawEllipse = false; | |||||
m_canDrawRectangle = true; | |||||
} | |||||
bool HMIDocument::saveToFile(const QString &filePath) | |||||
{ | |||||
QFile file(filePath); | |||||
if (!file.open(QIODevice::WriteOnly)) { | |||||
qWarning() << "无法打开文件进行写入:" << filePath; | |||||
return false; | |||||
} | |||||
// 创建JSON文档结构 | |||||
QJsonObject docObject; | |||||
docObject["title"] = m_title; | |||||
docObject["sceneRect"] = QJsonArray({ | |||||
m_scene->sceneRect().x(), | |||||
m_scene->sceneRect().y(), | |||||
m_scene->sceneRect().width(), | |||||
m_scene->sceneRect().height() | |||||
}); | |||||
docObject["backgroundColor"] = m_scene->backgroundBrush().color().name(); | |||||
// 序列化所有图形项 | |||||
QJsonArray itemsArray; | |||||
foreach (QGraphicsItem *item, m_scene->items()) { | |||||
QJsonObject itemJson = itemToJson(item); | |||||
if (!itemJson.isEmpty()) { | |||||
itemsArray.append(itemJson); | |||||
} | |||||
} | |||||
docObject["items"] = itemsArray; | |||||
// 写入文件 | |||||
QJsonDocument doc(docObject); | |||||
file.write(doc.toJson()); | |||||
file.close(); | |||||
// 更新文档状态 | |||||
setFilePath(filePath); | |||||
setModified(false); | |||||
QFileInfo fileInfo(filePath); | |||||
setTitle(fileInfo.baseName()); | |||||
return true; | |||||
} | |||||
// 新增:从文件加载文档 | |||||
bool HMIDocument::loadFromFile(const QString &filePath) | |||||
{ | |||||
QFile file(filePath); | |||||
if (!file.open(QIODevice::ReadOnly)) { | |||||
qWarning() << "无法打开文件进行读取:" << filePath; | |||||
return false; | |||||
} | |||||
// 读取JSON文档 | |||||
QByteArray data = file.readAll(); | |||||
QJsonDocument doc = QJsonDocument::fromJson(data); | |||||
if (doc.isNull()) { | |||||
qWarning() << "无效的JSON文档:" << filePath; | |||||
return false; | |||||
} | |||||
// 解析文档 | |||||
QJsonObject docObject = doc.object(); | |||||
m_title = docObject["title"].toString(); | |||||
// 设置场景属性 | |||||
QJsonArray sceneRectArray = docObject["sceneRect"].toArray(); | |||||
if (sceneRectArray.size() == 4) { | |||||
QRectF sceneRect( | |||||
sceneRectArray[0].toDouble(), | |||||
sceneRectArray[1].toDouble(), | |||||
sceneRectArray[2].toDouble(), | |||||
sceneRectArray[3].toDouble() | |||||
); | |||||
m_scene->setSceneRect(sceneRect); | |||||
} | |||||
if (docObject.contains("backgroundColor")) { | |||||
QColor bgColor(docObject["backgroundColor"].toString()); | |||||
m_scene->setBackgroundBrush(QBrush(bgColor)); | |||||
} | |||||
// 清除现有内容 | |||||
m_scene->clear(); | |||||
// 加载所有图形项 | |||||
QJsonArray itemsArray = docObject["items"].toArray(); | |||||
foreach (QJsonValue itemValue, itemsArray) { | |||||
QJsonObject itemJson = itemValue.toObject(); | |||||
QGraphicsItem *item = jsonToItem(itemJson); | |||||
if (item) { | |||||
m_scene->addItem(item); | |||||
} | |||||
} | |||||
// 更新文档状态 | |||||
setFilePath(filePath); | |||||
setModified(false); | |||||
return true; | |||||
} | |||||
// 新增:将图形项转换为JSON对象 | |||||
QJsonObject HMIDocument::itemToJson(QGraphicsItem *item) | |||||
{ | |||||
QJsonObject json; | |||||
// 基础属性 | |||||
json["type"] = item->type(); | |||||
json["x"] = item->x(); | |||||
json["y"] = item->y(); | |||||
json["zValue"] = item->zValue(); | |||||
json["visible"] = item->isVisible(); | |||||
// 具体图形项属性 | |||||
if (auto ellipse = dynamic_cast<ResizableEllipse*>(item)) { | |||||
json["itemType"] = "ellipse"; | |||||
json["rect"] = QJsonArray({ | |||||
ellipse->rect().x(), | |||||
ellipse->rect().y(), | |||||
ellipse->rect().width(), | |||||
ellipse->rect().height() | |||||
}); | |||||
json["onColor"] = ellipse->onColor().name(); | |||||
json["offColor"] = ellipse->offColor().name(); | |||||
json["currentColor"] = ellipse->brush().color().name(); | |||||
json["penColor"] = ellipse->pen().color().name(); | |||||
json["penWidth"] = ellipse->pen().width(); | |||||
json["name"] = ellipse->name(); | |||||
} | |||||
else if (auto rect = dynamic_cast<ResizableRectangle*>(item)) { | |||||
json["itemType"] = "rectangle"; | |||||
json["rect"] = QJsonArray({ | |||||
rect->rect().x(), | |||||
rect->rect().y(), | |||||
rect->rect().width(), | |||||
rect->rect().height() | |||||
}); | |||||
json["pressedColor"] = rect->pressedColor().name(); | |||||
json["releasedColor"] = rect->releasedColor().name(); | |||||
json["currentColor"] = rect->brush().color().name(); | |||||
json["penColor"] = rect->pen().color().name(); | |||||
json["penWidth"] = rect->pen().width(); | |||||
json["name"] = rect->name(); | |||||
} | |||||
return json; | |||||
} | |||||
// 新增:从JSON对象创建图形项 | |||||
QGraphicsItem *HMIDocument::jsonToItem(const QJsonObject &json) | |||||
{ | |||||
QString itemType = json["itemType"].toString(); | |||||
QGraphicsItem *item = nullptr; | |||||
if (itemType == "ellipse") { | |||||
QJsonArray rectArray = json["rect"].toArray(); | |||||
if (rectArray.size() != 4) return nullptr; | |||||
ResizableEllipse *ellipse = new ResizableEllipse( | |||||
rectArray[0].toDouble(), | |||||
rectArray[1].toDouble(), | |||||
rectArray[2].toDouble(), | |||||
rectArray[3].toDouble() | |||||
); | |||||
ellipse->setOnColor(QColor(json["onColor"].toString())); | |||||
ellipse->setOffColor(QColor(json["offColor"].toString())); | |||||
ellipse->setBrush(QColor(json["currentColor"].toString())); | |||||
ellipse->setPen(QPen( | |||||
QColor(json["penColor"].toString()), | |||||
json["penWidth"].toDouble() | |||||
)); | |||||
ellipse->setName(json["name"].toString()); | |||||
item = ellipse; | |||||
} | |||||
else if (itemType == "rectangle") { | |||||
QJsonArray rectArray = json["rect"].toArray(); | |||||
if (rectArray.size() != 4) return nullptr; | |||||
ResizableRectangle *rect = new ResizableRectangle( | |||||
rectArray[0].toDouble(), | |||||
rectArray[1].toDouble(), | |||||
rectArray[2].toDouble(), | |||||
rectArray[3].toDouble() | |||||
); | |||||
rect->setPressedColor(QColor(json["pressedColor"].toString())); | |||||
rect->setReleasedColor(QColor(json["releasedColor"].toString())); | |||||
rect->setBrush(QColor(json["currentColor"].toString())); | |||||
rect->setPen(QPen( | |||||
QColor(json["penColor"].toString()), | |||||
json["penWidth"].toDouble() | |||||
)); | |||||
rect->setName(json["name"].toString()); | |||||
item = rect; | |||||
} | |||||
// 设置基础属性 | |||||
if (item) { | |||||
item->setX(json["x"].toDouble()); | |||||
item->setY(json["y"].toDouble()); | |||||
item->setZValue(json["zValue"].toDouble()); | |||||
item->setVisible(json["visible"].toBool()); | |||||
} | |||||
return item; | |||||
} |
@@ -1,92 +0,0 @@ | |||||
#ifndef DOCUMENT_H | |||||
#define DOCUMENT_H | |||||
#include <QWidget> | |||||
#include <QGraphicsScene> | |||||
#include <QGraphicsView> | |||||
#include <QList> | |||||
#include <QByteArray> | |||||
#include <QPointF> | |||||
// 前向声明 | |||||
class ResizableRectangle; | |||||
class ResizableEllipse; | |||||
class NamedItem; | |||||
// 文档基类 | |||||
class BaseDocument : public QWidget | |||||
{ | |||||
Q_OBJECT | |||||
public: | |||||
enum DocumentType { HMI, PLC }; | |||||
BaseDocument(DocumentType type, QWidget *parent = nullptr) | |||||
: QWidget(parent), m_type(type) {} | |||||
virtual ~BaseDocument() = default; | |||||
DocumentType type() const { return m_type; } | |||||
virtual QString title() const = 0; | |||||
protected: | |||||
DocumentType m_type; | |||||
}; | |||||
// HMI文档类(包含绘图相关核心功能) | |||||
class HMIDocument : public BaseDocument | |||||
{ | |||||
Q_OBJECT | |||||
public: | |||||
HMIDocument(QWidget *parent = nullptr); | |||||
~HMIDocument() override; | |||||
QString title() const override { return m_title; } | |||||
void setTitle(const QString &title) { m_title = title; } | |||||
QGraphicsView *view() const { return m_view; } | |||||
QGraphicsScene *scene() const { return m_scene; } | |||||
// 绘图控制 | |||||
void setDrawEllipse(bool enable) { m_canDrawEllipse = enable; } | |||||
void setDrawRectangle(bool enable) { m_canDrawRectangle = enable; } | |||||
// 编辑功能 | |||||
void copySelectedItems(); | |||||
void pasteItems(); | |||||
void deleteSelectedItems(); | |||||
void showItemProperties(); | |||||
signals: | |||||
void titleChanged(const QString &title); | |||||
protected: | |||||
bool eventFilter(QObject *obj, QEvent *event) override; | |||||
private: | |||||
// 绘图相关 | |||||
void createEllipse(const QPointF &pos); | |||||
void createRectangle(const QPointF &pos); | |||||
void resetDrawFlags(); | |||||
void showContextMenu(QPoint globalPos); | |||||
// 图形项序列化/反序列化 | |||||
QByteArray serializeItem(QGraphicsItem *item); | |||||
QGraphicsItem *deserializeItem(const QByteArray &data); | |||||
QString m_title; | |||||
QGraphicsScene *m_scene; | |||||
QGraphicsView *m_view; | |||||
bool m_canDrawEllipse; | |||||
bool m_canDrawRectangle; | |||||
// 复制粘贴相关 | |||||
QList<QByteArray> m_copiedItemsData; | |||||
QPointF m_lastPastePos; | |||||
}; | |||||
// PLC文档类(预留,暂为空实现) | |||||
class PLCDocument : public BaseDocument | |||||
{ | |||||
Q_OBJECT | |||||
public: | |||||
PLCDocument(QWidget *parent = nullptr) : BaseDocument(PLC, parent) {} | |||||
QString title() const override { return "PLC文档"; } | |||||
}; | |||||
#endif // DOCUMENT_H |
@@ -1,26 +1,147 @@ | |||||
#include "graphicsitems.h" | |||||
#ifndef GRAPHICSITEMS_H | |||||
#define GRAPHICSITEMS_H | |||||
#include <QGraphicsRectItem> | |||||
#include <QGraphicsEllipseItem> | |||||
#include <QGraphicsSceneHoverEvent> | |||||
#include <QGraphicsSceneMouseEvent> | |||||
#include <QPainter> | #include <QPainter> | ||||
#include <QString> | |||||
#include <QColor> | |||||
#include <QCursor> | |||||
#include <QGraphicsScene> | |||||
// 命名项接口(用于属性设置) | |||||
class NamedItem | |||||
{ | |||||
public: | |||||
virtual QString name() const = 0; | |||||
virtual void setName(const QString &name) = 0; | |||||
virtual ~NamedItem() = default; | |||||
}; | |||||
// 可调整大小的图形基类(模板) | |||||
template <typename BaseShape> | |||||
class ResizableShape : public BaseShape, public NamedItem | |||||
{ | |||||
protected: | |||||
bool m_resizing; | |||||
QString m_name; | |||||
Qt::CursorShape m_currentCursor; | |||||
public: | |||||
ResizableShape(qreal x, qreal y, qreal w, qreal h) | |||||
: BaseShape(x, y, w, h), m_resizing(false), m_currentCursor(Qt::SizeAllCursor) | |||||
{ | |||||
this->setFlag(QGraphicsItem::ItemIsMovable, true); | |||||
this->setFlag(QGraphicsItem::ItemIsSelectable, true); | |||||
this->setAcceptHoverEvents(true); | |||||
} | |||||
// 事件处理(调整大小逻辑) | |||||
void hoverMoveEvent(QGraphicsSceneHoverEvent *event) override | |||||
{ | |||||
if (isInResizeArea(event->pos())) | |||||
{ | |||||
m_currentCursor = Qt::SizeFDiagCursor; | |||||
this->setCursor(Qt::SizeFDiagCursor); | |||||
} else { | |||||
m_currentCursor = Qt::SizeAllCursor; | |||||
} | |||||
BaseShape::hoverMoveEvent(event); | |||||
} | |||||
void mousePressEvent(QGraphicsSceneMouseEvent *event) override | |||||
{ | |||||
if (isInResizeArea(event->pos())) { | |||||
m_resizing = true; | |||||
m_currentCursor = Qt::SizeFDiagCursor; | |||||
this->setCursor(Qt::SizeFDiagCursor); | |||||
} else { | |||||
m_resizing = false; | |||||
m_currentCursor = Qt::SizeAllCursor; | |||||
BaseShape::mousePressEvent(event); | |||||
} | |||||
} | |||||
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override | |||||
{ | |||||
if (m_resizing) { | |||||
QRectF newRect = this->rect(); | |||||
newRect.setBottomRight(event->pos()); | |||||
this->setRect(newRect); | |||||
} else { | |||||
BaseShape::mouseMoveEvent(event); | |||||
} | |||||
} | |||||
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override | |||||
{ | |||||
m_resizing = false; | |||||
m_currentCursor = Qt::SizeAllCursor; | |||||
this->setCursor(Qt::SizeFDiagCursor); | |||||
BaseShape::mouseReleaseEvent(event); | |||||
} | |||||
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override | |||||
{ | |||||
BaseShape::paint(painter, option, widget); | |||||
} | |||||
// 可调整大小的矩形(按钮)实现 | |||||
ResizableRectangle::ResizableRectangle(qreal x, qreal y, qreal w, qreal h) | |||||
: ResizableShape<QGraphicsRectItem>(x, y, w, h) | |||||
// 命名项接口实现 | |||||
QString name() const override { return m_name; } | |||||
void setName(const QString &name) override { m_name = name; } | |||||
// 光标获取方法 | |||||
Qt::CursorShape currentCursor() const { return m_currentCursor; } | |||||
protected: | |||||
bool isInResizeArea(const QPointF &pos) const | |||||
{ | |||||
QRectF r = this->rect(); | |||||
QRectF resizeArea(r.bottomRight() - QPointF(50, 50), r.bottomRight()); | |||||
return resizeArea.contains(pos); | |||||
} | |||||
}; | |||||
// 可调整大小的矩形(按钮) | |||||
class ResizableRectangle : public ResizableShape<QGraphicsRectItem> | |||||
{ | { | ||||
m_name = "按钮"; | |||||
m_pressedColor = Qt::green; | |||||
m_releasedColor = Qt::yellow; | |||||
this->setBrush(m_releasedColor); | |||||
} | |||||
// 可调整大小的椭圆(指示灯)实现 | |||||
ResizableEllipse::ResizableEllipse(qreal x, qreal y, qreal w, qreal h) | |||||
: ResizableShape<QGraphicsEllipseItem>(x, y, w, h) | |||||
public: | |||||
ResizableRectangle(qreal x, qreal y, qreal w, qreal h); | |||||
QColor pressedColor() const { return m_pressedColor; } | |||||
void setPressedColor(const QColor &color) { m_pressedColor = color; } | |||||
QColor releasedColor() const { return m_releasedColor; } | |||||
void setReleasedColor(const QColor &color) { | |||||
m_releasedColor = color; | |||||
this->setBrush(m_releasedColor); // 更新显示 | |||||
} | |||||
private: | |||||
QColor m_pressedColor; // 按下颜色 | |||||
QColor m_releasedColor; // 释放颜色 | |||||
}; | |||||
// 可调整大小的椭圆(指示灯) | |||||
class ResizableEllipse : public ResizableShape<QGraphicsEllipseItem> | |||||
{ | { | ||||
m_name = "指示灯"; | |||||
m_onColor = Qt::green; | |||||
m_offColor = Qt::red; | |||||
this->setBrush(m_onColor); | |||||
} | |||||
// 显式实例化模板类 | |||||
template class ResizableShape<QGraphicsRectItem>; | |||||
template class ResizableShape<QGraphicsEllipseItem>; | |||||
public: | |||||
ResizableEllipse(qreal x, qreal y, qreal w, qreal h); | |||||
QColor onColor() const { return m_onColor; } | |||||
void setOnColor(const QColor &color) { | |||||
m_onColor = color; | |||||
this->setBrush(m_onColor); // 更新显示 | |||||
} | |||||
QColor offColor() const { return m_offColor; } | |||||
void setOffColor(const QColor &color) { m_offColor = color; } | |||||
private: | |||||
QColor m_onColor; // 开状态颜色 | |||||
QColor m_offColor; // 关状态颜色 | |||||
}; | |||||
#endif // GRAPHICSITEMS_H |
@@ -27,24 +27,35 @@ class ResizableShape : public BaseShape, public NamedItem | |||||
protected: | protected: | ||||
bool m_resizing; | bool m_resizing; | ||||
QString m_name; | QString m_name; | ||||
Qt::CursorShape m_currentCursor; | |||||
QPointF m_initialPos; // 用于存储调整开始时的位置 | |||||
// 获取右下角调整区域 | |||||
QRectF getResizeArea() const | |||||
{ | |||||
QRectF r = this->rect(); | |||||
return QRectF(r.bottomRight() - QPointF(20, 20), r.bottomRight()); | |||||
} | |||||
// 检测是否在右下角调整区域 | |||||
bool isInResizeArea(const QPointF &pos) const | |||||
{ | |||||
return getResizeArea().contains(pos); | |||||
} | |||||
public: | public: | ||||
ResizableShape(qreal x, qreal y, qreal w, qreal h) | ResizableShape(qreal x, qreal y, qreal w, qreal h) | ||||
: BaseShape(x, y, w, h), m_resizing(false), m_currentCursor(Qt::SizeAllCursor) | |||||
: BaseShape(x, y, w, h), m_resizing(false) | |||||
{ | { | ||||
this->setFlag(QGraphicsItem::ItemIsMovable, true); | this->setFlag(QGraphicsItem::ItemIsMovable, true); | ||||
this->setFlag(QGraphicsItem::ItemIsSelectable, true); | this->setFlag(QGraphicsItem::ItemIsSelectable, true); | ||||
this->setAcceptHoverEvents(true); | this->setAcceptHoverEvents(true); | ||||
this->setCursor(Qt::SizeAllCursor); // 默认光标为移动光标 | |||||
} | } | ||||
// 事件处理(调整大小逻辑) | // 事件处理(调整大小逻辑) | ||||
void hoverMoveEvent(QGraphicsSceneHoverEvent *event) override | void hoverMoveEvent(QGraphicsSceneHoverEvent *event) override | ||||
{ | { | ||||
if (isInResizeArea(event->pos())) { | if (isInResizeArea(event->pos())) { | ||||
m_currentCursor = Qt::SizeFDiagCursor; | |||||
this->setCursor(Qt::SizeFDiagCursor); // 右下角显示缩放光标 | |||||
} else { | } else { | ||||
m_currentCursor = Qt::SizeAllCursor; | |||||
this->setCursor(Qt::SizeAllCursor); // 其他区域显示移动光标 | |||||
} | } | ||||
BaseShape::hoverMoveEvent(event); | BaseShape::hoverMoveEvent(event); | ||||
} | } | ||||
@@ -53,10 +64,12 @@ public: | |||||
{ | { | ||||
if (isInResizeArea(event->pos())) { | if (isInResizeArea(event->pos())) { | ||||
m_resizing = true; | m_resizing = true; | ||||
m_currentCursor = Qt::SizeFDiagCursor; | |||||
} else { | |||||
m_initialPos = event->pos(); // 存储初始位置 | |||||
this->setCursor(Qt::SizeFDiagCursor); | |||||
} | |||||
else | |||||
{ | |||||
m_resizing = false; | m_resizing = false; | ||||
m_currentCursor = Qt::SizeAllCursor; | |||||
BaseShape::mousePressEvent(event); | BaseShape::mousePressEvent(event); | ||||
} | } | ||||
} | } | ||||
@@ -64,9 +77,22 @@ public: | |||||
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override | void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override | ||||
{ | { | ||||
if (m_resizing) { | if (m_resizing) { | ||||
// 计算尺寸变化量 | |||||
QPointF delta = event->pos() - m_initialPos; | |||||
QRectF newRect = this->rect(); | QRectF newRect = this->rect(); | ||||
newRect.setBottomRight(event->pos()); | |||||
// 保持左上角不变,调整右下角 | |||||
newRect.setWidth(newRect.width() + delta.x()); | |||||
newRect.setHeight(newRect.height() + delta.y()); | |||||
// 设置最小尺寸 | |||||
if (newRect.width() < 20) newRect.setWidth(20); | |||||
if (newRect.height() < 20) newRect.setHeight(20); | |||||
this->setRect(newRect); | this->setRect(newRect); | ||||
this->update(); | |||||
m_initialPos = event->pos(); // 更新初始位置 | |||||
} else { | } else { | ||||
BaseShape::mouseMoveEvent(event); | BaseShape::mouseMoveEvent(event); | ||||
} | } | ||||
@@ -74,8 +100,16 @@ public: | |||||
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override | void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override | ||||
{ | { | ||||
m_resizing = false; | |||||
m_currentCursor = Qt::SizeAllCursor; | |||||
if (m_resizing) { | |||||
m_resizing = false; | |||||
// 根据位置恢复光标 | |||||
if (isInResizeArea(event->pos())) { | |||||
this->setCursor(Qt::SizeFDiagCursor); // 仍在调整区域 | |||||
} else { | |||||
this->setCursor(Qt::SizeAllCursor); // 离开调整区域 | |||||
} | |||||
} | |||||
BaseShape::mouseReleaseEvent(event); | BaseShape::mouseReleaseEvent(event); | ||||
} | } | ||||
@@ -87,24 +121,19 @@ public: | |||||
// 命名项接口实现 | // 命名项接口实现 | ||||
QString name() const override { return m_name; } | QString name() const override { return m_name; } | ||||
void setName(const QString &name) override { m_name = name; } | void setName(const QString &name) override { m_name = name; } | ||||
// 光标获取方法 | |||||
Qt::CursorShape currentCursor() const { return m_currentCursor; } | |||||
protected: | |||||
bool isInResizeArea(const QPointF &pos) const | |||||
{ | |||||
QRectF r = this->rect(); | |||||
QRectF resizeArea(r.bottomRight() - QPointF(20, 20), r.bottomRight()); | |||||
return resizeArea.contains(pos); | |||||
} | |||||
}; | }; | ||||
// 可调整大小的矩形(按钮) | // 可调整大小的矩形(按钮) | ||||
class ResizableRectangle : public ResizableShape<QGraphicsRectItem> | class ResizableRectangle : public ResizableShape<QGraphicsRectItem> | ||||
{ | { | ||||
public: | public: | ||||
ResizableRectangle(qreal x, qreal y, qreal w, qreal h); | |||||
ResizableRectangle(qreal x, qreal y, qreal w, qreal h) | |||||
: ResizableShape<QGraphicsRectItem>(x, y, w, h) | |||||
{ | |||||
m_pressedColor = Qt::darkYellow; | |||||
m_releasedColor = Qt::yellow; | |||||
this->setBrush(m_releasedColor); | |||||
} | |||||
QColor pressedColor() const { return m_pressedColor; } | QColor pressedColor() const { return m_pressedColor; } | ||||
void setPressedColor(const QColor &color) { m_pressedColor = color; } | void setPressedColor(const QColor &color) { m_pressedColor = color; } | ||||
@@ -124,7 +153,13 @@ private: | |||||
class ResizableEllipse : public ResizableShape<QGraphicsEllipseItem> | class ResizableEllipse : public ResizableShape<QGraphicsEllipseItem> | ||||
{ | { | ||||
public: | public: | ||||
ResizableEllipse(qreal x, qreal y, qreal w, qreal h); | |||||
ResizableEllipse(qreal x, qreal y, qreal w, qreal h) | |||||
: ResizableShape<QGraphicsEllipseItem>(x, y, w, h) | |||||
{ | |||||
m_onColor = Qt::green; | |||||
m_offColor = Qt::red; | |||||
this->setBrush(m_offColor); | |||||
} | |||||
QColor onColor() const { return m_onColor; } | QColor onColor() const { return m_onColor; } | ||||
void setOnColor(const QColor &color) { | void setOnColor(const QColor &color) { | ||||
@@ -0,0 +1,651 @@ | |||||
#include "hmidocument.h" | |||||
#include "graphicsitems.h" | |||||
#include <QGraphicsItem> | |||||
#include <QGraphicsSceneHoverEvent> | |||||
#include <QMouseEvent> | |||||
#include <QMenu> | |||||
#include <QAction> | |||||
#include <QDataStream> | |||||
#include <QColorDialog> | |||||
#include <QDialog> | |||||
#include <QFormLayout> | |||||
#include <QLineEdit> | |||||
#include <QPushButton> | |||||
#include <QDialogButtonBox> | |||||
#include <QMimeData> | |||||
#include <QDragEnterEvent> | |||||
#include <QDropEvent> | |||||
#include <QDebug> | |||||
#include <QFileInfo> | |||||
#include <QVBoxLayout> | |||||
#include <QJsonArray> | |||||
#include <QJsonDocument> | |||||
#include <QJsonObject> | |||||
HMIDocument::HMIDocument(QWidget *parent) | |||||
: BaseDocument(HMI, parent), | |||||
m_title("未命名HMI"), | |||||
m_canDrawEllipse(false), | |||||
m_canDrawRectangle(false), | |||||
m_lastPastePos(0, 0) | |||||
{ | |||||
// 创建绘图场景 | |||||
m_scene = new QGraphicsScene(this); | |||||
m_scene->setSceneRect(0, 0, 800, 600); | |||||
m_scene->setBackgroundBrush(QBrush(Qt::lightGray)); | |||||
// 创建视图 | |||||
m_view = new QGraphicsView(m_scene, this); | |||||
m_view->setRenderHint(QPainter::Antialiasing); | |||||
m_view->setDragMode(QGraphicsView::RubberBandDrag); | |||||
m_view->setAcceptDrops(true); | |||||
m_view->viewport()->installEventFilter(this); | |||||
// 布局(让视图占满文档区域) | |||||
auto layout = new QVBoxLayout(this); | |||||
layout->setContentsMargins(0, 0, 0, 0); | |||||
layout->addWidget(m_view); | |||||
setLayout(layout); | |||||
} | |||||
HMIDocument::~HMIDocument() | |||||
{ | |||||
} | |||||
// 事件过滤器(处理绘图、拖拽等事件) | |||||
bool HMIDocument::eventFilter(QObject *obj, QEvent *event) | |||||
{ | |||||
if (obj != m_view->viewport()) { | |||||
return QWidget::eventFilter(obj, event); | |||||
} | |||||
// 鼠标按下事件(绘制图形) | |||||
if (event->type() == QEvent::MouseButtonPress) | |||||
{ | |||||
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event); | |||||
setModified(true); | |||||
if (mouseEvent->button() == Qt::RightButton) | |||||
{ | |||||
showContextMenu(mouseEvent->globalPos());// 右键菜单 | |||||
return true; | |||||
} | |||||
if (mouseEvent->button() == Qt::LeftButton) | |||||
{ | |||||
QPointF scenePos = m_view->mapToScene(mouseEvent->pos()); | |||||
QGraphicsItem *item = m_scene->itemAt(scenePos, m_view->transform()); | |||||
// 如果点击空白区域且有绘制标志,创建图形 | |||||
if (!item) { | |||||
if (m_canDrawEllipse) | |||||
{ | |||||
createShape("ellipse", scenePos); | |||||
resetDrawFlags(); | |||||
} | |||||
else if (m_canDrawRectangle) | |||||
{ | |||||
createShape("rectangle", scenePos); | |||||
resetDrawFlags(); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
// 拖拽事件(从工具栏拖拽绘制) | |||||
if (event->type() == QEvent::DragEnter) | |||||
{ | |||||
QDragEnterEvent *dragEvent = static_cast<QDragEnterEvent*>(event); | |||||
dragEvent->acceptProposedAction(); | |||||
return true; | |||||
} | |||||
if (event->type() == QEvent::DragMove) | |||||
{ | |||||
static_cast<QDragMoveEvent*>(event)->acceptProposedAction(); | |||||
return true; | |||||
} | |||||
if (event->type() == QEvent::Drop) | |||||
{ | |||||
QDropEvent *dropEvent = static_cast<QDropEvent*>(event); | |||||
const QMimeData *mimeData = dropEvent->mimeData(); | |||||
QPointF scenePos = m_view->mapToScene(dropEvent->pos()); | |||||
// 使用QMimeData | |||||
if (mimeData && mimeData->hasText()) | |||||
{ | |||||
createShape(mimeData->text(), scenePos); | |||||
resetDrawFlags();//重置防止拖拽后再次点击生成 | |||||
} | |||||
dropEvent->acceptProposedAction(); | |||||
return true; | |||||
} | |||||
return QWidget::eventFilter(obj, event); | |||||
} | |||||
void HMIDocument::createShape(const QString& type, const QPointF &pos) | |||||
{ | |||||
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") { | |||||
ResizableRectangle *rect = new ResizableRectangle(pos.x(), pos.y(), 100, 50); | |||||
rect->setBrush(QBrush(Qt::yellow)); | |||||
rect->setPen(QPen(Qt::black, 1)); | |||||
m_scene->addItem(rect); | |||||
rect->setSelected(true); | |||||
} | |||||
setModified(true); | |||||
} | |||||
// 重置绘制标志 | |||||
void HMIDocument::resetDrawFlags() | |||||
{ | |||||
m_canDrawEllipse = false; | |||||
m_canDrawRectangle = false; | |||||
} | |||||
// 右键菜单 | |||||
void HMIDocument::showContextMenu(QPoint globalPos) | |||||
{ | |||||
QPoint viewportPos = m_view->mapFromGlobal(globalPos); | |||||
QPointF scenePos = m_view->mapToScene(viewportPos); | |||||
QGraphicsItem *clickedItem = m_scene->itemAt(scenePos, m_view->transform()); | |||||
QMenu menu(this); | |||||
if (!clickedItem) | |||||
{ | |||||
// 空白区域:仅显示粘贴 | |||||
QAction *pasteAction = menu.addAction("粘贴"); | |||||
pasteAction->setEnabled(!m_copiedItemsData.isEmpty()); | |||||
connect(pasteAction, &QAction::triggered, this, &HMIDocument::pasteItems); | |||||
} | |||||
else | |||||
{ | |||||
// 选中图形:显示完整菜单 | |||||
QAction *propAction = menu.addAction("属性"); | |||||
QAction *copyAction = menu.addAction("复制"); | |||||
QAction *pasteAction = menu.addAction("粘贴"); | |||||
QAction *deleteAction = menu.addAction("删除"); | |||||
QAction *onAction = menu.addAction("置为ON"); | |||||
QAction *offAction = menu.addAction("置为OFF"); | |||||
pasteAction->setEnabled(!m_copiedItemsData.isEmpty()); | |||||
connect(propAction, &QAction::triggered, this, &HMIDocument::showItemProperties); | |||||
connect(copyAction, &QAction::triggered, this, &HMIDocument::copySelectedItems); | |||||
connect(pasteAction, &QAction::triggered, this, &HMIDocument::pasteItems); | |||||
connect(deleteAction, &QAction::triggered, this, &HMIDocument::deleteSelectedItems); | |||||
// ON/OFF动作(针对指示灯和按钮) | |||||
connect(onAction, &QAction::triggered, [=]() { | |||||
if (auto ellipse = dynamic_cast<ResizableEllipse*>(clickedItem)) { | |||||
ellipse->setBrush(ellipse->onColor()); | |||||
} else if (auto rect = dynamic_cast<ResizableRectangle*>(clickedItem)) { | |||||
rect->setBrush(rect->pressedColor()); | |||||
} | |||||
}); | |||||
connect(offAction, &QAction::triggered, [=]() { | |||||
if (auto ellipse = dynamic_cast<ResizableEllipse*>(clickedItem)) { | |||||
ellipse->setBrush(ellipse->offColor()); | |||||
} else if (auto rect = dynamic_cast<ResizableRectangle*>(clickedItem)) { | |||||
rect->setBrush(rect->releasedColor()); | |||||
} | |||||
}); | |||||
} | |||||
menu.exec(globalPos + QPoint(10, 10)); | |||||
} | |||||
// 复制选中项 | |||||
void HMIDocument::copySelectedItems() | |||||
{ | |||||
m_copiedItemsData.clear(); | |||||
QList<QGraphicsItem*> selectedItems = m_scene->selectedItems(); | |||||
if (selectedItems.isEmpty()) return; | |||||
foreach (QGraphicsItem *item, selectedItems) | |||||
{ | |||||
m_copiedItemsData.append(serializeItem(item)); | |||||
} | |||||
m_lastPastePos = selectedItems.first()->pos(); | |||||
setModified(true); | |||||
} | |||||
// 粘贴项 | |||||
void HMIDocument::pasteItems() | |||||
{ | |||||
if (m_copiedItemsData.isEmpty()) return; | |||||
QPointF offset(20, 20); | |||||
m_lastPastePos += offset; | |||||
m_scene->clearSelection(); | |||||
foreach (const QByteArray &itemData, m_copiedItemsData) | |||||
{ | |||||
QGraphicsItem *newItem = deserializeItem(itemData); | |||||
if (newItem) | |||||
{ | |||||
m_scene->addItem(newItem); | |||||
newItem->setPos(m_lastPastePos); | |||||
newItem->setSelected(true); | |||||
} | |||||
} | |||||
setModified(true); | |||||
} | |||||
// 删除选中项 | |||||
void HMIDocument::deleteSelectedItems() | |||||
{ | |||||
QList<QGraphicsItem*> selectedItems = m_scene->selectedItems(); | |||||
foreach (QGraphicsItem *item, selectedItems) { | |||||
m_scene->removeItem(item); | |||||
delete item; | |||||
} | |||||
setModified(true); | |||||
} | |||||
// 显示属性对话框 | |||||
void HMIDocument::showItemProperties() | |||||
{ | |||||
QList<QGraphicsItem*> selectedItems = m_scene->selectedItems(); | |||||
if (selectedItems.isEmpty()) return; | |||||
QGraphicsItem *item = selectedItems.first(); | |||||
// 将QGraphicsItem转换为NamedItem | |||||
NamedItem *namedItem = dynamic_cast<NamedItem*>(item); | |||||
if (!namedItem) return; | |||||
QDialog dialog(this); | |||||
dialog.setWindowTitle("属性设置"); | |||||
QFormLayout *form = new QFormLayout(&dialog); | |||||
// 名称输入 - 使用NamedItem接口 | |||||
QLineEdit *nameEdit = new QLineEdit(namedItem->name()); | |||||
// 颜色选择(根据图形类型) | |||||
QColor tempColor1, tempColor2; | |||||
QPushButton *colorBtn1 = new QPushButton; | |||||
QPushButton *colorBtn2 = new QPushButton; | |||||
if (auto ellipse = dynamic_cast<ResizableEllipse*>(item)) { | |||||
tempColor1 = ellipse->onColor(); | |||||
tempColor2 = ellipse->offColor(); | |||||
form->addRow("ON颜色:", colorBtn1); | |||||
form->addRow("OFF颜色:", colorBtn2); | |||||
} else if (auto rect = dynamic_cast<ResizableRectangle*>(item)) { | |||||
tempColor1 = rect->pressedColor(); | |||||
tempColor2 = rect->releasedColor(); | |||||
form->addRow("按下颜色:", colorBtn1); | |||||
form->addRow("释放颜色:", colorBtn2); | |||||
} | |||||
// 初始化颜色按钮 | |||||
colorBtn1->setStyleSheet("background-color:" + tempColor1.name()); | |||||
colorBtn2->setStyleSheet("background-color:" + tempColor2.name()); | |||||
// 颜色选择对话框 | |||||
connect(colorBtn1, &QPushButton::clicked, [&]() { | |||||
QColor c = QColorDialog::getColor(tempColor1, &dialog); | |||||
if (c.isValid()) { | |||||
tempColor1 = c; | |||||
colorBtn1->setStyleSheet("background-color:" + c.name()); | |||||
} | |||||
}); | |||||
connect(colorBtn2, &QPushButton::clicked, [&]() { | |||||
QColor c = QColorDialog::getColor(tempColor2, &dialog); | |||||
if (c.isValid()) { | |||||
tempColor2 = c; | |||||
colorBtn2->setStyleSheet("background-color:" + c.name()); | |||||
} | |||||
}); | |||||
// 布局组装 | |||||
form->addRow("对象:", nameEdit); | |||||
QDialogButtonBox *btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); | |||||
form->addRow(btnBox); | |||||
connect(btnBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); | |||||
connect(btnBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); | |||||
// 应用属性 | |||||
if (dialog.exec() == QDialog::Accepted) { | |||||
setModified(true); | |||||
// 使用NamedItem接口设置名称 | |||||
namedItem->setName(nameEdit->text()); | |||||
if (auto ellipse = dynamic_cast<ResizableEllipse*>(item)) { | |||||
ellipse->setOnColor(tempColor1); | |||||
ellipse->setOffColor(tempColor2); | |||||
} else if (auto rect = dynamic_cast<ResizableRectangle*>(item)) { | |||||
rect->setPressedColor(tempColor1); | |||||
rect->setReleasedColor(tempColor2); | |||||
} | |||||
item->update(); | |||||
} | |||||
} | |||||
// 序列化图形项(用于复制粘贴) | |||||
QByteArray HMIDocument::serializeItem(QGraphicsItem *item) | |||||
{ | |||||
QByteArray data; | |||||
QDataStream stream(&data, QIODevice::WriteOnly); | |||||
// 基础信息 | |||||
stream << item->type(); | |||||
stream << item->pos(); | |||||
stream << item->flags(); | |||||
stream << item->transform(); | |||||
stream << item->isVisible(); | |||||
// 形状信息 | |||||
if (auto shape = dynamic_cast<QAbstractGraphicsShapeItem*>(item)) | |||||
{ | |||||
stream << shape->pen(); | |||||
stream << shape->brush(); | |||||
stream << shape->boundingRect(); | |||||
} | |||||
// 具体图形信息 | |||||
if (auto rect = dynamic_cast<ResizableRectangle*>(item)) | |||||
{ | |||||
stream << rect->rect(); | |||||
stream << rect->pressedColor(); | |||||
stream << rect->releasedColor(); | |||||
stream << rect->name(); | |||||
} else if (auto ellipse = dynamic_cast<ResizableEllipse*>(item)) | |||||
{ | |||||
stream << ellipse->rect(); | |||||
stream << ellipse->onColor(); | |||||
stream << ellipse->offColor(); | |||||
stream << ellipse->name(); | |||||
} | |||||
return data; | |||||
} | |||||
// 反序列化图形项(用于粘贴) | |||||
QGraphicsItem *HMIDocument::deserializeItem(const QByteArray &data) | |||||
{ | |||||
QDataStream stream(data); | |||||
int type; | |||||
QPointF pos; | |||||
QGraphicsItem::GraphicsItemFlags flags; | |||||
QTransform transform; | |||||
bool visible; | |||||
stream >> type >> pos >> flags >> transform >> visible; | |||||
QGraphicsItem *item = nullptr; | |||||
if (type == QGraphicsRectItem::Type) { | |||||
QPen pen; | |||||
QBrush brush; | |||||
QRectF boundRect; | |||||
QRectF rect; | |||||
QColor pressedColor, releasedColor; | |||||
QString name; | |||||
stream >> pen >> brush >> boundRect >> rect >> pressedColor >> releasedColor >> name; | |||||
ResizableRectangle *rectItem = new ResizableRectangle(0, 0, rect.width(), rect.height()); | |||||
rectItem->setPen(pen); | |||||
rectItem->setBrush(brush); | |||||
rectItem->setPressedColor(pressedColor); | |||||
rectItem->setReleasedColor(releasedColor); | |||||
rectItem->setName(name); | |||||
item = rectItem; | |||||
} else if (type == QGraphicsEllipseItem::Type) { | |||||
QPen pen; | |||||
QBrush brush; | |||||
QRectF boundRect; | |||||
QRectF rect; | |||||
QColor onColor, offColor; | |||||
QString name; | |||||
stream >> pen >> brush >> boundRect >> rect >> onColor >> offColor >> name; | |||||
ResizableEllipse *ellipseItem = new ResizableEllipse(0, 0, rect.width(), rect.height()); | |||||
ellipseItem->setPen(pen); | |||||
ellipseItem->setBrush(brush); | |||||
ellipseItem->setOnColor(onColor); | |||||
ellipseItem->setOffColor(offColor); | |||||
ellipseItem->setName(name); | |||||
item = ellipseItem; | |||||
} | |||||
if (item) { | |||||
item->setFlags(flags); | |||||
item->setTransform(transform); | |||||
item->setVisible(visible); | |||||
} | |||||
return item; | |||||
} | |||||
void HMIDocument::startDrawingEllipse() | |||||
{ | |||||
m_canDrawEllipse = true; | |||||
m_canDrawRectangle = false; | |||||
} | |||||
void HMIDocument::startDrawingRectangle() | |||||
{ | |||||
m_canDrawEllipse = false; | |||||
m_canDrawRectangle = true; | |||||
} | |||||
bool HMIDocument::saveToFile(const QString &filePath) | |||||
{ | |||||
QFile file(filePath); | |||||
if (!file.open(QIODevice::WriteOnly)) { | |||||
qWarning() << "无法打开文件进行写入:" << filePath; | |||||
return false; | |||||
} | |||||
// 创建JSON文档结构 | |||||
QJsonObject docObject; | |||||
docObject["title"] = m_title; | |||||
docObject["sceneRect"] = QJsonArray({ | |||||
m_scene->sceneRect().x(), | |||||
m_scene->sceneRect().y(), | |||||
m_scene->sceneRect().width(), | |||||
m_scene->sceneRect().height() | |||||
}); | |||||
docObject["backgroundColor"] = m_scene->backgroundBrush().color().name(); | |||||
// 序列化所有图形项 | |||||
QJsonArray itemsArray; | |||||
foreach (QGraphicsItem *item, m_scene->items()) { | |||||
QJsonObject itemJson = itemToJson(item); | |||||
if (!itemJson.isEmpty()) { | |||||
itemsArray.append(itemJson); | |||||
} | |||||
} | |||||
docObject["items"] = itemsArray; | |||||
// 写入文件 | |||||
QJsonDocument doc(docObject); | |||||
file.write(doc.toJson()); | |||||
file.close(); | |||||
// 更新文档状态 | |||||
setFilePath(filePath); | |||||
setModified(false); | |||||
QFileInfo fileInfo(filePath); | |||||
setTitle(fileInfo.baseName()); | |||||
return true; | |||||
} | |||||
// 从文件加载文档 | |||||
bool HMIDocument::loadFromFile(const QString &filePath) | |||||
{ | |||||
QFile file(filePath); | |||||
if (!file.open(QIODevice::ReadOnly)) { | |||||
qWarning() << "无法打开文件进行读取:" << filePath; | |||||
return false; | |||||
} | |||||
// 读取JSON文档 | |||||
QByteArray data = file.readAll(); | |||||
QJsonDocument doc = QJsonDocument::fromJson(data); | |||||
if (doc.isNull()) { | |||||
qWarning() << "无效的JSON文档:" << filePath; | |||||
return false; | |||||
} | |||||
// 解析文档 | |||||
QJsonObject docObject = doc.object(); | |||||
m_title = docObject["title"].toString(); | |||||
// 设置场景属性 | |||||
QJsonArray sceneRectArray = docObject["sceneRect"].toArray(); | |||||
if (sceneRectArray.size() == 4) { | |||||
QRectF sceneRect( | |||||
sceneRectArray[0].toDouble(), | |||||
sceneRectArray[1].toDouble(), | |||||
sceneRectArray[2].toDouble(), | |||||
sceneRectArray[3].toDouble() | |||||
); | |||||
m_scene->setSceneRect(sceneRect); | |||||
} | |||||
if (docObject.contains("backgroundColor")) { | |||||
QColor bgColor(docObject["backgroundColor"].toString()); | |||||
m_scene->setBackgroundBrush(QBrush(bgColor)); | |||||
} | |||||
// 清除现有内容 | |||||
m_scene->clear(); | |||||
// 加载所有图形项 | |||||
QJsonArray itemsArray = docObject["items"].toArray(); | |||||
foreach (QJsonValue itemValue, itemsArray) { | |||||
QJsonObject itemJson = itemValue.toObject(); | |||||
QGraphicsItem *item = jsonToItem(itemJson); | |||||
if (item) { | |||||
m_scene->addItem(item); | |||||
} | |||||
} | |||||
// 更新文档状态 | |||||
setFilePath(filePath); | |||||
setModified(false); | |||||
return true; | |||||
} | |||||
// 将图形项转换为JSON对象 | |||||
QJsonObject HMIDocument::itemToJson(QGraphicsItem *item) | |||||
{ | |||||
QJsonObject json; | |||||
// 基础属性 | |||||
json["type"] = item->type(); | |||||
json["x"] = item->x(); | |||||
json["y"] = item->y(); | |||||
json["zValue"] = item->zValue(); | |||||
json["visible"] = item->isVisible(); | |||||
// 具体图形项属性 | |||||
if (auto ellipse = dynamic_cast<ResizableEllipse*>(item)) { | |||||
json["itemType"] = "ellipse"; | |||||
json["rect"] = QJsonArray({ | |||||
ellipse->rect().x(), | |||||
ellipse->rect().y(), | |||||
ellipse->rect().width(), | |||||
ellipse->rect().height() | |||||
}); | |||||
json["onColor"] = ellipse->onColor().name(); | |||||
json["offColor"] = ellipse->offColor().name(); | |||||
json["currentColor"] = ellipse->brush().color().name(); | |||||
json["penColor"] = ellipse->pen().color().name(); | |||||
json["penWidth"] = ellipse->pen().width(); | |||||
json["name"] = ellipse->name(); | |||||
} | |||||
else if (auto rect = dynamic_cast<ResizableRectangle*>(item)) { | |||||
json["itemType"] = "rectangle"; | |||||
json["rect"] = QJsonArray({ | |||||
rect->rect().x(), | |||||
rect->rect().y(), | |||||
rect->rect().width(), | |||||
rect->rect().height() | |||||
}); | |||||
json["pressedColor"] = rect->pressedColor().name(); | |||||
json["releasedColor"] = rect->releasedColor().name(); | |||||
json["currentColor"] = rect->brush().color().name(); | |||||
json["penColor"] = rect->pen().color().name(); | |||||
json["penWidth"] = rect->pen().width(); | |||||
json["name"] = rect->name(); | |||||
} | |||||
return json; | |||||
} | |||||
// 从JSON对象创建图形项 | |||||
QGraphicsItem *HMIDocument::jsonToItem(const QJsonObject &json) | |||||
{ | |||||
QString itemType = json["itemType"].toString(); | |||||
QGraphicsItem *item = nullptr; | |||||
if (itemType == "ellipse") { | |||||
QJsonArray rectArray = json["rect"].toArray(); | |||||
if (rectArray.size() != 4) return nullptr; | |||||
ResizableEllipse *ellipse = new ResizableEllipse( | |||||
rectArray[0].toDouble(), | |||||
rectArray[1].toDouble(), | |||||
rectArray[2].toDouble(), | |||||
rectArray[3].toDouble() | |||||
); | |||||
ellipse->setOnColor(QColor(json["onColor"].toString())); | |||||
ellipse->setOffColor(QColor(json["offColor"].toString())); | |||||
ellipse->setBrush(QColor(json["currentColor"].toString())); | |||||
ellipse->setPen(QPen( | |||||
QColor(json["penColor"].toString()), | |||||
json["penWidth"].toDouble() | |||||
)); | |||||
ellipse->setName(json["name"].toString()); | |||||
item = ellipse; | |||||
} | |||||
else if (itemType == "rectangle") { | |||||
QJsonArray rectArray = json["rect"].toArray(); | |||||
if (rectArray.size() != 4) return nullptr; | |||||
ResizableRectangle *rect = new ResizableRectangle( | |||||
rectArray[0].toDouble(), | |||||
rectArray[1].toDouble(), | |||||
rectArray[2].toDouble(), | |||||
rectArray[3].toDouble() | |||||
); | |||||
rect->setPressedColor(QColor(json["pressedColor"].toString())); | |||||
rect->setReleasedColor(QColor(json["releasedColor"].toString())); | |||||
rect->setBrush(QColor(json["currentColor"].toString())); | |||||
rect->setPen(QPen( | |||||
QColor(json["penColor"].toString()), | |||||
json["penWidth"].toDouble() | |||||
)); | |||||
rect->setName(json["name"].toString()); | |||||
item = rect; | |||||
} | |||||
// 设置基础属性 | |||||
if (item) { | |||||
item->setX(json["x"].toDouble()); | |||||
item->setY(json["y"].toDouble()); | |||||
item->setZValue(json["zValue"].toDouble()); | |||||
item->setVisible(json["visible"].toBool()); | |||||
} | |||||
return item; | |||||
} | |||||
QString HMIDocument::title() const { return m_title; } | |||||
void HMIDocument::setTitle(const QString &title) { m_title = title; } | |||||
QGraphicsView *HMIDocument::view() const { return m_view; } | |||||
QGraphicsScene *HMIDocument::scene() const { return m_scene; } | |||||
void HMIDocument::setDrawEllipse(bool enable) { m_canDrawEllipse = enable; } | |||||
void HMIDocument::setDrawRectangle(bool enable) { m_canDrawRectangle = enable; } | |||||
void HMIDocument::markModified() { setModified(true); } |
@@ -0,0 +1,74 @@ | |||||
#ifndef HMIDOCUMENT_H | |||||
#define HMIDOCUMENT_H | |||||
#include "basedocument.h" | |||||
#include <QGraphicsScene> | |||||
#include <QGraphicsView> | |||||
#include <QJsonObject> | |||||
// 前向声明 | |||||
class ResizableRectangle; | |||||
class ResizableEllipse; | |||||
class NamedItem; | |||||
class HMIDocument : public BaseDocument | |||||
{ | |||||
Q_OBJECT | |||||
public: | |||||
explicit HMIDocument(QWidget *parent = nullptr); | |||||
~HMIDocument() override; | |||||
QString title() const override; | |||||
void setTitle(const QString &title); | |||||
QGraphicsView *view() const; | |||||
QGraphicsScene *scene() const; | |||||
// 绘图控制 | |||||
void setDrawEllipse(bool enable); | |||||
void setDrawRectangle(bool enable); | |||||
// 编辑功能 | |||||
void copySelectedItems(); | |||||
void pasteItems(); | |||||
void deleteSelectedItems(); | |||||
void showItemProperties(); | |||||
void startDrawingEllipse(); | |||||
void startDrawingRectangle(); | |||||
// 保存/加载 | |||||
bool saveToFile(const QString &filePath) override; | |||||
bool loadFromFile(const QString &filePath) override; | |||||
void markModified(); | |||||
signals: | |||||
void titleChanged(const QString &title); | |||||
protected: | |||||
bool eventFilter(QObject *obj, QEvent *event) override; | |||||
private: | |||||
// 辅助方法 | |||||
void createShape(const QString& type, const QPointF &pos); | |||||
void resetDrawFlags(); | |||||
void showContextMenu(QPoint globalPos); | |||||
// 序列化相关 | |||||
QByteArray serializeItem(QGraphicsItem *item); | |||||
QGraphicsItem *deserializeItem(const QByteArray &data); | |||||
QJsonObject itemToJson(QGraphicsItem *item); | |||||
QGraphicsItem *jsonToItem(const QJsonObject &json); | |||||
// 成员变量 | |||||
QString m_title; | |||||
QGraphicsScene *m_scene; | |||||
QGraphicsView *m_view; | |||||
bool m_canDrawEllipse; | |||||
bool m_canDrawRectangle; | |||||
// 复制粘贴缓冲区 | |||||
QList<QByteArray> m_copiedItemsData; | |||||
QPointF m_lastPastePos; | |||||
}; | |||||
#endif // HMIDOCUMENT_H |
@@ -1,4 +1,6 @@ | |||||
#include "mainwindow.h" | #include "mainwindow.h" | ||||
#include "hmidocument.h" | |||||
#include "plcdocument.h" | |||||
#include <QMenuBar> | #include <QMenuBar> | ||||
#include <QAction> | #include <QAction> | ||||
#include <QToolButton> | #include <QToolButton> | ||||
@@ -10,19 +12,24 @@ | |||||
#include <QDragEnterEvent> | #include <QDragEnterEvent> | ||||
#include <QDragMoveEvent> | #include <QDragMoveEvent> | ||||
#include <QDropEvent> | #include <QDropEvent> | ||||
#include <QDebug> | |||||
#include <QVBoxLayout> | |||||
#include <QFileDialog> | |||||
#include <QMessageBox> | |||||
#include <QTextEdit> | |||||
MainWindow::MainWindow(QWidget *parent) | MainWindow::MainWindow(QWidget *parent) | ||||
: QMainWindow(parent) | : QMainWindow(parent) | ||||
{ | { | ||||
setWindowTitle("综合平台编程器"); | setWindowTitle("综合平台编程器"); | ||||
setGeometry(100, 100, 1000, 700); | |||||
setGeometry(500, 200, 1000, 700); | |||||
// 初始化标签页 | // 初始化标签页 | ||||
m_tabWidget = new QTabWidget(this); | m_tabWidget = new QTabWidget(this); | ||||
m_tabWidget->setTabsClosable(true); | m_tabWidget->setTabsClosable(true); | ||||
setCentralWidget(m_tabWidget); | setCentralWidget(m_tabWidget); | ||||
connect(m_tabWidget, &QTabWidget::currentChanged, this, &MainWindow::onTabChanged); | connect(m_tabWidget, &QTabWidget::currentChanged, this, &MainWindow::onTabChanged); | ||||
connect(m_tabWidget, &QTabWidget::tabCloseRequested, this, &MainWindow::onCloseTab); | |||||
// 创建菜单和工具栏 | // 创建菜单和工具栏 | ||||
createMenus(); | createMenus(); | ||||
createToolbars(); | createToolbars(); | ||||
@@ -34,29 +41,45 @@ MainWindow::~MainWindow() | |||||
} | } | ||||
// 创建菜单栏 | // 创建菜单栏 | ||||
// 在createMenus()函数中添加以下代码,用于创建操作菜单及其功能 | |||||
void MainWindow::createMenus() | void MainWindow::createMenus() | ||||
{ | { | ||||
// 创建文件菜单(保持原有代码) | |||||
// 创建文件菜单 | |||||
QMenu *fileMenu = menuBar()->addMenu("文件"); | QMenu *fileMenu = menuBar()->addMenu("文件"); | ||||
QMenu *editMenu = menuBar()->addMenu("操作"); | QMenu *editMenu = menuBar()->addMenu("操作"); | ||||
QMenu *simulationMenu = menuBar()->addMenu("仿真"); | |||||
// 新建HMI动作(保持原有代码) | |||||
// 新建HMI动作 | |||||
QAction *newHmiAction = new QAction("新建HMI(&H)", this); | QAction *newHmiAction = new QAction("新建HMI(&H)", this); | ||||
newHmiAction->setShortcut(tr("Ctrl+H")); | newHmiAction->setShortcut(tr("Ctrl+H")); | ||||
newHmiAction->setStatusTip("创建新的HMI文档"); | |||||
connect(newHmiAction, &QAction::triggered, this, &MainWindow::onNewHMI); | connect(newHmiAction, &QAction::triggered, this, &MainWindow::onNewHMI); | ||||
fileMenu->addAction(newHmiAction); | fileMenu->addAction(newHmiAction); | ||||
// 新建PLC动作(保持原有代码) | |||||
// 新建PLC动作 | |||||
QAction *newPlcAction = new QAction("新建PLC(&P)", this); | QAction *newPlcAction = new QAction("新建PLC(&P)", this); | ||||
newPlcAction->setShortcut(tr("Ctrl+P")); | newPlcAction->setShortcut(tr("Ctrl+P")); | ||||
newPlcAction->setStatusTip("创建新的PLC文档"); | |||||
connect(newPlcAction, &QAction::triggered, this, &MainWindow::onNewPLC); | connect(newPlcAction, &QAction::triggered, this, &MainWindow::onNewPLC); | ||||
fileMenu->addAction(newPlcAction); | fileMenu->addAction(newPlcAction); | ||||
// 打开动作 | |||||
m_openAction = new QAction("打开(&O)", this); | |||||
m_openAction->setShortcut(QKeySequence::Open); | |||||
m_openAction->setStatusTip("打开现有文档"); | |||||
connect(m_openAction, &QAction::triggered, this, &MainWindow::onOpen); | |||||
fileMenu->addAction(m_openAction); | |||||
// 保存动作 | |||||
m_saveAction = new QAction("保存(&S)", this); | |||||
m_saveAction->setShortcut(QKeySequence::Save); | |||||
m_saveAction->setStatusTip("保存当前文档"); | |||||
connect(m_saveAction, &QAction::triggered, this, &MainWindow::onSave); | |||||
fileMenu->addAction(m_saveAction); | |||||
// 另存为动作 | |||||
m_saveAsAction = new QAction("另存为(&A)", this); | |||||
m_saveAsAction->setShortcut(QKeySequence::SaveAs); | |||||
m_saveAsAction->setStatusTip("将文档另存为"); | |||||
connect(m_saveAsAction, &QAction::triggered, this, &MainWindow::onSaveAs); | |||||
fileMenu->addAction(m_saveAsAction); | |||||
// 操作菜单 - 添加复制、粘贴、删除功能 | // 操作菜单 - 添加复制、粘贴、删除功能 | ||||
QAction *copyAction = new QAction("复制(&C)", this); | QAction *copyAction = new QAction("复制(&C)", this); | ||||
copyAction->setShortcut(QKeySequence::Copy); // 标准复制快捷键 Ctrl+C | copyAction->setShortcut(QKeySequence::Copy); // 标准复制快捷键 Ctrl+C | ||||
@@ -92,87 +115,278 @@ void MainWindow::createMenus() | |||||
editMenu->addAction(deleteAction); | editMenu->addAction(deleteAction); | ||||
} | } | ||||
// 创建左侧工具栏 | // 创建左侧工具栏 | ||||
void MainWindow::createToolbars() | void MainWindow::createToolbars() | ||||
{ | { | ||||
m_leftToolBar = new QToolBar("绘图工具", this); | |||||
m_leftToolBar = new QToolBar("绘图工具栏", this); | |||||
addToolBar(Qt::LeftToolBarArea, m_leftToolBar); | addToolBar(Qt::LeftToolBarArea, m_leftToolBar); | ||||
m_leftToolBar->setAllowedAreas(Qt::LeftToolBarArea); // 仅允许在左侧 | m_leftToolBar->setAllowedAreas(Qt::LeftToolBarArea); // 仅允许在左侧 | ||||
m_leftToolBar->setFixedWidth(200); | |||||
} | } | ||||
// 更新工具栏(根据当前文档类型) | // 更新工具栏(根据当前文档类型) | ||||
void MainWindow::updateToolBar(BaseDocument *doc) | void MainWindow::updateToolBar(BaseDocument *doc) | ||||
{ | { | ||||
// 清空现有工具 | |||||
m_leftToolBar->clear(); | |||||
m_leftToolBar->clear();// 清空现有工具 | |||||
if (!doc) return; | if (!doc) return; | ||||
// HMI文档显示绘图工具 | |||||
if (doc->type() == BaseDocument::HMI) | |||||
if (doc->type() == BaseDocument::HMI)// HMI文档显示绘图工具 | |||||
{ | { | ||||
HMIDocument *hmiDoc = dynamic_cast<HMIDocument*>(doc); | HMIDocument *hmiDoc = dynamic_cast<HMIDocument*>(doc); | ||||
if (!hmiDoc) return; | if (!hmiDoc) return; | ||||
// 画指示灯按钮(支持拖拽) | // 画指示灯按钮(支持拖拽) | ||||
QToolButton *ellipseBtn = new QToolButton; | QToolButton *ellipseBtn = new QToolButton; | ||||
ellipseBtn->setText("指示灯"); | ellipseBtn->setText("指示灯"); | ||||
ellipseBtn->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); | |||||
ellipseBtn->setIcon(QIcon("../two/untitled/images/灯泡.png")); // 可替换为实际图标 | |||||
ellipseBtn->setMinimumSize(120, 120); | |||||
connect(ellipseBtn, &QToolButton::clicked, [=]() { | |||||
hmiDoc->setDrawEllipse(true); | |||||
hmiDoc->setDrawRectangle(false); | |||||
}); | |||||
// 为按钮安装事件过滤器处理拖拽 | |||||
ellipseBtn->installEventFilter(this); | |||||
ellipseBtn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); | |||||
ellipseBtn->setIcon(QIcon("../two/untitled/images/灯泡.png"));//可替换为实际图标 | |||||
ellipseBtn->installEventFilter(this);//为按钮安装事件过滤器处理拖拽 | |||||
ellipseBtn->setProperty("toolType", "指示灯"); | ellipseBtn->setProperty("toolType", "指示灯"); | ||||
m_leftToolBar->addWidget(ellipseBtn); | m_leftToolBar->addWidget(ellipseBtn); | ||||
ellipseBtn->setStyleSheet(R"( | |||||
QToolButton { | |||||
margin: 2 auto; | |||||
background-color: #f0f0f0; | |||||
border-radius: 10px; | |||||
border: 1px solid #ccc; | |||||
padding: 10px 12px; | |||||
font-size: 18px; | |||||
font-weight: bold; | |||||
color: #333; | |||||
} | |||||
QToolButton:hover { | |||||
background-color: #e0e0e0; | |||||
} | |||||
)"); | |||||
// 画按钮按钮(支持拖拽) | // 画按钮按钮(支持拖拽) | ||||
QToolButton *rectBtn = new QToolButton; | QToolButton *rectBtn = new QToolButton; | ||||
rectBtn->setText("按钮"); | |||||
rectBtn->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); | |||||
rectBtn->setIcon(QIcon("../two/untitled/images/按钮.png")); // 可替换为实际图标 | |||||
rectBtn->setMinimumSize(120, 120); | |||||
connect(rectBtn, &QToolButton::clicked, [=]() { | |||||
hmiDoc->setDrawRectangle(true); | |||||
hmiDoc->setDrawEllipse(false); | |||||
}); | |||||
// 为按钮安装事件过滤器处理拖拽 | |||||
rectBtn->setText("按 钮"); | |||||
rectBtn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); | |||||
rectBtn->setIcon(QIcon("../two/untitled/images/按钮.png"));//可替换为实际图标 | |||||
rectBtn->installEventFilter(this); | rectBtn->installEventFilter(this); | ||||
rectBtn->setProperty("toolType", "按钮"); | rectBtn->setProperty("toolType", "按钮"); | ||||
m_leftToolBar->addWidget(rectBtn); | m_leftToolBar->addWidget(rectBtn); | ||||
rectBtn->setStyleSheet(R"( | |||||
QToolButton { | |||||
margin: 2 auto; | |||||
background-color: #f0f0f0; | |||||
border-radius: 10px; | |||||
border: 1px solid #ccc; | |||||
padding: 10px 15px; | |||||
font-size: 18px; | |||||
font-weight: bold; | |||||
color: #333; | |||||
} | |||||
QToolButton:hover { | |||||
background-color: #e0e0e0; | |||||
} | |||||
)"); | |||||
} | } | ||||
// PLC按钮工具 | |||||
else if (doc->type() == BaseDocument::PLC) | |||||
{ | |||||
// 常开触点按钮 | |||||
QToolButton *normallyOpenBtn = new QToolButton; | |||||
normallyOpenBtn->setText("常开触点"); | |||||
normallyOpenBtn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); | |||||
normallyOpenBtn->setIcon(QIcon("../two/untitled/images/T-常开触点-01.png")); // 替换为实际图标 | |||||
normallyOpenBtn->installEventFilter(this); | |||||
normallyOpenBtn->setProperty("toolType", "常开触点"); | |||||
m_leftToolBar->addWidget(normallyOpenBtn); | |||||
normallyOpenBtn->setStyleSheet(R"( | |||||
QToolButton { | |||||
margin: 2 auto; | |||||
background-color: #f0f0f0; | |||||
border-radius: 10px; | |||||
border: 1px solid #ccc; | |||||
padding: 10px 1px; | |||||
font-size: 18px; | |||||
font-weight: bold; | |||||
color: #333; | |||||
} | |||||
QToolButton:hover { | |||||
background-color: #e0e0e0; | |||||
} | |||||
)"); | |||||
// 常闭触点按钮 | |||||
QToolButton *normallyClosedBtn = new QToolButton; | |||||
normallyClosedBtn->setText("常闭触点"); | |||||
normallyClosedBtn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); | |||||
normallyClosedBtn->setIcon(QIcon("../two/untitled/images/T-常闭触点-01-01.png")); // 替换为实际图标 | |||||
normallyClosedBtn->installEventFilter(this); | |||||
normallyClosedBtn->setProperty("toolType", "常闭触点"); | |||||
m_leftToolBar->addWidget(normallyClosedBtn); | |||||
normallyClosedBtn->setStyleSheet(R"( | |||||
QToolButton { | |||||
margin: 2 auto; | |||||
background-color: #f0f0f0; | |||||
border-radius: 10px; | |||||
border: 1px solid #ccc; | |||||
padding: 10px 1px; | |||||
font-size: 18px; | |||||
font-weight: bold; | |||||
color: #333; | |||||
} | |||||
QToolButton:hover { | |||||
background-color: #e0e0e0; | |||||
} | |||||
)"); | |||||
// 大于按钮 | |||||
QToolButton *greaterThanBtn = new QToolButton; | |||||
greaterThanBtn->setText("大于"); | |||||
greaterThanBtn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); | |||||
greaterThanBtn->setIcon(QIcon("../two/untitled/images/大于号.png")); // 替换为实际图标 | |||||
greaterThanBtn->installEventFilter(this); | |||||
greaterThanBtn->setProperty("toolType", "大于"); | |||||
m_leftToolBar->addWidget(greaterThanBtn); | |||||
greaterThanBtn->setStyleSheet(R"( | |||||
QToolButton { | |||||
margin: 2 auto; | |||||
background-color: #f0f0f0; | |||||
border-radius: 10px; | |||||
border: 1px solid #ccc; | |||||
padding: 10px 20px; | |||||
font-size: 18px; | |||||
font-weight: bold; | |||||
color: #333; | |||||
} | |||||
QToolButton:hover { | |||||
background-color: #e0e0e0; | |||||
} | |||||
)"); | |||||
// 大于等于按钮 | |||||
QToolButton *greaterThanEqualBtn = new QToolButton; | |||||
greaterThanEqualBtn->setText("大于等于"); | |||||
greaterThanEqualBtn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); | |||||
greaterThanEqualBtn->setIcon(QIcon("../two/untitled/images/大于等于.png")); // 替换为实际图标 | |||||
greaterThanEqualBtn->installEventFilter(this); | |||||
greaterThanEqualBtn->setProperty("toolType", "大于等于"); | |||||
m_leftToolBar->addWidget(greaterThanEqualBtn); | |||||
greaterThanEqualBtn->setStyleSheet(R"( | |||||
QToolButton { | |||||
margin: 2 auto; | |||||
background-color: #f0f0f0; | |||||
border-radius: 10px; | |||||
border: 1px solid #ccc; | |||||
padding: 10px 1px; | |||||
font-size: 18px; | |||||
font-weight: bold; | |||||
color: #333; | |||||
} | |||||
QToolButton:hover { | |||||
background-color: #e0e0e0; | |||||
} | |||||
)"); | |||||
// 小于按钮 | |||||
QToolButton *lessThanBtn = new QToolButton; | |||||
lessThanBtn->setText("小于"); | |||||
lessThanBtn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); | |||||
lessThanBtn->setIcon(QIcon("../two/untitled/images/小于号.png")); // 替换为实际图标 | |||||
lessThanBtn->installEventFilter(this); | |||||
lessThanBtn->setProperty("toolType", "小于"); | |||||
m_leftToolBar->addWidget(lessThanBtn); | |||||
lessThanBtn->setStyleSheet(R"( | |||||
QToolButton { | |||||
margin: 2 auto; | |||||
background-color: #f0f0f0; | |||||
border-radius: 10px; | |||||
border: 1px solid #ccc; | |||||
padding: 10px 20px; | |||||
font-size: 18px; | |||||
font-weight: bold; | |||||
color: #333; | |||||
} | |||||
QToolButton:hover { | |||||
background-color: #e0e0e0; | |||||
} | |||||
)"); | |||||
// 小于等于按钮 | |||||
QToolButton *lessThanEqualBtn = new QToolButton; | |||||
lessThanEqualBtn->setText("小于等于"); | |||||
lessThanEqualBtn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); | |||||
lessThanEqualBtn->setIcon(QIcon("../two/untitled/images/小于等于.png")); // 替换为实际图标 | |||||
lessThanEqualBtn->installEventFilter(this); | |||||
lessThanEqualBtn->setProperty("toolType", "小于等于"); | |||||
m_leftToolBar->addWidget(lessThanEqualBtn); | |||||
lessThanEqualBtn->setStyleSheet(R"( | |||||
QToolButton { | |||||
margin: 2 auto; | |||||
background-color: #f0f0f0; | |||||
border-radius: 10px; | |||||
border: 1px solid #ccc; | |||||
padding: 10px 1px; | |||||
font-size: 18px; | |||||
font-weight: bold; | |||||
color: #333; | |||||
} | |||||
QToolButton:hover { | |||||
background-color: #e0e0e0; | |||||
} | |||||
)"); | |||||
// PLC文档可添加自己的工具 | |||||
else if (doc->type() == BaseDocument::PLC) { | |||||
m_leftToolBar->addAction("常开触点"); | |||||
m_leftToolBar->addAction("常闭触点"); | |||||
// 线圈按钮 | |||||
QToolButton *coilBtn = new QToolButton; | |||||
coilBtn->setText("线圈"); | |||||
coilBtn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); | |||||
coilBtn->setIcon(QIcon("../two/untitled/images/线-圈-圈.png")); // 替换为实际图标 | |||||
coilBtn->installEventFilter(this); | |||||
coilBtn->setProperty("toolType", "小于等于"); | |||||
m_leftToolBar->addWidget(coilBtn); | |||||
coilBtn->setStyleSheet(R"( | |||||
QToolButton { | |||||
margin: 2 auto; | |||||
background-color: #f0f0f0; | |||||
border-radius: 10px; | |||||
border: 1px solid #ccc; | |||||
padding: 10px 20px; | |||||
font-size: 18px; | |||||
font-weight: bold; | |||||
color: #333; | |||||
} | |||||
QToolButton:hover { | |||||
background-color: #e0e0e0; | |||||
} | |||||
)"); | |||||
} | } | ||||
} | } | ||||
// 事件过滤器处理拖拽 | // 事件过滤器处理拖拽 | ||||
bool MainWindow::eventFilter(QObject *obj, QEvent *event) | bool MainWindow::eventFilter(QObject *obj, QEvent *event) | ||||
{ | { | ||||
// 检查是否是工具栏按钮的鼠标按下事件 | |||||
QToolButton *toolBtn = qobject_cast<QToolButton*>(obj); | QToolButton *toolBtn = qobject_cast<QToolButton*>(obj); | ||||
if (toolBtn && event->type() == QEvent::MouseButtonPress) | if (toolBtn && event->type() == QEvent::MouseButtonPress) | ||||
{ | { | ||||
QMouseEvent *me = static_cast<QMouseEvent*>(event); | QMouseEvent *me = static_cast<QMouseEvent*>(event); | ||||
if (me->button() == Qt::LeftButton) | if (me->button() == Qt::LeftButton) | ||||
{ | { | ||||
// 获取工具类型 | |||||
QString toolType = toolBtn->property("toolType").toString(); | QString toolType = toolBtn->property("toolType").toString(); | ||||
if (!toolType.isEmpty()) | if (!toolType.isEmpty()) | ||||
{ | { | ||||
// 创建拖拽 | |||||
BaseDocument* currentDoc = dynamic_cast<BaseDocument*>(m_tabWidget->currentWidget()); | |||||
if (currentDoc && currentDoc->type() == BaseDocument::HMI) { | |||||
HMIDocument* hmiDoc = static_cast<HMIDocument*>(currentDoc); | |||||
// 设置对应的绘制模式 | |||||
if (toolType == "指示灯") | |||||
{ | |||||
hmiDoc->startDrawingEllipse(); | |||||
} | |||||
else if (toolType == "按钮") | |||||
{ | |||||
hmiDoc->startDrawingRectangle(); | |||||
} | |||||
} | |||||
// 创建拖拽对象 | |||||
QMimeData *mime = new QMimeData; | QMimeData *mime = new QMimeData; | ||||
mime->setText(toolType); | mime->setText(toolType); | ||||
QDrag *drag = new QDrag(obj); | |||||
QDrag *drag = new QDrag(toolBtn); | |||||
drag->setMimeData(mime); | drag->setMimeData(mime); | ||||
drag->exec(Qt::CopyAction); | drag->exec(Qt::CopyAction); | ||||
return true; | return true; | ||||
@@ -187,7 +401,7 @@ void MainWindow::onNewHMI() | |||||
{ | { | ||||
m_hmiCount++; | m_hmiCount++; | ||||
HMIDocument *doc = new HMIDocument; | HMIDocument *doc = new HMIDocument; | ||||
doc->setTitle(QString("HMI文档 %1").arg(m_hmiCount)); | |||||
doc->setTitle("HMI文档 " + QString::number(m_hmiCount)); | |||||
m_tabWidget->addTab(doc, doc->title()); | m_tabWidget->addTab(doc, doc->title()); | ||||
m_tabWidget->setCurrentWidget(doc); | m_tabWidget->setCurrentWidget(doc); | ||||
updateToolBar(doc); // 更新工具栏为HMI工具 | updateToolBar(doc); // 更新工具栏为HMI工具 | ||||
@@ -206,10 +420,125 @@ void MainWindow::onNewPLC() | |||||
// 标签页切换时更新工具栏 | // 标签页切换时更新工具栏 | ||||
void MainWindow::onTabChanged(int idx) | void MainWindow::onTabChanged(int idx) | ||||
{ | { | ||||
if (idx < 0) { | |||||
if (idx < 0) | |||||
{ | |||||
updateToolBar(nullptr); | updateToolBar(nullptr); | ||||
return; | return; | ||||
} | } | ||||
BaseDocument *doc = dynamic_cast<BaseDocument*>(m_tabWidget->widget(idx)); | BaseDocument *doc = dynamic_cast<BaseDocument*>(m_tabWidget->widget(idx)); | ||||
updateToolBar(doc); | updateToolBar(doc); | ||||
} | } | ||||
// 保存文档 | |||||
void MainWindow::onSave() | |||||
{ | |||||
BaseDocument *doc = dynamic_cast<BaseDocument*>(m_tabWidget->currentWidget()); | |||||
if (!doc) return; | |||||
if (doc->filePath().isEmpty()) { | |||||
saveDocumentAs(doc); | |||||
} else { | |||||
saveDocument(doc); | |||||
} | |||||
} | |||||
// 另存为文档 | |||||
void MainWindow::onSaveAs() | |||||
{ | |||||
BaseDocument *doc = dynamic_cast<BaseDocument*>(m_tabWidget->currentWidget()); | |||||
if (doc) { | |||||
saveDocumentAs(doc); | |||||
} | |||||
} | |||||
// 打开文档 | |||||
void MainWindow::onOpen() | |||||
{ | |||||
QString filePath = QFileDialog::getOpenFileName( | |||||
this, | |||||
"打开文档", | |||||
"", | |||||
"HMI文档 (*.hmi);;PLC文档 (*.plc)" | |||||
); | |||||
if (filePath.isEmpty()) return; | |||||
QFileInfo fileInfo(filePath); | |||||
BaseDocument *doc = nullptr; | |||||
if (fileInfo.suffix().toLower() == "hmi") { | |||||
doc = new HMIDocument; | |||||
} else if (fileInfo.suffix().toLower() == "plc") { | |||||
doc = new PLCDocument; | |||||
} else { | |||||
QMessageBox::warning(this, "打开文档", "不支持的文件格式"); | |||||
return; | |||||
} | |||||
if (doc->loadFromFile(filePath)) { | |||||
m_tabWidget->addTab(doc, doc->title()); | |||||
m_tabWidget->setCurrentWidget(doc); | |||||
updateToolBar(doc); | |||||
} else { | |||||
QMessageBox::critical(this, "打开文档", "无法加载文档"); | |||||
delete doc; | |||||
} | |||||
} | |||||
// 关闭标签页 | |||||
void MainWindow::onCloseTab(int index) | |||||
{ | |||||
BaseDocument *doc = dynamic_cast<BaseDocument*>(m_tabWidget->widget(index)); | |||||
if (!doc) return; | |||||
if (doc->isModified()) | |||||
{ | |||||
QMessageBox::StandardButton reply = QMessageBox::question( | |||||
this, | |||||
"保存文档", | |||||
QString("文档 '%1' 已修改,是否保存更改?").arg(doc->title()), | |||||
QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel | |||||
); | |||||
if (reply == QMessageBox::Save) { | |||||
saveDocument(doc); | |||||
} else if (reply == QMessageBox::Cancel) { | |||||
return; | |||||
} | |||||
} | |||||
m_tabWidget->removeTab(index); | |||||
delete doc; | |||||
} | |||||
// 保存文档 | |||||
void MainWindow::saveDocument(BaseDocument *doc) | |||||
{ | |||||
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)) { | |||||
doc->setModified(false); // 清除修改状态 | |||||
QMessageBox::information(this, "保存文档", "文档保存成功"); | |||||
} else { | |||||
QMessageBox::critical(this, "保存文档", "无法保存文档"); | |||||
} | |||||
} |
@@ -4,7 +4,8 @@ | |||||
#include <QMainWindow> | #include <QMainWindow> | ||||
#include <QTabWidget> | #include <QTabWidget> | ||||
#include <QToolBar> | #include <QToolBar> | ||||
#include "document.h" | |||||
#include <QAction> | |||||
#include "basedocument.h" | |||||
QT_BEGIN_NAMESPACE | QT_BEGIN_NAMESPACE | ||||
namespace Ui { class MainWindow; } | namespace Ui { class MainWindow; } | ||||
@@ -25,16 +26,27 @@ private slots: | |||||
void onNewHMI(); // 新建HMI文档 | void onNewHMI(); // 新建HMI文档 | ||||
void onNewPLC(); // 新建PLC文档 | void onNewPLC(); // 新建PLC文档 | ||||
void onTabChanged(int idx); // 标签页切换时更新工具栏 | void onTabChanged(int idx); // 标签页切换时更新工具栏 | ||||
void onSave(); // 保存文档 | |||||
void onSaveAs(); // 另存为文档 | |||||
void onOpen(); // 打开文档 | |||||
void onCloseTab(int index); // 关闭标签页 | |||||
private: | private: | ||||
void createMenus(); // 创建菜单栏 | void createMenus(); // 创建菜单栏 | ||||
void createToolbars(); // 创建工具栏(左侧) | void createToolbars(); // 创建工具栏(左侧) | ||||
void updateToolBar(BaseDocument *doc); // 根据文档类型更新工具栏 | void updateToolBar(BaseDocument *doc); // 根据文档类型更新工具栏 | ||||
void saveDocument(BaseDocument *doc); // 保存文档 | |||||
void saveDocumentAs(BaseDocument *doc); // 另存为文档 | |||||
QTabWidget *m_tabWidget; // 多文档标签页 | QTabWidget *m_tabWidget; // 多文档标签页 | ||||
QToolBar *m_leftToolBar; // 左侧工具栏 | QToolBar *m_leftToolBar; // 左侧工具栏 | ||||
int m_hmiCount = 0; // HMI文档计数器 | int m_hmiCount = 0; // HMI文档计数器 | ||||
int m_plcCount = 0; // PLC文档计数器 | int m_plcCount = 0; // PLC文档计数器 | ||||
// 菜单动作 | |||||
QAction *m_saveAction; | |||||
QAction *m_saveAsAction; | |||||
QAction *m_openAction; | |||||
}; | }; | ||||
#endif // MAINWINDOW_H | #endif // MAINWINDOW_H |
@@ -0,0 +1,88 @@ | |||||
#include "plcdocument.h" | |||||
#include <QGraphicsScene> | |||||
#include <QGraphicsView> | |||||
#include <QPainter> | |||||
#include <QBrush> | |||||
#include <QPen> | |||||
#include <QFile> | |||||
#include <QJsonDocument> | |||||
#include <QJsonObject> | |||||
#include <QJsonArray> | |||||
#include <QFileInfo> | |||||
#include <QVBoxLayout> | |||||
PLCDocument::PLCDocument(QWidget *parent) | |||||
: BaseDocument(PLC, parent) | |||||
{ | |||||
// 创建绘图场景 | |||||
m_scene = new QGraphicsScene(this); | |||||
m_scene->setSceneRect(0, 0, 800, 600); | |||||
// 创建网格背景 | |||||
createGridBackground(); | |||||
// 创建视图 | |||||
m_view = new QGraphicsView(m_scene, this); | |||||
m_view->setRenderHint(QPainter::Antialiasing); | |||||
m_view->setDragMode(QGraphicsView::RubberBandDrag); | |||||
// 布局(让视图占满文档区域) | |||||
auto layout = new QVBoxLayout(this); | |||||
layout->setContentsMargins(0, 0, 0, 0); | |||||
layout->addWidget(m_view); | |||||
setLayout(layout); | |||||
} | |||||
PLCDocument::~PLCDocument() | |||||
{ | |||||
// 场景和视图由Qt自动销毁 | |||||
} | |||||
QString PLCDocument::title() const { | |||||
return "PLC文档"; | |||||
} | |||||
void PLCDocument::createGridBackground() | |||||
{ | |||||
// 创建网格图案 | |||||
createGridPattern(); | |||||
// 设置场景背景 | |||||
QBrush gridBrush(m_gridPattern); | |||||
m_scene->setBackgroundBrush(gridBrush); | |||||
} | |||||
void PLCDocument::createGridPattern() | |||||
{ | |||||
// 网格大小 | |||||
const int size = m_gridSize; | |||||
// 创建网格图案 | |||||
m_gridPattern = QPixmap(size * 2, size * 2); | |||||
m_gridPattern.fill(Qt::white); | |||||
QPainter painter(&m_gridPattern); | |||||
painter.setPen(QPen(QColor(220, 220, 220), 1)); | |||||
// 绘制网格线 | |||||
painter.drawLine(0, size, size * 2, size); // 水平线 | |||||
painter.drawLine(size, 0, size, size * 2); // 垂直线 | |||||
// 绘制网格交点 | |||||
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); | |||||
} | |||||
bool PLCDocument::saveToFile(const QString &filePath) | |||||
{ | |||||
} | |||||
bool PLCDocument::loadFromFile(const QString &filePath) | |||||
{ | |||||
} |
@@ -0,0 +1,32 @@ | |||||
#ifndef PLCDOCUMENT_H | |||||
#define PLCDOCUMENT_H | |||||
#include "basedocument.h" | |||||
#include <QGraphicsScene> | |||||
#include <QGraphicsView> | |||||
class PLCDocument : public BaseDocument | |||||
{ | |||||
Q_OBJECT | |||||
public: | |||||
explicit PLCDocument(QWidget *parent = nullptr); | |||||
~PLCDocument() override; | |||||
QString title() const override; | |||||
QGraphicsView *view() const { return m_view; } | |||||
QGraphicsScene *scene() const { return m_scene; } | |||||
bool saveToFile(const QString &filePath) override; | |||||
bool loadFromFile(const QString &filePath) override; | |||||
private: | |||||
void createGridBackground(); | |||||
void createGridPattern(); | |||||
QGraphicsScene *m_scene; | |||||
QGraphicsView *m_view; | |||||
QPixmap m_gridPattern; // 网格图案缓存 | |||||
int m_gridSize = 20; // 网格大小(像素) | |||||
}; | |||||
#endif // PLCDOCUMENT_H |
@@ -16,15 +16,19 @@ DEFINES += QT_DEPRECATED_WARNINGS | |||||
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 | ||||
SOURCES += \ | SOURCES += \ | ||||
document.cpp \ | |||||
basedocument.cpp \ | |||||
graphicsitems.cpp \ | graphicsitems.cpp \ | ||||
hmidocument.cpp \ | |||||
main.cpp \ | main.cpp \ | ||||
mainwindow.cpp | |||||
mainwindow.cpp \ | |||||
plcdocument.cpp | |||||
HEADERS += \ | HEADERS += \ | ||||
document.h \ | |||||
basedocument.h \ | |||||
graphicsitems.h \ | graphicsitems.h \ | ||||
mainwindow.h | |||||
hmidocument.h \ | |||||
mainwindow.h \ | |||||
plcdocument.h | |||||
FORMS += \ | FORMS += \ | ||||
mainwindow.ui | mainwindow.ui | ||||