瀏覽代碼

抽象界面类 & 文件管理模块实现

master
kai__wei 21 小時之前
父節點
當前提交
a80821a563
共有 4 個文件被更改,包括 563 次插入0 次删除
  1. +124
    -0
      ComprehensivePlatformProgrammer/editorwidget.cpp
  2. +114
    -0
      ComprehensivePlatformProgrammer/editorwidget.h
  3. +288
    -0
      ComprehensivePlatformProgrammer/filemanager.cpp
  4. +37
    -0
      ComprehensivePlatformProgrammer/filemanager.h

+ 124
- 0
ComprehensivePlatformProgrammer/editorwidget.cpp 查看文件

@@ -0,0 +1,124 @@
#include "editorwidget.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QCoreApplication>
#include <QDateTime>
#include <QKeyEvent>
#include <QContextMenuEvent>

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);
}

+ 114
- 0
ComprehensivePlatformProgrammer/editorwidget.h 查看文件

@@ -0,0 +1,114 @@
#pragma once

// Qt 核心模块
#include <QWidget>
#include <QListWidget>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QLabel>
#include <QPushButton>
#include <QTextEdit>
#include <QList>

/**
* @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<QGraphicsScene*> 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; ///< 首次显示标志(用于延迟初始化)
};

+ 288
- 0
ComprehensivePlatformProgrammer/filemanager.cpp 查看文件

@@ -0,0 +1,288 @@
#include "filemanager.h"
#include <QFile>
#include <QJsonDocument>
#include <QJsonArray>
#include <QMessageBox>
#include <QDebug>
#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<HmiEditorWidget*>(editor);
PlcEditorWidget* plcWidget = dynamic_cast<PlcEditorWidget*>(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<HmiEditorWidget*>(editor);
PlcEditorWidget* plcWidget = dynamic_cast<PlcEditorWidget*>(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<QGraphicsScene*> 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<QGraphicsScene*>& 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<HmiControlItem*>(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<QGraphicsScene*> 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;
}



+ 37
- 0
ComprehensivePlatformProgrammer/filemanager.h 查看文件

@@ -0,0 +1,37 @@
#pragma once
#include <QObject>
#include <QJsonObject>
#include <QString>
#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<QGraphicsScene*>& 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);
};

Loading…
取消
儲存