Model Indices in Job Tree View

The Job Tree View uses RowLocation objects in it’s API as an abstraction over QModelIndexs, which are used internally to access QStandardItems from the ‘models’ provided by Qt. As such, code which simply uses the JobTreeView does not need to know anything about QModelIndexs.

MVC Models

Sections of Qt such as QTreeView are designed as an MVC framework rather than an MVP framework, so working with some sections of it’s API becomes difficult when using MVP. Models in MVC are directly accessible from the view which is free to read the data directly from it. In MVP however, data access is marshalled through the presenter, and both models and presenters are not supposed to be coupled to any particular view implementation.

The Main Model

The JobTreeView solves this problem by keeping an internal instance of QStandardItemModel, a Qt ‘model’ which fulfils Qt’s requirements for a ‘model’ and acts as the view state. We refer to this instance as the ‘main model’.

Filtered Model

To take advantage of the filtering functionality offered by the QTreeView, the JobTreeView also manages an instance of FilteredTreeModel. This is a class derived from QSortFilterProxyModel - a filtered version of the ‘main model’.

Strongly Typed Indexes

The QModelIndexs for the filtered model cannot be used to directly access items in the main model. Likewise, the QModelIndexs for the main model cannot be used to directly access items in the filtered model. Indexes must be explicitly converted between the two spaces.

To make this less bug prone, the header file StrictQModelIndices.h defines two types, QModelIndexForMainModel and QModelIndexForFilteredModel. Functions which wish to constrain the indices they accept and/or return can now make that explicit and use the type system to catch errors.

Converting Between Index Spaces

The filtered model holds a subset of the items in the main model, therefore:

  • Conversion from an index for the filtered model to an index for the main model should always be successful.
  • Conversion from an index for the main model to a valid index for the main model could be unsuccessful.

Given a QModelIndex, in order to convert to a strongly typed variant you must know whether it originated from the filtered model or the main model. Conversion to the appropriate strongly typed variant is done via assertion using the functions fromMainModel and fromFilteredModel provided by StrictQModelIndices.h. These functions attempt to check the assertion by requiring a reference to the model you are claiming the index is for and comparing it with the pointer returned by calling .model() on the model index. However this is only a heuristic since the index could refer to a cell which has since been removed from the filtered model due to a change of filter.

After the construction of the JobTreeView, indices provided through QTreeView APIs are indices for the filtered model and must be mapped to the main model before being used to modify it. RowLocations on the other hand always correspond with indices for the main model.

Asserting that an index is for one space or the other as described above is not the same as mapping from one space into the other. This is performed using the functions mapToMainModel and mapToFilteredModel defined in JobTreeView which internally call methods of the filtered model.

Type Erasure

Some functions are ambivalent to which model an index belongs to, a good example might be those in QtBasicNavigation.h - in order to still be able to use these functions without requiring them all to be templates, the strict versions have a member function .untyped() which returns the internal plain old QModelIndex.