GTK+ 2.0 Tree View Tutorial | ||
---|---|---|
<<< Previous | Chapter 11. Writing Custom Models | Next >>> |
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, ie. 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:
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.
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:
GType custom_list_get_type (void) { static GType custom_list_type = 0; /* Some boilerplate type registration stuff */ if (custom_list_type == 0) { static const GTypeInfo custom_list_info = { sizeof (CustomListClass), NULL, /* base_init */ NULL, /* base_finalize */ (GClassInitFunc) custom_list_class_init, NULL, /* class finalize */ NULL, /* class_data */ sizeof (CustomList), 0, /* n_preallocs */ (GInstanceInitFunc) custom_list_init }; static const GInterfaceInfo tree_model_info = { (GInterfaceInitFunc) custom_list_tree_model_init, NULL, NULL }; /* First register our new derived type with the GObject type system */ custom_list_type = g_type_register_static (G_TYPE_OBJECT, "CustomList", &custom_list_info, (GTypeFlags)0); /* Then register our GtkTreeModel interface with the type system */ g_type_add_interface_static (custom_list_type, GTK_TYPE_TREE_MODEL, &tree_model_info); } return custom_list_type; } |
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):
static void custom_list_tree_model_init (GtkTreeModelIface *iface) { /* Here we override the GtkTreeModel * interface functions that we implement */ iface->get_flags = custom_list_get_flags; iface->get_n_columns = custom_list_get_n_columns; iface->get_column_type = custom_list_get_column_type; iface->get_iter = custom_list_get_iter; iface->get_path = custom_list_get_path; iface->get_value = custom_list_get_value; iface->iter_next = custom_list_iter_next; iface->iter_children = custom_list_iter_children; iface->iter_has_child = custom_list_iter_has_child; iface->iter_n_children = custom_list_iter_n_children; iface->iter_nth_child = custom_list_iter_nth_child; iface->iter_parent = custom_list_iter_parent; } |
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:
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_changed (makes tree view redraw that row)
And that is all you have to do to write a custom model.
<<< Previous | Home | Next >>> |
What Does Writing a Custom Model Involve? | Up | From a List to a Tree |