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 <noreply@anthropic.com>
This commit is contained in:
parent
acf56d7c83
commit
f6d6e71e32
|
|
@ -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; // 文档文件名
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ public:
|
|||
void rotateOperationCopy(const double&);
|
||||
|
||||
void addItems(const QList<QGraphicsItem*>&);
|
||||
void updateBoundingRectFromChildren();
|
||||
//QList<QGraphicsItem*> getItems() {return m_listItem;}
|
||||
|
||||
protected:
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ public:
|
|||
void addPoint(const QPointF&);
|
||||
bool endDrawing();
|
||||
QPolygonF getPoints(void) { return m_points; }
|
||||
void updateBoundingRectFromPoints();
|
||||
|
||||
protected:
|
||||
virtual QPainterPath shape();
|
||||
|
|
|
|||
|
|
@ -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*);
|
||||
|
|
|
|||
|
|
@ -4,8 +4,7 @@
|
|||
#include "graphicsItem/graphicsItemGroup.h"
|
||||
#include "graphicsItem/graphicsRectItem.h"
|
||||
#include "graphicsItem/graphicsBusSectionItem.h"
|
||||
|
||||
class GraphicsPolygonItem; // 前向声明,暂不使用多边形
|
||||
#include "graphicsItem/graphicsPolygonItem.h"
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
|
|
@ -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<GraphicsItemGroup*>(item)) {
|
||||
return "GraphicsItemGroup";
|
||||
} else if (qobject_cast<GraphicPolygonItem*>(item)) {
|
||||
return "GraphicPolygonItem";
|
||||
}
|
||||
// GraphicsPolygonItem 没有 Q_OBJECT 宏,暂时不支持序列化
|
||||
return "GraphicsBaseItem";
|
||||
}
|
||||
|
||||
|
|
@ -482,11 +486,26 @@ QJsonObject Document::serializeItemProperties(GraphicsBaseItem* item) const
|
|||
// 类型特定的属性序列化
|
||||
GraphicsRectItem* rectItem = qobject_cast<GraphicsRectItem*>(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<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;
|
||||
}
|
||||
|
|
@ -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<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)
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<QGraphicsItem*>& items)
|
||||
{
|
||||
foreach (QGraphicsItem *item, items)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue