From 309457dfc6f4f97a317002f1d9e8e0e63c5a1e03 Mon Sep 17 00:00:00 2001 From: email <15737449156@163.com> Date: Fri, 8 Aug 2025 19:05:24 +0800 Subject: [PATCH] =?UTF-8?q?HMI=E7=95=8C=E9=9D=A2=E7=BC=96=E8=BE=91?= =?UTF-8?q?=E5=AE=8C=E6=88=90=EF=BC=8C=E6=96=B0=E5=BB=BAPLC=E8=BF=9B?= =?UTF-8?q?=E8=A1=8C=E7=BB=98=E5=9B=BE=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IntegratedPlatform/hmiwidget.cpp | 22 +- untitled/basedocument.cpp | 24 ++ untitled/basedocument.h | 32 ++ untitled/document.cpp | 349 ++++++++++++++--- untitled/document.h | 92 ----- untitled/graphicsitems.cpp | 165 ++++++-- untitled/graphicsitems.h | 83 ++-- untitled/hmidocument.cpp | 651 +++++++++++++++++++++++++++++++ untitled/hmidocument.h | 74 ++++ untitled/images/大于号.png | Bin 0 -> 4564 bytes untitled/images/大于等于.png | Bin 5931 -> 3500 bytes untitled/images/小于号.png | Bin 0 -> 4608 bytes untitled/images/小于等于.png | Bin 0 -> 3529 bytes untitled/mainwindow.cpp | 427 +++++++++++++++++--- untitled/mainwindow.h | 14 +- untitled/plcdocument.cpp | 88 +++++ untitled/plcdocument.h | 32 ++ untitled/untitled.pro | 12 +- 18 files changed, 1814 insertions(+), 251 deletions(-) create mode 100644 untitled/basedocument.cpp create mode 100644 untitled/basedocument.h delete mode 100644 untitled/document.h create mode 100644 untitled/hmidocument.cpp create mode 100644 untitled/hmidocument.h create mode 100644 untitled/images/大于号.png create mode 100644 untitled/images/小于号.png create mode 100644 untitled/images/小于等于.png create mode 100644 untitled/plcdocument.cpp create mode 100644 untitled/plcdocument.h diff --git a/IntegratedPlatform/hmiwidget.cpp b/IntegratedPlatform/hmiwidget.cpp index 8d0c559..10fa273 100644 --- a/IntegratedPlatform/hmiwidget.cpp +++ b/IntegratedPlatform/hmiwidget.cpp @@ -11,13 +11,15 @@ ResizableShape::ResizableShape(qreal x, qreal y, qreal w, qreal h) this->setFlag(QGraphicsItem::ItemIsSelectable, true); this->setAcceptHoverEvents(true); } - template void ResizableShape::hoverMoveEvent(QGraphicsSceneHoverEvent *event) { - if (isInResizeArea(event->pos())) { + if (isInResizeArea(event->pos())) + { this->setCursor(Qt::SizeFDiagCursor); - } else { + } + else + { this->setCursor(Qt::SizeAllCursor); } BaseShape::hoverMoveEvent(event); @@ -34,10 +36,13 @@ bool ResizableShape::isInResizeArea(const QPointF &pos) const template void ResizableShape::mousePressEvent(QGraphicsSceneMouseEvent *event) { - if (isInResizeArea(event->pos())) { + if (isInResizeArea(event->pos())) + { resizing = true; this->setCursor(Qt::SizeFDiagCursor); - } else { + } + else + { resizing = false; this->setCursor(Qt::SizeAllCursor); BaseShape::mousePressEvent(event); @@ -47,11 +52,14 @@ void ResizableShape::mousePressEvent(QGraphicsSceneMouseEvent *event) template void ResizableShape::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { - if (resizing) { + if (resizing) + { QRectF newRect = this->rect(); newRect.setBottomRight(event->pos()); this->setRect(newRect); - } else { + } + else + { BaseShape::mouseMoveEvent(event); } } diff --git a/untitled/basedocument.cpp b/untitled/basedocument.cpp new file mode 100644 index 0000000..0bd2745 --- /dev/null +++ b/untitled/basedocument.cpp @@ -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; +} diff --git a/untitled/basedocument.h b/untitled/basedocument.h new file mode 100644 index 0000000..d7f9e78 --- /dev/null +++ b/untitled/basedocument.h @@ -0,0 +1,32 @@ +#ifndef BASEDOCUMENT_H +#define BASEDOCUMENT_H + +#include +#include + +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 diff --git a/untitled/document.cpp b/untitled/document.cpp index 3bbb386..8af041e 100644 --- a/untitled/document.cpp +++ b/untitled/document.cpp @@ -16,7 +16,7 @@ #include #include #include - +#include HMIDocument::HMIDocument(QWidget *parent) : BaseDocument(HMI, parent), m_title("未命名HMI"), @@ -28,6 +28,24 @@ HMIDocument::HMIDocument(QWidget *parent) m_scene = new QGraphicsScene(this); m_scene->setSceneRect(0, 0, 800, 600); 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); @@ -53,16 +71,14 @@ 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(event); - + setModified(true); if (mouseEvent->button() == Qt::RightButton) { - // 右键菜单 - showContextMenu(mouseEvent->globalPos()); + showContextMenu(mouseEvent->globalPos());// 右键菜单 return true; } if (mouseEvent->button() == Qt::LeftButton) @@ -72,42 +88,43 @@ bool HMIDocument::eventFilter(QObject *obj, QEvent *event) // 如果点击空白区域且有绘制标志,创建图形 if (!item) { - if (m_canDrawEllipse) { - createEllipse(scenePos); + if (m_canDrawEllipse) + { + createShape("ellipse", scenePos); resetDrawFlags(); - } else if (m_canDrawRectangle) { - createRectangle(scenePos); + } + else if (m_canDrawRectangle) + { + createShape("rectangle", scenePos); resetDrawFlags(); } - m_scene->clearSelection(); // 清除选择 - return true; } } } // 拖拽事件(从工具栏拖拽绘制) - if (event->type() == QEvent::DragEnter) { + if (event->type() == QEvent::DragEnter) + { QDragEnterEvent *dragEvent = static_cast(event); dragEvent->acceptProposedAction(); return true; } - if (event->type() == QEvent::DragMove) { + if (event->type() == QEvent::DragMove) + { static_cast(event)->acceptProposedAction(); return true; } - if (event->type() == QEvent::Drop) { + if (event->type() == QEvent::Drop) + { QDropEvent *dropEvent = static_cast(event); - const QMimeData *mimeData = dropEvent->mimeData(); // 显式获取mimeData + const QMimeData *mimeData = dropEvent->mimeData(); QPointF scenePos = m_view->mapToScene(dropEvent->pos()); // 使用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(); return true; @@ -116,26 +133,24 @@ bool HMIDocument::eventFilter(QObject *obj, QEvent *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() { @@ -152,12 +167,15 @@ void HMIDocument::showContextMenu(QPoint globalPos) QMenu menu(this); - if (!clickedItem) { + if (!clickedItem) + { // 空白区域:仅显示粘贴 QAction *pasteAction = menu.addAction("粘贴"); pasteAction->setEnabled(!m_copiedItemsData.isEmpty()); connect(pasteAction, &QAction::triggered, this, &HMIDocument::pasteItems); - } else { + } + else + { // 选中图形:显示完整菜单 QAction *propAction = menu.addAction("属性"); QAction *copyAction = menu.addAction("复制"); @@ -200,10 +218,12 @@ void HMIDocument::copySelectedItems() QList selectedItems = m_scene->selectedItems(); if (selectedItems.isEmpty()) return; - foreach (QGraphicsItem *item, selectedItems) { + foreach (QGraphicsItem *item, selectedItems) + { m_copiedItemsData.append(serializeItem(item)); } m_lastPastePos = selectedItems.first()->pos(); + setModified(true); } // 粘贴项 @@ -215,14 +235,17 @@ void HMIDocument::pasteItems() m_lastPastePos += offset; m_scene->clearSelection(); - foreach (const QByteArray &itemData, m_copiedItemsData) { + foreach (const QByteArray &itemData, m_copiedItemsData) + { QGraphicsItem *newItem = deserializeItem(itemData); - if (newItem) { + if (newItem) + { m_scene->addItem(newItem); newItem->setPos(m_lastPastePos); newItem->setSelected(true); } } + setModified(true); } // 删除选中项 @@ -233,6 +256,7 @@ void HMIDocument::deleteSelectedItems() m_scene->removeItem(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); form->addRow(btnBox); connect(btnBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); @@ -299,6 +323,7 @@ void HMIDocument::showItemProperties() // 应用属性 if (dialog.exec() == QDialog::Accepted) { + setModified(true); // 使用NamedItem接口设置名称 namedItem->setName(nameEdit->text()); @@ -325,27 +350,27 @@ QByteArray HMIDocument::serializeItem(QGraphicsItem *item) stream << item->flags(); stream << item->transform(); stream << item->isVisible(); - // 形状信息 - if (auto shape = dynamic_cast(item)) { + if (auto shape = dynamic_cast(item)) + { stream << shape->pen(); stream << shape->brush(); stream << shape->boundingRect(); } - // 具体图形信息 - if (auto rect = dynamic_cast(item)) { + if (auto rect = dynamic_cast(item)) + { stream << rect->rect(); stream << rect->pressedColor(); stream << rect->releasedColor(); stream << rect->name(); - } else if (auto ellipse = dynamic_cast(item)) { + } else if (auto ellipse = dynamic_cast(item)) + { stream << ellipse->rect(); stream << ellipse->onColor(); stream << ellipse->offColor(); stream << ellipse->name(); } - return data; } @@ -406,3 +431,223 @@ QGraphicsItem *HMIDocument::deserializeItem(const QByteArray &data) 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(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(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; +} diff --git a/untitled/document.h b/untitled/document.h deleted file mode 100644 index 8c0a012..0000000 --- a/untitled/document.h +++ /dev/null @@ -1,92 +0,0 @@ -#ifndef DOCUMENT_H -#define DOCUMENT_H - -#include -#include -#include -#include -#include -#include - -// 前向声明 -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 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 diff --git a/untitled/graphicsitems.cpp b/untitled/graphicsitems.cpp index 28f7e29..c6afd3c 100644 --- a/untitled/graphicsitems.cpp +++ b/untitled/graphicsitems.cpp @@ -1,26 +1,147 @@ -#include "graphicsitems.h" +#ifndef GRAPHICSITEMS_H +#define GRAPHICSITEMS_H + +#include +#include +#include +#include #include +#include +#include +#include +#include + +// 命名项接口(用于属性设置) +class NamedItem +{ +public: + virtual QString name() const = 0; + virtual void setName(const QString &name) = 0; + virtual ~NamedItem() = default; +}; + +// 可调整大小的图形基类(模板) +template +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(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 { - 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(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 { - m_name = "指示灯"; - m_onColor = Qt::green; - m_offColor = Qt::red; - this->setBrush(m_onColor); -} - -// 显式实例化模板类 -template class ResizableShape; -template class ResizableShape; +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 diff --git a/untitled/graphicsitems.h b/untitled/graphicsitems.h index 6e5fd61..f892c2e 100644 --- a/untitled/graphicsitems.h +++ b/untitled/graphicsitems.h @@ -27,24 +27,35 @@ class ResizableShape : public BaseShape, public NamedItem protected: bool m_resizing; 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: 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::ItemIsSelectable, true); this->setAcceptHoverEvents(true); + this->setCursor(Qt::SizeAllCursor); // 默认光标为移动光标 } // 事件处理(调整大小逻辑) void hoverMoveEvent(QGraphicsSceneHoverEvent *event) override { if (isInResizeArea(event->pos())) { - m_currentCursor = Qt::SizeFDiagCursor; + this->setCursor(Qt::SizeFDiagCursor); // 右下角显示缩放光标 } else { - m_currentCursor = Qt::SizeAllCursor; + this->setCursor(Qt::SizeAllCursor); // 其他区域显示移动光标 } BaseShape::hoverMoveEvent(event); } @@ -53,10 +64,12 @@ public: { if (isInResizeArea(event->pos())) { m_resizing = true; - m_currentCursor = Qt::SizeFDiagCursor; - } else { + m_initialPos = event->pos(); // 存储初始位置 + this->setCursor(Qt::SizeFDiagCursor); + } + else + { m_resizing = false; - m_currentCursor = Qt::SizeAllCursor; BaseShape::mousePressEvent(event); } } @@ -64,9 +77,22 @@ public: void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override { if (m_resizing) { + // 计算尺寸变化量 + QPointF delta = event->pos() - m_initialPos; 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->update(); + + m_initialPos = event->pos(); // 更新初始位置 } else { BaseShape::mouseMoveEvent(event); } @@ -74,8 +100,16 @@ public: 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); } @@ -87,24 +121,19 @@ public: // 命名项接口实现 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(20, 20), r.bottomRight()); - return resizeArea.contains(pos); - } }; // 可调整大小的矩形(按钮) class ResizableRectangle : public ResizableShape { public: - ResizableRectangle(qreal x, qreal y, qreal w, qreal h); + ResizableRectangle(qreal x, qreal y, qreal w, qreal h) + : ResizableShape(x, y, w, h) + { + m_pressedColor = Qt::darkYellow; + m_releasedColor = Qt::yellow; + this->setBrush(m_releasedColor); + } QColor pressedColor() const { return m_pressedColor; } void setPressedColor(const QColor &color) { m_pressedColor = color; } @@ -124,7 +153,13 @@ private: class ResizableEllipse : public ResizableShape { public: - ResizableEllipse(qreal x, qreal y, qreal w, qreal h); + ResizableEllipse(qreal x, qreal y, qreal w, qreal h) + : ResizableShape(x, y, w, h) + { + m_onColor = Qt::green; + m_offColor = Qt::red; + this->setBrush(m_offColor); + } QColor onColor() const { return m_onColor; } void setOnColor(const QColor &color) { diff --git a/untitled/hmidocument.cpp b/untitled/hmidocument.cpp new file mode 100644 index 0000000..fd8fd84 --- /dev/null +++ b/untitled/hmidocument.cpp @@ -0,0 +1,651 @@ +#include "hmidocument.h" +#include "graphicsitems.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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(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(event); + dragEvent->acceptProposedAction(); + return true; + } + if (event->type() == QEvent::DragMove) + { + static_cast(event)->acceptProposedAction(); + return true; + } + if (event->type() == QEvent::Drop) + { + QDropEvent *dropEvent = static_cast(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(clickedItem)) { + ellipse->setBrush(ellipse->onColor()); + } else if (auto rect = dynamic_cast(clickedItem)) { + rect->setBrush(rect->pressedColor()); + } + }); + connect(offAction, &QAction::triggered, [=]() { + if (auto ellipse = dynamic_cast(clickedItem)) { + ellipse->setBrush(ellipse->offColor()); + } else if (auto rect = dynamic_cast(clickedItem)) { + rect->setBrush(rect->releasedColor()); + } + }); + } + + menu.exec(globalPos + QPoint(10, 10)); +} + +// 复制选中项 +void HMIDocument::copySelectedItems() +{ + m_copiedItemsData.clear(); + QList 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 selectedItems = m_scene->selectedItems(); + foreach (QGraphicsItem *item, selectedItems) { + m_scene->removeItem(item); + delete item; + } + setModified(true); +} + +// 显示属性对话框 +void HMIDocument::showItemProperties() +{ + QList selectedItems = m_scene->selectedItems(); + if (selectedItems.isEmpty()) return; + QGraphicsItem *item = selectedItems.first(); + + // 将QGraphicsItem转换为NamedItem + NamedItem *namedItem = dynamic_cast(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(item)) { + tempColor1 = ellipse->onColor(); + tempColor2 = ellipse->offColor(); + form->addRow("ON颜色:", colorBtn1); + form->addRow("OFF颜色:", colorBtn2); + } else if (auto rect = dynamic_cast(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(item)) { + ellipse->setOnColor(tempColor1); + ellipse->setOffColor(tempColor2); + } else if (auto rect = dynamic_cast(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(item)) + { + stream << shape->pen(); + stream << shape->brush(); + stream << shape->boundingRect(); + } + // 具体图形信息 + if (auto rect = dynamic_cast(item)) + { + stream << rect->rect(); + stream << rect->pressedColor(); + stream << rect->releasedColor(); + stream << rect->name(); + } else if (auto ellipse = dynamic_cast(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(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(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); } diff --git a/untitled/hmidocument.h b/untitled/hmidocument.h new file mode 100644 index 0000000..edb698b --- /dev/null +++ b/untitled/hmidocument.h @@ -0,0 +1,74 @@ +#ifndef HMIDOCUMENT_H +#define HMIDOCUMENT_H + +#include "basedocument.h" +#include +#include +#include + +// 前向声明 +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 m_copiedItemsData; + QPointF m_lastPastePos; +}; + +#endif // HMIDOCUMENT_H diff --git a/untitled/images/大于号.png b/untitled/images/大于号.png new file mode 100644 index 0000000000000000000000000000000000000000..fbc0ea7a1af020218ecce12726a2ab39b99c0258 GIT binary patch literal 4564 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 literal 5931 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 diff --git a/untitled/images/小于号.png b/untitled/images/小于号.png new file mode 100644 index 0000000000000000000000000000000000000000..c980b92c0877289ad6651d0ab403439801b8f99b GIT binary patch literal 4608 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 literal 0 HcmV?d00001 diff --git a/untitled/images/小于等于.png b/untitled/images/小于等于.png new file mode 100644 index 0000000000000000000000000000000000000000..dfa037eefc4a1d30eecaa3f7f390f12cd53c4027 GIT binary patch literal 3529 zcmeH~S5VVm7RLibA&43RgertCRjMMz@C$)ZLKP`WuOdn>Qk5W>PyjI zbPys%qy(f`070s>1i_%8Y<6ZJ_i-Qh;hr;d=R5bD`OZCaKbK%*g*nbG!VLm}j+>dH z?O3_@-;3)gE7mwYFJUFNa61eN^n6fkg(XThLj(3V-PVh5WVrYW^b9p#iHgbT99-j8 zK&83<0Qk^Az#-ZLX?~MpQe-e!o zZ20{)Sm?ST2z&wy!hNT*v2#j-02&R%#)azvaW(LEXyQfBMF_JtGn7Ttr??EE=Yp0s zP#~O;vzAvA?`=fTw_)EqAh7z&tCR?75b&+xaX%LumzNI}zhVNyHG~=!@UlZGy=30e zOb|HqN2;nc7=h0*maY(G~a26VEA1EgEo@K^LR;1SFgIaQ4CtOLQnjb9k%4TQ96; zw6j>M<-RF=NSqC*0yTe-mc(N^RBkj{n{^bU?GKnGNGL^m4Q~PvV{mTul}j{(yH=5~ zK07JhC66D7UVYD=;S+$^ zzA*6ROYDjLV6|VV(>th$CW-hw%Ope|-5eD;b!=oLk?D+jX9v41E=?0GjCr9p(yZMb z*g-u3{^>xBkUKms)tFQ`82NqfiLvP$&K8dsYA>Un79tK z0C(=Sp;ALBM@`#5pS?V>8SdU>OHW#NDq%M6kgrF({N>ax#8`G0!4u8j%~bj;;WPlr zy|#KF#@X_5q6c~ZI^&FYY_8E~8=)(V!i()a6%ld_u)6#AP@k!+IW-r%0-UmboBY^h zJ>!AY!UR0*I#%@C0GiRyF0R}*%rwVN&`P`|@ritq@}}WuZ@F>whw(O{vfo8NS@X%w zOraj!jko@-eiFB=FrI=qb*le?oCU^B8m&Z9sLtMen@egtBP*<&Rm*Ii=}UYHKn;e9 z;N`Eq2S}byO)G}Tp_(4GQuCNBg-I8o^#qR#7s+cWh}bXM3Z{w=n$sAq!J$PLs%FgL zF-}fL)64d)nZ+*q0Z{Z%gC97cZ_x@i7tBjN{`cmz5~eV~!n2%y7`(i5XZk5d3yNC? z_a6?XSq~oKuP$Q}dE0|vk$kT#y8St(XwRIBJY!2{m7714bK4=;G$YuH{)2xw;r;Bi`%m?82U#$6 zQz;6_3R*oM3#diD!v7;xA-!F&@j47gNbP(S+C>Q&Qmwtmr^AC=E`U8itXyaK7hcVS zx>mNkwCAC=5CC@8*;D|Df01}AmuAyD`c)3I znRvbtAL5%2L7%}7&lJYo@T`w+_NDc@iGs<3J`x2}GWC`s^K32e97Rv^S33$9AU0|6 zTzAdUvn>ZN{z9=)8a)3R4~KyEp^DzIfhG4 z&mVS#WP}x$C#$@tMz))tHF0rmg_$-WHa#RH7i-@0o%nn^`@yGGBXv@vB3veKpvGhN z7=GoYjuc_o;o8*(f3KVC;h_(+HE-}%IuV`q7iu{S%0sVd=X^6q13Z(7N)(25#_t3D z)K{V9yzGOxj|=yLhE!xBA6_bFC4JQ)Tvw_{TMbwmecp?csFVCB16{0#7;BlcdsPwy_eW)zhs*AdMn|ao zeQM=n*zHRRGTC`3Bv0e4-o3KG(j6V`-?qCBeEZzweO8QIcl`XYb^UZjssOgW==9&s zK`%1$4lJlhX!RCF9Yy}ant)TnGjcg9GYHC-1{Qc+tUDRw+UtL@K?n9CH%3S29F0XJOO|X&M%JU;7 zz7&^(r*mc7)YeNEWIL1jyj!)ynMZ)_=Gquw(WLm#)Y8Xlvt<_pc4QvP9#Iizx7Xc4 zqqWBEVQU8YxyVbG9dRw;%(3x(g4*{7_<2PG?$W$H8?%au_Onrp(gdp=?PIuQv+*Yg z=2T)|$*L1>3^hXTnX9&oGVkEv>qhslKWQ&#dNc4T27G@fXY^GVYWL%%sg9e-EHfd zosh8C(9il35qA&Z&?Fnh@I⁡Wq z7PDMyXZq&ceV_g6Gdsv1!HROmmXCgxE@tWuHxC6Gyk0VT8CPXt>Hwuea@@0fHN|L# zp5>eJIi;B#OO}r|j*u))Pn9s)Av1>P(&BTCA2?eS(5vz$%vi->V~h(J3(+|CXZ>rf zs#KG@ISmyd4<_X9?hR=u^eVsET6YX!Ob2q17f|;}$iZam+lVo_wOefg!0+b$pS|wg zl6mgcF7pz<+6d6{%RbfT1aU3tH^QR^dN@ipq-K5F>WrW>=xXFioY-x;de65o$|v4T zSFGlteSB{ACBy%`=lm^KG@@NZ!kqFKex+@3F^z~Hs`iq}Q5=q!t`Dg(wsTfyu}U3MtC2Xe^-VhAlu$6PdN3o10qP~}YlLZ1( zLs5CQEF4~*lxT>Igc%=Kp2?w #include #include @@ -10,19 +12,24 @@ #include #include #include +#include +#include +#include +#include +#include MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { setWindowTitle("综合平台编程器"); - setGeometry(100, 100, 1000, 700); + setGeometry(500, 200, 1000, 700); // 初始化标签页 m_tabWidget = new QTabWidget(this); m_tabWidget->setTabsClosable(true); setCentralWidget(m_tabWidget); connect(m_tabWidget, &QTabWidget::currentChanged, this, &MainWindow::onTabChanged); - + connect(m_tabWidget, &QTabWidget::tabCloseRequested, this, &MainWindow::onCloseTab); // 创建菜单和工具栏 createMenus(); createToolbars(); @@ -34,29 +41,45 @@ MainWindow::~MainWindow() } // 创建菜单栏 -// 在createMenus()函数中添加以下代码,用于创建操作菜单及其功能 - void MainWindow::createMenus() { - // 创建文件菜单(保持原有代码) + // 创建文件菜单 QMenu *fileMenu = menuBar()->addMenu("文件"); QMenu *editMenu = menuBar()->addMenu("操作"); - QMenu *simulationMenu = menuBar()->addMenu("仿真"); - // 新建HMI动作(保持原有代码) + // 新建HMI动作 QAction *newHmiAction = new QAction("新建HMI(&H)", this); newHmiAction->setShortcut(tr("Ctrl+H")); - newHmiAction->setStatusTip("创建新的HMI文档"); connect(newHmiAction, &QAction::triggered, this, &MainWindow::onNewHMI); fileMenu->addAction(newHmiAction); - // 新建PLC动作(保持原有代码) + // 新建PLC动作 QAction *newPlcAction = new QAction("新建PLC(&P)", this); newPlcAction->setShortcut(tr("Ctrl+P")); - newPlcAction->setStatusTip("创建新的PLC文档"); connect(newPlcAction, &QAction::triggered, this, &MainWindow::onNewPLC); 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); copyAction->setShortcut(QKeySequence::Copy); // 标准复制快捷键 Ctrl+C @@ -92,87 +115,278 @@ void MainWindow::createMenus() editMenu->addAction(deleteAction); } - // 创建左侧工具栏 void MainWindow::createToolbars() { - m_leftToolBar = new QToolBar("绘图工具", this); + m_leftToolBar = new QToolBar("绘图工具栏", this); addToolBar(Qt::LeftToolBarArea, m_leftToolBar); m_leftToolBar->setAllowedAreas(Qt::LeftToolBarArea); // 仅允许在左侧 + m_leftToolBar->setFixedWidth(200); } // 更新工具栏(根据当前文档类型) void MainWindow::updateToolBar(BaseDocument *doc) { - // 清空现有工具 - m_leftToolBar->clear(); - + m_leftToolBar->clear();// 清空现有工具 if (!doc) return; - - // HMI文档显示绘图工具 - if (doc->type() == BaseDocument::HMI) + if (doc->type() == BaseDocument::HMI)// HMI文档显示绘图工具 { HMIDocument *hmiDoc = dynamic_cast(doc); if (!hmiDoc) return; - // 画指示灯按钮(支持拖拽) QToolButton *ellipseBtn = new QToolButton; 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", "指示灯"); 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; - 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->setProperty("toolType", "按钮"); 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) { - // 检查是否是工具栏按钮的鼠标按下事件 QToolButton *toolBtn = qobject_cast(obj); if (toolBtn && event->type() == QEvent::MouseButtonPress) { QMouseEvent *me = static_cast(event); if (me->button() == Qt::LeftButton) { - // 获取工具类型 QString toolType = toolBtn->property("toolType").toString(); if (!toolType.isEmpty()) { - // 创建拖拽 + BaseDocument* currentDoc = dynamic_cast(m_tabWidget->currentWidget()); + if (currentDoc && currentDoc->type() == BaseDocument::HMI) { + HMIDocument* hmiDoc = static_cast(currentDoc); + + // 设置对应的绘制模式 + if (toolType == "指示灯") + { + hmiDoc->startDrawingEllipse(); + + } + else if (toolType == "按钮") + { + hmiDoc->startDrawingRectangle(); + } + } + + // 创建拖拽对象 QMimeData *mime = new QMimeData; mime->setText(toolType); - QDrag *drag = new QDrag(obj); + + QDrag *drag = new QDrag(toolBtn); drag->setMimeData(mime); drag->exec(Qt::CopyAction); return true; @@ -187,7 +401,7 @@ void MainWindow::onNewHMI() { m_hmiCount++; 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->setCurrentWidget(doc); updateToolBar(doc); // 更新工具栏为HMI工具 @@ -206,10 +420,125 @@ void MainWindow::onNewPLC() // 标签页切换时更新工具栏 void MainWindow::onTabChanged(int idx) { - if (idx < 0) { + if (idx < 0) + { updateToolBar(nullptr); return; } BaseDocument *doc = dynamic_cast(m_tabWidget->widget(idx)); updateToolBar(doc); } + +// 保存文档 +void MainWindow::onSave() +{ + BaseDocument *doc = dynamic_cast(m_tabWidget->currentWidget()); + if (!doc) return; + + if (doc->filePath().isEmpty()) { + saveDocumentAs(doc); + } else { + saveDocument(doc); + } +} + +// 另存为文档 +void MainWindow::onSaveAs() +{ + BaseDocument *doc = dynamic_cast(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(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, "保存文档", "无法保存文档"); + } +} diff --git a/untitled/mainwindow.h b/untitled/mainwindow.h index 2ecc0d3..8120c61 100644 --- a/untitled/mainwindow.h +++ b/untitled/mainwindow.h @@ -4,7 +4,8 @@ #include #include #include -#include "document.h" +#include +#include "basedocument.h" QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } @@ -25,16 +26,27 @@ private slots: void onNewHMI(); // 新建HMI文档 void onNewPLC(); // 新建PLC文档 void onTabChanged(int idx); // 标签页切换时更新工具栏 + void onSave(); // 保存文档 + void onSaveAs(); // 另存为文档 + void onOpen(); // 打开文档 + void onCloseTab(int index); // 关闭标签页 private: void createMenus(); // 创建菜单栏 void createToolbars(); // 创建工具栏(左侧) void updateToolBar(BaseDocument *doc); // 根据文档类型更新工具栏 + void saveDocument(BaseDocument *doc); // 保存文档 + void saveDocumentAs(BaseDocument *doc); // 另存为文档 QTabWidget *m_tabWidget; // 多文档标签页 QToolBar *m_leftToolBar; // 左侧工具栏 int m_hmiCount = 0; // HMI文档计数器 int m_plcCount = 0; // PLC文档计数器 + + // 菜单动作 + QAction *m_saveAction; + QAction *m_saveAsAction; + QAction *m_openAction; }; #endif // MAINWINDOW_H diff --git a/untitled/plcdocument.cpp b/untitled/plcdocument.cpp new file mode 100644 index 0000000..d6362f3 --- /dev/null +++ b/untitled/plcdocument.cpp @@ -0,0 +1,88 @@ +#include "plcdocument.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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) +{ + +} diff --git a/untitled/plcdocument.h b/untitled/plcdocument.h new file mode 100644 index 0000000..2388e78 --- /dev/null +++ b/untitled/plcdocument.h @@ -0,0 +1,32 @@ +#ifndef PLCDOCUMENT_H +#define PLCDOCUMENT_H + +#include "basedocument.h" +#include +#include + +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 diff --git a/untitled/untitled.pro b/untitled/untitled.pro index af0ada3..bfde661 100644 --- a/untitled/untitled.pro +++ b/untitled/untitled.pro @@ -16,15 +16,19 @@ DEFINES += QT_DEPRECATED_WARNINGS #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 SOURCES += \ - document.cpp \ + basedocument.cpp \ graphicsitems.cpp \ + hmidocument.cpp \ main.cpp \ - mainwindow.cpp + mainwindow.cpp \ + plcdocument.cpp HEADERS += \ - document.h \ + basedocument.h \ graphicsitems.h \ - mainwindow.h + hmidocument.h \ + mainwindow.h \ + plcdocument.h FORMS += \ mainwindow.ui