Implement for reading

In this article I’ll explain what steps need to be performed to have a working GL model which is ready for providing data for the views. The article will consist of two parts – in the first part I’ll be saying about things which apply to implementing any custom model and in the second part I’ll focus on the GL model itself.

Implementing a model in general

To have a working model, one needs to implement some methods from the QAbstractItemModel interface. If you take a look at QAbstractItemModel docs, you’ll notice that some of the methods are declared pure abstract:

virtual int columnCount ( const QModelIndex & parent = QModelIndex() ) const = 0
virtual QVariant data ( const QModelIndex & index, int role = Qt::DisplayRole ) const = 0
virtual QModelIndex index ( int row, int column, const QModelIndex & parent = QModelIndex() ) const = 0
virtual QModelIndex parent ( const QModelIndex & index ) const = 0
virtual int rowCount ( const QModelIndex & parent = QModelIndex() ) const = 0

These need to be implemented if you want to create instances of your model class, so this suggests that these methods are responsible for uniqueness of the model (and that suggestion is correct).

Model geometry

int columnCount ( const QModelIndex & parent = QModelIndex() ) const
int rowCount ( const QModelIndex & parent = QModelIndex() ) const

These two methods are responsible for defining the geometry of the model. Both of them take a QModelIndex object (null one by default) which informs about the item we have to provide data for. As the method names suggest, these return the number of columns and number of rows of a table containing children of the item specified by the parent argument. If the argument is not valid (like the default value), methods need to return values for the top level of the model (it’s often said that the index referrs to a so called root of the model).

For example, if you want to have a tabular model containing data for a player in the “Battleships” game, you’d implement those methods like so:

int BattleshipsModel::columnCount(const QModelIndex &parent) const {
    if(parent.isValid())
        return 0;  // items don't have children
    return 10;  // "root item" has children in 10 columns
}
int BattleshipsModel::rowCount(const QModelIndex &parent) const {
    if(parent.isValid())
        return 0;  // items don't have children
    return 10;  // "root item" has children in 10 rows
}

The view will first query the model for the number of rows and columns for the root item (invalid parent index) and then it’ll query for the number of rows and columns for each of the children of the root item and then do the same for their children and so on until it reaches a node without children (0 rows or 0 columns).

Model traversal

QModelIndex parent ( const QModelIndex & index ) const
QModelIndex index ( int row, int column, const QModelIndex & parent = QModelIndex() ) const

Very often it is needed to traverse the model and to be able to do that the two above mentioned methods need to be implemented. The first one returns an index of an item which is a parent item to the one given by the index argument which allows the model to be traversed bottom-up (from lower levels to the top level). The second method returns an index for an item which is in row row and column column and is a child of an item described by theparent index. This in turn allows the model to be traversed top-bottom (from upper to lower levels) and to reach siblings of an item (traversal on the same level).

The index() method looks up the item specified by given coordinates and uses a protected method of the abstract item model called createIndex() which creates an index for given coordinates and allows to store an additional pointer or number (identifier) in the index. You can use this additional value for whatever you see fit – it is most often used to hold a pointer to an item in internal data structures which holds information about that particular cell of the model (for example you can store a pointer to an item in a QList object) which makes it really easy to fetch information about the object in other methods using QModelIndex::internalPointer() or QModelIndex::internalId(). Of course if you don’t need such an internal value, you don’t have to use it and in such a situation you can create a QModelIndex object without using createIndex().

Here is an example implementation of the method for a single column tree model keeping its data in a Node structure (kept in m_root member of the model) having a QList> m_children member to keep its children:

QModelIndex ListModel::index(int row, int column, const QModelIndex &parent) const {
    if(column!=0)
      return QModelIndex(); // the model only has a single column
    if(row<0 || row > rowCount(parent))
      return QModelIndex(); // make sure the row number is valid
      Node *node = m_root;
    if(parent.isValid())
      node = parent.internalPointer(); // fetch the internal pointer from the parent
    return createIndex(row, 0, node->m_children.at(row)); // create and return an index
}

Using an internal pointer has allowed me to escape from the need to find the parent item in internal data structures.

If you have a flat model, then parent() will always return an invalid index (meaning that items don’t have a parent, which is another way for saying that the “root item” is the parent of all items) and for hierarchical models it usually looks up the parent of the item in internal datastructures and returns an index for it.

An example implementation for a model holding QGraphicsScene items follows:

QModelIndex GraphicsScene::parent ( const QModelIndex & index ) const {
  if(!index.isValid())
    return QModelIndex(); // invalid items don't have parents
 
  QGraphicsItem *gitem = dynamic_cast<QGraphicsItem*>(index.internalPointer());
  if(!gitem)
    return QModelIndex(); // invalid internal pointer
 
  QGraphicsItem *gparent = gitem->parentItem(); // fetch the parent item
  if(!gparent)
    return QModelIndex(); // item is top level (doesn't have a parent)
 
  bool parentIsTop = gparent->parentItem() !=0; // does parent have a parent?
 
   // fetch the list of all children of the grandparent item (if exists)
   // or the scene (for parent being a top level item)
  QList<QGraphicsItem*> items = parentIsTop ? gparent->scene()->items();
                                            : gparent->parentItem()->children();
 
  // "items" is now used to determine the row of the parent
  int row = items.indexOf(gparent); // get row of the parent in its parent's child list
 
  return createIndex(row, 0, gparent); // create and return an index
}

Fetching data

QVariant data ( const QModelIndex & index, int role = Qt::DisplayRole ) const

The last thing that needs to be done is to actually provide a way to read data from the model. The data() method is responsible for that. In this method you have to look up the piece of data from your source of data (be it an internal structure or an external source) and return it to the caller. You should provide data for all available roles and model indexes. If a data piece for a particular index or a particular role is not available, simply return an empty QVariant.

Here is an example for returning data for a model holding items as pointers to QGraphicsItem objects. Let’s assume we want to display names of items (held in QGraphicsItem::data() within a “0” key) and we want to display a red “unnamed” text if the item doesn’t have a name and we want the name to be aligned to the right if the name is an integer:

QVariant GraphicsScene::data ( const QModelIndex & index, int role = Qt::DisplayRole ) const {
  if(!index.isValid())
    return QVariant(); // return empty values for invalid items
  QGraphicsItem *item = dynamic_cast<QGraphicsItem*>(index.internalPointer());
  if(!item)
    return QVariant(); // return empty values for invalid internal pointers
  QVariant name = item->data(0); // fetch the name
  switch(role){
  case Qt::DisplayRole:
    if(name.toString().isEmpty())
      return "unnamed";            // return "unnamed" for empty names
    else return name;              // return the name otherwise
  case Qt::TextAlignmentRole:
    if(name.canConvert(QVariant::Int))
      return Qt::AlignRight;       // return right alignment if variant is int
    else return Qt::AlignLeft;     // return left alignment otherwise
  case Qt::ForegroundRole:
    if(name.toString().isEmpty())
      return Qt::red;              // return red colour for unnamed items
    else return QVariant();        // return default colour otherwise
  default:
    return QVariant();             // return empty (default) values for other roles
  }
}

There is also one more method which you might want to reimplement (it’s not abstract):

QVariant headerData ( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const

This is very similar to data(), but is related to headers of the model. The implementation is straightforward, so I won’t show an example for that.

Readable GL model

The implementation of the GL model is very simple – it comes down to implementing all the above mentioned methods. First the class header:

class QwwGLModel : public QAbstractItemModel {
public:
  enum { XRole = Qt::UserRole, YRole, ZRole, MaterialRole,
         TextureRole, FlagsRole, TypeRole, XNormalRole,
         YNormalRole, ZNormalRole, NameRole, SizeXRole,
         SizeYRole, SizeZRole, Attr1Role, Attr2Role };
  QwwGLModel(QObject *parent = 0);
  ~QwwGLModel();
  int columnCount ( const QModelIndex & parent = QModelIndex() ) const;
  QVariant data ( const QModelIndex & index, int role = Qt::DisplayRole ) const;
  QVariant headerData ( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const;
  QModelIndex index ( int row, int column, const QModelIndex & parent = QModelIndex() ) const;
  QModelIndex parent ( const QModelIndex & index ) const;
  int rowCount ( const QModelIndex & parent = QModelIndex() ) const;
private:
  QwwGLModelNode *_rootItem;
};

Now let’s try to implement those methods, first two are very simple:

QwwGLModel::QwwGLModel(QObject *parent) : QAbstractItemModel(parent){
  _rootItem = new QwwGLModelNode;
}
 
QwwGLModel::~QwwGLModel(){
  delete _rootItem;
}

I think the only thing that needs explanation is the rootItem member. It is a convenient way to store data for your model. Thanks to that rootItem it’ll be able to operate easily on parents of items as you’ll see in a while.

Model geometry is very easy as well, so I don’t think there is any need to comment that. Please notice that in rowCount() I exploit the fact that every valid item has a valid QwwGLModelNode parent.

int QwwGLModel::columnCount( const QModelIndex &parent) const {
  return 1;
}
 
int QwwGLModel::rowCount( const QModelIndex &parent) const {
  QwwGLModelNode *parentNode = _rootItem;
  if(parent.isValid())
    parentNode = static_cast<QwwGLModelNode*>(parent.internalPointer());
  return parentNode->children().count();
}

Now for the traversal:

QModelIndex QwwGLModel::index ( int row, int column, const QModelIndex & parent ) const {
  if(column!=0)
    return QModelIndex();
  QwwGLModelNode *parentNode = _rootItem;
  if(parent.isValid())
    parentNode = static_cast<QwwGLModelNode*>(parent.internalPointer());
  QList<QwwGLModelNode*> children = parentNode->children();
  if(row>=children.count() || row <0)
    return QModelIndex();
  return createIndex(row, column, children.at(row));
}

First I check whether column is valid and then I try to get a pointer to a parent item. If the parent index is not valid, that means that the parent node is really _rootItem, otherwise I retrieve the internal pointer of the parent item. All that remains is to get the pointer to the specified item using parent’s children list.

QModelIndex QwwGLModel::parent ( const QModelIndex & index ) const {
  if(!index.isValid())
    return QModelIndex();
  QwwGLModelNode *parentNode = static_cast<QwwGLModelNode*>(index.internalPointer())->parent();
  if(parentNode==_rootItem)
    return QModelIndex();
  int row = parentNode->parent()->children().indexOf(parentNode);
  return createIndex(row, 0, parentNode);
}

This method is very simmilar to the previous one. First I check whether the given index is valid (which implies that it has a valid parent parent (either the _rootItem or a real item). If index points to a top level item, then I return an invalid index for the parent. Otherwise I determine the row number of the parent in its parent’s children list and return a proper index for it.

Now for methods returning the actual data:

QVariant QwwGLModel::headerData ( int section, Qt::Orientation orientation, int role ) const {
  if(orientation!=Qt::Horizontal || section!=0 || role!=Qt::DisplayRole)
    return QVariant();
  return tr("GL Scene");
}

The header is very simple – if the query is for the display piece of the first section of the horizontal header, return “GL Scene”. Otherwise return an empty value.

QVariant QwwGLModel::data ( const QModelIndex & index, int role = Qt::DisplayRole ) const {
  static char *__roleAttrs[] = { "X", "Y", "Z", "Material", "Texture", "Flags", "Type", "XN",
                                 "YN", "ZN", "Name", "XS", "YS", "ZS", "Attr1", "Attr2" };
  if(!index.isValid())
    return QVariant();
  QwwGLModelNode *node = static_cast<QwwGLModelNode*>(index.internalPointer());
  switch(role){
    case Qt::NameRole:
    case Qt::DisplayRole: // return the name
      return node->attribute("Name");
    case Qt::DecorationRole: // return an icon for each type
      return QIcon(QPixmap(QString(":/types/%1.png").arg((int)node->type())));
    case Qt::ToolTipRole:  // return the coordinate set
      return QString("X: %1; Y: %2; Z: %3").arg(node->attribute("X"))
                                           .arg(node->attribute("Y"))
                                           .arg(node->attribute("Z"));
    case TypeRole:
      return (int)node->type();
    default:
      if(role>Attr2Role || role <0 || role-XRole>15)
        return QVariant();
      return node->attribute(__roleAttrs[role-XRole]);
  }
}

I provided only a simple implementation of the data() method. You could extend it to get rid of QwwGLModelNode::atribute() or provide some other data pieces.

The model is readable now, but as it’s not writable, I need to initialise it with some data to be able to test it, so here goes:

void QwwGLModel::init(){
  QwwGLModelNode *t1 = new QwwGLModelNode(_rootItem);
  t1->setType(0); // point
  t1->setAttribute("Name", "Point");
  t1->setAttribute("X", 10.0);
  t1->setAttribute("Y", 10.0);
  t1->setAttribute("Z", -5.0);
  QwwGLModelNode *t2 = new QwwGLModelNode(_rootItem);
  t2->setType(1); // line
  t2->setAttribute("Name", "Line");
  QwwGLModelNode *p1 = new QwwGLModelNode(t2);
  p1->setType(0); // point
  p1->setAttribute("X", 0);
  p1->setAttribute("Y", 0);
  p1->setAttribute("Z", 0);
  p1->setAttribute("Name", "Point 1");
  QwwGLModelNode *p2 = new QwwGLModelNode(t2);
  p2->setType(0); // point
  p2->setAttribute("X", 5.0);
  p2->setAttribute("Y", 5.0);
  p2->setAttribute("Z", 15.5);
  p2->setAttribute("Name", "Point 2");
}

And just to see the result:

int main(int argc, char **argv){
  QApplication app(argc, argv);
  QwwGLModel model;
  model.init();
  QTreeView tv;
  tv.setModel(&model);
  tv.show();
  return app.exec();
}

Remember to add proper includes and to map QwwGLModelType to int.

You can download the complete example here.

Leave a Reply