j.dog j.dog - 3 months ago 13
C++ Question

Iterator for elements in QGraphicsScene

I have a

QGraphicsScene
which contains custom objects (with
QGraphicsItem
as base class). I can retrieve these objects with:

foreach (QGraphicsItem* item, items()) {
if (item->type() == CustomItem::Type)
qgraphicsitem_cast<CustomItem*>(item).doStuff(...);
}


Is it possible to write a custom
iterator
for just this type of objects? I want to be able to use range-based for-loops and algorithms from the STL.

for(CustomItem& a : custom_items())
a.doStuff(...);


I want to avoid storing pointers to these objects manually (for example in a vector) since this involves additional book-keeping when objects are deleted, which is error prone. How would such an iterator look like. How would I implement the
custom_items()
method?

Answer

The layout iterator adapter from this answer can be easily coerced to work on scene items:

// https://github.com/KubaO/stackoverflown/tree/master/questions/scene-iterator-adapter-32567814
#include <QGraphicsScene>
#include <QList>
#include <QDebug>
#include <utility>

template<class> class IterableSceneAdapter;

template<typename IT>
class ItemListIterator {
   const QList<QGraphicsItem*> * m_items;
   int m_index;
   friend class IterableSceneAdapter<IT>;
   ItemListIterator(const QList<QGraphicsItem*> * items, int dir) :
      m_items(items), m_index(dir>0 ? -1 : items->count()) {
      if (dir > 0) ++*this;
   }
   friend QDebug operator<<(QDebug dbg, const ItemListIterator & it) {
      return dbg << (it.m_items->isEmpty() ? nullptr : it.m_items->first()->scene())
                 << it.m_index;
   }
   friend void swap(ItemListIterator& a, ItemListIterator& b) {
      using std::swap;
      swap(a.m_items, b.m_items);
      swap(a.m_index, b.m_index);
   }
   template <typename T> T item_cast(QGraphicsItem* item) {
      // use qgraphicsitem_cast if you don't have RTTI enabled
      return dynamic_cast<T>(item);
   }
public:
   ItemListIterator() : m_index(0) {}
   ItemListIterator(const ItemListIterator & o) :
      m_items(o.m_items), m_index(o.m_index) {}
   ItemListIterator(ItemListIterator && o) { swap(*this, o); }
   ItemListIterator & operator=(ItemListIterator o) {
      swap(*this, o);
      return *this;
   }
   IT * operator*() const { return static_cast<IT*>(m_items->at(m_index)); }
   const ItemListIterator & operator++() {
      while (++m_index < m_items->count() && !item_cast<IT*>(m_items->at(m_index)));
      return *this;
   }
   ItemListIterator operator++(int) {
      ItemListIterator temp{*this};
      ++*this;
      return temp;
   }
   const ItemListIterator & operator--() {
      while (!item_cast<IT*>(m_items->at(--m_index)) && m_index > 0);
      return *this;
   }
   ItemListIterator operator--(int) {
      ItemListIterator temp(*this);
      --*this;
      return temp;
   }
   bool operator==(const ItemListIterator & o) const { return m_index == o.m_index; }
   bool operator!=(const ItemListIterator & o) const { return m_index != o.m_index; }
};

template <class IT = QGraphicsItem>
class IterableSceneAdapter {
   const QList<QGraphicsItem*> m_items;
public:
   typedef ItemListIterator<IT> iterator;
   typedef iterator const_iterator;
   IterableSceneAdapter(QGraphicsScene * scene) : m_items(scene->items()) {}
   const_iterator begin() const { return const_iterator(&m_items, 1); }
   const_iterator end() const { return const_iterator(&m_items, -1); }
   const_iterator cbegin() const { return const_iterator(&m_items, 1); }
   const_iterator cend() const { return const_iterator(&m_items, -1); }
};

template <class IT = QGraphicsItem>
class ConstIterableSceneAdapter : public IterableSceneAdapter<const IT> {
public:
   ConstIterableSceneAdapter(const QGraphicsScene * scene) :
      IterableSceneAdapter<const IT>(const_cast<QGraphicsScene*>(scene)) {}
};

It is used as follows:

int main(int argc, char ** argv) {
   QApplication app(argc, argv);
   tests();
   QGraphicsScene s;
   QGraphicsLineItem b1, b3;
   QGraphicsEllipseItem b2;

   s.addItem(&b1);
   s.addItem(&b2);
   s.addItem(&b3);

   // Iterate all item types as constant items
   qDebug() << "all, range-for";
   for (auto item : ConstIterableSceneAdapter<>(&s)) qDebug() << item;
   qDebug() << "all, Q_FOREACH";
   Q_FOREACH (const QGraphicsItem * item, ConstIterableSceneAdapter<>(&s)) qDebug() << item;

   // Iterate ellipses only
   qDebug() << "ellipses, range-for";
   for (auto item : IterableSceneAdapter<QGraphicsEllipseItem>(&s)) qDebug() << item;
   qDebug() << "ellipses, Q_FOREACH";
   Q_FOREACH (QGraphicsEllipseItem * item, IterableSceneAdapter<QGraphicsEllipseItem>(&s)) qDebug() << item;
}

Some rudimentary tests:

#include <QtWidgets>

void tests() {
   QGraphicsScene s;
   QGraphicsLineItem b1, b3;
   QGraphicsEllipseItem b2;

   IterableSceneAdapter<> s0{&s};
   auto i0 = s0.begin();
   qDebug() << i0; Q_ASSERT(i0 == s0.begin() && i0 == s0.end());

   s.addItem(&b1);
   s.addItem(&b2);
   s.addItem(&b3);

   IterableSceneAdapter<> s1{&s};
   auto i1 = s1.begin();
         qDebug() << i1; Q_ASSERT(i1 == s1.begin() && i1 != s1.end());
   ++i1; qDebug() << i1; Q_ASSERT(i1 != s1.begin() && i1 != s1.end());
   ++i1; qDebug() << i1; Q_ASSERT(i1 != s1.begin() && i1 != s1.end());
   ++i1; qDebug() << i1; Q_ASSERT(i1 != s1.begin() && i1 == s1.end());
   --i1; qDebug() << i1; Q_ASSERT(i1 != s1.begin() && i1 != s1.end());
   --i1; qDebug() << i1; Q_ASSERT(i1 != s1.begin() && i1 != s1.end());
   --i1; qDebug() << i1; Q_ASSERT(i1 == s1.begin() && i1 != s1.end());

   IterableSceneAdapter<QGraphicsEllipseItem> s2{&s};
   auto i2 = s2.begin();
         qDebug() << i2; Q_ASSERT(i2 == s2.begin() && i2 != s2.end());
   ++i2; qDebug() << i2; Q_ASSERT(i2 != s2.begin() && i2 == s2.end());
   --i2; qDebug() << i2; Q_ASSERT(i2 == s2.begin() && i2 != s2.end());
}
Comments