GTK+ By Example/Tree View/Custom Models

When is a Custom Model Useful?
A custom tree model gives you complete control over your data and how it is represented to the outside (e.g. to the tree view widget). It has the advantage that you can store, access and modify your data exactly how you need it, and you can optimise the way your data is stored and retrieved, as you can write your own functions to access your data and need not rely solely on the gtk_tree_model_get. A model tailored to your needs will probably also be a lot faster than the generic list and tree stores that come with gtk and that have been designed with flexibility in mind.

Another case where a custom model might come in handy is when you have all your data already stored in an external tree-like structure (for example a libxml2 XML tree) and only want to display that structure. Then you could write a custom model that maps that structure to a tree model (which is probably not quite as trivial as it sounds though).

Using a custom model you could also implement a filter model that only displays certain rows according to some filter criterion instead of displaying all rows (Gtk+-2.4 has a filter model, GtkTreeModelFilter, that does exactly that and much more, but you might want to implement this yourself anyway. If you need to use GtkTreeModelFilter in Gtk-2.0 or Gtk-2.2, check out the code examples of this tutorial - there is GuiTreeModelFilter, which is basically just the original GtkTreeModelFilter but has been made to work with earlier Gtk-2.x versions and has a different name space, so that it does not clash with Gtk-2.4).

However, all this comes at a cost: you are unlikely to write a useful custom model in less than a thousand lines, unless you strip all newline characters. Writing a custom model is not as difficult as it might sound though, and it may well be worth the effort, not least because it will result in much saner code if you have a lot of data to keep track of.

What Does Writing a Custom Model Involve?
Basically, all you need to do is to write a new GObject that implements the GtkTreeModel interface, GtkTreeModelIface. Intimate knowledge about the GLib GObject system is not a requirement - you just need to copy some boilerplate code and modify it a bit. The core of your custom tree model is your own implementation of a couple of gtk_tree_model_foo functions that reveal the structure of your data, i.e. how many rows there are, how many children a row has, how many columns there are and what type of data they contain. Furthermore, you need to provide functions that convert a tree path to a tree iter and a tree iter to a tree path. Additionally, you should provide some functions to add and remove rows to your custom model, but those are only ever used by yourself anyway, so they do not fall within the scope of the tree model interface.

The functions you need to implement are:


 * get_flags - tells the outside that your model has certain special characteristics, like persistent iters.
 * get_n_columns - how many data fields per row are visible to the outside that uses gtk_tree_model_get, e.g. cell renderer attributes
 * get_column_type - what type of data is stored in a data field (model column) that is visible to the outside
 * get_iter - take a tree path and fill an iter structure so that you know which row it refers to
 * get_path - take an iter and convert it into a tree path, i.e. the 'physical' position within the model
 * get_value - retrieve data from a row
 * iter_next - take an iter structure and make it point to the next row
 * iter_children - tell whether the row represented by a given iter has any children or not
 * iter_n_children - tell how many children a row represented by a given iter has
 * iter_nth_child - set a given iter structure to the n-th child of a given parent iter
 * iter_parent - set a given iter structure to the parent of a given child iter

It is up to you to decide which of your data you make 'visible' to the outside in form of model columns and which not. You can always implement functions specific to your custom model that will return any data in any form you desire. You only need to make data 'visble' to the outside via the GType and GValue system if you want the tree view components to access it (e.g. when setting cell renderer attributes).

Example: A Simple Custom List Model
What follows is the outline for a simple custom list model. You can find the complete source code for this model below. The beginning of the code might look a bit scary, but you can just skip most of the GObject and GType stuff and proceed to the heart of the custom list, i.e. the implementation of the tree model functions.

Our list model is represented by a simple list of records, where each row corresponds to a CustomRecord structure which keeps track of the data we are interested in. For now, we only want to keep track of persons' names and years of birth (usually this would not really justify a custom model, but this is still just an example). It is trivial to extend the model to deal with additional fields in the CustomRecord structure.

Within the model, more precisely: the CustomList structure, the list is stored as a pointer array, which not only provides fast access to the n-th record in the list, but also comes in handy later on when we add sorting. Apart from that, any other kind of list-specific data would go in this structure as well (the active sort column, for example, or hash tables to speed up searching for a specific row, etc.).

Each row in our list is represented by a CustomRecord structure. You can store whatever other data you need in that structure. How you make row data available is up to you. Either you export it via the tree model interface using the GValue system, so that you can use gtk_tree_model_get to retrieve your data, or you provide custom model-specific functions to retrieve data, for example custom_list_get_name, taking a tree iter or a tree path as argument. Of course you can also do both.

Furthermore, you will need to provide your own functions to add rows, remove rows, and set or modify row data, and you need to let the view and others know whenever something changes in your model by emitting the appropriate signals via the provided tree model functions.

Some thought should go into how exactly you fill the GtkTreeIter fields of the tree iters used by your model. You have three pointer fields at your disposal. These should be filled so that you can easily identify the row given the iter, and should also facilitate access to the next row and the parent row (if any). If your model advertises to have persistent iters, you need to make sure that the content of your iters is perfectly valid even if the user stores it somewhere for later use and the model gets changed or reordered. The 'stamp' field of a tree iter should be filled by a random model-instance-specific integer that was assigned to the model when it was created. This way you can catch iters that do not belong to your model. If your model does not have persistent iters, then you should change the model's stamp whenever the model changes, so that you can catch invalid iters that get passed to your functions (note: in the code below we do not check the stamp of the iters in order to save a couple of lines of code to print here).

In our specific example, we simply store a pointer to a row's CustomRecord structure in our model's tree iters, which is valid as long as the row exists. Additionally we store the position of a row within the list in the CustomRecord as well, which is not only intuitive, but is also useful later on when we resort the list.

If you want to store an integer value in an iter's fields, you should use GLib's GINT_TO_POINTER and GPOINTER_TO_INT macros for that.

Let's look at the code sections in a bit more detail:

custom-list.h
The header file for our custom list model defines some standard type casts and type check macros, our CustomRecord structure, our CustomList structure, and some enums for the model columns we are exporting.

The CustomRecord structure represents one row, while the CustomList structure contains all list-specific data. You can add additional fields to both structures without problems. For example, you might need a function that quickly looks up rows given the name or year of birth, for which additional hashtables or so might come in handy (which you would need to keep up to date as you insert, modify or remove rows of course).

The only function you must export is custom_list_get_type, as it is used by the type check and type cast macros that are also defined in the header file. Additionally, we want to export a function to create one instance of our custom model, and a function that adds some rows. You will probably add more custom model-specific functions to modify the model as you extend it to suit your needs.

custom-list.c
Firstly, we need some boilerplate code to register our custom model with the GObject type system. You can skip this section and proceed to the tree model implementation.

Functions of interested in this section are custom_list_init and custom_list_get_type. In custom_list_init we define what data type our exported model columns have, and how many columns we export. Towards the end of custom_list_get_type we register the GtkTreeModel interface with our custom model object. This is where we can also register additional interfaces (e.g. GtkTreeSortable or one of the Drag'n'Drop interfaces) that we want to implement.

In custom_list_tree_model_init we override those tree model functions that we need to implement with our own functions. If it is beneficial for your model to know which rows are currently displayed in the tree view (for example for caching), you might want to override the ref_node and unref_node functions as well.

Let's have a look at the heart of the object type registration:

Here we just return the type assigned to our custom list by the type system if we have already registered it. If not, we register it and save the type. Of the three callbacks that we pass to the type system, only two are of immediate interest to us, namely custom_list_tree_model_init and custom_list_init.

In custom_list_tree_model_init we fill the tree model interface structure with pointers to our own functions (at least the ones we implement):

In custom_list_init we initialised the custom list structure to sensible default values. This function will be called whenever a new instance of our custom list is created, which we do in custom_list_new.

custom_list_finalize is called just before one of our lists is going to be destroyed. You should free all resources that you have dynamically allocated in there.

Having taken care of all the type system stuff, we now come to the heart of our custom model, namely the tree model implementation. Our tree model functions need to behave exactly as the API reference requires them to behave, including all special cases, otherwise things will not work. Here is a list of links to the API reference descriptions of the functions we are implementing:


 * gtk_tree_model_get_flags
 * gtk_tree_model_get_n_columns
 * gtk_tree_model_get_column_type
 * gtk_tree_model_get_iter
 * gtk_tree_model_get_path
 * gtk_tree_model_get_value
 * gtk_tree_model_iter_next
 * gtk_tree_model_iter_children
 * gtk_tree_model_iter_has_child
 * gtk_tree_model_iter_n_children
 * gtk_tree_model_iter_nth_child
 * gtk_tree_model_iter_parent

Almost all functions are more or less straight-forward and self-explanatory in connection with the API reference descriptions, so you should be able to jump right into the code and see how it works.

After the tree model implementation we have those functions that are specific to our custom model. custom_list_new will create a new custom list for us, and custom_list_append_record will append a new record to the end of the list. Note the call to gtk_tree_model_row_inserted at the end of our append function, which emits a "row-inserted" signal on the model and informs all interested objects (tree views, tree row references) that a new row has been inserted, and where it has been inserted.

You will need to emit tree model signals whenever something changes, e.g. rows are inserted, removed, or reordered, or when a row changes from a child-less row to a row which has children, or if a row's data changes. Here are the functions you need to use in those cases (we only implement row insertions here - other cases are left as an exercise for the reader):


 * gtk_tree_model_row_inserted
 * gtk_tree_model_row_changed (makes tree view redraw that row)
 * gtk_tree_model_row_has_child_toggled
 * gtk_tree_model_row_deleted
 * gtk_tree_model_rows_reordered (note bug 124790)

And that is all you have to do to write a custom model.

From a List to a Tree
Writing a custom model for a tree is a bit trickier than a simple list model, but follows the same pattern. Basically you just need to extend the above model to cater for the case of children. You could do this by keeping track of the whole tree hierarchy in the CustomList structure, using GLib N-ary trees for example, or you could do this by keeping track of each row's children within the row's CustomRecord structure, keeping only a pointer to the (invisible) root record in the CustomList structure.

TODO: do we need anything else here?

Additional interfaces, here: the GtkTreeSortable interface
A custom model can implement additional interfaces to extend its functionality. Additional interfaces are:


 * GtkTreeSortableIface
 * GtkTreeDragDestIface
 * GtkTreeDragSourceIface

Here, we will show how to implement additional interfaces at the example of the GtkTreeSortable interface, which we will implement only partially (enough to make it functional and useful though).

Three things are necessary to add another interface: we will need to register the interface with our model in custom_list_get_type, provide an interface init function where we set the interface to our own implementation of the interface functions, and then provide the implementation of those functions.

Firstly, we need to provide the function prototypes for our functions at the beginning of the file:

Next, let's extend our CustomList structure with a field for the currently active sort column ID and one for the sort order, and add an enum for the sort column IDs:

Now, we make sure we initialise the new fields in custom_list_new, and add our new interface:

Now, last but not least, the only thing missing is the function that does the actual sorting. We do not implement set_sort_func, set_default_sort_func and set_has_default_sort_func because we use our own internal sort function here.

The actual sorting is done using GLib's g_qsort_with_data function, which sorts an array using the QuickSort algorithm. Note how we notify the tree view and other objects of the new row order by emitting the "rows-reordered" signal on the tree model.

Finally, we should make sure that the model is resorted after we have inserted a new row by adding a call to custom_list_resort to the end of custom_list_append:

And that is it. Adding two calls to gtk_tree_view_column_set_sort_column_id in main.c is left as yet another exercise for the reader.

If you are interested in seeing string sorting speed issues in action, you should modify main.c like this:

Most likely, sorting 24000 rows by name will take up to several seconds now. Now, if you go back to custom_list_compare_records and replace the call to g_utf8_collate with:

... then you should hopefully register a dramatic speed increase when sorting by name.

Working Example: Custom List Model Source Code
Here is the complete source code for the custom list model presented above. Compile with:

gcc -o customlist custom-list.c main.c `pkg-config --cflags --libs gtk+-2.0`

*

custom-list.h   *

custom-list.c   *

main.c

custom-list.h

 * custom-list.h
 * custom-list.c
 * main.c

custom-list.c

 * custom-list.h
 * custom-list.c
 * main.c

main.c
The following couple of lines provide a working test case that makes use of our custom list. It creates one of our custom lists, adds some records, and displays it in a tree view.