2026-05-13 16:24:21 +08:00
|
|
|
|
#include "document.h"
|
|
|
|
|
|
#include "designerScene.h"
|
|
|
|
|
|
#include "graphicsItem/graphicsBaseItem.h"
|
|
|
|
|
|
#include "graphicsItem/graphicsItemGroup.h"
|
|
|
|
|
|
#include "graphicsItem/graphicsRectItem.h"
|
|
|
|
|
|
#include "graphicsItem/graphicsBusSectionItem.h"
|
|
|
|
|
|
|
|
|
|
|
|
class GraphicsPolygonItem; // 前向声明,暂不使用多边形
|
|
|
|
|
|
|
|
|
|
|
|
#include <QJsonObject>
|
|
|
|
|
|
#include <QJsonArray>
|
|
|
|
|
|
#include <QJsonDocument>
|
|
|
|
|
|
#include <QFile>
|
|
|
|
|
|
#include <QDataStream>
|
|
|
|
|
|
#include <QSet>
|
|
|
|
|
|
#include <QJsonArray>
|
|
|
|
|
|
#include <QJsonDocument>
|
|
|
|
|
|
#include <QFile>
|
|
|
|
|
|
#include <QDataStream>
|
|
|
|
|
|
|
|
|
|
|
|
// 文档格式版本号
|
|
|
|
|
|
static const QString DOCUMENT_VERSION = "1.0";
|
|
|
|
|
|
|
|
|
|
|
|
Document::Document(QObject *parent)
|
|
|
|
|
|
: QObject(parent)
|
|
|
|
|
|
, m_filename()
|
|
|
|
|
|
, m_modified(false)
|
|
|
|
|
|
, m_version(DOCUMENT_VERSION)
|
|
|
|
|
|
, m_created(QDateTime::currentDateTime())
|
|
|
|
|
|
, m_modifiedTime(QDateTime::currentDateTime())
|
|
|
|
|
|
, m_lastSavedTime()
|
|
|
|
|
|
, m_pScene(nullptr)
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Document::~Document()
|
|
|
|
|
|
{
|
|
|
|
|
|
// Document 不拥有 scene 的所有权,不需要删除
|
|
|
|
|
|
m_pScene = nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// =================================================================
|
|
|
|
|
|
// 场景关联
|
|
|
|
|
|
// =================================================================
|
|
|
|
|
|
|
|
|
|
|
|
void Document::setScene(DesignerScene* scene)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pScene = scene;
|
|
|
|
|
|
|
|
|
|
|
|
// 如果关联了新的 scene,连接 selectionChanged 信号来追踪修改
|
|
|
|
|
|
if (m_pScene) {
|
|
|
|
|
|
connect(m_pScene, &QGraphicsScene::selectionChanged,
|
|
|
|
|
|
this, [this]() {
|
|
|
|
|
|
// 选择改变本身不代表文档修改,但可以作为用户活动的信号
|
|
|
|
|
|
// 实际的修改由具体的操作(添加、删除、移动等)触发
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DesignerScene* Document::scene() const
|
|
|
|
|
|
{
|
|
|
|
|
|
return m_pScene;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// =================================================================
|
|
|
|
|
|
// 文件操作
|
|
|
|
|
|
// =================================================================
|
|
|
|
|
|
|
|
|
|
|
|
bool Document::saveToFile(const QString& filename)
|
|
|
|
|
|
{
|
|
|
|
|
|
QString targetFile = filename.isEmpty() ? m_filename : filename;
|
|
|
|
|
|
|
|
|
|
|
|
if (targetFile.isEmpty()) {
|
2026-05-18 17:55:56 +08:00
|
|
|
|
// 没有目标文件名,无法保存
|
|
|
|
|
|
emit saveStatusChanged(false, tr("未指定文件路径,请使用另存为功能"));
|
2026-05-13 16:24:21 +08:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool success = saveInternal(targetFile);
|
|
|
|
|
|
|
|
|
|
|
|
if (success) {
|
|
|
|
|
|
m_lastSavedTime = QDateTime::currentDateTime();
|
|
|
|
|
|
m_modified = false;
|
2026-05-18 17:55:56 +08:00
|
|
|
|
|
|
|
|
|
|
// 如果传入的文件名与当前文件名不同,更新 m_filename
|
|
|
|
|
|
if (!filename.isEmpty() && filename != m_filename) {
|
|
|
|
|
|
m_filename = filename;
|
|
|
|
|
|
emit filenameChanged(m_filename);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-13 16:24:21 +08:00
|
|
|
|
emit modifiedChanged(false);
|
|
|
|
|
|
emit saveStatusChanged(true, tr("保存成功"));
|
|
|
|
|
|
} else {
|
|
|
|
|
|
emit saveStatusChanged(false, tr("保存失败"));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return success;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-18 17:55:56 +08:00
|
|
|
|
bool Document::saveAsToFile(const QString& filename)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (filename.isEmpty()) {
|
|
|
|
|
|
emit saveStatusChanged(false, tr("未指定文件路径"));
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool success = saveInternal(filename);
|
|
|
|
|
|
|
|
|
|
|
|
if (success) {
|
|
|
|
|
|
QString oldFilename = m_filename;
|
|
|
|
|
|
m_filename = filename;
|
|
|
|
|
|
m_lastSavedTime = QDateTime::currentDateTime();
|
|
|
|
|
|
m_modified = false;
|
|
|
|
|
|
|
|
|
|
|
|
// 另存为总是会更新文件名
|
|
|
|
|
|
emit filenameChanged(m_filename);
|
|
|
|
|
|
emit modifiedChanged(false);
|
|
|
|
|
|
emit saveStatusChanged(true, tr("另存为成功"));
|
|
|
|
|
|
} else {
|
|
|
|
|
|
emit saveStatusChanged(false, tr("另存为失败"));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return success;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-13 16:24:21 +08:00
|
|
|
|
bool Document::loadFromFile(const QString& filename)
|
|
|
|
|
|
{
|
|
|
|
|
|
bool success = loadInternal(filename);
|
|
|
|
|
|
|
|
|
|
|
|
if (success) {
|
|
|
|
|
|
m_filename = filename;
|
|
|
|
|
|
m_modified = false;
|
|
|
|
|
|
m_lastSavedTime = QDateTime::currentDateTime();
|
|
|
|
|
|
emit filenameChanged(m_filename);
|
|
|
|
|
|
emit modifiedChanged(false);
|
|
|
|
|
|
emit saveStatusChanged(true, tr("加载成功"));
|
|
|
|
|
|
} else {
|
|
|
|
|
|
emit saveStatusChanged(false, tr("加载失败"));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return success;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// =================================================================
|
|
|
|
|
|
// 序列化核心实现
|
|
|
|
|
|
// =================================================================
|
|
|
|
|
|
|
|
|
|
|
|
QByteArray Document::serialize() const
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!m_pScene) {
|
|
|
|
|
|
return QByteArray();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QJsonObject root;
|
|
|
|
|
|
root.insert("version", m_version);
|
|
|
|
|
|
root.insert("created", m_created.toString(Qt::ISODate));
|
|
|
|
|
|
root.insert("modified", m_modifiedTime.toString(Qt::ISODate));
|
|
|
|
|
|
root.insert("filename", m_filename);
|
|
|
|
|
|
|
|
|
|
|
|
// 序列化元数据
|
|
|
|
|
|
if (!m_metaData.isEmpty()) {
|
|
|
|
|
|
QJsonObject metaObj;
|
|
|
|
|
|
for (auto it = m_metaData.begin(); it != m_metaData.end(); ++it) {
|
|
|
|
|
|
// QVariant 需要转换为 QJsonValue
|
|
|
|
|
|
metaObj.insert(it.key(), toJsonValue(it.value()));
|
|
|
|
|
|
}
|
|
|
|
|
|
root.insert("metaData", metaObj);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 序列化图元
|
|
|
|
|
|
QJsonArray itemsArray;
|
|
|
|
|
|
QList<QGraphicsItem*> items = m_pScene->items();
|
|
|
|
|
|
|
|
|
|
|
|
for (QGraphicsItem* item : items) {
|
|
|
|
|
|
// 跳过 handle 等辅助元素
|
|
|
|
|
|
// QGraphicsItem 不是 QObject,所以不能用 qobject_cast
|
|
|
|
|
|
// GraphicsBaseItem 继承 QObject 和 AbstractShape (QGraphicsItem),可以用 qgraphicsitem_cast
|
|
|
|
|
|
GraphicsBaseItem* baseItem = qgraphicsitem_cast<GraphicsBaseItem*>(item);
|
|
|
|
|
|
if (!baseItem) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-18 17:55:56 +08:00
|
|
|
|
// 双重检查:通过 isValidItem() 确保不是辅助元素(如 ItemControlHandle)
|
|
|
|
|
|
// ItemControlHandle 是 QGraphicsRectItem 子类,不是 GraphicsBaseItem 子类
|
|
|
|
|
|
// 即使 qgraphicsitem_cast 错误返回非 null,isValidItem() 也能过滤掉(m_type 为 T_undefined)
|
|
|
|
|
|
if (!baseItem->isValidItem()) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-13 16:24:21 +08:00
|
|
|
|
QJsonObject itemObj = serializeItem(baseItem);
|
|
|
|
|
|
itemsArray.append(itemObj);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
root.insert("items", itemsArray);
|
|
|
|
|
|
|
|
|
|
|
|
QJsonDocument doc(root);
|
|
|
|
|
|
return doc.toJson(QJsonDocument::Indented);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Document::deserialize(const QByteArray& data)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (data.isEmpty() || !m_pScene) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QJsonParseError jsonError;
|
|
|
|
|
|
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
|
|
|
|
|
|
if (jsonError.error != QJsonParseError::NoError) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QJsonObject root = doc.object();
|
|
|
|
|
|
|
|
|
|
|
|
// 解析版本信息
|
|
|
|
|
|
QString version = root["version"].toString();
|
|
|
|
|
|
if (version.isEmpty()) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 解析时间信息
|
|
|
|
|
|
if (root.contains("created")) {
|
|
|
|
|
|
m_created = QDateTime::fromString(root["created"].toString(), Qt::ISODate);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (root.contains("modified")) {
|
|
|
|
|
|
m_modifiedTime = QDateTime::fromString(root["modified"].toString(), Qt::ISODate);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 解析元数据
|
|
|
|
|
|
if (root.contains("metaData")) {
|
|
|
|
|
|
QJsonObject metaObj = root["metaData"].toObject();
|
|
|
|
|
|
for (auto it = metaObj.begin(); it != metaObj.end(); ++it) {
|
|
|
|
|
|
// QJsonValue 需要转换为 QVariant
|
|
|
|
|
|
m_metaData.insert(it.key(), fromJsonValue(it.value()));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 清空当前场景
|
|
|
|
|
|
if (m_pScene) {
|
|
|
|
|
|
m_pScene->clear();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 反序列化图元
|
|
|
|
|
|
QJsonArray itemsArray = root["items"].toArray();
|
|
|
|
|
|
for (auto itemIt : itemsArray) {
|
|
|
|
|
|
QJsonObject itemObj = itemIt.toObject();
|
|
|
|
|
|
deserializeItem(itemObj, m_pScene, nullptr);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// =================================================================
|
|
|
|
|
|
// 内部实现
|
|
|
|
|
|
// =================================================================
|
|
|
|
|
|
|
|
|
|
|
|
bool Document::saveInternal(const QString& filename)
|
|
|
|
|
|
{
|
|
|
|
|
|
QByteArray data = serialize();
|
|
|
|
|
|
if (data.isEmpty()) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QFile file(filename);
|
|
|
|
|
|
if (!file.open(QIODevice::WriteOnly)) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
file.write(data);
|
|
|
|
|
|
file.close();
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Document::loadInternal(const QString& filename)
|
|
|
|
|
|
{
|
|
|
|
|
|
QFile file(filename);
|
|
|
|
|
|
if (!file.open(QIODevice::ReadOnly)) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QByteArray data = file.readAll();
|
|
|
|
|
|
file.close();
|
|
|
|
|
|
|
|
|
|
|
|
return deserialize(data);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// =================================================================
|
|
|
|
|
|
// 图元序列化/反序列化
|
|
|
|
|
|
// =================================================================
|
|
|
|
|
|
|
|
|
|
|
|
QJsonObject Document::serializeItem(GraphicsBaseItem* item) const
|
|
|
|
|
|
{
|
|
|
|
|
|
QJsonObject obj;
|
|
|
|
|
|
|
|
|
|
|
|
// 基本属性
|
|
|
|
|
|
obj.insert("type", getTypeString(item));
|
|
|
|
|
|
obj.insert("id", QString::number(reinterpret_cast<quintptr>(item)));
|
|
|
|
|
|
|
|
|
|
|
|
// 位置和变换
|
|
|
|
|
|
QPointF pos = item->pos();
|
|
|
|
|
|
QJsonObject posObj;
|
|
|
|
|
|
posObj.insert("x", pos.x());
|
|
|
|
|
|
posObj.insert("y", pos.y());
|
|
|
|
|
|
obj.insert("pos", posObj);
|
|
|
|
|
|
|
|
|
|
|
|
obj.insert("rotation", item->rotation());
|
|
|
|
|
|
|
|
|
|
|
|
QJsonObject scaleObj;
|
|
|
|
|
|
// Qt 的 scale() 返回 qreal,不是 QPointF
|
|
|
|
|
|
scaleObj.insert("x", item->scale());
|
|
|
|
|
|
scaleObj.insert("y", item->scale());
|
|
|
|
|
|
obj.insert("scale", scaleObj);
|
|
|
|
|
|
|
2026-05-18 17:55:56 +08:00
|
|
|
|
// 样式属性(只用于非组项,GraphicsItemGroup 没有 pen/brush)
|
|
|
|
|
|
GraphicsItemGroup* group = qgraphicsitem_cast<GraphicsItemGroup*>(item);
|
|
|
|
|
|
if (!group) {
|
|
|
|
|
|
// 普通图元才有 pen/brush
|
|
|
|
|
|
QJsonObject penObj;
|
|
|
|
|
|
QColor penColor = item->penColor();
|
|
|
|
|
|
if (penColor.isValid()) {
|
|
|
|
|
|
penObj.insert("color", penColor.name());
|
|
|
|
|
|
} else {
|
|
|
|
|
|
penObj.insert("color", QString());
|
|
|
|
|
|
}
|
|
|
|
|
|
penObj.insert("width", item->pen().widthF());
|
|
|
|
|
|
penObj.insert("style", static_cast<int>(item->pen().style()));
|
|
|
|
|
|
obj.insert("pen", penObj);
|
2026-05-13 16:24:21 +08:00
|
|
|
|
|
2026-05-18 17:55:56 +08:00
|
|
|
|
QJsonObject brushObj;
|
|
|
|
|
|
brushObj.insert("color", item->brushColor().name());
|
|
|
|
|
|
obj.insert("brush", brushObj);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 组没有 pen/brush,插入空对象
|
|
|
|
|
|
obj.insert("pen", QJsonObject());
|
|
|
|
|
|
obj.insert("brush", QJsonObject());
|
|
|
|
|
|
}
|
2026-05-13 16:24:21 +08:00
|
|
|
|
|
|
|
|
|
|
// 尺寸
|
|
|
|
|
|
obj.insert("width", item->width());
|
|
|
|
|
|
obj.insert("height", item->height());
|
|
|
|
|
|
|
|
|
|
|
|
// 类型特定属性
|
|
|
|
|
|
obj.insert("properties", serializeItemProperties(item));
|
|
|
|
|
|
|
|
|
|
|
|
// 如果是组,递归序列化子项
|
|
|
|
|
|
if (group) {
|
|
|
|
|
|
QJsonArray childrenArray;
|
|
|
|
|
|
QList<QGraphicsItem*> children = group->childItems();
|
|
|
|
|
|
for (QGraphicsItem* child : children) {
|
2026-05-18 17:55:56 +08:00
|
|
|
|
// 使用 qgraphicsitem_cast 安全转换,只序列化 GraphicsBaseItem 子类的项
|
|
|
|
|
|
GraphicsBaseItem* childBase = qgraphicsitem_cast<GraphicsBaseItem*>(child);
|
2026-05-13 16:24:21 +08:00
|
|
|
|
if (childBase) {
|
|
|
|
|
|
childrenArray.append(serializeItem(childBase));
|
|
|
|
|
|
}
|
2026-05-18 17:55:56 +08:00
|
|
|
|
// 跳过 ItemControlHandle 等非 GraphicsBaseItem 的子项
|
2026-05-13 16:24:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
obj.insert("children", childrenArray);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return obj;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Document::deserializeItem(const QJsonObject& obj, QGraphicsScene* scene, QGraphicsItem* parent)
|
|
|
|
|
|
{
|
|
|
|
|
|
QString typeStr = obj["type"].toString();
|
|
|
|
|
|
|
|
|
|
|
|
GraphicsBaseItem* item = nullptr;
|
2026-05-18 17:55:56 +08:00
|
|
|
|
GraphicsItemGroup* group = nullptr;
|
2026-05-13 16:24:21 +08:00
|
|
|
|
|
2026-05-18 17:55:56 +08:00
|
|
|
|
// 根据类型创建图元(属性在工厂函数中已设置)
|
2026-05-13 16:24:21 +08:00
|
|
|
|
if (typeStr == "GraphicsRectItem") {
|
|
|
|
|
|
item = deserializeRectItem(obj);
|
|
|
|
|
|
} else if (typeStr == "GraphicsBusSectionItem") {
|
|
|
|
|
|
item = deserializeBusSectionItem(obj);
|
|
|
|
|
|
} else if (typeStr == "GraphicsItemGroup") {
|
2026-05-18 17:55:56 +08:00
|
|
|
|
group = deserializeItemGroup(obj, scene, parent);
|
|
|
|
|
|
// 先添加到 scene 或父组,再递归处理子项
|
|
|
|
|
|
if (parent) {
|
|
|
|
|
|
GraphicsItemGroup* parentGroup = qgraphicsitem_cast<GraphicsItemGroup*>(parent);
|
|
|
|
|
|
if (parentGroup) {
|
|
|
|
|
|
parentGroup->addToGroup(group);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
scene->addItem(group);
|
|
|
|
|
|
}
|
|
|
|
|
|
// 递归处理子项(此时 group 已在 scene 中)
|
2026-05-13 16:24:21 +08:00
|
|
|
|
QJsonArray childrenArray = obj["children"].toArray();
|
|
|
|
|
|
for (auto childIt : childrenArray) {
|
|
|
|
|
|
deserializeItem(childIt.toObject(), scene, group);
|
|
|
|
|
|
}
|
2026-05-18 17:55:56 +08:00
|
|
|
|
// 添加完子项后,更新 group 的 coordinate(会自动计算 boundingRect)
|
|
|
|
|
|
group->updateCoordinate();
|
|
|
|
|
|
// 更新 handle 位置
|
|
|
|
|
|
group->updateHandles();
|
2026-05-13 16:24:21 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-05-18 17:55:56 +08:00
|
|
|
|
// GraphicsPolygonItem 暂时跳过
|
2026-05-13 16:24:21 +08:00
|
|
|
|
|
|
|
|
|
|
if (!item) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 添加到场景或父项
|
|
|
|
|
|
if (parent) {
|
2026-05-18 17:55:56 +08:00
|
|
|
|
GraphicsItemGroup* parentGroup = qgraphicsitem_cast<GraphicsItemGroup*>(parent);
|
|
|
|
|
|
if (parentGroup) {
|
|
|
|
|
|
parentGroup->addToGroup(item);
|
2026-05-13 16:24:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
scene->addItem(item);
|
|
|
|
|
|
}
|
2026-05-18 17:55:56 +08:00
|
|
|
|
// 普通图元没有子项
|
2026-05-13 16:24:21 +08:00
|
|
|
|
|
2026-05-18 17:55:56 +08:00
|
|
|
|
// 添加后更新 handle 位置(如果有)
|
|
|
|
|
|
item->setHandleVisible(false); // 默认不显示 handle
|
2026-05-13 16:24:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// =================================================================
|
|
|
|
|
|
// QVariant <-> QJsonValue 转换辅助函数
|
|
|
|
|
|
// =================================================================
|
|
|
|
|
|
|
|
|
|
|
|
QJsonValue Document::toJsonValue(const QVariant& value) const
|
|
|
|
|
|
{
|
|
|
|
|
|
if (value.typeId() == qMetaTypeId<bool>()) {
|
|
|
|
|
|
return value.toBool();
|
|
|
|
|
|
} else if (value.typeId() == qMetaTypeId<int>()) {
|
|
|
|
|
|
return value.toInt();
|
|
|
|
|
|
} else if (value.typeId() == qMetaTypeId<double>()) {
|
|
|
|
|
|
return value.toDouble();
|
|
|
|
|
|
} else if (value.typeId() == QMetaType::Type::QString) {
|
|
|
|
|
|
return value.toString();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return value.toString();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QVariant Document::fromJsonValue(const QJsonValue& value) const
|
|
|
|
|
|
{
|
|
|
|
|
|
if (value.isDouble()) {
|
|
|
|
|
|
return value.toDouble();
|
|
|
|
|
|
} else if (value.isBool()) {
|
|
|
|
|
|
return value.toBool();
|
|
|
|
|
|
} else if (value.isArray()) {
|
|
|
|
|
|
QList<QVariant> list;
|
|
|
|
|
|
for (const QJsonValue& v : value.toArray()) {
|
|
|
|
|
|
list.append(fromJsonValue(v));
|
|
|
|
|
|
}
|
|
|
|
|
|
return list;
|
|
|
|
|
|
} else if (value.isObject()) {
|
|
|
|
|
|
QMap<QString, QVariant> map;
|
|
|
|
|
|
for (auto it = value.toObject().begin(); it != value.toObject().end(); ++it) {
|
|
|
|
|
|
map.insert(it.key(), fromJsonValue(it.value()));
|
|
|
|
|
|
}
|
|
|
|
|
|
return map;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return value.toString();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// =================================================================
|
|
|
|
|
|
// 辅助函数
|
|
|
|
|
|
// =================================================================
|
|
|
|
|
|
|
|
|
|
|
|
QString Document::getTypeString(GraphicsBaseItem* item) const
|
|
|
|
|
|
{
|
|
|
|
|
|
// 使用动态类型信息获取类型字符串(qobject_cast 要求类型有 Q_OBJECT 宏)
|
|
|
|
|
|
// GraphicsRectItem, GraphicsBusSectionItem, GraphicsItemGroup 都有 Q_OBJECT 宏
|
|
|
|
|
|
if (qobject_cast<GraphicsRectItem*>(item)) {
|
|
|
|
|
|
return "GraphicsRectItem";
|
|
|
|
|
|
} else if (qobject_cast<GraphicsBusSectionItem*>(item)) {
|
|
|
|
|
|
return "GraphicsBusSectionItem";
|
|
|
|
|
|
} else if (qobject_cast<GraphicsItemGroup*>(item)) {
|
|
|
|
|
|
return "GraphicsItemGroup";
|
|
|
|
|
|
}
|
|
|
|
|
|
// GraphicsPolygonItem 没有 Q_OBJECT 宏,暂时不支持序列化
|
|
|
|
|
|
return "GraphicsBaseItem";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QJsonObject Document::serializeItemProperties(GraphicsBaseItem* item) const
|
|
|
|
|
|
{
|
|
|
|
|
|
QJsonObject props;
|
|
|
|
|
|
|
|
|
|
|
|
// 类型特定的属性序列化
|
|
|
|
|
|
GraphicsRectItem* rectItem = qobject_cast<GraphicsRectItem*>(item);
|
|
|
|
|
|
if (rectItem) {
|
|
|
|
|
|
// 这里可以添加圆角矩形等特殊属性
|
|
|
|
|
|
// props.insert("isRound", rectItem->m_bIsRound);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 多边形等特殊属性可以在这里扩展
|
|
|
|
|
|
|
|
|
|
|
|
return props;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
GraphicsBaseItem* Document::deserializeRectItem(const QJsonObject& obj)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 根据保存的属性重建矩形
|
|
|
|
|
|
double width = obj["width"].toDouble();
|
|
|
|
|
|
double height = obj["height"].toDouble();
|
|
|
|
|
|
|
|
|
|
|
|
// 图元的原点在中心,因此 rect 的左上角为 (-width/2, -height/2)
|
2026-05-18 17:55:56 +08:00
|
|
|
|
// 在构造函数中已经设置了 boundingRect,所以不需要再调用 setWidth/setHeight
|
2026-05-13 16:24:21 +08:00
|
|
|
|
QRect rect(-width/2, -height/2, width, height);
|
2026-05-18 17:55:56 +08:00
|
|
|
|
GraphicsRectItem* item = new GraphicsRectItem(rect, false);
|
|
|
|
|
|
|
|
|
|
|
|
// 从 JSON 恢复属性
|
|
|
|
|
|
QJsonObject posObj = obj["pos"].toObject();
|
|
|
|
|
|
item->setPos(posObj["x"].toDouble(), posObj["y"].toDouble());
|
|
|
|
|
|
item->setRotation(obj["rotation"].toDouble());
|
|
|
|
|
|
|
|
|
|
|
|
// 使用 setScale 而不是 setTransform(避免与 item 的 transform 冲突)
|
|
|
|
|
|
QJsonObject scaleObj = obj["scale"].toObject();
|
|
|
|
|
|
double scaleX = scaleObj["x"].toDouble();
|
|
|
|
|
|
double scaleY = scaleObj["y"].toDouble();
|
|
|
|
|
|
if (scaleX != 1.0 || scaleY != 1.0) {
|
|
|
|
|
|
item->setScale(qMax(scaleX, scaleY)); // 使用统一缩放
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QJsonObject penObj = obj["pen"].toObject();
|
|
|
|
|
|
if (penObj.isEmpty()) {
|
|
|
|
|
|
// 如果 pen 对象为空(来自组的序列化),使用默认值
|
|
|
|
|
|
QPen pen(Qt::black);
|
|
|
|
|
|
pen.setWidthF(1.0);
|
|
|
|
|
|
pen.setStyle(Qt::SolidLine);
|
|
|
|
|
|
item->setPen(pen);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
QPen pen;
|
|
|
|
|
|
pen.setColor(QColor(penObj["color"].toString()));
|
|
|
|
|
|
pen.setWidthF(penObj["width"].toDouble());
|
|
|
|
|
|
pen.setStyle(static_cast<Qt::PenStyle>(penObj["style"].toInt()));
|
|
|
|
|
|
item->setPen(pen);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QJsonObject brushObj = obj["brush"].toObject();
|
|
|
|
|
|
if (brushObj.isEmpty()) {
|
|
|
|
|
|
item->setBrush(QBrush(Qt::NoBrush));
|
|
|
|
|
|
} else {
|
|
|
|
|
|
QBrush brush;
|
|
|
|
|
|
brush.setColor(QColor(brushObj["color"].toString()));
|
|
|
|
|
|
item->setBrush(brush);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return item;
|
2026-05-13 16:24:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
GraphicsBaseItem* Document::deserializeBusSectionItem(const QJsonObject& obj)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 总线段重建
|
|
|
|
|
|
double width = obj["width"].toDouble();
|
|
|
|
|
|
double height = obj["height"].toDouble();
|
2026-05-18 17:55:56 +08:00
|
|
|
|
QRect rect(-width/2, -height/2, width, height);
|
|
|
|
|
|
GraphicsBusSectionItem* item = new GraphicsBusSectionItem(rect);
|
|
|
|
|
|
|
|
|
|
|
|
// 从 JSON 恢复属性
|
|
|
|
|
|
QJsonObject posObj = obj["pos"].toObject();
|
|
|
|
|
|
item->setPos(posObj["x"].toDouble(), posObj["y"].toDouble());
|
|
|
|
|
|
item->setRotation(obj["rotation"].toDouble());
|
|
|
|
|
|
|
|
|
|
|
|
QJsonObject scaleObj = obj["scale"].toObject();
|
|
|
|
|
|
double scaleX = scaleObj["x"].toDouble();
|
|
|
|
|
|
double scaleY = scaleObj["y"].toDouble();
|
|
|
|
|
|
if (scaleX != 1.0 || scaleY != 1.0) {
|
|
|
|
|
|
item->setScale(qMax(scaleX, scaleY));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QJsonObject penObj = obj["pen"].toObject();
|
|
|
|
|
|
if (penObj.isEmpty()) {
|
|
|
|
|
|
QPen pen(Qt::black);
|
|
|
|
|
|
pen.setWidthF(1.0);
|
|
|
|
|
|
pen.setStyle(Qt::SolidLine);
|
|
|
|
|
|
item->setPen(pen);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
QPen pen;
|
|
|
|
|
|
pen.setColor(QColor(penObj["color"].toString()));
|
|
|
|
|
|
pen.setWidthF(penObj["width"].toDouble());
|
|
|
|
|
|
pen.setStyle(static_cast<Qt::PenStyle>(penObj["style"].toInt()));
|
|
|
|
|
|
item->setPen(pen);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QJsonObject brushObj = obj["brush"].toObject();
|
|
|
|
|
|
if (brushObj.isEmpty()) {
|
|
|
|
|
|
item->setBrush(QBrush(Qt::NoBrush));
|
|
|
|
|
|
} else {
|
|
|
|
|
|
QBrush brush;
|
|
|
|
|
|
brush.setColor(QColor(brushObj["color"].toString()));
|
|
|
|
|
|
item->setBrush(brush);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return item;
|
2026-05-13 16:24:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
GraphicsItemGroup* Document::deserializeItemGroup(const QJsonObject& obj,
|
|
|
|
|
|
QGraphicsScene* scene,
|
|
|
|
|
|
QGraphicsItem* parent)
|
|
|
|
|
|
{
|
|
|
|
|
|
GraphicsItemGroup* group = new GraphicsItemGroup(parent);
|
2026-05-18 17:55:56 +08:00
|
|
|
|
|
|
|
|
|
|
// 先设置位置、旋转等变换属性
|
|
|
|
|
|
QJsonObject posObj = obj["pos"].toObject();
|
|
|
|
|
|
group->setPos(posObj["x"].toDouble(), posObj["y"].toDouble());
|
|
|
|
|
|
group->setRotation(obj["rotation"].toDouble());
|
|
|
|
|
|
|
|
|
|
|
|
QJsonObject scaleObj = obj["scale"].toObject();
|
|
|
|
|
|
double scaleX = scaleObj["x"].toDouble();
|
|
|
|
|
|
double scaleY = scaleObj["y"].toDouble();
|
|
|
|
|
|
if (scaleX != 1.0 || scaleY != 1.0) {
|
|
|
|
|
|
group->setScale(qMax(scaleX, scaleY));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取宽高,但不立即调用 setWidth/setHeight(会触发 updateCoordinate)
|
|
|
|
|
|
double width = obj["width"].toDouble();
|
|
|
|
|
|
double height = obj["height"].toDouble();
|
|
|
|
|
|
|
|
|
|
|
|
// 注意:对于 GraphicsItemGroup,其 boundingRect 是由子项自动计算的
|
|
|
|
|
|
// 我们创建一个临时的空矩形,待子项添加后会自动更新
|
|
|
|
|
|
// 暂时不设置宽高,因为 updateCoordinate 需要 parentItem 为空才能正常工作
|
|
|
|
|
|
// 子项添加后,group 的 boundingRect 会自动重新计算
|
|
|
|
|
|
|
2026-05-13 16:24:21 +08:00
|
|
|
|
return group;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Document::setMetaData(const QString& key, const QVariant& value)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_metaData.insert(key, value);
|
|
|
|
|
|
m_modifiedTime = QDateTime::currentDateTime();
|
|
|
|
|
|
if (m_pScene) {
|
|
|
|
|
|
setModified(true);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QVariant Document::metaData(const QString& key) const
|
|
|
|
|
|
{
|
|
|
|
|
|
return m_metaData.value(key);
|
|
|
|
|
|
}
|