refactor: distribute serialization/deserialization from Document into item classes
Move per-type serialization logic out of Document's monolithic
serializeItem()/deserializeItem() into each item class:
- Add virtual serialize() to AbstractShapeType template, overridden by
GraphicsRectItem, GraphicPolygonItem, and GraphicsItemGroup
- Add static createFromJson() factory + self-registration to each item
- Extract shared pen/brush/common helpers into serializationUtils
- Add type registry (serializationRegistry) for factory-based deserialization
- Document::saveAs()/load() now delegate to item->serialize() and
createItemFromJson() — no longer owns per-type dispatch
- Adding a new shape type no longer requires touching Document
This commit is contained in:
parent
c8f71cb9e3
commit
3ad8b211e6
|
|
@ -58,6 +58,9 @@ set(H_HEADER_FILES
|
||||||
include/util/editingSelector.h
|
include/util/editingSelector.h
|
||||||
include/util/selectorManager.h
|
include/util/selectorManager.h
|
||||||
|
|
||||||
|
include/util/serializationUtils.h
|
||||||
|
include/util/serializationRegistry.h
|
||||||
|
|
||||||
include/graphicsItem/itemControlHandle.h
|
include/graphicsItem/itemControlHandle.h
|
||||||
include/graphicsItem/graphicsBaseItem.h
|
include/graphicsItem/graphicsBaseItem.h
|
||||||
include/graphicsItem/graphicsRectItem.h
|
include/graphicsItem/graphicsRectItem.h
|
||||||
|
|
@ -83,6 +86,8 @@ set(CPP_SOURCE_FILES
|
||||||
source/util/scalingSelector.cpp
|
source/util/scalingSelector.cpp
|
||||||
source/util/editingSelector.cpp
|
source/util/editingSelector.cpp
|
||||||
source/util/selectorManager.cpp
|
source/util/selectorManager.cpp
|
||||||
|
source/util/serializationUtils.cpp
|
||||||
|
source/util/serializationRegistry.cpp
|
||||||
|
|
||||||
source/graphicsItem/itemControlHandle.cpp
|
source/graphicsItem/itemControlHandle.cpp
|
||||||
source/graphicsItem/graphicsBaseItem.cpp
|
source/graphicsItem/graphicsBaseItem.cpp
|
||||||
|
|
|
||||||
|
|
@ -51,10 +51,6 @@ private:
|
||||||
void setModified(bool modified);
|
void setModified(bool modified);
|
||||||
void setupNewScene();
|
void setupNewScene();
|
||||||
|
|
||||||
// Serialization helpers
|
|
||||||
QJsonObject serializeItem(QGraphicsItem *item) const;
|
|
||||||
QGraphicsItem *deserializeItem(const QJsonObject &obj);
|
|
||||||
|
|
||||||
DesignerScene *m_pScene;
|
DesignerScene *m_pScene;
|
||||||
QUndoStack *m_pUndoStack;
|
QUndoStack *m_pUndoStack;
|
||||||
bool m_bModified;
|
bool m_bModified;
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
#include <QGraphicsItem>
|
#include <QGraphicsItem>
|
||||||
#include <QGraphicsSceneMouseEvent>
|
#include <QGraphicsSceneMouseEvent>
|
||||||
#include <QPen>
|
#include <QPen>
|
||||||
|
#include <QJsonObject>
|
||||||
|
|
||||||
enum ShapeType
|
enum ShapeType
|
||||||
{
|
{
|
||||||
|
|
@ -95,6 +96,8 @@ public:
|
||||||
|
|
||||||
virtual void updateCoordinate() {}
|
virtual void updateCoordinate() {}
|
||||||
|
|
||||||
|
virtual QJsonObject serialize() const = 0;
|
||||||
|
|
||||||
//handle相关
|
//handle相关
|
||||||
virtual int handleCount() { return m_vecHanle.count(); }
|
virtual int handleCount() { return m_vecHanle.count(); }
|
||||||
virtual ItemControlHandle* getHandle(int nHandle)
|
virtual ItemControlHandle* getHandle(int nHandle)
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@ public:
|
||||||
void removeOperationCopy();
|
void removeOperationCopy();
|
||||||
void moveOperationCopy(const QPointF&);
|
void moveOperationCopy(const QPointF&);
|
||||||
void rotateOperationCopy(const double&);
|
void rotateOperationCopy(const double&);
|
||||||
|
QJsonObject serialize() const override;
|
||||||
|
static QGraphicsItem *createFromJson(const QJsonObject &obj);
|
||||||
|
|
||||||
void addItems(const QList<QGraphicsItem*>&);
|
void addItems(const QList<QGraphicsItem*>&);
|
||||||
//QList<QGraphicsItem*> getItems() {return m_listItem;}
|
//QList<QGraphicsItem*> getItems() {return m_listItem;}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ public:
|
||||||
void resize(int,double, double, const QPointF&);
|
void resize(int,double, double, const QPointF&);
|
||||||
void updateCoordinate();
|
void updateCoordinate();
|
||||||
void move(const QPointF&);
|
void move(const QPointF&);
|
||||||
|
QJsonObject serialize() const override;
|
||||||
|
static QGraphicsItem *createFromJson(const QJsonObject &obj);
|
||||||
void editShape(int, const QPointF&);
|
void editShape(int, const QPointF&);
|
||||||
|
|
||||||
void addPoint(const QPointF&);
|
void addPoint(const QPointF&);
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ public:
|
||||||
void setRatioX(double rx) { m_dRatioX = rx; }
|
void setRatioX(double rx) { m_dRatioX = rx; }
|
||||||
void setRatioY(double ry) { m_dRatioY = ry; }
|
void setRatioY(double ry) { m_dRatioY = ry; }
|
||||||
void move(const QPointF&);
|
void move(const QPointF&);
|
||||||
|
QJsonObject serialize() const override;
|
||||||
|
static QGraphicsItem *createFromJson(const QJsonObject &obj);
|
||||||
void editShape(int, const QPointF&);
|
void editShape(int, const QPointF&);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
#ifndef SERIALIZATIONREGISTRY_H
|
||||||
|
#define SERIALIZATIONREGISTRY_H
|
||||||
|
|
||||||
|
#include <QGraphicsItem>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
using ItemFactory = std::function<QGraphicsItem*(const QJsonObject&)>;
|
||||||
|
|
||||||
|
void registerItemFactory(const QString &type, ItemFactory factory);
|
||||||
|
QGraphicsItem *createItemFromJson(const QJsonObject &obj);
|
||||||
|
|
||||||
|
#endif // SERIALIZATIONREGISTRY_H
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
#ifndef SERIALIZATIONUTILS_H
|
||||||
|
#define SERIALIZATIONUTILS_H
|
||||||
|
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QPen>
|
||||||
|
#include <QBrush>
|
||||||
|
#include <QGraphicsItem>
|
||||||
|
|
||||||
|
QString penStyleToString(Qt::PenStyle style);
|
||||||
|
Qt::PenStyle penStyleFromString(const QString &str);
|
||||||
|
|
||||||
|
QJsonObject serializePen(const QPen &pen);
|
||||||
|
QPen deserializePen(const QJsonObject &obj);
|
||||||
|
QJsonObject serializeBrush(const QBrush &brush);
|
||||||
|
|
||||||
|
QJsonObject serializeCommon(const QGraphicsItem *item, const QString &type);
|
||||||
|
void applyCommon(QGraphicsItem *item, const QJsonObject &obj);
|
||||||
|
|
||||||
|
#endif // SERIALIZATIONUTILS_H
|
||||||
|
|
@ -2,10 +2,8 @@
|
||||||
#include "designerScene.h"
|
#include "designerScene.h"
|
||||||
|
|
||||||
#include "graphicsItem/graphicsBaseItem.h"
|
#include "graphicsItem/graphicsBaseItem.h"
|
||||||
#include "graphicsItem/graphicsRectItem.h"
|
|
||||||
#include "graphicsItem/graphicsPolygonItem.h"
|
|
||||||
#include "graphicsItem/graphicsItemGroup.h"
|
#include "graphicsItem/graphicsItemGroup.h"
|
||||||
#include "graphicsItem/itemControlHandle.h"
|
#include "util/serializationRegistry.h"
|
||||||
#include "global.h"
|
#include "global.h"
|
||||||
|
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
|
|
@ -38,8 +36,6 @@ Document::~Document()
|
||||||
void Document::setupNewScene()
|
void Document::setupNewScene()
|
||||||
{
|
{
|
||||||
if (m_pScene) {
|
if (m_pScene) {
|
||||||
// Clear items only — do NOT delete the scene. The view in DrawingPanel
|
|
||||||
// still references it, and deleting would leave a dangling pointer.
|
|
||||||
m_pScene->clear();
|
m_pScene->clear();
|
||||||
} else {
|
} else {
|
||||||
m_pScene = new DesignerScene(this);
|
m_pScene = new DesignerScene(this);
|
||||||
|
|
@ -94,219 +90,6 @@ void Document::clear()
|
||||||
emit filePathChanged(m_sFilePath);
|
emit filePathChanged(m_sFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- Pen style string conversion ------------------------------------------
|
|
||||||
|
|
||||||
static QString penStyleToString(Qt::PenStyle style)
|
|
||||||
{
|
|
||||||
switch (style) {
|
|
||||||
case Qt::NoPen: return "none";
|
|
||||||
case Qt::SolidLine: return "solid";
|
|
||||||
case Qt::DashLine: return "dash";
|
|
||||||
case Qt::DotLine: return "dot";
|
|
||||||
case Qt::DashDotLine: return "dashDot";
|
|
||||||
case Qt::DashDotDotLine: return "dashDotDot";
|
|
||||||
default: return "solid";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static Qt::PenStyle penStyleFromString(const QString &str)
|
|
||||||
{
|
|
||||||
static const QHash<QString, Qt::PenStyle> map = {
|
|
||||||
{"none", Qt::NoPen},
|
|
||||||
{"solid", Qt::SolidLine},
|
|
||||||
{"dash", Qt::DashLine},
|
|
||||||
{"dot", Qt::DotLine},
|
|
||||||
{"dashDot", Qt::DashDotLine},
|
|
||||||
{"dashDotDot", Qt::DashDotDotLine}
|
|
||||||
};
|
|
||||||
return map.value(str, Qt::SolidLine);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -- Write helpers ---------------------------------------------------------
|
|
||||||
|
|
||||||
static QJsonObject serializePen(const QPen &pen)
|
|
||||||
{
|
|
||||||
QJsonObject obj;
|
|
||||||
obj["color"] = pen.color().name(QColor::HexArgb);
|
|
||||||
obj["width"] = pen.widthF();
|
|
||||||
obj["style"] = penStyleToString(pen.style());
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
static QJsonObject serializeBrush(const QBrush &brush)
|
|
||||||
{
|
|
||||||
QJsonObject obj;
|
|
||||||
obj["color"] = brush.color().name(QColor::HexArgb);
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
static QJsonObject serializeCommon(QGraphicsItem *item, const QString &type)
|
|
||||||
{
|
|
||||||
QJsonObject obj;
|
|
||||||
obj["type"] = type;
|
|
||||||
obj["pos"] = QJsonObject{{"x", item->pos().x()}, {"y", item->pos().y()}};
|
|
||||||
obj["rotation"] = item->rotation();
|
|
||||||
obj["zValue"] = item->zValue();
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -- Item serialization ----------------------------------------------------
|
|
||||||
|
|
||||||
QJsonObject Document::serializeItem(QGraphicsItem *item) const
|
|
||||||
{
|
|
||||||
// Groups use a different template instantiation (AbstractShapeType<QGraphicsItemGroup>),
|
|
||||||
// so qgraphicsitem_cast<AbstractShape*> will not match them.
|
|
||||||
// Check for groups first.
|
|
||||||
GraphicsItemGroup *group = dynamic_cast<GraphicsItemGroup *>(item);
|
|
||||||
if (group) {
|
|
||||||
QJsonObject obj = serializeCommon(item, "group");
|
|
||||||
obj["pen"] = serializePen(group->pen());
|
|
||||||
obj["brush"] = serializeBrush(group->brush());
|
|
||||||
|
|
||||||
QJsonArray children;
|
|
||||||
const auto kids = group->childItems();
|
|
||||||
for (QGraphicsItem *child : kids) {
|
|
||||||
if (qgraphicsitem_cast<ItemControlHandle *>(child))
|
|
||||||
continue;
|
|
||||||
QJsonObject childObj = serializeItem(child);
|
|
||||||
if (!childObj.isEmpty())
|
|
||||||
children.append(childObj);
|
|
||||||
}
|
|
||||||
obj["children"] = children;
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
AbstractShape *shape = qgraphicsitem_cast<AbstractShape *>(item);
|
|
||||||
if (!shape)
|
|
||||||
return {};
|
|
||||||
|
|
||||||
// Use dynamic_cast for type dispatch — GraphicsBaseItem subclasses do not
|
|
||||||
// override QGraphicsItem::type(), so the GIT_* enum values are never returned.
|
|
||||||
if (GraphicsRectItem *rectItem = dynamic_cast<GraphicsRectItem *>(item)) {
|
|
||||||
bool isRound = rectItem->isRound();
|
|
||||||
QJsonObject obj = serializeCommon(item, isRound ? "roundRect" : "rect");
|
|
||||||
obj["width"] = shape->width();
|
|
||||||
obj["height"] = shape->height();
|
|
||||||
if (isRound) {
|
|
||||||
obj["ratioX"] = rectItem->ratioX();
|
|
||||||
obj["ratioY"] = rectItem->ratioY();
|
|
||||||
}
|
|
||||||
obj["pen"] = serializePen(shape->pen());
|
|
||||||
obj["brush"] = serializeBrush(shape->brush());
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (GraphicPolygonItem *poly = dynamic_cast<GraphicPolygonItem *>(item)) {
|
|
||||||
QJsonObject obj = serializeCommon(item, "polygon");
|
|
||||||
QJsonArray pts;
|
|
||||||
for (const QPointF &pt : poly->getPoints()) {
|
|
||||||
pts.append(QJsonObject{{"x", pt.x()}, {"y", pt.y()}});
|
|
||||||
}
|
|
||||||
obj["points"] = pts;
|
|
||||||
obj["pen"] = serializePen(shape->pen());
|
|
||||||
obj["brush"] = serializeBrush(shape->brush());
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
qWarning("Document::serializeItem: unrecognized item");
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// -- Item deserialization --------------------------------------------------
|
|
||||||
|
|
||||||
static void applyCommon(QGraphicsItem *item, const QJsonObject &obj)
|
|
||||||
{
|
|
||||||
QJsonObject pos = obj["pos"].toObject();
|
|
||||||
item->setPos(pos["x"].toDouble(), pos["y"].toDouble());
|
|
||||||
item->setRotation(obj["rotation"].toDouble());
|
|
||||||
item->setZValue(obj["zValue"].toDouble());
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
static void applyPen(AbstractShapeType<T> *shape, const QJsonObject &penObj)
|
|
||||||
{
|
|
||||||
QPen pen;
|
|
||||||
pen.setColor(QColor(penObj["color"].toString()));
|
|
||||||
pen.setWidthF(penObj["width"].toDouble());
|
|
||||||
pen.setStyle(penStyleFromString(penObj["style"].toString()));
|
|
||||||
shape->setPen(pen);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
static void applyBrush(AbstractShapeType<T> *shape, const QJsonObject &brushObj)
|
|
||||||
{
|
|
||||||
shape->setBrushColor(QColor(brushObj["color"].toString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
QGraphicsItem *Document::deserializeItem(const QJsonObject &obj)
|
|
||||||
{
|
|
||||||
// Note: deserializeItem creates the item but does NOT add it to the scene.
|
|
||||||
// The caller (load() or a parent group) is responsible for scene addition.
|
|
||||||
QString type = obj["type"].toString();
|
|
||||||
|
|
||||||
if (type == "group") {
|
|
||||||
GraphicsItemGroup *group = new GraphicsItemGroup();
|
|
||||||
applyCommon(group, obj);
|
|
||||||
|
|
||||||
QJsonArray children = obj["children"].toArray();
|
|
||||||
QList<QGraphicsItem *> childItems;
|
|
||||||
for (const QJsonValue &val : children) {
|
|
||||||
QGraphicsItem *child = deserializeItem(val.toObject());
|
|
||||||
if (child)
|
|
||||||
childItems.append(child);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Child positions in JSON are group-relative, but addToGroup()
|
|
||||||
// treats child pos() as scene-absolute. Convert before grouping.
|
|
||||||
for (QGraphicsItem *child : childItems)
|
|
||||||
child->setPos(group->mapToScene(child->pos()));
|
|
||||||
|
|
||||||
group->addItems(childItems);
|
|
||||||
applyPen(group, obj["pen"].toObject());
|
|
||||||
applyBrush(group, obj["brush"].toObject());
|
|
||||||
group->setHandleVisible(false);
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == "rect" || type == "roundRect") {
|
|
||||||
double w = obj["width"].toDouble();
|
|
||||||
double h = obj["height"].toDouble();
|
|
||||||
bool isRound = (type == "roundRect");
|
|
||||||
GraphicsRectItem *item = new GraphicsRectItem(
|
|
||||||
QRectF(-w / 2, -h / 2, w, h).toRect(), isRound);
|
|
||||||
applyCommon(item, obj);
|
|
||||||
applyPen(item, obj["pen"].toObject());
|
|
||||||
applyBrush(item, obj["brush"].toObject());
|
|
||||||
if (isRound) {
|
|
||||||
item->setRatioX(obj["ratioX"].toDouble(0.1));
|
|
||||||
item->setRatioY(obj["ratioY"].toDouble(0.1));
|
|
||||||
}
|
|
||||||
item->updateCoordinate();
|
|
||||||
item->setHandleVisible(false);
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == "polygon") {
|
|
||||||
GraphicPolygonItem *item = new GraphicPolygonItem();
|
|
||||||
QJsonArray pts = obj["points"].toArray();
|
|
||||||
for (const QJsonValue &val : pts) {
|
|
||||||
QJsonObject pt = val.toObject();
|
|
||||||
item->addPoint(QPointF(pt["x"].toDouble(), pt["y"].toDouble()));
|
|
||||||
}
|
|
||||||
item->endDrawing();
|
|
||||||
applyCommon(item, obj);
|
|
||||||
applyPen(item, obj["pen"].toObject());
|
|
||||||
applyBrush(item, obj["brush"].toObject());
|
|
||||||
item->updateCoordinate();
|
|
||||||
item->setHandleVisible(false);
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -- Save / Load -----------------------------------------------------------
|
|
||||||
|
|
||||||
bool Document::save()
|
bool Document::save()
|
||||||
{
|
{
|
||||||
if (m_sFilePath.isEmpty())
|
if (m_sFilePath.isEmpty())
|
||||||
|
|
@ -319,10 +102,13 @@ bool Document::saveAs(const QString &filePath)
|
||||||
QJsonArray items;
|
QJsonArray items;
|
||||||
const auto sceneItems = m_pScene->items();
|
const auto sceneItems = m_pScene->items();
|
||||||
for (QGraphicsItem *item : sceneItems) {
|
for (QGraphicsItem *item : sceneItems) {
|
||||||
// Only serialize top-level items (children of groups are serialized recursively)
|
|
||||||
if (item->parentItem())
|
if (item->parentItem())
|
||||||
continue;
|
continue;
|
||||||
QJsonObject obj = serializeItem(item);
|
QJsonObject obj;
|
||||||
|
if (auto *group = dynamic_cast<GraphicsItemGroup *>(item))
|
||||||
|
obj = group->serialize();
|
||||||
|
else if (auto *shape = qgraphicsitem_cast<AbstractShape *>(item))
|
||||||
|
obj = shape->serialize();
|
||||||
if (!obj.isEmpty())
|
if (!obj.isEmpty())
|
||||||
items.append(obj);
|
items.append(obj);
|
||||||
}
|
}
|
||||||
|
|
@ -362,14 +148,13 @@ bool Document::load(const QString &filePath)
|
||||||
|
|
||||||
QJsonObject root = doc.object();
|
QJsonObject root = doc.object();
|
||||||
|
|
||||||
// Clear current state without triggering undo
|
|
||||||
m_nSavedIndex = 0;
|
m_nSavedIndex = 0;
|
||||||
m_pUndoStack->clear();
|
m_pUndoStack->clear();
|
||||||
setupNewScene();
|
setupNewScene();
|
||||||
|
|
||||||
QJsonArray items = root["items"].toArray();
|
QJsonArray items = root["items"].toArray();
|
||||||
for (const QJsonValue &val : items) {
|
for (const QJsonValue &val : items) {
|
||||||
QGraphicsItem *item = deserializeItem(val.toObject());
|
QGraphicsItem *item = createItemFromJson(val.toObject());
|
||||||
if (item)
|
if (item)
|
||||||
m_pScene->addItem(item);
|
m_pScene->addItem(item);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QStyleOption>
|
#include <QStyleOption>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
#include <QJsonArray>
|
||||||
|
|
||||||
GraphicsItemGroup::GraphicsItemGroup(QGraphicsItem *parent)
|
GraphicsItemGroup::GraphicsItemGroup(QGraphicsItem *parent)
|
||||||
: AbstractShapeType<QGraphicsItemGroup>(parent)
|
: AbstractShapeType<QGraphicsItemGroup>(parent)
|
||||||
|
|
@ -286,3 +287,63 @@ void GraphicsItemGroup::addItems(const QList<QGraphicsItem*>& items)
|
||||||
|
|
||||||
updateCoordinate();
|
updateCoordinate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#include "util/serializationUtils.h"
|
||||||
|
#include "graphicsItem/graphicsBaseItem.h"
|
||||||
|
#include "graphicsItem/itemControlHandle.h"
|
||||||
|
|
||||||
|
QJsonObject GraphicsItemGroup::serialize() const
|
||||||
|
{
|
||||||
|
QJsonObject obj = serializeCommon(this, "group");
|
||||||
|
obj["pen"] = serializePen(m_pen);
|
||||||
|
obj["brush"] = serializeBrush(m_brush);
|
||||||
|
|
||||||
|
QJsonArray children;
|
||||||
|
const auto kids = childItems();
|
||||||
|
for (QGraphicsItem *child : kids) {
|
||||||
|
if (qgraphicsitem_cast<ItemControlHandle *>(child))
|
||||||
|
continue;
|
||||||
|
QJsonObject childObj;
|
||||||
|
if (auto *group = dynamic_cast<GraphicsItemGroup *>(child))
|
||||||
|
childObj = group->serialize();
|
||||||
|
else if (auto *shape = qgraphicsitem_cast<AbstractShape *>(child))
|
||||||
|
childObj = shape->serialize();
|
||||||
|
if (!childObj.isEmpty())
|
||||||
|
children.append(childObj);
|
||||||
|
}
|
||||||
|
obj["children"] = children;
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "util/serializationRegistry.h"
|
||||||
|
|
||||||
|
QGraphicsItem *GraphicsItemGroup::createFromJson(const QJsonObject &obj)
|
||||||
|
{
|
||||||
|
GraphicsItemGroup *group = new GraphicsItemGroup();
|
||||||
|
applyCommon(group, obj);
|
||||||
|
|
||||||
|
QJsonArray children = obj["children"].toArray();
|
||||||
|
QList<QGraphicsItem *> childItems;
|
||||||
|
for (const QJsonValue &val : children) {
|
||||||
|
QGraphicsItem *child = createItemFromJson(val.toObject());
|
||||||
|
if (child)
|
||||||
|
childItems.append(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Child positions in JSON are group-relative, but addToGroup()
|
||||||
|
// treats child pos() as scene-absolute. Convert before grouping.
|
||||||
|
for (QGraphicsItem *child : childItems)
|
||||||
|
child->setPos(group->mapToScene(child->pos()));
|
||||||
|
|
||||||
|
group->addItems(childItems);
|
||||||
|
group->setPen(deserializePen(obj["pen"].toObject()));
|
||||||
|
group->setBrushColor(QColor(obj["brush"].toObject()["color"].toString()));
|
||||||
|
group->setHandleVisible(false);
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct GroupItemRegistrar {
|
||||||
|
GroupItemRegistrar() {
|
||||||
|
registerItemFactory("group", &GraphicsItemGroup::createFromJson);
|
||||||
|
}
|
||||||
|
} _groupItemRegistrar;
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QStyleOption>
|
#include <QStyleOption>
|
||||||
|
#include <QJsonArray>
|
||||||
|
|
||||||
GraphicPolygonItem::GraphicPolygonItem(QGraphicsItem *parent)
|
GraphicPolygonItem::GraphicPolygonItem(QGraphicsItem *parent)
|
||||||
: GraphicsBaseItem(parent)
|
: GraphicsBaseItem(parent)
|
||||||
|
|
@ -150,3 +151,43 @@ bool GraphicPolygonItem::endDrawing()
|
||||||
|
|
||||||
return bSuccess;
|
return bSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#include "util/serializationUtils.h"
|
||||||
|
|
||||||
|
QJsonObject GraphicPolygonItem::serialize() const
|
||||||
|
{
|
||||||
|
QJsonObject obj = serializeCommon(this, "polygon");
|
||||||
|
QJsonArray pts;
|
||||||
|
for (const QPointF &pt : m_points) {
|
||||||
|
pts.append(QJsonObject{{"x", pt.x()}, {"y", pt.y()}});
|
||||||
|
}
|
||||||
|
obj["points"] = pts;
|
||||||
|
obj["pen"] = serializePen(m_pen);
|
||||||
|
obj["brush"] = serializeBrush(m_brush);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "util/serializationRegistry.h"
|
||||||
|
|
||||||
|
QGraphicsItem *GraphicPolygonItem::createFromJson(const QJsonObject &obj)
|
||||||
|
{
|
||||||
|
GraphicPolygonItem *item = new GraphicPolygonItem();
|
||||||
|
QJsonArray pts = obj["points"].toArray();
|
||||||
|
for (const QJsonValue &val : pts) {
|
||||||
|
QJsonObject pt = val.toObject();
|
||||||
|
item->addPoint(QPointF(pt["x"].toDouble(), pt["y"].toDouble()));
|
||||||
|
}
|
||||||
|
item->endDrawing();
|
||||||
|
applyCommon(item, obj);
|
||||||
|
item->setPen(deserializePen(obj["pen"].toObject()));
|
||||||
|
item->setBrushColor(QColor(obj["brush"].toObject()["color"].toString()));
|
||||||
|
item->updateCoordinate();
|
||||||
|
item->setHandleVisible(false);
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct PolygonItemRegistrar {
|
||||||
|
PolygonItemRegistrar() {
|
||||||
|
registerItemFactory("polygon", &GraphicPolygonItem::createFromJson);
|
||||||
|
}
|
||||||
|
} _polygonItemRegistrar;
|
||||||
|
|
|
||||||
|
|
@ -196,3 +196,48 @@ void GraphicsRectItem::editShape(int nHandle,const QPointF& ptMouse)
|
||||||
prepareGeometryChange();
|
prepareGeometryChange();
|
||||||
updateHandles();
|
updateHandles();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#include "util/serializationUtils.h"
|
||||||
|
|
||||||
|
QJsonObject GraphicsRectItem::serialize() const
|
||||||
|
{
|
||||||
|
QJsonObject obj = serializeCommon(this, m_bIsRound ? "roundRect" : "rect");
|
||||||
|
obj["width"] = m_dWidth;
|
||||||
|
obj["height"] = m_dHeight;
|
||||||
|
if (m_bIsRound) {
|
||||||
|
obj["ratioX"] = m_dRatioX;
|
||||||
|
obj["ratioY"] = m_dRatioY;
|
||||||
|
}
|
||||||
|
obj["pen"] = serializePen(m_pen);
|
||||||
|
obj["brush"] = serializeBrush(m_brush);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "util/serializationRegistry.h"
|
||||||
|
|
||||||
|
QGraphicsItem *GraphicsRectItem::createFromJson(const QJsonObject &obj)
|
||||||
|
{
|
||||||
|
QString type = obj["type"].toString();
|
||||||
|
bool isRound = (type == "roundRect");
|
||||||
|
double w = obj["width"].toDouble();
|
||||||
|
double h = obj["height"].toDouble();
|
||||||
|
GraphicsRectItem *item = new GraphicsRectItem(
|
||||||
|
QRectF(-w / 2, -h / 2, w, h).toRect(), isRound);
|
||||||
|
applyCommon(item, obj);
|
||||||
|
item->setPen(deserializePen(obj["pen"].toObject()));
|
||||||
|
item->setBrushColor(QColor(obj["brush"].toObject()["color"].toString()));
|
||||||
|
if (isRound) {
|
||||||
|
item->setRatioX(obj["ratioX"].toDouble(0.1));
|
||||||
|
item->setRatioY(obj["ratioY"].toDouble(0.1));
|
||||||
|
}
|
||||||
|
item->updateCoordinate();
|
||||||
|
item->setHandleVisible(false);
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct RectItemRegistrar {
|
||||||
|
RectItemRegistrar() {
|
||||||
|
registerItemFactory("rect", &GraphicsRectItem::createFromJson);
|
||||||
|
registerItemFactory("roundRect", &GraphicsRectItem::createFromJson);
|
||||||
|
}
|
||||||
|
} _rectItemRegistrar;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
#include "util/serializationRegistry.h"
|
||||||
|
|
||||||
|
#include <QHash>
|
||||||
|
#include <QJsonObject>
|
||||||
|
|
||||||
|
static QHash<QString, ItemFactory> ®istry()
|
||||||
|
{
|
||||||
|
static QHash<QString, ItemFactory> map;
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
void registerItemFactory(const QString &type, ItemFactory factory)
|
||||||
|
{
|
||||||
|
registry().insert(type, factory);
|
||||||
|
}
|
||||||
|
|
||||||
|
QGraphicsItem *createItemFromJson(const QJsonObject &obj)
|
||||||
|
{
|
||||||
|
QString type = obj["type"].toString();
|
||||||
|
ItemFactory factory = registry().value(type);
|
||||||
|
if (factory)
|
||||||
|
return factory(obj);
|
||||||
|
qWarning("createItemFromJson: unknown type '%s'", qPrintable(type));
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
#include "util/serializationUtils.h"
|
||||||
|
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QHash>
|
||||||
|
|
||||||
|
QString penStyleToString(Qt::PenStyle style)
|
||||||
|
{
|
||||||
|
switch (style) {
|
||||||
|
case Qt::NoPen: return "none";
|
||||||
|
case Qt::SolidLine: return "solid";
|
||||||
|
case Qt::DashLine: return "dash";
|
||||||
|
case Qt::DotLine: return "dot";
|
||||||
|
case Qt::DashDotLine: return "dashDot";
|
||||||
|
case Qt::DashDotDotLine: return "dashDotDot";
|
||||||
|
default: return "solid";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Qt::PenStyle penStyleFromString(const QString &str)
|
||||||
|
{
|
||||||
|
static const QHash<QString, Qt::PenStyle> map = {
|
||||||
|
{"none", Qt::NoPen},
|
||||||
|
{"solid", Qt::SolidLine},
|
||||||
|
{"dash", Qt::DashLine},
|
||||||
|
{"dot", Qt::DotLine},
|
||||||
|
{"dashDot", Qt::DashDotLine},
|
||||||
|
{"dashDotDot", Qt::DashDotDotLine}
|
||||||
|
};
|
||||||
|
return map.value(str, Qt::SolidLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject serializePen(const QPen &pen)
|
||||||
|
{
|
||||||
|
QJsonObject obj;
|
||||||
|
obj["color"] = pen.color().name(QColor::HexArgb);
|
||||||
|
obj["width"] = pen.widthF();
|
||||||
|
obj["style"] = penStyleToString(pen.style());
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPen deserializePen(const QJsonObject &obj)
|
||||||
|
{
|
||||||
|
QPen pen;
|
||||||
|
pen.setColor(QColor(obj["color"].toString()));
|
||||||
|
pen.setWidthF(obj["width"].toDouble());
|
||||||
|
pen.setStyle(penStyleFromString(obj["style"].toString()));
|
||||||
|
return pen;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject serializeBrush(const QBrush &brush)
|
||||||
|
{
|
||||||
|
QJsonObject obj;
|
||||||
|
obj["color"] = brush.color().name(QColor::HexArgb);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject serializeCommon(const QGraphicsItem *item, const QString &type)
|
||||||
|
{
|
||||||
|
QJsonObject obj;
|
||||||
|
obj["type"] = type;
|
||||||
|
obj["pos"] = QJsonObject{{"x", item->pos().x()}, {"y", item->pos().y()}};
|
||||||
|
obj["rotation"] = item->rotation();
|
||||||
|
obj["zValue"] = item->zValue();
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
void applyCommon(QGraphicsItem *item, const QJsonObject &obj)
|
||||||
|
{
|
||||||
|
QJsonObject pos = obj["pos"].toObject();
|
||||||
|
item->setPos(pos["x"].toDouble(), pos["y"].toDouble());
|
||||||
|
item->setRotation(obj["rotation"].toDouble());
|
||||||
|
item->setZValue(obj["zValue"].toDouble());
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue