#include "document.h" #include "designerScene.h" #include "graphicsItem/graphicsBaseItem.h" #include "graphicsItem/graphicsItemGroup.h" #include "graphicsItem/graphicsRectItem.h" #include "graphicsItem/graphicsBusSectionItem.h" #include "graphicsItem/graphicsPolygonItem.h" #include #include #include #include #include #include #include #include #include #include // 文档格式版本号 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()) { // 没有目标文件名,无法保存 emit saveStatusChanged(false, tr("未指定文件路径,请使用另存为功能")); return false; } bool success = saveInternal(targetFile); if (success) { m_lastSavedTime = QDateTime::currentDateTime(); m_modified = false; // 如果传入的文件名与当前文件名不同,更新 m_filename if (!filename.isEmpty() && filename != m_filename) { m_filename = filename; emit filenameChanged(m_filename); } emit modifiedChanged(false); emit saveStatusChanged(true, tr("保存成功")); } else { emit saveStatusChanged(false, tr("保存失败")); } return success; } 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; } 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 items = m_pScene->items(); for (QGraphicsItem* item : items) { // 跳过 handle 等辅助元素 // QGraphicsItem 不是 QObject,所以不能用 qobject_cast // GraphicsBaseItem 继承 QObject 和 AbstractShape (QGraphicsItem),可以用 qgraphicsitem_cast GraphicsBaseItem* baseItem = qgraphicsitem_cast(item); if (!baseItem) { continue; } // 双重检查:通过 isValidItem() 确保不是辅助元素(如 ItemControlHandle) // ItemControlHandle 是 QGraphicsRectItem 子类,不是 GraphicsBaseItem 子类 // 即使 qgraphicsitem_cast 错误返回非 null,isValidItem() 也能过滤掉(m_type 为 T_undefined) if (!baseItem->isValidItem()) { continue; } 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(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); // 样式属性(只用于非组项,GraphicsItemGroup 没有 pen/brush) GraphicsItemGroup* group = qgraphicsitem_cast(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(item->pen().style())); obj.insert("pen", penObj); QJsonObject brushObj; brushObj.insert("color", item->brushColor().name()); obj.insert("brush", brushObj); } else { // 组没有 pen/brush,插入空对象 obj.insert("pen", QJsonObject()); obj.insert("brush", QJsonObject()); } // 尺寸 obj.insert("width", item->width()); obj.insert("height", item->height()); // 类型特定属性 obj.insert("properties", serializeItemProperties(item)); // 如果是组,递归序列化子项 if (group) { QJsonArray childrenArray; QList children = group->childItems(); for (QGraphicsItem* child : children) { // 使用 qgraphicsitem_cast 安全转换,只序列化 GraphicsBaseItem 子类的项 GraphicsBaseItem* childBase = qgraphicsitem_cast(child); if (childBase) { childrenArray.append(serializeItem(childBase)); } // 跳过 ItemControlHandle 等非 GraphicsBaseItem 的子项 } obj.insert("children", childrenArray); } return obj; } void Document::deserializeItem(const QJsonObject& obj, QGraphicsScene* scene, QGraphicsItem* parent) { QString typeStr = obj["type"].toString(); GraphicsBaseItem* item = nullptr; GraphicsItemGroup* group = nullptr; // 根据类型创建图元(属性在工厂函数中已设置) if (typeStr == "GraphicsRectItem") { item = deserializeRectItem(obj); } else if (typeStr == "GraphicsBusSectionItem") { item = deserializeBusSectionItem(obj); } else if (typeStr == "GraphicPolygonItem") { item = deserializePolygonItem(obj); } else if (typeStr == "GraphicsItemGroup") { group = deserializeItemGroup(obj, scene, parent); // 先添加到 scene 或父组,再递归处理子项 if (parent) { GraphicsItemGroup* parentGroup = qgraphicsitem_cast(parent); if (parentGroup) { parentGroup->addToGroup(group); } } else { scene->addItem(group); } // 递归处理子项(此时 group 已在 scene 中) QJsonArray childrenArray = obj["children"].toArray(); for (auto childIt : childrenArray) { deserializeItem(childIt.toObject(), scene, group); } // 添加完子项后,更新 group 的 m_boundingRect // 使用 Qt 的 QGraphicsItemGroup::boundingRect() 计算实际包围盒, // 然后更新 m_boundingRect、m_boundingRect_selected 和 handle 位置 // 这适用于顶层和嵌套 group(updateCoordinate 对嵌套 group 不更新 m_boundingRect) group->updateBoundingRectFromChildren(); return; } if (!item) { return; } // 添加到场景或父项 if (parent) { GraphicsItemGroup* parentGroup = qgraphicsitem_cast(parent); if (parentGroup) { parentGroup->addToGroup(item); } } else { scene->addItem(item); } // 普通图元没有子项 // 添加后更新 handles 位置和 m_boundingRect_selected // 这一步至关重要:deserialization 后需要重新计算选中框和 handle 位置 item->updateHandles(); item->setHandleVisible(false); // 默认不显示 handle } // ================================================================= // QVariant <-> QJsonValue 转换辅助函数 // ================================================================= QJsonValue Document::toJsonValue(const QVariant& value) const { if (value.typeId() == qMetaTypeId()) { return value.toBool(); } else if (value.typeId() == qMetaTypeId()) { return value.toInt(); } else if (value.typeId() == qMetaTypeId()) { 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 list; for (const QJsonValue& v : value.toArray()) { list.append(fromJsonValue(v)); } return list; } else if (value.isObject()) { QMap 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(item)) { return "GraphicsRectItem"; } else if (qobject_cast(item)) { return "GraphicsBusSectionItem"; } else if (qobject_cast(item)) { return "GraphicsItemGroup"; } else if (qobject_cast(item)) { return "GraphicPolygonItem"; } return "GraphicsBaseItem"; } QJsonObject Document::serializeItemProperties(GraphicsBaseItem* item) const { QJsonObject props; // 类型特定的属性序列化 GraphicsRectItem* rectItem = qobject_cast(item); if (rectItem) { props.insert("isRound", rectItem->isRound()); if (rectItem->isRound()) { props.insert("ratioX", rectItem->ratioX()); props.insert("ratioY", rectItem->ratioY()); } } // 多边形顶点序列化 GraphicPolygonItem* polygonItem = qobject_cast(item); if (polygonItem) { QPolygonF points = polygonItem->getPoints(); QJsonArray pointsArray; for (const QPointF& pt : points) { QJsonObject ptObj; ptObj.insert("x", pt.x()); ptObj.insert("y", pt.y()); pointsArray.append(ptObj); } props.insert("points", pointsArray); } return props; } GraphicsBaseItem* Document::deserializeRectItem(const QJsonObject& obj) { // 根据保存的属性重建矩形 double width = obj["width"].toDouble(); double height = obj["height"].toDouble(); // 先读取 properties,获取 isRound 以在构造时创建正确的 control handles QJsonObject props = obj["properties"].toObject(); bool isRound = props["isRound"].toBool(false); // 图元的原点在中心,因此 rect 的左上角为 (-width/2, -height/2) // 在构造函数中已经设置了 boundingRect,所以不需要再调用 setWidth/setHeight QRect rect(-width/2, -height/2, width, height); GraphicsRectItem* item = new GraphicsRectItem(rect, isRound); // 从 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(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); } // 恢复圆角比例 if (isRound) { item->setRatioX(props["ratioX"].toDouble(0.1)); item->setRatioY(props["ratioY"].toDouble(0.1)); } return item; } GraphicsBaseItem* Document::deserializeBusSectionItem(const QJsonObject& obj) { // 总线段重建 double width = obj["width"].toDouble(); double height = obj["height"].toDouble(); 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(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; } GraphicsBaseItem* Document::deserializePolygonItem(const QJsonObject& obj) { GraphicPolygonItem* item = new GraphicPolygonItem(); // 恢复顶点数据(必须在设置变换之前,因为 addPoint 内部使用 mapFromScene 转换, // 此时 item 无变换,mapToScene/mapFromScene 为恒等变换,顶点坐标不变) QJsonObject props = obj["properties"].toObject(); QJsonArray pointsArray = props["points"].toArray(); for (const QJsonValue& val : pointsArray) { QJsonObject ptObj = val.toObject(); QPointF localPt(ptObj["x"].toDouble(), ptObj["y"].toDouble()); item->addPoint(item->mapToScene(localPt)); } // 从 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(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); } // 从顶点数据更新 boundingRect,否则 hit-test 和 handles 都会失效 item->updateBoundingRectFromPoints(); return item; } GraphicsItemGroup* Document::deserializeItemGroup(const QJsonObject& obj, QGraphicsScene* scene, QGraphicsItem* parent) { GraphicsItemGroup* group = new GraphicsItemGroup(parent); // 先设置位置、旋转等变换属性 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)); } // 注意:不设置宽高,因为 GraphicsItemGroup 的 m_boundingRect 需要在添加子项后才能正确计算 // 在 deserializeItem() 中,添加子项后会调用 updateCoordinate() 和 updateHandles() // 如果 group 没有 parent(顶层 group),updateCoordinate() 会自动更新 m_boundingRect // 如果 group 有 parent,我们需要在 addChildren 后手动调用 updateHandles() 来更新 handle 位置 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); }