14.10. TreeModelSort and TreeModelFilter

The TreeModelSort and TreeModelFilter objects are tree models that interpose between the base TreeModel (either a TreeStore or a ListStore) and the TreeView to provide a modified model while still retaining the original structure of the base model. These interposing models implement the TreeModel and TreeSortable interfaces but do not provide any methods for inserting or removing rows in the model; you have to insert or remove rows from the underlying store. The TreeModelSort provides a model where the rows are always sorted while the TreeModelFilter provides a model containing a subset of the rows of the base model.

These models can be chained to an arbitrary length if desired; i.e a TreeModelFilter could have a child TreeModelSort that could have a child TreeModelFilter, and so on. As long as there is a TreeStore or ListStore as the anchor of the chain it should just work. In PyGTK 2.0 and 2.2 the TreeModelSort and TreeModelFilter objects do not support the TreeModel Python mapping protocol.

14.10.1. TreeModelSort

The TreeModelSort maintains a sorted model of the child model specified in its constructor. The main use of a TreeModelSort is to provide multiple views of a model that can be sorted differently. If you have multiple views of the same model then any sorting activity is reflected in all the views. By using the TreeModelSort the base store is left in its original unsorted state and the sort models absorb all the sorting activity. To create a TreeModelSort use the constructor:

  treemodelsort = gtk.TreeModelSort(child_model)

where child_model is a TreeModel. Most of the methods of a TreeModelSort deal with converting tree paths and TreeIters from the child model to the sorted model and back:

  sorted_path = treemodelsort.convert_child_path_to_path(child_path)
  child_path = treemodelsort.convert_path_to_child_path(sorted_path)

These path conversion methods return None if the given path cannot be converted to a path in the sorted model or the child model respectively. The TreeIter conversion methods are:

  sorted_iter = treemodelsort.convert_child_iter_to_iter(sorted_iter, child_iter)
 child_iter = treemodelsort.convert_iter_to_child_iter(child_iter, sorted_iter)

The TreeIter conversion methods duplicate the converted argument (its both the return value and the first argument) due to backward compatibility issues; you should set the first arguments to None and just use the return value. For example:

  sorted_iter = treemodelsort.convert_child_iter_to_iter(None, child_iter)
  child_iter = treemodelsort.convert_iter_to_child_iter(None, sorted_iter)

Like the path conversion methods, these methods return None if the given TreeIter cannot be converted.

You can retrieve the child TreeModel using the get_model() method.

A simple example program using TreeModelSort objects is treemodelsort.py. Figure 14.9, “TreeModelSort Example” illustrates the result of running the program and adding six rows:

Figure 14.9. TreeModelSort Example

TreeModelSort Example

Each of the columns in the windows can be clicked to change the sort order independent of the other windows. When the "Add a Row" button is clicked a new row is added to the base ListStore and the new row is displayed in each TreeView as the selected row.

14.10.2. TreeModelFilter

Note

The TreeModelFilter is available in PyGTK 2.4 and above.

A TreeModelFilter object provides several ways of modifying the view of the base TreeModel including:

  • displaying a subset of the rows in the child model either based on boolean data in a "visible column", or based on the boolean return value of a "visible function" that takes the child model, a TreeIter pointing at a row in the child model and user data. In both cases if the boolean value is TRUE the row will be displayed; otherwise, the row will be hidden.
  • using a virtual root node to provide a view of a subtree of the children of a row in the child model. This only makes sense if the underlying store is a TreeStore.
  • synthesizing the columns and data of a model based on the data in the child model. For example, you can provide a column where the data is calculated from data in several child model columns.

A TreeModelFilter object is created using the TreeModel method:

  treemodelfilter = treemodel.filter_new(root=None)

where root is a tree path in treemodel specifying the virtual root for the model or None if the root node of treemodel is to be used.

By setting a "virtual root" when creating the TreeModelFilter, you can limit the model view to the child rows of "root" row in the child model hierarchy. This, of course is only useful when the child model is based on a TreeStore. For example, you might want to provide a view of the parts list that makes up a CDROM drive separate from the full parts list of a computer.

The visibility modes are mutually exclusive and can only be set once i.e. once a visibility function or column is set it cannot be changed and the alternative mode cannot be set. The simplest visibility mode extracts a boolean value from a column in the child model to determine if the row should be displayed. The visibility columns is set using:

  treemodelfilter.set_visible_column(column)

where column is the number of the column in the child TreeModel to extract the boolean values from. For example, the following code fragment uses the values in the third column to set the visibility of the rows:

  ...
  treestore = gtk.TreeStore(str, str, "gboolean")
  ...
  modelfilter = treestore.filter_new()
  modelfilter.set_visible_column(2)
  ...

Thus any rows in treestore that have a value of TRUE in the third column will be displayed.

If you have more complicated visibility criteria setting a visibility function should provide sufficient power:

  treemodelfilter.set_visible_func(func, data=None)

where func is the function called for each child model row to determine if it should be displayed and data is user data passed to func. func should return TRUE if the row should be displayed. The signature of func is:

  def func(model, iter, user_data)

where model is the child TreeModel, iter is a TreeIter pointing at a row in model and user_data is the passed in data.

If you make a change to the visibility criteria you should call:

  treemodelfilter.refilter()

to force a refiltering of the child model rows.

For example, the following code fragment illustrates a TreeModelFilter that displays rows based on a comparison between the value in the third column and the contents of the user data:

  ...
  def match_type(model, iter, udata):
      value = model.get_value(iter, 2)
      return value in udata
  ...
  show_vals = ['OPEN', 'NEW', 'RESO']
  liststore = gtk.ListStore(str, str, str)
  ...
  modelfilter = liststore.filter_new()
  modelfilter.set_visible_func(match_type, show_vals)
  ...

The program treemodelfilter.py illustrates the use of the set_visible_func() method. Figure 14.10, “TreeModelFilter Visibility Example” shows the result of running the program.

Figure 14.10. TreeModelFilter Visibility Example

TreeModelFilter Visibility Example

By toggling the buttons at the bottom the contents of the TreeView are changed to display only the rows that match one of the active buttons.

A modify function gives you another level of control over the TreeView display to the point where you can synthesize one or more (or even all) columns that are represented by the TreeModelFilter. You still have to use a base child model that is a TreeStore or ListStore to determine the number of rows and the hierarchy but the columns can be anything you specify in the method:

  treemodelfilter.set_modify_func(types, func, data=None)

where types is a sequence (list or tuple) specifying the column types being represented, func is a function called to return the value for a row and column and data is an argument to be passed to func. The signature of func is:

  def func(model, iter, column, user_data)

where model is the TreeModelFilter, iter is a TreeIter that points to a row in model, column is the number of the column that a value is needed for and user_data is the parameter data. func must return a value matching the type for column.

A modify function is useful where you want to provide a column of data that needs to be generated using the data in the child model columns. For example if you had a column containing birth dates and wanted to provide a column displaying ages, a modify function could generate the age information using the birth date and the current date. Another example would be to decide what image to display based on some analysis of the data (say, a filename) in a column. This effect can also be achieved using the TreeViewColumn set_cell_data_func() method.

Usually within the modify function, you will have to convert the TreeModelFilter TreeIter to a TreeIter in the child model using:

  child_iter = treemodelfilter.convert_iter_to_child_iter(filter_iter)

Of course, you'll also need to retrieve the child model using:

  child_model = treemodelfilter.get_model()

These give you access to the child model row and its values for generating the value for the specified TreeModelFilter row and column. There's also a method to convert a child TreeIter to a filter model TreeIter and methods to convert filter model paths to and from child tree paths:

  filter_iter = treemodelfilter.convert_child_iter_to_iter(child_iter)

  child_path = treemodelfilter.convert_path_to_child_path(filter_path)
  filter_path = treemodelfilter.convert_child_path_to_path(child_path)

Of course, you can combine the visibility modes and the modify function to both filter rows and synthesize columns. To get even more control over the view you would have to use a custom TreeModel.