From a80821a5634789e708cd61b0032ed67dec7e60e8 Mon Sep 17 00:00:00 2001 From: kai__wei <2553229853@qq.com> Date: Wed, 13 Aug 2025 20:17:03 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8A=BD=E8=B1=A1=E7=95=8C=E9=9D=A2=E7=B1=BB?= =?UTF-8?q?=20&=20=E6=96=87=E4=BB=B6=E7=AE=A1=E7=90=86=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../editorwidget.cpp | 124 ++++++++ .../editorwidget.h | 114 +++++++ .../filemanager.cpp | 288 ++++++++++++++++++ ComprehensivePlatformProgrammer/filemanager.h | 37 +++ 4 files changed, 563 insertions(+) create mode 100644 ComprehensivePlatformProgrammer/editorwidget.cpp create mode 100644 ComprehensivePlatformProgrammer/editorwidget.h create mode 100644 ComprehensivePlatformProgrammer/filemanager.cpp create mode 100644 ComprehensivePlatformProgrammer/filemanager.h diff --git a/ComprehensivePlatformProgrammer/editorwidget.cpp b/ComprehensivePlatformProgrammer/editorwidget.cpp new file mode 100644 index 0000000..bf8c815 --- /dev/null +++ b/ComprehensivePlatformProgrammer/editorwidget.cpp @@ -0,0 +1,124 @@ +#include "editorwidget.h" +#include +#include +#include +#include +#include +#include + +EditorWidget::EditorWidget(QWidget* parent) : QWidget(parent) { + // 主垂直布局(包含上部编辑区和下部输出框) + QVBoxLayout* mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(10, 10, 10, 10);//布局管理器内边距 + mainLayout->setSpacing(10);//布局内容之间边距 + + + // 上部容器(包含工具栏和编辑区) + QWidget* topContainer = new QWidget(); + QHBoxLayout* topLayout = new QHBoxLayout(topContainer); + topLayout->setContentsMargins(10, 10, 10, 10); + topLayout->setSpacing(10); + + // 左侧工具栏布局 + QVBoxLayout* leftLayout = new QVBoxLayout(); + leftLayout->setSpacing(5); + + // 初始化工具栏 + toolbar = new QListWidget(this); + toolbar->setViewMode(QListWidget::IconMode); + toolbar->setIconSize(QSize(48, 48)); + toolbar->setFixedWidth(100); + toolbar->setSpacing(10); + toolbar->setDragEnabled(false); + leftLayout->addWidget(toolbar); + + // 初始化编辑区域 + editArea = new QGraphicsView(this); + editArea->setRenderHint(QPainter::Antialiasing);//抗锯齿 + editArea->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);//全视口更新 + editArea->setOptimizationFlags(QGraphicsView::DontSavePainterState | QGraphicsView::DontAdjustForAntialiasing); + editArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);//按需显示滚动条 + editArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + //editArea->setDragMode(QGraphicsView::ScrollHandDrag); + // 添加到上部布局 + topLayout->addLayout(leftLayout); + topLayout->addWidget(editArea, 1); + + // 初始化输出框 + outputTextEdit = new QTextEdit(this); + outputTextEdit->setReadOnly(true); + outputTextEdit->setFixedHeight(120); + outputTextEdit->setStyleSheet( + "QTextEdit {" + " background-color: #f0f0f0;" + " border: 1px solid #cccccc;" + " padding: 5px;" + " font-family: Consolas, 'Courier New', monospace;" + " font-size: 10pt;" + "}" + ); + + // 添加到主布局 + mainLayout->addWidget(topContainer, 1); + mainLayout->addWidget(outputTextEdit); + + // 安装事件过滤器 + editArea->installEventFilter(this); + + // 连接工具栏点击信号 + connect(toolbar, &QListWidget::itemClicked, this, &EditorWidget::onToolbarItemClicked); + + appendOutput("编辑器初始化完成"); +} + +EditorWidget::~EditorWidget() {} + +void EditorWidget::appendOutput(const QString& message) { + QString timestamp = QDateTime::currentDateTime().toString("[hh:mm:ss] "); + outputTextEdit->append(timestamp + message); + QTextCursor cursor = outputTextEdit->textCursor(); + cursor.movePosition(QTextCursor::End); + outputTextEdit->setTextCursor(cursor); +} + +//界面初始化 +void EditorWidget::ensureViewReady() { + //强制处理 + QCoreApplication::processEvents(); + //重置 + editArea->resetTransform(); + //视图更新 + editArea->viewport()->update(); + //如果为空 + if (editArea->viewport()->rect().isEmpty()) { + editArea->setFixedSize(600, 400); + QCoreApplication::processEvents(); + } +} + +void EditorWidget::showEvent(QShowEvent* event) { + QWidget::showEvent(event); + //首次显示 + if (firstShow) { + firstShow = false; + ensureViewReady(); + } +} + +void EditorWidget::resizeEvent(QResizeEvent* event) { + QWidget::resizeEvent(event); + ensureViewReady(); +} + +bool EditorWidget::eventFilter(QObject* obj, QEvent* event) { + if (obj == editArea && event->type() == QEvent::ContextMenu) { + // 可由派生类扩展上下文菜单逻辑 + return true; + } + return QWidget::eventFilter(obj, event); +} + +void EditorWidget::keyPressEvent(QKeyEvent* event) { + // 可由派生类扩展键盘事件逻辑 + QWidget::keyPressEvent(event); +} diff --git a/ComprehensivePlatformProgrammer/editorwidget.h b/ComprehensivePlatformProgrammer/editorwidget.h new file mode 100644 index 0000000..445ee45 --- /dev/null +++ b/ComprehensivePlatformProgrammer/editorwidget.h @@ -0,0 +1,114 @@ +#pragma once + +// Qt 核心模块 +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @class EditorWidget + * @brief 图形编辑器基础抽象类 + * + * 提供编辑器基础框架,包含工具栏、绘图区和输出控制台。 + * 作为抽象基类,需要派生类实现特定的编辑功能。 + */ +class EditorWidget : public QWidget { + Q_OBJECT // 启用Qt元对象系统(信号槽、属性等) + +public: + explicit EditorWidget(QWidget* parent = nullptr); + + /** + * @brief 虚析构函数 + */ + virtual ~EditorWidget(); + + // ========== 纯虚接口 ========== + /** + * @brief 获取当前场景 + * @return 当前活动的QGraphicsScene指针 + */ + virtual QGraphicsScene* getCurrentScene() const = 0; + + /** + * @brief 获取所有页面场景 + * @return 场景列表(HMI多页面返回多个,PLC单页面返回单个场景的列表) + */ + virtual QList getPages() const = 0; + + /** + * @brief 获取当前页面索引 + * @return 当前页索引(HMI使用,PLC等单页面编辑器可固定返回0) + */ + virtual int getCurrentPageIndex() const = 0; + + /** + * @brief 清空所有页面/场景内容 + */ + virtual void clearPages() = 0; + +protected: + // ========== 需要派生类实现的接口 ========== + /** + * @brief 初始化工具栏内容 + */ + virtual void initToolbar() = 0; + + /** + * @brief 工具栏项点击处理 + * @param item 被点击的QListWidgetItem对象 + */ + virtual void onToolbarItemClicked(QListWidgetItem* item) = 0; + + // ========== 公共工具方法 ========== + /** + * @brief 向输出控制台追加消息 + * @param message 要显示的消息内容 + */ + void appendOutput(const QString& message); + + /** + * @brief 确保视图准备就绪 + * 首次显示时执行必要的初始化操作 + */ + void ensureViewReady(); + + // ========== 事件重写 ========== + /** + * @brief 键盘按下事件处理 + * @param event 键盘事件对象 + */ + virtual void keyPressEvent(QKeyEvent* event) override; + + /** + * @brief 事件过滤器 + * @param obj 事件目标对象 + * @param event 事件对象 + * @return 是否已处理该事件 + */ + virtual bool eventFilter(QObject* obj, QEvent* event) override; + + /** + * @brief 窗口大小调整事件 + * @param event 大小调整事件对象 + */ + virtual void resizeEvent(QResizeEvent* event) override; + + /** + * @brief 窗口显示事件 + * @param event 显示事件对象 + */ + virtual void showEvent(QShowEvent* event) override; + +protected: + // ========== 成员变量 ========== + QListWidget* toolbar; ///< 左侧工具栏(图标模式) + QGraphicsView* editArea; ///< 中央绘图视图区域 + QTextEdit* outputTextEdit; ///< 底部输出信息控制台 + bool firstShow = true; ///< 首次显示标志(用于延迟初始化) +}; diff --git a/ComprehensivePlatformProgrammer/filemanager.cpp b/ComprehensivePlatformProgrammer/filemanager.cpp new file mode 100644 index 0000000..701ceed --- /dev/null +++ b/ComprehensivePlatformProgrammer/filemanager.cpp @@ -0,0 +1,288 @@ +#include "filemanager.h" +#include +#include +#include +#include +#include +#include "modules/HMI/hmicontrolitem.h" +#include "modules/HMI/hmibutton.h" +#include "modules/HMI/hmiled.h" + +FileManager::FileManager(QObject* parent) : QObject(parent) {} + +bool FileManager::saveFile(EditorWidget* editor, const QString& filePath) { + if (!editor || filePath.isEmpty()) { + qDebug() << "保存文件失败:无效的编辑器部件或文件路径"; + return false; + } + + HmiEditorWidget* hmiWidget = dynamic_cast(editor); + PlcEditorWidget* plcWidget = dynamic_cast(editor); + + if (hmiWidget) { + return saveHmiFile(hmiWidget, filePath); + } else if (plcWidget) { + return savePlcFile(plcWidget, filePath); + } + + qDebug() << "保存文件失败:无法识别的编辑器类型"; + return false; +} + +bool FileManager::loadFile(EditorWidget* editor, const QString& filePath) { + if (!editor || filePath.isEmpty()) { + qDebug() << "加载文件失败:无效的编辑器部件或文件路径"; + return false; + } + + HmiEditorWidget* hmiWidget = dynamic_cast(editor); + PlcEditorWidget* plcWidget = dynamic_cast(editor); + + if (hmiWidget) { + return loadHmiFile(hmiWidget, filePath); + } else if (plcWidget) { + return loadPlcFile(plcWidget, filePath); + } + + qDebug() << "加载文件失败:无法识别的编辑器类型"; + return false; +} + +bool FileManager::saveHmiFile(HmiEditorWidget* widget, const QString& filePath) { + if (!widget || filePath.isEmpty()) { + qDebug() << "保存HMI文件失败:无效的编辑器部件或文件路径"; + return false; + } + + QList pages = widget->getPages(); + if (pages.isEmpty()) { + qDebug() << "保存HMI文件失败:没有页面数据"; + return false; + } + + QJsonObject jsonObj = serializeHmiPages(pages); + jsonObj["currentPageIndex"] = widget->getCurrentPageIndex(); + jsonObj["type"] = "HMI"; // 新增:标记文件类型 + + QJsonDocument doc(jsonObj); + QFile file(filePath); + if (!file.open(QIODevice::WriteOnly)) { + qDebug() << "保存HMI文件失败:无法打开文件" << filePath; + return false; + } + + file.write(doc.toJson(QJsonDocument::Indented)); + file.close(); + qDebug() << "HMI文件保存成功:" << filePath; + return true; +} + +bool FileManager::loadHmiFile(HmiEditorWidget* widget, const QString& filePath) { + qDebug() << "尝试加载 HMI 文件:" << filePath; + + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly)) { + qDebug() << "加载HMI文件失败:无法打开文件" << filePath; + return false; + } + + QByteArray fileData = file.readAll(); + file.close(); + + QJsonDocument doc = QJsonDocument::fromJson(fileData); + if (doc.isNull() || !doc.isObject()) { + qDebug() << "加载HMI文件失败:无效的JSON格式"; + return false; + } + + QJsonObject jsonObj = doc.object(); + if (jsonObj.value("type").toString() != "HMI") { + qDebug() << "加载HMI文件失败:文件类型不匹配"; + return false; + } + + qDebug() << "JSON 数据解析成功,准备反序列化页面..."; + return deserializeHmiPages(widget, jsonObj); +} + +bool FileManager::savePlcFile(PlcEditorWidget* widget, const QString& filePath) { + if (!widget || filePath.isEmpty()) { + qDebug() << "保存PLC文件失败:无效的编辑器部件或文件路径"; + return false; + } + + QGraphicsScene* scene = widget->getCurrentScene(); + if (!scene) { + qDebug() << "保存PLC文件失败:没有场景数据"; + return false; + } + + QJsonObject jsonObj = serializePlcScene(scene); + jsonObj["type"] = "PLC"; // 新增:标记文件类型 + + QJsonDocument doc(jsonObj); + QFile file(filePath); + if (!file.open(QIODevice::WriteOnly)) { + qDebug() << "保存PLC文件失败:无法打开文件" << filePath; + return false; + } + + file.write(doc.toJson(QJsonDocument::Indented)); + file.close(); + qDebug() << "PLC文件保存成功:" << filePath; + return true; +} + +bool FileManager::loadPlcFile(PlcEditorWidget* widget, const QString& filePath) { + if (!widget || filePath.isEmpty()) { + qDebug() << "加载PLC文件失败:无效的编辑器部件或文件路径"; + return false; + } + + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly)) { + qDebug() << "加载PLC文件失败:无法打开文件" << filePath; + return false; + } + + QJsonDocument doc = QJsonDocument::fromJson(file.readAll()); + file.close(); + + if (doc.isNull() || !doc.isObject()) { + qDebug() << "加载PLC文件失败:无效的JSON格式"; + return false; + } + + QJsonObject jsonObj = doc.object(); + if (jsonObj.value("type").toString() != "PLC") { + qDebug() << "加载PLC文件失败:文件类型不匹配"; + return false; + } + + return deserializePlcScene(widget, jsonObj); +} + +QJsonObject FileManager::serializeHmiPages(const QList& pages) { + QJsonObject jsonObj; + QJsonArray pagesArray; + + for (int i = 0; i < pages.size(); ++i) { + QJsonObject pageObj; + QJsonArray itemsArray; + QGraphicsScene* scene = pages[i]; + + // 保存场景尺寸 + pageObj["sceneWidth"] = scene->width(); + pageObj["sceneHeight"] = scene->height(); + + for (QGraphicsItem* item : scene->items()) { + HmiControlItem* controlItem = dynamic_cast(item); + if (controlItem) { + QVariantMap itemData = controlItem->saveToClipboard(); + QJsonObject itemObj; + for (auto it = itemData.constBegin(); it != itemData.constEnd(); ++it) { + itemObj.insert(it.key(), QJsonValue::fromVariant(it.value())); + } + itemsArray.append(itemObj); + } + } + + pageObj["items"] = itemsArray; + pagesArray.append(pageObj); + } + + jsonObj["pages"] = pagesArray; + jsonObj["version"] = "1.1"; + return jsonObj; +} + +bool FileManager::deserializeHmiPages(HmiEditorWidget* widget, const QJsonObject& jsonObj) { + if (!widget) { + qDebug() << "反序列化失败:widget 为空"; + return false; + } + + if (!jsonObj.contains("pages") || !jsonObj["pages"].isArray()) { + qDebug() << "反序列化HMI页面失败:缺少页面数据"; + return false; + } + + QJsonArray pagesArray = jsonObj["pages"].toArray(); + if (pagesArray.isEmpty()) { + qDebug() << "反序列化HMI页面失败:pagesArray 为空"; + return false; + } + + widget->clearPages(); + qDebug() << "已清空现有页面,准备加载新页面..."; + + widget->clearPages(); + + // 获取保存的当前页面索引 + int savedPageIndex = jsonObj.value("currentPageIndex").toInt(0); + + pagesArray = jsonObj["pages"].toArray(); + for (const QJsonValue& pageVal : pagesArray) { + if (!pageVal.isObject()) continue; + QJsonObject pageObj = pageVal.toObject(); + + widget->createNewPage(); + int currentPageIndex = widget->getCurrentPageIndex(); + if (currentPageIndex < 0) continue; + + QList pages = widget->getPages(); + QGraphicsScene* scene = pages[currentPageIndex]; + + // 恢复场景尺寸 + if (pageObj.contains("sceneWidth") && pageObj.contains("sceneHeight")) { + scene->setSceneRect(0, 0, + pageObj["sceneWidth"].toDouble(), + pageObj["sceneHeight"].toDouble()); + } + + if (pageObj.contains("items") && pageObj["items"].isArray()) { + QJsonArray itemsArray = pageObj["items"].toArray(); + for (const QJsonValue& itemVal : itemsArray) { + if (!itemVal.isObject()) continue; + QJsonObject itemObj = itemVal.toObject(); + QVariantMap itemData; + for (auto it = itemObj.constBegin(); it != itemObj.constEnd(); ++it) { + itemData.insert(it.key(), it.value().toVariant()); + } + + QPointF pos(itemData.value("posX", 0.0).toDouble(), + itemData.value("posY", 0.0).toDouble()); + HmiControlItem* newItem = HmiControlItem::createFromClipboard(itemData, pos); + if (newItem) { + scene->addItem(newItem); + } + } + } + } + + // 恢复保存时的当前页面 + if (!widget->getPages().isEmpty()) { + savedPageIndex = qBound(0, savedPageIndex, widget->getPages().size() - 1); + widget->switchToPage(savedPageIndex); + QMetaObject::invokeMethod(widget, [widget]() { + widget->refreshView(); + }, Qt::QueuedConnection); + } + + return true; +} +QJsonObject FileManager::serializePlcScene(QGraphicsScene* /*scene*/) +{ + // 现在先不做 PLC 场景的序列化,返回一个空对象即可 + QJsonObject obj; + obj["type"] = "plc_scene_stub"; + return obj; +} + +bool FileManager::deserializePlcScene(PlcEditorWidget* /*widget*/, const QJsonObject& /*jsonObj*/) +{ + // 暂不实现反序列化,返回 false 表示“未处理” + return false; +} + + diff --git a/ComprehensivePlatformProgrammer/filemanager.h b/ComprehensivePlatformProgrammer/filemanager.h new file mode 100644 index 0000000..acf1cb2 --- /dev/null +++ b/ComprehensivePlatformProgrammer/filemanager.h @@ -0,0 +1,37 @@ +#pragma once +#include +#include +#include +#include "editorwidget.h" +#include "modules/HMI/hmieditorwidget.h" +#include "modules/PLC/plceditorwidget.h" + +class FileManager : public QObject { + Q_OBJECT +public: + explicit FileManager(QObject* parent = nullptr); + + // 通用接口:保存文件 + bool saveFile(EditorWidget* editor, const QString& filePath); + // 通用接口:加载文件 + bool loadFile(EditorWidget* editor, const QString& filePath); + +private: + // HMI 特定保存/加载方法 + bool saveHmiFile(HmiEditorWidget* widget, const QString& filePath); + bool loadHmiFile(HmiEditorWidget* widget, const QString& filePath); + + // PLC 特定保存/加载方法 + bool savePlcFile(PlcEditorWidget* widget, const QString& filePath); + bool loadPlcFile(PlcEditorWidget* widget, const QString& filePath); + + // 将 HMI 页面序列化为 JSON 对象 + QJsonObject serializeHmiPages(const QList& pages); + // 从 JSON 对象反序列化 HMI 页面 + bool deserializeHmiPages(HmiEditorWidget* widget, const QJsonObject& jsonObj); + + // 将 PLC 场景序列化为 JSON 对象 + QJsonObject serializePlcScene(QGraphicsScene* scene); + // 从 JSON 对象反序列化 PLC 场景 + bool deserializePlcScene(PlcEditorWidget* widget, const QJsonObject& jsonObj); +};