Serializing QGraphicsScene contents
Asked Answered
S

4

9

I am using the Qt QGraphicsScene class, adding pre-defined items such as QGraphicsRectItem, QGraphicsLineItem, etc. and I want to serialize the scene contents to disk. However, the base QGraphicsItem class (that the other items I use derive from) doesn't support serialization so I need to roll my own code. The problem is that all access to these objects is via a base QGraphicsItem pointer, so the serialization code I have is horrible:

QGraphicsScene* scene = new QGraphicsScene;
scene->addRect(QRectF(0, 0, 100, 100));
scene->addLine(QLineF(0, 0, 100, 100));
...
QList<QGraphicsItem*> list = scene->items();
foreach (QGraphicsItem* item, items)
{
  if (item->type() == QGraphicsRectItem::Type)
  {
    QGraphicsRectItem* rect = qgraphicsitem_cast<QGraphicsRectItem*>(item);
    // Access QGraphicsRectItem members here
  }
  else if (item->type() == QGraphicsLineItem::Type)
  {
    QGraphicsLineItem* line = qgraphicsitem_cast<QGraphicsLineItem*>(item);
    // Access QGraphicsLineItem members here
  }
  ...
}

This is not good code IMHO. So, instead I could create an ABC class like this:

class Item
{
public:
  virtual void serialize(QDataStream& strm, int version) = 0;
};

class Rect : public QGraphicsRectItem, public Item
{
public:
  void serialize(QDataStream& strm, int version)
  {
    // Serialize this object
  }
  ...
};

I can then add Rect objects using QGraphicsScene::addItem(new Rect(,,,));

But this doesn't really help me as the following will crash:

QList<QGraphicsItem*> list = scene->items();
foreach (QGraphicsItem* item, items)
{
  Item* myitem = reinterpret_class<Item*>(item);
  myitem->serialize(...) // FAIL
}

Any way I can make this work?

Sunk answered 8/6, 2010 at 18:1 Comment(2)
Why does myitem->serialize() crash. I'm not seeing it?Radiolarian
since reinterpret_class doesn't perform required conversion (pointer is just interpret differently). Pointer to Item is a bit shifted in relation to QGraphicsItem. First you need cast to Rect then default cast to Item will correct hardware address to point properly to Item.Goldeneye
P
3

I agree with the other posters, the QGraphicsItem could really be viewed as a view item and so separating your model data into its own class would probably be better.

That said, I think your crash is caused by an inappropriate cast.

If you do the following:

Rect *r = new Rect();
QGraphicsItem *qi = reinterpret_cast<QGraphicsItem*>(r);
QGraphicsRectItem *qr = reinterpret_cast<QGraphicsRectItem*>(r);
Item *i = reinterpret_cast<Item*>(r);
qDebug("r = %p, qi = %p, qr = %p, i = %p", r, qi, qr, i);

You should see that r == qi, r == qr, but r != i. If you think about how an object that multiply inherits is represented in memory, the first base class is first in memory, the second base class is second, and so forth. So the pointer to the second base class will be offset by [approximately] the sizeof the first base class.

So to fix your code, I think you need to do something like:

QList<QGraphicsItem*> list = scene->items();
foreach (QGraphicsItem* item, items)
{
  Rect* myrect = reinterpret_class<Rect*>(item);  // needed to figure out the offset to the Item part
  Item* myitem = reinterpret_class<Item*>(myrect);
  myitem->serialize(...);
}

This is one of many reasons, I like to avoid multiple inheritance whenever possible. I strongly recommend separating you model data, as previously recommended.

Pithos answered 15/6, 2010 at 18:45 Comment(1)
WTF: reinterpret_cast?Goldeneye
C
1

Serializing QGraphicsItem is not a good idea. A QGrahpicsScene is supposed to represent your data. Instead of serializing the representation, it's better to serialize the data/model of your application.

If you want to record arrangement of graphics entities, maybe you can use a custom QGraphicsItem and draw into a QPicture.

Cystectomy answered 8/6, 2010 at 18:40 Comment(2)
The items contain everything I need to serialize, so can be considered the data/model in this case.Sunk
BTW there doesn't seem to be support for linking a QGraphicsScene to a separate model.Sunk
E
1

The Qt metatype system can be leveraged to interface QGraphicsItem with QDataStream. Below is a complete example that handles basic Qt QGraphicsItem-derived types, as well as hierarchies of objects - i.e. child items are saved and restored as well. The interface is very small - below is the header that you'd need to support storage of some of the common types.

Adding custom types requires only adding a single DECLARE_GRAPHICSITEM_METATYPE(CustomItemType) line in the interface.

// https://github.com/KubaO/stackoverflown/tree/master/questions/qgraphicsitem-stream-51492181
// Interface
#include <QDataStream>
#include <QGraphicsItem>
#include <QGraphicsTransform>
#include <QMetaType>
#include <algorithm>
#include <type_traits>

template <class B, class T>
struct NonConstructibleFunctionHelper {
   static void *Construct(void *, const void *) { return {}; }
   static void Destruct(void *t) { static_cast<T *>(static_cast<B *>(t))->~T(); }
   static void Save(QDataStream &ds, const void *t) {
      ds << *static_cast<const T *>(static_cast<const B *>(t));
   }
   static void Load(QDataStream &ds, void *t) {
      ds >> *static_cast<T *>(static_cast<B *>(t));
   }
};

template <class B, class T>
struct NonCopyableFunctionHelper : NonConstructibleFunctionHelper<B, T> {
   static void *Construct(void *where, const void *t) {
      return (!t) ? static_cast<B *>(new (where) T) : nullptr;
   }
};

#define DECLARE_POLYMORPHIC_METATYPE(BASE_TYPE, TYPE) \
   DECLARE_POLYMORPHIC_METATYPE_IMPL(BASE_TYPE, TYPE)
#define DECLARE_POLYMORPHIC_METATYPE_IMPL(BASE_TYPE, TYPE)                            \
   QT_BEGIN_NAMESPACE namespace QtMetaTypePrivate {                                   \
      template <>                                                                     \
      struct QMetaTypeFunctionHelper<TYPE>                                            \
          : std::conditional<                                                         \
                std::is_copy_constructible<TYPE>::value, void,                        \
                std::conditional<                                                     \
                    std::is_default_constructible<TYPE>::value,                       \
                    NonCopyableFunctionHelper<BASE_TYPE, TYPE>,                       \
                    NonConstructibleFunctionHelper<BASE_TYPE, TYPE>>::type>::type {}; \
      QT_END_NAMESPACE                                                                \
   }                                                                                  \
   Q_DECLARE_METATYPE_IMPL(TYPE)
#define DECLARE_POLYSTREAMING_METATYPE(BASE_TYPE, TYPE) \
   DECLARE_POLYSTREAMING_METATYPE_IMPL(BASE_TYPE, TYPE)
#define DECLARE_POLYSTREAMING_METATYPE_IMPL(BASE_TYPE, TYPE) \
   DECLARE_POLYMORPHIC_METATYPE_IMPL(BASE_TYPE, TYPE)        \
   QDataStream &operator<<(QDataStream &, const TYPE &);     \
   QDataStream &operator>>(QDataStream &, TYPE &);

#define DECLARE_GRAPHICSITEM_METATYPE(TYPE) \
   DECLARE_POLYSTREAMING_METATYPE_IMPL(QGraphicsItem, TYPE)

QDataStream &operator<<(QDataStream &, const QList<QGraphicsItem *> &);

void saveProperties(QDataStream &, const QObject *, const QByteArrayList &excluded = {});
void loadProperties(QDataStream &, QObject *);

QDataStream &operator<<(QDataStream &, const QGraphicsTransform *);
QDataStream &operator>>(QDataStream &, QGraphicsTransform *&);

void registerMapping(int typeId, int itemType);

template <typename T>
typename std::enable_if<std::is_base_of<QGraphicsItem, T>::value>::type
registerMapping() {
   qRegisterMetaTypeStreamOperators<T>();
   if (!std::is_base_of<QGraphicsObject, T>::value)
      // The QObject-derived types don't need such mappings.
      registerMapping(qMetaTypeId<T>(), T::Type);
}

QDataStream &operator<<(QDataStream &, const QGraphicsItem *);
QDataStream &operator>>(QDataStream &, QGraphicsItem *&);

DECLARE_POLYMORPHIC_METATYPE(QGraphicsTransform, QGraphicsRotation)
DECLARE_POLYMORPHIC_METATYPE(QGraphicsTransform, QGraphicsScale)
DECLARE_GRAPHICSITEM_METATYPE(QGraphicsItem)
DECLARE_GRAPHICSITEM_METATYPE(QGraphicsObject)
DECLARE_GRAPHICSITEM_METATYPE(QAbstractGraphicsShapeItem)
DECLARE_GRAPHICSITEM_METATYPE(QGraphicsItemGroup)
DECLARE_GRAPHICSITEM_METATYPE(QGraphicsLineItem)
DECLARE_GRAPHICSITEM_METATYPE(QGraphicsPixmapItem)
DECLARE_GRAPHICSITEM_METATYPE(QGraphicsEllipseItem)
DECLARE_GRAPHICSITEM_METATYPE(QGraphicsPathItem)
DECLARE_GRAPHICSITEM_METATYPE(QGraphicsPolygonItem)
DECLARE_GRAPHICSITEM_METATYPE(QGraphicsRectItem)
DECLARE_GRAPHICSITEM_METATYPE(QGraphicsSimpleTextItem)
DECLARE_GRAPHICSITEM_METATYPE(QGraphicsTextItem)

class ItemStream {
   QDataStream &ds;
   QGraphicsItem &item;

  public:
   using Version = qint8;
   static constexpr Version CurVersion = 1;
   enum { VersionKey = 0x9000 };
   ItemStream(QDataStream &ds, class QGraphicsItem &item) : ds(ds), item(item) {}
   template <typename C, typename T>
   ItemStream &operator>>(void (C::*set)(T)) {
      using decayed_type = typename std::decay<T>::type;
      using value_type = typename std::conditional<std::is_enum<decayed_type>::value,
                                                   std::underlying_type<decayed_type>,
                                                   std::decay<T>>::type::type;
      value_type value;
      ds >> value;
      (static_cast<C &>(item).*set)(static_cast<T>(value));
      return *this;
   }
   static Version formatOf(const QGraphicsItem &item) {
      auto version = item.data(VersionKey);
      return Version(version.isValid() ? version.value<int>() : 0);
   }
   static void setVersion(QGraphicsItem &item, Version version) {
      item.setData(VersionKey, version);
   }
};

The ItemStream class makes it easier to stream into property setters, and is useful when implementing the streaming operators. It allows to write simply itemStream >> &Class::setFoo instead of type foo; dataStream >> foo; obj.setFoo(foo);

This interface allows writing the following example - expanded from this answer.

// Test
#include <QtWidgets>

QPixmap makePixmap() {
   QPixmap pix(100, 50);
   pix.fill(Qt::transparent);
   QPainter p(&pix);
   p.setPen(Qt::darkCyan);
   p.setFont({"helvetica,arial", 15});
   p.drawText(pix.rect(), Qt::AlignCenter, "Hello!");
   return pix;
}

int main(int argc, char *argv[]) {
   QApplication a(argc, argv);
   QWidget ui;
   QGridLayout layout(&ui);
   QGraphicsScene scene;
   QGraphicsView view(&scene);
   QPushButton save("Save");
   QPushButton load("Load");
   layout.addWidget(&view, 0, 0, 1, 2);
   layout.addWidget(&load, 1, 0);
   layout.addWidget(&save, 1, 1);

   auto *eitem =
       scene.addEllipse(QRect(10, 10, 80, 50), QPen(Qt::green), QBrush(Qt::black));
   auto *xform = new QGraphicsRotation;
   xform->setAngle(10);
   eitem->setPos(100, 10);
   eitem->setRotation(60);
   eitem->setTransformations({xform});

   auto *litem = scene.addLine(QLineF(0, 0, 100, 100), QPen(Qt::red));
   litem->setPos(10, 10);
   litem->setRotation(100);

   scene.createItemGroup({eitem, litem});

   auto *ritem = scene.addRect(QRect(10, 0, 100, 100), QPen(Qt::blue), QBrush(Qt::red));
   ritem->setPos(10, 100);
   ritem->setRotation(10);

   QPainterPath path;
   path.moveTo(100, 100);
   path.lineTo(10, 0);
   path.addRect(QRect(0, 0, 100, 22));
   auto *pitem = scene.addPath(path, QPen(Qt::green), QBrush(Qt::black));
   pitem->setPos(100, 22);
   pitem->setRotation(120);

   scene.addPixmap(makePixmap());

   auto *stitem = scene.addSimpleText("Simple Text", {"arial", 17});
   stitem->setPos(-50, -10);
   stitem->setPen(QPen(Qt::darkGreen));

   auto *titem = scene.addText("Text", {"arial", 17, true});
   titem->setPos(-100, 0);
   titem->setDefaultTextColor(Qt::darkYellow);

   auto const flags = QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable |
                      QGraphicsItem::ItemIsFocusable;
   for (auto *it : scene.items()) it->setFlags(flags);

   QByteArray data;
   QObject::connect(&save, &QPushButton::clicked, [&scene, &data]() {
      qDebug() << "writing ...";
      QBuffer dev(&data);
      if (dev.open(QIODevice::WriteOnly)) {
         QDataStream out(&dev);
         out << scene.items(Qt::AscendingOrder);
         scene.clear();
         qDebug() << "done writing";
      }
   });
   QObject::connect(&load, &QPushButton::clicked, [&scene, &data]() {
      qDebug() << "reading ...";
      QBuffer dev(&data);
      if (dev.open(QIODevice::ReadOnly)) {
         QList<QGraphicsItem *> items;
         QDataStream in(&dev);
         in >> items;
         for (auto *item : items) scene.addItem(item);
         qDebug() << "done reading";
      }
   });

   ui.show();
   return a.exec();
}

The specializations for concrete types are fairly straightforward. For each DECLARE_GRAPHICSITEM_METATYPE(ItemType) declaration, you have to implement QDataStream &operator>>(QDataStream &in, ItemType &g) and QDataStream &operator<<(QDataStream &out, const ItemType &g). Those stream operators must first invoke the operator for the immediate base class. You must also register the mappings - this can be done in main, or in a static initializer.

These implementations are inspired by this answer.

// Implementation Specializations

static bool specInit = [] {
   qRegisterMetaType<QGraphicsRotation>();
   qRegisterMetaType<QGraphicsScale>();
   registerMapping<QGraphicsItemGroup>();
   registerMapping<QGraphicsLineItem>();
   registerMapping<QGraphicsPixmapItem>();
   registerMapping<QGraphicsEllipseItem>();
   registerMapping<QGraphicsPathItem>();
   registerMapping<QGraphicsPolygonItem>();
   registerMapping<QGraphicsRectItem>();
   registerMapping<QGraphicsSimpleTextItem>();
   registerMapping<QGraphicsTextItem>();
   return true;
}();

QDataStream &operator<<(QDataStream &out, const QGraphicsEllipseItem &g) {
   out << static_cast<const QAbstractGraphicsShapeItem &>(g);
   out << g.rect() << g.startAngle() << g.spanAngle();
   return out;
}

QDataStream &operator>>(QDataStream &in, QGraphicsEllipseItem &g) {
   using QGI = std::decay<decltype(g)>::type;
   in >> static_cast<QAbstractGraphicsShapeItem &>(g);
   ItemStream(in, g) >> &QGI::setRect >> &QGI::setStartAngle >> &QGI::setSpanAngle;
   return in;
}

QDataStream &operator<<(QDataStream &out, const QGraphicsPathItem &g) {
   out << static_cast<const QAbstractGraphicsShapeItem &>(g);
   out << g.path();
   return out;
}

QDataStream &operator>>(QDataStream &in, QGraphicsPathItem &g) {
   using QGI = std::decay<decltype(g)>::type;
   in >> static_cast<QAbstractGraphicsShapeItem &>(g);
   ItemStream(in, g) >> &QGI::setPath;
   return in;
}

QDataStream &operator<<(QDataStream &out, const QGraphicsPolygonItem &g) {
   out << static_cast<const QAbstractGraphicsShapeItem &>(g);
   out << g.polygon() << g.fillRule();
   return out;
}

QDataStream &operator>>(QDataStream &in, QGraphicsPolygonItem &g) {
   using QGI = std::decay<decltype(g)>::type;
   in >> static_cast<QAbstractGraphicsShapeItem &>(g);
   ItemStream(in, g) >> &QGI::setPolygon >> &QGI::setFillRule;
   return in;
}

QDataStream &operator<<(QDataStream &out, const QGraphicsRectItem &g) {
   out << static_cast<const QAbstractGraphicsShapeItem &>(g);
   out << g.rect();
   return out;
}

QDataStream &operator>>(QDataStream &in, QGraphicsRectItem &g) {
   using QGI = std::decay<decltype(g)>::type;
   in >> static_cast<QAbstractGraphicsShapeItem &>(g);
   ItemStream(in, g) >> &QGI::setRect;
   return in;
}

QDataStream &operator<<(QDataStream &out, const QGraphicsSimpleTextItem &g) {
   out << static_cast<const QAbstractGraphicsShapeItem &>(g);
   out << g.text() << g.font();
   return out;
}

QDataStream &operator>>(QDataStream &in, QGraphicsSimpleTextItem &g) {
   using QGI = std::decay<decltype(g)>::type;
   in >> static_cast<QAbstractGraphicsShapeItem &>(g);
   ItemStream(in, g) >> &QGI::setText >> &QGI::setFont;
   return in;
}

QDataStream &operator<<(QDataStream &out, const QGraphicsItemGroup &g) {
   return out << static_cast<const QGraphicsItem &>(g);
}

QDataStream &operator>>(QDataStream &in, QGraphicsItemGroup &g) {
   return in >> static_cast<QGraphicsItem &>(g);
}

QDataStream &operator<<(QDataStream &out, const QGraphicsLineItem &g) {
   out << static_cast<const QGraphicsItem &>(g);
   out << g.pen() << g.line();
   return out;
}

QDataStream &operator>>(QDataStream &in, QGraphicsLineItem &g) {
   using QGI = std::decay<decltype(g)>::type;
   in >> static_cast<QGraphicsItem &>(g);
   ItemStream(in, g) >> &QGI::setPen >> &QGI::setLine;
   return in;
}

QDataStream &operator<<(QDataStream &out, const QGraphicsPixmapItem &g) {
   out << static_cast<const QGraphicsItem &>(g);
   out << g.pixmap() << g.offset() << g.transformationMode() << g.shapeMode();
   return out;
}

QDataStream &operator>>(QDataStream &in, QGraphicsPixmapItem &g) {
   using QGI = std::decay<decltype(g)>::type;
   in >> static_cast<QGraphicsItem &>(g);
   ItemStream(in, g) >> &QGI::setPixmap >> &QGI::setOffset >>
       &QGI::setTransformationMode >> &QGI::setShapeMode;
   return in;
}

QDataStream &operator<<(QDataStream &out, const QGraphicsTextItem &g) {
   out << static_cast<const QGraphicsObject &>(g) << g.font() << g.textWidth()
       << g.defaultTextColor() << g.toHtml() << g.tabChangesFocus()
       << g.textInteractionFlags();
   return out;
}

QDataStream &operator>>(QDataStream &in, QGraphicsTextItem &g) {
   using QGI = std::decay<decltype(g)>::type;
   in >> static_cast<QGraphicsObject &>(g);
   ItemStream(in, g) >> &QGI::setFont >> &QGI::setTextWidth >>
       &QGI::setDefaultTextColor >> &QGI::setHtml >> &QGI::setTabChangesFocus >>
       &QGI::setTextInteractionFlags;
   return in;
}

The core of the implementation implements a mapping between metatype type names, and QGraphicsItem::type() item types for types that need that. Otherwise, for types derived from QObject (i.e. QGraphicsObject), it uses the type names directly. It also handles saving item lists without duplicated items.

// Implementation Core
#include <set>

void saveProperties(QDataStream &ds, const QObject *obj, const QByteArrayList &excluded) {
   QVariantMap map;
   QByteArray name;
   auto const &mo = obj->metaObject();
   for (int i = 0; i < mo->propertyCount(); ++i) {
      auto const &mp = mo->property(i);
      if (!mp.isStored(obj)) continue;
      name = mp.name();
      if (!excluded.contains(name)) {
         auto prop = mp.read(obj);
         if (!prop.isNull() && (!excluded.contains(name + '?') ||
                                prop != QVariant(prop.userType(), nullptr)))
            map.insert(QLatin1String(name), prop);
      }
   }
   for (auto &name : obj->dynamicPropertyNames())
      map.insert(QLatin1String(name), obj->property(name));
   qDebug() << obj->metaObject()->className() << map;
   ds << map;
}

void loadProperties(QDataStream &ds, QObject *obj) {
   QVariantMap map;
   ds >> map;
   for (auto it = map.cbegin(); it != map.cend(); ++it)
      obj->setProperty(it.key().toLatin1(), it.value());
}

QDataStream &operator<<(QDataStream &ds, const QGraphicsTransform *obj) {
   ds << obj->metaObject()->className();
   saveProperties(ds, obj);
   return ds;
}

QDataStream &operator>>(QDataStream &ds, QGraphicsTransform *&obj) {
   QByteArray className;
   ds >> className;
   auto const type = QMetaType::type(className);
   obj = static_cast<QGraphicsTransform *>(QMetaType::create(type));
   if (obj) loadProperties(ds, obj);
   return ds;
}

struct pair {
   int typeId;
   int itemType;
};
struct CoreData {
   QReadWriteLock mappingLock;
   QVector<pair> mapping;
};
Q_GLOBAL_STATIC(CoreData, coreData)

int getTypeIdForItemType(int itemType) {
   if (auto *const d = coreData()) {
      QReadLocker lock(&d->mappingLock);
      for (auto &m : qAsConst(d->mapping))
         if (m.itemType == itemType) return m.typeId;
   }
   return QMetaType::UnknownType;
}

void registerMapping(int typeId, int itemType) {
   if (getTypeIdForItemType(itemType) == typeId) return;
   if (auto *const d = coreData()) {
      QWriteLocker lock(&d->mappingLock);
      for (auto &m : qAsConst(d->mapping))
         if (m.typeId == typeId && m.itemType == itemType) return;
      d->mapping.push_back({typeId, itemType});
   }
}

QByteArray peekByteArray(QDataStream &ds) {
   qint32 size;
   auto read = ds.device()->peek(reinterpret_cast<char *>(&size), sizeof(size));
   if (read != sizeof(size)) {
      ds.setStatus(QDataStream::ReadPastEnd);
      return {};
   }
   if (ds.byteOrder() == QDataStream::BigEndian) size = qFromBigEndian(size);
   auto buf = ds.device()->peek(size + 4);
   if (buf.size() != size + 4) {
      ds.setStatus(QDataStream::ReadPastEnd);
      return {};
   }
   if (buf.endsWith('\0')) buf.chop(1);
   return buf.mid(4);
}

QDataStream &operator<<(QDataStream &ds, const QList<QGraphicsItem *> &list) {
   std::set<QGraphicsItem *> seen;
   QList<QGraphicsItem *> items;
   struct State {
      QList<QGraphicsItem *>::const_iterator it, end;
   };
   QVector<State> stack;
   stack.push_back({list.begin(), list.end()});
   while (!stack.isEmpty()) {
      auto &level = stack.back();
      while (level.it != level.end) {
         QGraphicsItem *item = *level.it++;
         if (!item || seen.find(item) != seen.end())
            continue;            // skip empty items and seen items
         if (stack.size() == 1)  // push direct items only
            items.push_back(item);
         seen.insert(item);
         const auto &children = item->childItems();
         if (!children.isEmpty()) {
            stack.push_back({children.begin(), children.end()});
            break;
         }
      }
      if (level.it == level.end) stack.pop_back();
   }
   ds << quint32(items.size());
   for (auto *item : items) ds << item;
   return ds;
}

QDataStream &operator<<(QDataStream &ds, const QGraphicsItem *item) {
   const QGraphicsObject *obj = item->toGraphicsObject();
   const int typeId = obj ? QMetaType::type(obj->metaObject()->className())
                          : getTypeIdForItemType(item->type());
   if (typeId != QMetaType::UnknownType)
      QMetaType::save(ds, typeId, item);
   else
      ds << "";
   return ds;
}

QDataStream &operator>>(QDataStream &ds, QGraphicsItem *&item) {
   QByteArray const typeName = peekByteArray(ds);
   if (ds.status() != QDataStream::Ok) return ds;
   const int typeId = QMetaType::type(typeName);
   item = static_cast<QGraphicsItem *>(QMetaType::create(typeId));
   if (item) QMetaType::load(ds, typeId, item);
   return ds;
}

QByteArray getTypeName(const QGraphicsItem &g) {
   const QGraphicsObject *obj = g.toGraphicsObject();
   if (obj) return obj->metaObject()->className();
   return QMetaType::typeName(getTypeIdForItemType(g.type()));
}

QDataStream &operator<<(QDataStream &out, const QGraphicsItem &g) {
   out << getTypeName(g) << ItemStream::CurVersion << bool(g.toGraphicsObject())
       << int(g.type()) << g.pos() << g.scale() << g.rotation() << g.transform()
       << g.transformations() << g.transformOriginPoint() << g.flags() << g.isEnabled()
       << g.isSelected() << g.isVisible() << g.acceptDrops() << g.acceptHoverEvents()
       << g.acceptTouchEvents() << g.acceptedMouseButtons() << g.filtersChildEvents()
       << g.cursor() << g.inputMethodHints() << g.opacity()
       << g.boundingRegionGranularity() << g.toolTip() << g.zValue() << g.childItems();
   return out;
}

QDataStream &operator>>(QDataStream &in, QGraphicsItem &g) {
   using QGI = std::decay<decltype(g)>::type;
   QByteArray typeName;
   bool isObject;
   int type;
   ItemStream::Version version;
   QTransform transform;
   QList<QGraphicsItem *> children;
   in >> typeName >> version;

   Q_ASSERT(getTypeName(g) == typeName);
   if (version > ItemStream::CurVersion) {
      qWarning() << "unsupported QGraphicsItem version" << version
                 << "maximum is:" << ItemStream::CurVersion;
      in.setStatus(QDataStream::ReadCorruptData);
      return in;
   }
   ItemStream::setVersion(g, version);
   in >> isObject >> type;
   ItemStream iin(in, g);
   iin >> &QGI::setPos >> &QGI::setScale >> &QGI::setRotation;
   in >> transform;
   g.setTransform(transform);
   iin >> &QGI::setTransformations >> &QGI::setTransformOriginPoint >> &QGI::setFlags >>
       &QGI::setEnabled >> &QGI::setSelected;
   if (version >= 1) {
      iin >> &QGI::setVisible >> &QGI::setAcceptDrops >> &QGI::setAcceptHoverEvents >>
          &QGI::setAcceptTouchEvents >> &QGI::setAcceptedMouseButtons >>
          &QGI::setFiltersChildEvents >> &QGI::setCursor >> &QGI::setInputMethodHints >>
          &QGI::setOpacity >> &QGI::setBoundingRegionGranularity >> &QGI::setToolTip;
   }
   iin >> &QGI::setZValue;
   in >> children;

   for (auto *c : qAsConst(children)) c->setParentItem(&g);
   return in;
}

QDataStream &operator<<(QDataStream &out, const QGraphicsObject &g) {
   static const QByteArrayList excluded{
       "effect",   "enabled",  "opacity", "parent",
       "pos",      "rotation", "scale",   "transformOriginPoint",
       "visible",  "x",        "y",       "z",
       "children", "height?",  "width?"};
   out << static_cast<const QGraphicsItem &>(g);
   saveProperties(out, &g, excluded);
   return out;
}

QDataStream &operator>>(QDataStream &in, QGraphicsObject &g) {
   in >> static_cast<QGraphicsItem &>(g);
   loadProperties(in, &g);
   return in;
}

QDataStream &operator<<(QDataStream &out, const QAbstractGraphicsShapeItem &g) {
   out << static_cast<const QGraphicsItem &>(g);
   out << g.pen() << g.brush();
   return out;
}

QDataStream &operator>>(QDataStream &in, QAbstractGraphicsShapeItem &g) {
   using QGI = std::decay<decltype(g)>::type;
   in >> static_cast<QGraphicsItem &>(g);
   ItemStream(in, g) >> &QGI::setPen >> &QGI::setBrush;
   return in;
}
Ephraimite answered 29/7, 2018 at 9:48 Comment(1)
you should mark the other question as a duplicate of this :)Apparatus
D
0

If you really want to serialize these items create

QDataStream &operator<<(QDataStream &, const AppropriateSubclass&);
QDataStream &operator>>(QDataStream &, AppropriateSubclass&);

for each of the QGraphicsItem subclasses that you would like to serialize, these functions are non-member function. The approach is also explained in the QDataStream documentation

But I support stephens answer, you might want to consider pulling the actual data out of the scene and putting it in a separate model class

Disannul answered 8/6, 2010 at 18:57 Comment(1)
This doesn't work unless I cast each QGraphicsItems to the correct type before attempting to stream it.Sunk

© 2022 - 2024 — McMap. All rights reserved.