BayTemplate/source/document.cpp

714 lines
22 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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 <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()) {
// 没有目标文件名,无法保存
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<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;
}
// 双重检查:通过 isValidItem() 确保不是辅助元素(如 ItemControlHandle
// ItemControlHandle 是 QGraphicsRectItem 子类,不是 GraphicsBaseItem 子类
// 即使 qgraphicsitem_cast 错误返回非 nullisValidItem() 也能过滤掉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<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);
// 样式属性只用于非组项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);
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<QGraphicsItem*> children = group->childItems();
for (QGraphicsItem* child : children) {
// 使用 qgraphicsitem_cast 安全转换,只序列化 GraphicsBaseItem 子类的项
GraphicsBaseItem* childBase = qgraphicsitem_cast<GraphicsBaseItem*>(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<GraphicsItemGroup*>(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 位置
// 这适用于顶层和嵌套 groupupdateCoordinate 对嵌套 group 不更新 m_boundingRect
group->updateBoundingRectFromChildren();
return;
}
if (!item) {
return;
}
// 添加到场景或父项
if (parent) {
GraphicsItemGroup* parentGroup = qgraphicsitem_cast<GraphicsItemGroup*>(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<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";
} else if (qobject_cast<GraphicPolygonItem*>(item)) {
return "GraphicPolygonItem";
}
return "GraphicsBaseItem";
}
QJsonObject Document::serializeItemProperties(GraphicsBaseItem* item) const
{
QJsonObject props;
// 类型特定的属性序列化
GraphicsRectItem* rectItem = qobject_cast<GraphicsRectItem*>(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<GraphicPolygonItem*>(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<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);
}
// 恢复圆角比例
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<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;
}
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<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);
}
// 从顶点数据更新 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顶层 groupupdateCoordinate() 会自动更新 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);
}