MVC Item Models

A common data model involves lists, tables and trees of more or less uniform items. Several standard widgets provided by the library use such a model. The abstract WAbstractItemModel base class provides the interface which is used by these view classes.

Not only Table Views and Tree Views display (and interact with) data from an item model, but so do also the list-oriented widgets such as Combo box, Selection box, Auto complete, and charting widgets such as Cartesian Chart, Scatter Plot and Pie Chart.

The model

An item model is essentially a table of items, where each item can be a parent to a nested table of items.

Since recursive statements like the one above can confuse even a seasoned programmer, let's start from a simple model, and extend it to more complex instances.

Lists

In its most simple and perhaps most common form, an item model simply stores a list of items. Such a model has only one column.

Each item may hold different facets of the data, stored as different Item Data Roles. If a table has two dimensions (rows and columns), then data roles could be considered as a third dimension.

The common use-case for item data roles is that for a single item, there may be a textual representation, but also an icon, a customized style class, a link, etc...

The built-in views will thus interpret subsets of this data to render a single item. In particular, the following data roles are commonly supported:

DisplayRole
Rendered as text
DecorationRole
Rendered as icons
CheckStateRole
Rendered as check box state
LinkRole
Rendered as links
ToolTipRole
Rendered as tool tips
StyleClassRole
Rendered as style classes
See ItemDataRole for a more complete list of standard roles, whose use may depend on the View.

Tables

As a straight forward generalization to lists, tables may include more than one more column, and each item can be identified by a (row, column) pair.

Trees

A tree model is - like a list - a model with a single column. In addition, each item may be the parent of another list.

At this point it becomes necessary to introduce the concept of a WModelIndex to uniquely identify an item. A model index is a data structure containing:

row
The row number
column
The column number
parent
The parent model index

The recursion is thus achieved by associating a parent index with each item index. By convention, top-level items have an Invalid index (which is a default constructed WModelIndex). To make the recursive definition consistent, one can also imagine an invisible root item (corresponding to the "invalid" index) which is the parent of the top-level items.

Tree Tables

Finally, a tree table extends the list model by allow additional columns of data to be associated with each item row.

None of the standard Views render hierarchical data that is not present in the first column ! While such data structures can indeed be defined by item models, this will effectively be ignored by the standard View classes.

Top

Standard model implementations

To get you going, and more than sufficient for simple needs, the library provides a number of standard and generic models, which store the data in memory.

  • WStandardItemModel can implement any kind of item model, being composed of a table of WStandardItem's, which each can contain itself a nested table of WStandardItem items.
  • WStringListModel only supports lists with a single column, and is thus optimized for list-oriented widgets (combo-boxes and the like).
One particularly useful standard model is the Dbo::QueryModel which is a tabular model backed by database data.

Custom models

Separating models from views would be not very useful, if it were not of the ability to implement customized models. These could be models that compute some or all data on the fly, or fetch this information from an underlying database or file system, or simply display information from an existing data structure in a tabular/tree-like way.

Table models

As a minimum, a custom table model should reimplement the following methods from WAbstractTableModel:

int rowCount()
Returns the table row count (only for the invalid parent!)
int columnCount()
Returns the table column count (only for the invalid parent!)
boost::any data()
Returns the data for an item, and a particular role
boost::any headerData()
Returns the header data for a column

As an example of a custom table model, consider the following (nonsensical) model that simply displays row/column information for each item.

Example
Column 0
Column 1
Column 2
Column 3
Column 4
Column 5
Column 6
Column 7
Column 8
Column 9
Column 10
Column 11
Column 12
Column 13
Column 14
Column 15
Column 16
Column 17
Column 18
Column 19
Column 20
Column 21
Column 22
Column 23
Column 24
Column 25
Column 26
Column 27
Column 28
Column 29
Column 30
Column 31
Column 32
Column 33
Column 34
Column 35
Column 36
Column 37
Column 38
Column 39
Column 40
Column 41
Column 42
Column 43
Column 44
Column 45
Column 46
Column 47
Column 48
Column 49
Row 0
Item row 0, col 1
Item row 0, col 2
Item row 0, col 3
Item row 0, col 4
Item row 0, col 5
Item row 0, col 6
Item row 0, col 7
Item row 0, col 8
Item row 0, col 9
Item row 0, col 10
Item row 0, col 11
Item row 0, col 12
Item row 0, col 13
Item row 0, col 14
Item row 0, col 15
Item row 0, col 16
Item row 0, col 17
Item row 0, col 18
Item row 0, col 19
Item row 0, col 20
Item row 0, col 21
Item row 0, col 22
Item row 0, col 23
Item row 0, col 24
Item row 0, col 25
Item row 0, col 26
Item row 0, col 27
Item row 0, col 28
Item row 0, col 29
Item row 0, col 30
Item row 0, col 31
Item row 0, col 32
Item row 0, col 33
Item row 0, col 34
Item row 0, col 35
Item row 0, col 36
Item row 0, col 37
Item row 0, col 38
Item row 0, col 39
Item row 0, col 40
Item row 0, col 41
Item row 0, col 42
Item row 0, col 43
Item row 0, col 44
Item row 0, col 45
Item row 0, col 46
Item row 0, col 47
Item row 0, col 48
Item row 0, col 49
Row 1
Item row 1, col 1
Item row 1, col 2
Item row 1, col 3
Item row 1, col 4
Item row 1, col 5
Item row 1, col 6
Item row 1, col 7
Item row 1, col 8
Item row 1, col 9
Item row 1, col 10
Item row 1, col 11
Item row 1, col 12
Item row 1, col 13
Item row 1, col 14
Item row 1, col 15
Item row 1, col 16
Item row 1, col 17
Item row 1, col 18
Item row 1, col 19
Item row 1, col 20
Item row 1, col 21
Item row 1, col 22
Item row 1, col 23
Item row 1, col 24
Item row 1, col 25
Item row 1, col 26
Item row 1, col 27
Item row 1, col 28
Item row 1, col 29
Item row 1, col 30
Item row 1, col 31
Item row 1, col 32
Item row 1, col 33
Item row 1, col 34
Item row 1, col 35
Item row 1, col 36
Item row 1, col 37
Item row 1, col 38
Item row 1, col 39
Item row 1, col 40
Item row 1, col 41
Item row 1, col 42
Item row 1, col 43
Item row 1, col 44
Item row 1, col 45
Item row 1, col 46
Item row 1, col 47
Item row 1, col 48
Item row 1, col 49
Row 2
Item row 2, col 1
Item row 2, col 2
Item row 2, col 3
Item row 2, col 4
Item row 2, col 5
Item row 2, col 6
Item row 2, col 7
Item row 2, col 8
Item row 2, col 9
Item row 2, col 10
Item row 2, col 11
Item row 2, col 12
Item row 2, col 13
Item row 2, col 14
Item row 2, col 15
Item row 2, col 16
Item row 2, col 17
Item row 2, col 18
Item row 2, col 19
Item row 2, col 20
Item row 2, col 21
Item row 2, col 22
Item row 2, col 23
Item row 2, col 24
Item row 2, col 25
Item row 2, col 26
Item row 2, col 27
Item row 2, col 28
Item row 2, col 29
Item row 2, col 30
Item row 2, col 31
Item row 2, col 32
Item row 2, col 33
Item row 2, col 34
Item row 2, col 35
Item row 2, col 36
Item row 2, col 37
Item row 2, col 38
Item row 2, col 39
Item row 2, col 40
Item row 2, col 41
Item row 2, col 42
Item row 2, col 43
Item row 2, col 44
Item row 2, col 45
Item row 2, col 46
Item row 2, col 47
Item row 2, col 48
Item row 2, col 49
Row 3
Item row 3, col 1
Item row 3, col 2
Item row 3, col 3
Item row 3, col 4
Item row 3, col 5
Item row 3, col 6
Item row 3, col 7
Item row 3, col 8
Item row 3, col 9
Item row 3, col 10
Item row 3, col 11
Item row 3, col 12
Item row 3, col 13
Item row 3, col 14
Item row 3, col 15
Item row 3, col 16
Item row 3, col 17
Item row 3, col 18
Item row 3, col 19
Item row 3, col 20
Item row 3, col 21
Item row 3, col 22
Item row 3, col 23
Item row 3, col 24
Item row 3, col 25
Item row 3, col 26
Item row 3, col 27
Item row 3, col 28
Item row 3, col 29
Item row 3, col 30
Item row 3, col 31
Item row 3, col 32
Item row 3, col 33
Item row 3, col 34
Item row 3, col 35
Item row 3, col 36
Item row 3, col 37
Item row 3, col 38
Item row 3, col 39
Item row 3, col 40
Item row 3, col 41
Item row 3, col 42
Item row 3, col 43
Item row 3, col 44
Item row 3, col 45
Item row 3, col 46
Item row 3, col 47
Item row 3, col 48
Item row 3, col 49
Row 4
Item row 4, col 1
Item row 4, col 2
Item row 4, col 3
Item row 4, col 4
Item row 4, col 5
Item row 4, col 6
Item row 4, col 7
Item row 4, col 8
Item row 4, col 9
Item row 4, col 10
Item row 4, col 11
Item row 4, col 12
Item row 4, col 13
Item row 4, col 14
Item row 4, col 15
Item row 4, col 16
Item row 4, col 17
Item row 4, col 18
Item row 4, col 19
Item row 4, col 20
Item row 4, col 21
Item row 4, col 22
Item row 4, col 23
Item row 4, col 24
Item row 4, col 25
Item row 4, col 26
Item row 4, col 27
Item row 4, col 28
Item row 4, col 29
Item row 4, col 30
Item row 4, col 31
Item row 4, col 32
Item row 4, col 33
Item row 4, col 34
Item row 4, col 35
Item row 4, col 36
Item row 4, col 37
Item row 4, col 38
Item row 4, col 39
Item row 4, col 40
Item row 4, col 41
Item row 4, col 42
Item row 4, col 43
Item row 4, col 44
Item row 4, col 45
Item row 4, col 46
Item row 4, col 47
Item row 4, col 48
Item row 4, col 49
Row 5
Item row 5, col 1
Item row 5, col 2
Item row 5, col 3
Item row 5, col 4
Item row 5, col 5
Item row 5, col 6
Item row 5, col 7
Item row 5, col 8
Item row 5, col 9
Item row 5, col 10
Item row 5, col 11
Item row 5, col 12
Item row 5, col 13
Item row 5, col 14
Item row 5, col 15
Item row 5, col 16
Item row 5, col 17
Item row 5, col 18
Item row 5, col 19
Item row 5, col 20
Item row 5, col 21
Item row 5, col 22
Item row 5, col 23
Item row 5, col 24
Item row 5, col 25
Item row 5, col 26
Item row 5, col 27
Item row 5, col 28
Item row 5, col 29
Item row 5, col 30
Item row 5, col 31
Item row 5, col 32
Item row 5, col 33
Item row 5, col 34
Item row 5, col 35
Item row 5, col 36
Item row 5, col 37
Item row 5, col 38
Item row 5, col 39
Item row 5, col 40
Item row 5, col 41
Item row 5, col 42
Item row 5, col 43
Item row 5, col 44
Item row 5, col 45
Item row 5, col 46
Item row 5, col 47
Item row 5, col 48
Item row 5, col 49
Row 6
Item row 6, col 1
Item row 6, col 2
Item row 6, col 3
Item row 6, col 4
Item row 6, col 5
Item row 6, col 6
Item row 6, col 7
Item row 6, col 8
Item row 6, col 9
Item row 6, col 10
Item row 6, col 11
Item row 6, col 12
Item row 6, col 13
Item row 6, col 14
Item row 6, col 15
Item row 6, col 16
Item row 6, col 17
Item row 6, col 18
Item row 6, col 19
Item row 6, col 20
Item row 6, col 21
Item row 6, col 22
Item row 6, col 23
Item row 6, col 24
Item row 6, col 25
Item row 6, col 26
Item row 6, col 27
Item row 6, col 28
Item row 6, col 29
Item row 6, col 30
Item row 6, col 31
Item row 6, col 32
Item row 6, col 33
Item row 6, col 34
Item row 6, col 35
Item row 6, col 36
Item row 6, col 37
Item row 6, col 38
Item row 6, col 39
Item row 6, col 40
Item row 6, col 41
Item row 6, col 42
Item row 6, col 43
Item row 6, col 44
Item row 6, col 45
Item row 6, col 46
Item row 6, col 47
Item row 6, col 48
Item row 6, col 49
Row 7
Item row 7, col 1
Item row 7, col 2
Item row 7, col 3
Item row 7, col 4
Item row 7, col 5
Item row 7, col 6
Item row 7, col 7
Item row 7, col 8
Item row 7, col 9
Item row 7, col 10
Item row 7, col 11
Item row 7, col 12
Item row 7, col 13
Item row 7, col 14
Item row 7, col 15
Item row 7, col 16
Item row 7, col 17
Item row 7, col 18
Item row 7, col 19
Item row 7, col 20
Item row 7, col 21
Item row 7, col 22
Item row 7, col 23
Item row 7, col 24
Item row 7, col 25
Item row 7, col 26
Item row 7, col 27
Item row 7, col 28
Item row 7, col 29
Item row 7, col 30
Item row 7, col 31
Item row 7, col 32
Item row 7, col 33
Item row 7, col 34
Item row 7, col 35
Item row 7, col 36
Item row 7, col 37
Item row 7, col 38
Item row 7, col 39
Item row 7, col 40
Item row 7, col 41
Item row 7, col 42
Item row 7, col 43
Item row 7, col 44
Item row 7, col 45
Item row 7, col 46
Item row 7, col 47
Item row 7, col 48
Item row 7, col 49
Row 8
Item row 8, col 1
Item row 8, col 2
Item row 8, col 3
Item row 8, col 4
Item row 8, col 5
Item row 8, col 6
Item row 8, col 7
Item row 8, col 8
Item row 8, col 9
Item row 8, col 10
Item row 8, col 11
Item row 8, col 12
Item row 8, col 13
Item row 8, col 14
Item row 8, col 15
Item row 8, col 16
Item row 8, col 17
Item row 8, col 18
Item row 8, col 19
Item row 8, col 20
Item row 8, col 21
Item row 8, col 22
Item row 8, col 23
Item row 8, col 24
Item row 8, col 25
Item row 8, col 26
Item row 8, col 27
Item row 8, col 28
Item row 8, col 29
Item row 8, col 30
Item row 8, col 31
Item row 8, col 32
Item row 8, col 33
Item row 8, col 34
Item row 8, col 35
Item row 8, col 36
Item row 8, col 37
Item row 8, col 38
Item row 8, col 39
Item row 8, col 40
Item row 8, col 41
Item row 8, col 42
Item row 8, col 43
Item row 8, col 44
Item row 8, col 45
Item row 8, col 46
Item row 8, col 47
Item row 8, col 48
Item row 8, col 49
Row 9
Item row 9, col 1
Item row 9, col 2
Item row 9, col 3
Item row 9, col 4
Item row 9, col 5
Item row 9, col 6
Item row 9, col 7
Item row 9, col 8
Item row 9, col 9
Item row 9, col 10
Item row 9, col 11
Item row 9, col 12
Item row 9, col 13
Item row 9, col 14
Item row 9, col 15
Item row 9, col 16
Item row 9, col 17
Item row 9, col 18
Item row 9, col 19
Item row 9, col 20
Item row 9, col 21
Item row 9, col 22
Item row 9, col 23
Item row 9, col 24
Item row 9, col 25
Item row 9, col 26
Item row 9, col 27
Item row 9, col 28
Item row 9, col 29
Item row 9, col 30
Item row 9, col 31
Item row 9, col 32
Item row 9, col 33
Item row 9, col 34
Item row 9, col 35
Item row 9, col 36
Item row 9, col 37
Item row 9, col 38
Item row 9, col 39
Item row 9, col 40
Item row 9, col 41
Item row 9, col 42
Item row 9, col 43
Item row 9, col 44
Item row 9, col 45
Item row 9, col 46
Item row 9, col 47
Item row 9, col 48
Item row 9, col 49
Row 10
Item row 10, col 1
Item row 10, col 2
Item row 10, col 3
Item row 10, col 4
Item row 10, col 5
Item row 10, col 6
Item row 10, col 7
Item row 10, col 8
Item row 10, col 9
Item row 10, col 10
Item row 10, col 11
Item row 10, col 12
Item row 10, col 13
Item row 10, col 14
Item row 10, col 15
Item row 10, col 16
Item row 10, col 17
Item row 10, col 18
Item row 10, col 19
Item row 10, col 20
Item row 10, col 21
Item row 10, col 22
Item row 10, col 23
Item row 10, col 24
Item row 10, col 25
Item row 10, col 26
Item row 10, col 27
Item row 10, col 28
Item row 10, col 29
Item row 10, col 30
Item row 10, col 31
Item row 10, col 32
Item row 10, col 33
Item row 10, col 34
Item row 10, col 35
Item row 10, col 36
Item row 10, col 37
Item row 10, col 38
Item row 10, col 39
Item row 10, col 40
Item row 10, col 41
Item row 10, col 42
Item row 10, col 43
Item row 10, col 44
Item row 10, col 45
Item row 10, col 46
Item row 10, col 47
Item row 10, col 48
Item row 10, col 49
Row 11
Item row 11, col 1
Item row 11, col 2
Item row 11, col 3
Item row 11, col 4
Item row 11, col 5
Item row 11, col 6
Item row 11, col 7
Item row 11, col 8
Item row 11, col 9
Item row 11, col 10
Item row 11, col 11
Item row 11, col 12
Item row 11, col 13
Item row 11, col 14
Item row 11, col 15
Item row 11, col 16
Item row 11, col 17
Item row 11, col 18
Item row 11, col 19
Item row 11, col 20
Item row 11, col 21
Item row 11, col 22
Item row 11, col 23
Item row 11, col 24
Item row 11, col 25
Item row 11, col 26
Item row 11, col 27
Item row 11, col 28
Item row 11, col 29
Item row 11, col 30
Item row 11, col 31
Item row 11, col 32
Item row 11, col 33
Item row 11, col 34
Item row 11, col 35
Item row 11, col 36
Item row 11, col 37
Item row 11, col 38
Item row 11, col 39
Item row 11, col 40
Item row 11, col 41
Item row 11, col 42
Item row 11, col 43
Item row 11, col 44
Item row 11, col 45
Item row 11, col 46
Item row 11, col 47
Item row 11, col 48
Item row 11, col 49
1 of 834

source
#include <Wt/WAbstractTableModel.h>

class VirtualModel : public Wt::WAbstractTableModel
{
public:
  VirtualModel(int rows, int columns)
    : WAbstractTableModel(),
      rows_(rows),
      columns_(columns)
  { }

  virtual int rowCount(const Wt::WModelIndex& parent = Wt::WModelIndex()) const
  {
    if (!parent.isValid())
      return rows_;
    else
      return 0;
  }

  virtual int columnCount(const Wt::WModelIndex& parent = Wt::WModelIndex()) const
  {
    if (!parent.isValid())
      return columns_;
    else
      return 0;
  }

  virtual Wt::cpp17::any data(const Wt::WModelIndex& index, Wt::ItemDataRole role = Wt::ItemDataRole::Display) const
  {
    if (role == Wt::ItemDataRole::Display) {
      if (index.column() == 0)
        return Wt::WString("Row {1}").arg(index.row());
      else
        return Wt::WString("Item row {1}, col {2}")
          .arg(index.row()).arg(index.column());
    } else {
      return Wt::cpp17::any();
    }
  }

  virtual Wt::cpp17::any headerData(int section,
                                Wt::Orientation orientation = Wt::Orientation::Horizontal,
                                Wt::ItemDataRole role = Wt::ItemDataRole::Display) const
  {
    if (orientation == Wt::Orientation::Horizontal) {
      if (role == Wt::ItemDataRole::Display) {
        return Wt::WString("Column {1}").arg(section);
      } else {
        return Wt::cpp17::any();
      }
    } else
      return Wt::cpp17::any();
  }

private:
  int rows_, columns_;
};

Note The instantiation of this model with a Table View is discussed in MVC Table Views.

Tree models

A custom tree model involves considerably more work. Each internal item (in the first column) which has children, needs to be identified by a unique 64-bit value (which may thus be a long long or a void * pointer). Depending on the source data, a suitable choice must be made for this data.

The following methods must be implemented for a minimally compliant hierarchical model:

int rowCount()
Returns the item children count
int columnCount()
Returns the column count (which is usually the same for each item in the first column)
boost::any data()
Returns the data for an item, and a particular role
boost::any headerData()
Returns the header data for a column
WModelIndex child()
Creates a child index
WModelIndex parent()
Creates a parent index

As an example of a tree table model, consider the following model that loads information from a git repository (in this case, Wt's git). Only a minimum of information is kept in memory: we allocate a data structure only for folders that are being expanded, for use as internal pointer data in model indexes.

Example
Type
File
  • .dprint.json
    • .editorconfig
      • .eslintignore
        • .eslintrc.yml
          • .gitignore
            • Folder
              .vscode/
              • Text
                CMakeLists.txt
                • Changelog
                  • Doxyfile
                    • INSTALL
                      • INSTALL.html
                        • INSTALL.win32.html
                          • LICENSE
                            • Markdown
                              README.md
                              • ReleaseNotes.html
                                • WConfig.h.in
                                  • WtInstall.cmake
                                    • Folder
                                      cmake/
                                      • Folder
                                        doc/
                                        • Folder
                                          examples/
                                          1 of 3

                                          source
                                          #include <Wt/WAbstractItemModel.h>
                                          
                                          #include "../../gitmodel/Git.h"
                                          
                                          class GitModel : public Wt::WAbstractItemModel
                                          {
                                          public:
                                              /*
                                               * A custom role for the file contents of a Git BLOB object.
                                               */
                                              static constexpr Wt::ItemDataRole ContentsRole = Wt::ItemDataRole::User + 1;
                                          
                                              GitModel(const std::string& repository)
                                                  : WAbstractItemModel()
                                              {
                                                  git_.setRepositoryPath(repository);
                                                  loadRevision("master");
                                              }
                                          
                                              void loadRevision(const std::string& revName) {
                                                  Git::ObjectId treeRoot = git_.getCommitTree(revName);
                                          
                                                  layoutAboutToBeChanged().emit(); // Invalidates model indexes
                                          
                                                  treeData_.clear();
                                                  childPointer_.clear();
                                          
                                                  /*
                                                   * This stores the tree root as treeData_[0]
                                                   */
                                                  treeData_.push_back(Tree(-1, -1, treeRoot, git_.treeSize(treeRoot)));
                                          
                                                  layoutChanged().emit();
                                              }
                                          
                                              virtual Wt::WModelIndex parent(const Wt::WModelIndex& index) const {
                                                  if (!index.isValid() || index.internalId() == 0) {
                                                      return Wt::WModelIndex(); // treeData_[0] is the tree root
                                                  } else {
                                                      const Tree& item = treeData_[index.internalId()];
                                                      return createIndex(item.index(), 0, item.parentId());
                                                  }
                                              }
                                          
                                              virtual Wt::WModelIndex index(int row, int column,
                                                                            const Wt::WModelIndex& parent = Wt::WModelIndex()) const {
                                                  int parentId;
                                          
                                                  if (!parent.isValid())
                                                      parentId = 0;
                                                  else {
                                                      int grandParentId = parent.internalId();
                                                      parentId = getTreeId(grandParentId, parent.row());
                                                  }
                                          
                                                  return createIndex(row, column, parentId);
                                              }
                                          
                                              virtual int columnCount(const Wt::WModelIndex& parent = Wt::WModelIndex()) const {
                                                  return 2;
                                              }
                                          
                                              virtual int rowCount(const Wt::WModelIndex& parent = Wt::WModelIndex()) const {
                                                  int treeId;
                                          
                                                  if (parent.isValid()) {
                                                      if (parent.column() != 0)
                                                          return 0;
                                                      Git::Object o = getObject(parent);
                                                      if (o.type == Git::Tree) { // is a folder
                                                          treeId = getTreeId(parent.internalId(), parent.row());
                                                      } else                     // is a file
                                                          return 0;
                                                  } else {
                                                      treeId = 0;
                                                  }
                                          
                                                  return treeData_[treeId].rowCount();
                                              }
                                          
                                              virtual Wt::cpp17::any data(const Wt::WModelIndex& index, Wt::ItemDataRole role = Wt::ItemDataRole::Display) const {
                                                  if (!index.isValid())
                                                      return Wt::cpp17::any();
                                          
                                                  Git::Object object = getObject(index);
                                          
                                                  switch (index.column()) {
                                                  case 0:
                                                      if (role == Wt::ItemDataRole::Display) {
                                                          if (object.type == Git::Tree)
                                                              return object.name + '/';
                                                          else
                                                              return object.name;
                                                      } else if (role == Wt::ItemDataRole::Decoration) {
                                                          if (object.type == Git::Blob)
                                                              return std::string("icons/git-blob.png");
                                                          else if (object.type == Git::Tree)
                                                              return std::string("icons/git-tree.png");
                                                      } else if (role == ContentsRole) {
                                                          if (object.type == Git::Blob)
                                                              return git_.catFile(object.id);
                                                      }
                                          
                                                      break;
                                                  case 1:
                                                      if (role == Wt::ItemDataRole::Display) {
                                                          if (object.type == Git::Tree)
                                                              return std::string("Folder");
                                                          else {
                                                              std::string suffix = getSuffix(object.name);
                                          
                                                              if (suffix == "C" || suffix == "cpp")
                                                                  return std::string("C++ Source");
                                                              else if (suffix == "h" ||
                                                                       (suffix == "" && !topLevel(index)))
                                                                  return std::string("C++ Header");
                                                              else if (suffix == "css")
                                                                  return std::string("CSS Stylesheet");
                                                              else if (suffix == "js")
                                                                  return std::string("JavaScript Source");
                                                              else if (suffix == "md")
                                                                  return std::string("Markdown");
                                                              else if (suffix == "png" || suffix == "gif")
                                                                  return std::string("Image");
                                                              else if (suffix == "txt")
                                                                  return std::string("Text");
                                                              else
                                                                  return Wt::cpp17::any();
                                                          }
                                                      }
                                                  }
                                          
                                                  return Wt::cpp17::any();
                                              }
                                          
                                              virtual Wt::cpp17::any headerData(int section,
                                                                            Wt::Orientation orientation = Wt::Orientation::Horizontal,
                                                                            Wt::ItemDataRole role = Wt::ItemDataRole::Display) const {
                                                  if (orientation == Wt::Orientation::Horizontal && role == Wt::ItemDataRole::Display) {
                                                      switch (section) {
                                                      case 0:
                                                          return std::string("File");
                                                      case 1:
                                                          return std::string("Type");
                                                      default:
                                                          return Wt::cpp17::any();
                                                      }
                                                  } else
                                                      return Wt::cpp17::any();
                                              }
                                          
                                          private:
                                              Git git_;
                                          
                                              /*
                                               * Identifies a folder given parent and index
                                               */
                                              struct ChildIndex {
                                                  int parentId;
                                                  int index;
                                          
                                                  ChildIndex(int aParent, int anIndex)
                                                      : parentId(aParent), index(anIndex) { }
                                          
                                                  bool operator< (const ChildIndex& other) const {
                                                      if (parentId < other.parentId)
                                                          return true;
                                                      else if (parentId > other.parentId)
                                                          return false;
                                                      else return index < other.index;
                                                  }
                                          
                                                  bool equals(Wt::cpp17::any o) {
                                                      ChildIndex *other = Wt::cpp17::any_cast<ChildIndex *>(o);
                                                      return parentId == other->parentId &&
                                                          index == other->index;
                                                  }
                                          
                                                  int hashCode() {
                                                      int hash = 1;
                                                      hash = hash * 31 + parentId;
                                                      hash = hash * 31 + index;
                                                      return hash;
                                                  }
                                              };
                                          
                                              /*
                                               * Data to be stored for an (expanded) folder
                                               */
                                              class Tree {
                                              public:
                                                  Tree(int parentId, int index, const Git::ObjectId& object, int rowCount)
                                                      : index_(parentId, index),
                                                        treeObject_(object),
                                                        rowCount_(rowCount)
                                                  { }
                                          
                                                  int parentId() const { return index_.parentId; }
                                                  int index() const { return index_.index; }
                                                  const Git::ObjectId& treeObject() const { return treeObject_; }
                                                  int rowCount() const { return rowCount_; }
                                          
                                              private:
                                                  ChildIndex    index_;
                                                  Git::ObjectId treeObject_;
                                                  int           rowCount_;
                                              };
                                          
                                              typedef std::map<ChildIndex, int> ChildPointerMap;
                                          
                                              /*
                                               * Expanded folder data
                                               */
                                              mutable std::vector<Tree> treeData_;
                                          
                                              /*
                                               * Indexes into treeData_
                                               */
                                              mutable ChildPointerMap childPointer_;
                                          
                                              /*
                                               * Gets or allocates an id for a folder.
                                               */
                                              int getTreeId(int parentId, int childIndex) const {
                                                  ChildIndex index(parentId, childIndex);
                                          
                                                  ChildPointerMap::const_iterator i = childPointer_.find(index);
                                                  if (i == childPointer_.end()) {
                                                      const Tree& parentItem = treeData_[parentId];
                                                      Git::Object o = git_.treeGetObject(parentItem.treeObject(), childIndex);
                                          
                                                      treeData_.push_back(Tree(parentId, childIndex, o.id,
                                                                               git_.treeSize(o.id)));
                                                      int result = treeData_.size() - 1;
                                                      childPointer_[index] = result;
                                                      return result;
                                                  } else
                                                      return i->second;
                                              }
                                          
                                              /*
                                               * Gets the Git::Object that corresponds to an index.
                                               */
                                              Git::Object getObject(const Wt::WModelIndex& index) const {
                                                  int parentId = index.internalId();
                                                  const Tree& parentItem = treeData_[parentId];
                                                  return git_.treeGetObject(parentItem.treeObject(), index.row());
                                              }
                                          
                                              static std::string getSuffix(const std::string& fileName) {
                                                  std::size_t dot = fileName.rfind('.');
                                                  if (dot == std::string::npos)
                                                      return "";
                                                  else
                                                      return fileName.substr(dot + 1);
                                              }
                                          
                                              bool topLevel(const Wt::WModelIndex& index) const {
                                                  return !parent(index).isValid();
                                              }
                                          };
                                          
                                          constexpr Wt::ItemDataRole GitModel::ContentsRole;
                                          

                                          Note The instantiation of this model with a Tree View is discussed in MVC Tree Views.

                                          Top

                                          Sorting

                                          A model may support sorting by one of its columns. This sorting can be implemented within the model itself.

                                          void sort()
                                          Sorts the model according to one of its columns.

                                          Sorting may be bolted onto a source model using the WSortFilterProxyModel, which is one of the standard proxy models.

                                          Model Changes

                                          A model does not necessarily need to be a static data source, but its data can also change, and data (rows/columns) can be added or removed. A model needs to generate events to inform Views of these modifications (for the events to which a View is subscribed). When implementing a custom model which is dynamic in nature, it is therefore important to emit these signals when making the modifications.

                                          Editing

                                          The model API also provides a standard interface to perform editing of the data, and some Views (such as the Tree View and Table Views) can be configured to allow editing of the data.

                                          If a custom wants to support this editing API, it needs to reimplement the following methods from WAbstractTableModel:

                                          bool setData()
                                          Updates data. Views typically use the EditRole for the data used in editing
                                          bool insertColumns()
                                          Inserts one or more columns
                                          bool insertRows()
                                          Inserts one or more rows
                                          bool removeColumns()
                                          Removes one or more columns
                                          bool removeRows()
                                          Removes one or more rows

                                          Top