From f6d6e71e32425383cc69937f589dc2541e47477b Mon Sep 17 00:00:00 2001 From: Jesse Qu Date: Tue, 19 May 2026 10:27:47 +0800 Subject: [PATCH] Fix serialization/deserialization for rounded rects, polygons, and groups - Rounded rects: serialize/restore isRound, ratioX, ratioY; pass isRound to constructor so rounded-corner control handles are created - Polygons: implement full serialization/deserialization including vertex points; add updateBoundingRectFromPoints() to fix hit-test and handles after deserialization - Groups: add updateBoundingRectFromChildren() to correctly compute bounding rect after child items are deserialized Co-Authored-By: Claude Opus 4.7 --- include/document.h | 1 + include/graphicsItem/graphicsItemGroup.h | 1 + include/graphicsItem/graphicsPolygonItem.h | 1 + include/graphicsItem/graphicsRectItem.h | 7 ++ source/document.cpp | 122 ++++++++++++++++---- source/graphicsItem/graphicsItemGroup.cpp | 8 ++ source/graphicsItem/graphicsPolygonItem.cpp | 8 ++ 7 files changed, 127 insertions(+), 21 deletions(-) diff --git a/include/document.h b/include/document.h index 6e028ca..0b434b8 100644 --- a/include/document.h +++ b/include/document.h @@ -221,6 +221,7 @@ private: GraphicsBaseItem* deserializeRectItem(const QJsonObject& obj); GraphicsBaseItem* deserializeBusSectionItem(const QJsonObject& obj); + GraphicsBaseItem* deserializePolygonItem(const QJsonObject& obj); GraphicsItemGroup* deserializeItemGroup(const QJsonObject& obj, QGraphicsScene* scene, QGraphicsItem* parent); QString m_filename; // 文档文件名 diff --git a/include/graphicsItem/graphicsItemGroup.h b/include/graphicsItem/graphicsItemGroup.h index 1a39cf8..4321260 100644 --- a/include/graphicsItem/graphicsItemGroup.h +++ b/include/graphicsItem/graphicsItemGroup.h @@ -20,6 +20,7 @@ public: void rotateOperationCopy(const double&); void addItems(const QList&); + void updateBoundingRectFromChildren(); //QList getItems() {return m_listItem;} protected: diff --git a/include/graphicsItem/graphicsPolygonItem.h b/include/graphicsItem/graphicsPolygonItem.h index 27d0398..6d58694 100644 --- a/include/graphicsItem/graphicsPolygonItem.h +++ b/include/graphicsItem/graphicsPolygonItem.h @@ -18,6 +18,7 @@ public: void addPoint(const QPointF&); bool endDrawing(); QPolygonF getPoints(void) { return m_points; } + void updateBoundingRectFromPoints(); protected: virtual QPainterPath shape(); diff --git a/include/graphicsItem/graphicsRectItem.h b/include/graphicsItem/graphicsRectItem.h index edc9c49..423566e 100644 --- a/include/graphicsItem/graphicsRectItem.h +++ b/include/graphicsItem/graphicsRectItem.h @@ -15,6 +15,13 @@ public: void move(const QPointF&); void editShape(int, const QPointF&); + bool isRound() const { return m_bIsRound; } + void setIsRound(bool round) { m_bIsRound = round; } + double ratioX() const { return m_dRatioX; } + double ratioY() const { return m_dRatioY; } + void setRatioX(double rx) { m_dRatioX = rx; } + void setRatioY(double ry) { m_dRatioY = ry; } + protected: virtual QPainterPath shape(); virtual void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*); diff --git a/source/document.cpp b/source/document.cpp index 7947256..995c8f0 100644 --- a/source/document.cpp +++ b/source/document.cpp @@ -4,8 +4,7 @@ #include "graphicsItem/graphicsItemGroup.h" #include "graphicsItem/graphicsRectItem.h" #include "graphicsItem/graphicsBusSectionItem.h" - -class GraphicsPolygonItem; // 前向声明,暂不使用多边形 +#include "graphicsItem/graphicsPolygonItem.h" #include #include @@ -371,6 +370,8 @@ void Document::deserializeItem(const QJsonObject& obj, QGraphicsScene* scene, QG 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 或父组,再递归处理子项 @@ -387,13 +388,13 @@ void Document::deserializeItem(const QJsonObject& obj, QGraphicsScene* scene, QG for (auto childIt : childrenArray) { deserializeItem(childIt.toObject(), scene, group); } - // 添加完子项后,更新 group 的 coordinate(会自动计算 boundingRect) - group->updateCoordinate(); - // 更新 handle 位置 - group->updateHandles(); + // 添加完子项后,更新 group 的 m_boundingRect + // 使用 Qt 的 QGraphicsItemGroup::boundingRect() 计算实际包围盒, + // 然后更新 m_boundingRect、m_boundingRect_selected 和 handle 位置 + // 这适用于顶层和嵌套 group(updateCoordinate 对嵌套 group 不更新 m_boundingRect) + group->updateBoundingRectFromChildren(); return; } - // GraphicsPolygonItem 暂时跳过 if (!item) { return; @@ -410,7 +411,9 @@ void Document::deserializeItem(const QJsonObject& obj, QGraphicsScene* scene, QG } // 普通图元没有子项 - // 添加后更新 handle 位置(如果有) + // 添加后更新 handles 位置和 m_boundingRect_selected + // 这一步至关重要:deserialization 后需要重新计算选中框和 handle 位置 + item->updateHandles(); item->setHandleVisible(false); // 默认不显示 handle } @@ -470,8 +473,9 @@ QString Document::getTypeString(GraphicsBaseItem* item) const return "GraphicsBusSectionItem"; } else if (qobject_cast(item)) { return "GraphicsItemGroup"; + } else if (qobject_cast(item)) { + return "GraphicPolygonItem"; } - // GraphicsPolygonItem 没有 Q_OBJECT 宏,暂时不支持序列化 return "GraphicsBaseItem"; } @@ -482,11 +486,26 @@ QJsonObject Document::serializeItemProperties(GraphicsBaseItem* item) const // 类型特定的属性序列化 GraphicsRectItem* rectItem = qobject_cast(item); if (rectItem) { - // 这里可以添加圆角矩形等特殊属性 - // props.insert("isRound", rectItem->m_bIsRound); + 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; } @@ -497,10 +516,14 @@ 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, false); + GraphicsRectItem* item = new GraphicsRectItem(rect, isRound); // 从 JSON 恢复属性 QJsonObject posObj = obj["pos"].toObject(); @@ -539,6 +562,12 @@ GraphicsBaseItem* Document::deserializeRectItem(const QJsonObject& obj) item->setBrush(brush); } + // 恢复圆角比例 + if (isRound) { + item->setRatioX(props["ratioX"].toDouble(0.1)); + item->setRatioY(props["ratioY"].toDouble(0.1)); + } + return item; } @@ -588,6 +617,61 @@ GraphicsBaseItem* Document::deserializeBusSectionItem(const QJsonObject& obj) 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) @@ -606,14 +690,10 @@ GraphicsItemGroup* Document::deserializeItemGroup(const QJsonObject& obj, group->setScale(qMax(scaleX, scaleY)); } - // 获取宽高,但不立即调用 setWidth/setHeight(会触发 updateCoordinate) - double width = obj["width"].toDouble(); - double height = obj["height"].toDouble(); - - // 注意:对于 GraphicsItemGroup,其 boundingRect 是由子项自动计算的 - // 我们创建一个临时的空矩形,待子项添加后会自动更新 - // 暂时不设置宽高,因为 updateCoordinate 需要 parentItem 为空才能正常工作 - // 子项添加后,group 的 boundingRect 会自动重新计算 + // 注意:不设置宽高,因为 GraphicsItemGroup 的 m_boundingRect 需要在添加子项后才能正确计算 + // 在 deserializeItem() 中,添加子项后会调用 updateCoordinate() 和 updateHandles() + // 如果 group 没有 parent(顶层 group),updateCoordinate() 会自动更新 m_boundingRect + // 如果 group 有 parent,我们需要在 addChildren 后手动调用 updateHandles() 来更新 handle 位置 return group; } diff --git a/source/graphicsItem/graphicsItemGroup.cpp b/source/graphicsItem/graphicsItemGroup.cpp index 4aa4184..1936ef9 100644 --- a/source/graphicsItem/graphicsItemGroup.cpp +++ b/source/graphicsItem/graphicsItemGroup.cpp @@ -275,6 +275,14 @@ void GraphicsItemGroup::rotateOperationCopy(const double& dAngle) } +void GraphicsItemGroup::updateBoundingRectFromChildren() +{ + m_boundingRect = QGraphicsItemGroup::boundingRect(); + m_dWidth = m_boundingRect.width(); + m_dHeight = m_boundingRect.height(); + updateHandles(); +} + void GraphicsItemGroup::addItems(const QList& items) { foreach (QGraphicsItem *item, items) diff --git a/source/graphicsItem/graphicsPolygonItem.cpp b/source/graphicsItem/graphicsPolygonItem.cpp index 787b4bf..dec2b5b 100644 --- a/source/graphicsItem/graphicsPolygonItem.cpp +++ b/source/graphicsItem/graphicsPolygonItem.cpp @@ -150,3 +150,11 @@ bool GraphicPolygonItem::endDrawing() return bSuccess; } + +void GraphicPolygonItem::updateBoundingRectFromPoints() +{ + m_boundingRect = m_points.boundingRect(); + m_dWidth = m_boundingRect.width(); + m_dHeight = m_boundingRect.height(); + m_lastPoints = m_points; +}