GTK+ By Example/Tree View/Custom Models
Writing Custom Models
[edit | edit source]When is a Custom Model Useful?
[edit | edit source]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?
[edit | edit source]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
[edit | edit source]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
[edit | edit source]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
[edit | edit source]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:
- 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
[edit | edit source]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
[edit | edit source]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:
/* custom-list.c */
...
/* -- GtkTreeSortable interface functions -- */
static gboolean custom_list_sortable_get_sort_column_id (GtkTreeSortable *sortable,
gint *sort_col_id,
GtkSortType *order);
static void custom_list_sortable_set_sort_column_id (GtkTreeSortable *sortable,
gint sort_col_id,
GtkSortType order);
static void custom_list_sortable_set_sort_func (GtkTreeSortable *sortable,
gint sort_col_id,
GtkTreeIterCompareFunc sort_func,
gpointer user_data,
GtkDestroyNotify destroy_func);
static void custom_list_sortable_set_default_sort_func (GtkTreeSortable *sortable,
GtkTreeIterCompareFunc sort_func,
gpointer user_data,
GtkDestroyNotify destroy_func);
static gboolean custom_list_sortable_has_default_sort_func (GtkTreeSortable *sortable);
static void custom_list_resort (CustomList *custom_list);
...
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:
/* custom-list.h */
enum
{
SORT_ID_NONE = 0,
SORT_ID_NAME,
SORT_ID_YEAR_BORN,
};
...
struct _CustomList
{
GObject parent;
guint num_rows; /* number of rows that we have */
CustomRecord **rows; /* a dynamically allocated array of pointers to the
* CustomRecord structure for each row */
gint n_columns;
GType column_types[CUSTOM_LIST_N_COLUMNS];
gint sort_id;
GtkSortType sort_order;
gint stamp; /* Random integer to check whether an iter belongs to our model */
};
...
Now, we make sure we initialise the new fields in custom_list_new, and add our new interface:
...
static void custom_list_sortable_init (GtkTreeSortableIface *iface);
...
void
custom_list_init (CustomList *custom_list)
{
...
custom_list->sort_id = SORT_ID_NONE;
custom_list->sort_order = GTK_SORT_ASCENDING;
...
}
GType
custom_list_get_type (void)
{
...
/* Add GtkTreeSortable interface */
{
static const GInterfaceInfo tree_sortable_info =
{
(GInterfaceInitFunc) custom_list_sortable_init,
NULL,
NULL
};
g_type_add_interface_static (custom_list_type, GTK_TYPE_TREE_SORTABLE, &tree_sortable_info);
}
...
}
static void
custom_list_sortable_init (GtkTreeSortableIface *iface)
{
iface->get_sort_column_id = custom_list_sortable_get_sort_column_id;
iface->set_sort_column_id = custom_list_sortable_set_sort_column_id;
iface->set_sort_func = custom_list_sortable_set_sort_func; /* NOT SUPPORTED */
iface->set_default_sort_func = custom_list_sortable_set_default_sort_func; /* NOT SUPPORTED */
iface->has_default_sort_func = custom_list_sortable_has_default_sort_func; /* NOT SUPPORTED */
}
Now that we have finally taken care of the administrativa, we implement the tree sortable interface functions:
static gboolean
custom_list_sortable_get_sort_column_id (GtkTreeSortable *sortable,
gint *sort_col_id,
GtkSortType *order)
{
CustomList *custom_list;
g_return_val_if_fail ( sortable != NULL , FALSE );
g_return_val_if_fail ( CUSTOM_IS_LIST(sortable), FALSE );
custom_list = CUSTOM_LIST(sortable);
if (sort_col_id)
*sort_col_id = custom_list->sort_id;
if (order)
*order = custom_list->sort_order;
return TRUE;
}
static void
custom_list_sortable_set_sort_column_id (GtkTreeSortable *sortable,
gint sort_col_id,
GtkSortType order)
{
CustomList *custom_list;
g_return_if_fail ( sortable != NULL );
g_return_if_fail ( CUSTOM_IS_LIST(sortable) );
custom_list = CUSTOM_LIST(sortable);
if (custom_list->sort_id == sort_col_id && custom_list->sort_order == order)
return;
custom_list->sort_id = sort_col_id;
custom_list->sort_order = order;
custom_list_resort(custom_list);
/* emit "sort-column-changed" signal to tell any tree views
* that the sort column has changed (so the little arrow
* in the column header of the sort column is drawn
* in the right column) */
gtk_tree_sortable_sort_column_changed(sortable);
}
static void
custom_list_sortable_set_sort_func (GtkTreeSortable *sortable,
gint sort_col_id,
GtkTreeIterCompareFunc sort_func,
gpointer user_data,
GtkDestroyNotify destroy_func)
{
g_warning ("%s is not supported by the CustomList model.\n", __FUNCTION__);
}
static void
custom_list_sortable_set_default_sort_func (GtkTreeSortable *sortable,
GtkTreeIterCompareFunc sort_func,
gpointer user_data,
GtkDestroyNotify destroy_func)
{
g_warning ("%s is not supported by the CustomList model.\n", __FUNCTION__);
}
static gboolean
custom_list_sortable_has_default_sort_func (GtkTreeSortable *sortable)
{
return FALSE;
}
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.
static gint
custom_list_compare_records (gint sort_id, CustomRecord *a, CustomRecord *b)
{
switch(sort_id)
{
case SORT_ID_NONE:
return 0;
case SORT_ID_NAME:
{
if ((a->name) && (b->name))
return g_utf8_collate(a->name, b->name);
if (a->name == b->name)
return 0; /* both are NULL */
else
return (a->name == NULL) ? -1 : 1;
}
case SORT_ID_YEAR_BORN:
{
if (a->year_born == b->year_born)
return 0;
return (a->year_born > b->year_born) ? 1 : -1;
}
}
g_return_val_if_reached(0);
}
static gint
custom_list_qsort_compare_func (CustomRecord **a, CustomRecord **b, CustomList *custom_list)
{
gint ret;
g_assert ((a) && (b) && (custom_list));
ret = custom_list_compare_records(custom_list->sort_id, *a, *b);
/* Swap -1 and 1 if sort order is reverse */
if (ret != 0 && custom_list->sort_order == GTK_SORT_DESCENDING)
ret = (ret < 0) ? 1 : -1;
return ret;
}
static void
custom_list_resort (CustomList *custom_list)
{
GtkTreePath *path;
gint *neworder, i;
g_return_if_fail ( custom_list != NULL );
g_return_if_fail ( CUSTOM_IS_LIST(custom_list) );
if (custom_list->sort_id == SORT_ID_NONE)
return;
if (custom_list->num_rows == 0)
return;
/* resort */
g_qsort_with_data(custom_list->rows,
custom_list->num_rows,
sizeof(CustomRecord*),
(GCompareDataFunc) custom_list_qsort_compare_func,
custom_list);
/* let other objects know about the new order */
neworder = g_new0(gint, custom_list->num_rows);
for (i = 0; i < custom_list->num_rows; ++i)
{
/* Note that the API reference might be wrong about
* this, see bug number 124790 on bugs.gnome.org.
* Both will work, but one will give you 'jumpy'
* selections after row reordering. */
/* neworder[(custom_list->rows[i])->pos] = i; */
neworder[i] = (custom_list->rows[i])->pos;
(custom_list->rows[i])->pos = i;
}
path = gtk_tree_path_new();
gtk_tree_model_rows_reordered(GTK_TREE_MODEL(custom_list), path, NULL, neworder);
gtk_tree_path_free(path);
g_free(neworder);
}
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:
...
void
custom_list_append_record (CustomList *custom_list, const gchar *name, guint year_born)
{
...
custom_list_resort(custom_list);
}
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:
GtkWidget *
create_view_and_model (void)
{
gint i;
...
for (i=0; i < 1000; ++i)
{
fill_model(customlist);
}
...
}
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:
static gint
custom_list_compare_records (gint sort_id, CustomRecord *a, CustomRecord *b)
{
...
if ((a->name) && (b->name))
return strcmp(a->name_collate_key,b->name_collate_key);
...
}
... then you should hopefully register a dramatic speed increase when sorting by name.
Working Example: Custom List Model Source Code
[edit | edit source]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
[edit | edit source]#ifndef _custom_list_h_included_
#define _custom_list_h_included_
#include <gtk/gtk.h>
/* Some boilerplate GObject defines. 'klass' is used
* instead of 'class', because 'class' is a C++ keyword */
#define CUSTOM_TYPE_LIST (custom_list_get_type ())
#define CUSTOM_LIST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CUSTOM_TYPE_LIST, CustomList))
#define CUSTOM_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CUSTOM_TYPE_LIST, CustomListClass))
#define CUSTOM_IS_LIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CUSTOM_TYPE_LIST))
#define CUSTOM_IS_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CUSTOM_TYPE_LIST))
#define CUSTOM_LIST_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CUSTOM_TYPE_LIST, CustomListClass))
/* The data columns that we export via the tree model interface */
enum
{
CUSTOM_LIST_COL_RECORD = 0,
CUSTOM_LIST_COL_NAME,
CUSTOM_LIST_COL_YEAR_BORN,
CUSTOM_LIST_N_COLUMNS,
} ;
typedef struct _CustomRecord CustomRecord;
typedef struct _CustomList CustomList;
typedef struct _CustomListClass CustomListClass;
/* CustomRecord: this structure represents a row */
struct _CustomRecord
{
/* data - you can extend this */
gchar *name;
gchar *name_collate_key;
guint year_born;
/* admin stuff used by the custom list model */
guint pos; /* pos within the array */
};
/* CustomList: this structure contains everything we need for our
* model implementation. You can add extra fields to
* this structure, e.g. hashtables to quickly lookup
* rows or whatever else you might need, but it is
* crucial that 'parent' is the first member of the
* structure. */
struct _CustomList
{
GObject parent; /* this MUST be the first member */
guint num_rows; /* number of rows that we have */
CustomRecord **rows; /* a dynamically allocated array of pointers to
* the CustomRecord structure for each row */
/* These two fields are not absolutely necessary, but they */
/* speed things up a bit in our get_value implementation */
gint n_columns;
GType column_types[CUSTOM_LIST_N_COLUMNS];
gint stamp; /* Random integer to check whether an iter belongs to our model */
};
/* CustomListClass: more boilerplate GObject stuff */
struct _CustomListClass
{
GObjectClass parent_class;
};
GType custom_list_get_type (void);
CustomList *custom_list_new (void);
void custom_list_append_record (CustomList *custom_list,
const gchar *name,
guint year_born);
#endif /* _custom_list_h_included_ */
- custom-list.h
- custom-list.c
- main.c
custom-list.c
[edit | edit source]#include "custom-list.h"
/* boring declarations of local functions */
static void custom_list_init (CustomList *pkg_tree);
static void custom_list_class_init (CustomListClass *klass);
static void custom_list_tree_model_init (GtkTreeModelIface *iface);
static void custom_list_finalize (GObject *object);
static GtkTreeModelFlags custom_list_get_flags (GtkTreeModel *tree_model);
static gint custom_list_get_n_columns (GtkTreeModel *tree_model);
static GType custom_list_get_column_type (GtkTreeModel *tree_model,
gint index);
static gboolean custom_list_get_iter (GtkTreeModel *tree_model,
GtkTreeIter *iter,
GtkTreePath *path);
static GtkTreePath *custom_list_get_path (GtkTreeModel *tree_model,
GtkTreeIter *iter);
static void custom_list_get_value (GtkTreeModel *tree_model,
GtkTreeIter *iter,
gint column,
GValue *value);
static gboolean custom_list_iter_next (GtkTreeModel *tree_model,
GtkTreeIter *iter);
static gboolean custom_list_iter_children (GtkTreeModel *tree_model,
GtkTreeIter *iter,
GtkTreeIter *parent);
static gboolean custom_list_iter_has_child (GtkTreeModel *tree_model,
GtkTreeIter *iter);
static gint custom_list_iter_n_children (GtkTreeModel *tree_model,
GtkTreeIter *iter);
static gboolean custom_list_iter_nth_child (GtkTreeModel *tree_model,
GtkTreeIter *iter,
GtkTreeIter *parent,
gint n);
static gboolean custom_list_iter_parent (GtkTreeModel *tree_model,
GtkTreeIter *iter,
GtkTreeIter *child);
static GObjectClass *parent_class = NULL; /* GObject stuff - nothing to worry about */
/*****************************************************************************
*
* custom_list_get_type: here we register our new type and its interfaces
* with the type system. If you want to implement
* additional interfaces like GtkTreeSortable, you
* will need to do it here.
*
*****************************************************************************/
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 the new derived type with the GObject type system */
custom_list_type = g_type_register_static (G_TYPE_OBJECT, "CustomList",
&custom_list_info, (GTypeFlags)0);
/* Now 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;
}
/*****************************************************************************
*
* custom_list_class_init: more boilerplate GObject/GType stuff.
* Init callback for the type system,
* called once when our new class is created.
*
*****************************************************************************/
static void
custom_list_class_init (CustomListClass *klass)
{
GObjectClass *object_class;
parent_class = (GObjectClass*) g_type_class_peek_parent (klass);
object_class = (GObjectClass*) klass;
object_class->finalize = custom_list_finalize;
}
/*****************************************************************************
*
* custom_list_tree_model_init: init callback for the interface registration
* in custom_list_get_type. Here we override
* the GtkTreeModel interface functions that
* we implement.
*
*****************************************************************************/
static void
custom_list_tree_model_init (GtkTreeModelIface *iface)
{
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;
}
/*****************************************************************************
*
* custom_list_init: this is called everytime a new custom list object
* instance is created (we do that in custom_list_new).
* Initialise the list structure's fields here.
*
*****************************************************************************/
static void
custom_list_init (CustomList *custom_list)
{
custom_list->n_columns = CUSTOM_LIST_N_COLUMNS;
custom_list->column_types[0] = G_TYPE_POINTER; /* CUSTOM_LIST_COL_RECORD */
custom_list->column_types[1] = G_TYPE_STRING; /* CUSTOM_LIST_COL_NAME */
custom_list->column_types[2] = G_TYPE_UINT; /* CUSTOM_LIST_COL_YEAR_BORN */
g_assert (CUSTOM_LIST_N_COLUMNS == 3);
custom_list->num_rows = 0;
custom_list->rows = NULL;
custom_list->stamp = g_random_int(); /* Random int to check whether an iter belongs to our model */
}
/*****************************************************************************
*
* custom_list_finalize: this is called just before a custom list is
* destroyed. Free dynamically allocated memory here.
*
*****************************************************************************/
static void
custom_list_finalize (GObject *object)
{
/* CustomList *custom_list = CUSTOM_LIST(object); */
/* free all records and free all memory used by the list */
#warning IMPLEMENT
/* must chain up - finalize parent */
(* parent_class->finalize) (object);
}
/*****************************************************************************
*
* custom_list_get_flags: tells the rest of the world whether our tree model
* has any special characteristics. In our case,
* we have a list model (instead of a tree), and each
* tree iter is valid as long as the row in question
* exists, as it only contains a pointer to our struct.
*
*****************************************************************************/
static GtkTreeModelFlags
custom_list_get_flags (GtkTreeModel *tree_model)
{
g_return_val_if_fail (CUSTOM_IS_LIST(tree_model), (GtkTreeModelFlags)0);
return (GTK_TREE_MODEL_LIST_ONLY | GTK_TREE_MODEL_ITERS_PERSIST);
}
/*****************************************************************************
*
* custom_list_get_n_columns: tells the rest of the world how many data
* columns we export via the tree model interface
*
*****************************************************************************/
static gint
custom_list_get_n_columns (GtkTreeModel *tree_model)
{
g_return_val_if_fail (CUSTOM_IS_LIST(tree_model), 0);
return CUSTOM_LIST(tree_model)->n_columns;
}
/*****************************************************************************
*
* custom_list_get_column_type: tells the rest of the world which type of
* data an exported model column contains
*
*****************************************************************************/
static GType
custom_list_get_column_type (GtkTreeModel *tree_model,
gint index)
{
g_return_val_if_fail (CUSTOM_IS_LIST(tree_model), G_TYPE_INVALID);
g_return_val_if_fail (index < CUSTOM_LIST(tree_model)->n_columns && index >= 0, G_TYPE_INVALID);
return CUSTOM_LIST(tree_model)->column_types[index];
}
/*****************************************************************************
*
* custom_list_get_iter: converts a tree path (physical position) into a
* tree iter structure (the content of the iter
* fields will only be used internally by our model).
* We simply store a pointer to our CustomRecord
* structure that represents that row in the tree iter.
*
*****************************************************************************/
static gboolean
custom_list_get_iter (GtkTreeModel *tree_model,
GtkTreeIter *iter,
GtkTreePath *path)
{
CustomList *custom_list;
CustomRecord *record;
gint *indices, n, depth;
g_assert(CUSTOM_IS_LIST(tree_model));
g_assert(path!=NULL);
custom_list = CUSTOM_LIST(tree_model);
indices = gtk_tree_path_get_indices(path);
depth = gtk_tree_path_get_depth(path);
/* we do not allow children */
g_assert(depth == 1); /* depth 1 = top level; a list only has top level nodes and no children */
n = indices[0]; /* the n-th top level row */
if ( n >= custom_list->num_rows || n < 0 )
return FALSE;
record = custom_list->rows[n];
g_assert(record != NULL);
g_assert(record->pos == n);
/* We simply store a pointer to our custom record in the iter */
iter->stamp = custom_list->stamp;
iter->user_data = record;
iter->user_data2 = NULL; /* unused */
iter->user_data3 = NULL; /* unused */
return TRUE;
}
/*****************************************************************************
*
* custom_list_get_path: converts a tree iter into a tree path (ie. the
* physical position of that row in the list).
*
*****************************************************************************/
static GtkTreePath *
custom_list_get_path (GtkTreeModel *tree_model,
GtkTreeIter *iter)
{
GtkTreePath *path;
CustomRecord *record;
CustomList *custom_list;
g_return_val_if_fail (CUSTOM_IS_LIST(tree_model), NULL);
g_return_val_if_fail (iter != NULL, NULL);
g_return_val_if_fail (iter->user_data != NULL, NULL);
custom_list = CUSTOM_LIST(tree_model);
record = (CustomRecord*) iter->user_data;
path = gtk_tree_path_new();
gtk_tree_path_append_index(path, record->pos);
return path;
}
/*****************************************************************************
*
* custom_list_get_value: Returns a row's exported data columns
* (_get_value is what gtk_tree_model_get uses)
*
*****************************************************************************/
static void
custom_list_get_value (GtkTreeModel *tree_model,
GtkTreeIter *iter,
gint column,
GValue *value)
{
CustomRecord *record;
CustomList *custom_list;
g_return_if_fail (CUSTOM_IS_LIST (tree_model));
g_return_if_fail (iter != NULL);
g_return_if_fail (column < CUSTOM_LIST(tree_model)->n_columns);
g_value_init (value, CUSTOM_LIST(tree_model)->column_types[column]);
custom_list = CUSTOM_LIST(tree_model);
record = (CustomRecord*) iter->user_data;
g_return_if_fail ( record != NULL );
if(record->pos >= custom_list->num_rows)
g_return_if_reached();
switch(column)
{
case CUSTOM_LIST_COL_RECORD:
g_value_set_pointer(value, record);
break;
case CUSTOM_LIST_COL_NAME:
g_value_set_string(value, record->name);
break;
case CUSTOM_LIST_COL_YEAR_BORN:
g_value_set_uint(value, record->year_born);
break;
}
}
/*****************************************************************************
*
* custom_list_iter_next: Takes an iter structure and sets it to point
* to the next row.
*
*****************************************************************************/
static gboolean
custom_list_iter_next (GtkTreeModel *tree_model,
GtkTreeIter *iter)
{
CustomRecord *record, *nextrecord;
CustomList *custom_list;
g_return_val_if_fail (CUSTOM_IS_LIST (tree_model), FALSE);
if (iter == NULL || iter->user_data == NULL)
return FALSE;
custom_list = CUSTOM_LIST(tree_model);
record = (CustomRecord *) iter->user_data;
/* Is this the last record in the list? */
if ((record->pos + 1) >= custom_list->num_rows)
return FALSE;
nextrecord = custom_list->rows[(record->pos + 1)];
g_assert ( nextrecord != NULL );
g_assert ( nextrecord->pos == (record->pos + 1) );
iter->stamp = custom_list->stamp;
iter->user_data = nextrecord;
return TRUE;
}
/*****************************************************************************
*
* custom_list_iter_children: Returns TRUE or FALSE depending on whether
* the row specified by 'parent' has any children.
* If it has children, then 'iter' is set to
* point to the first child. Special case: if
* 'parent' is NULL, then the first top-level
* row should be returned if it exists.
*
*****************************************************************************/
static gboolean
custom_list_iter_children (GtkTreeModel *tree_model,
GtkTreeIter *iter,
GtkTreeIter *parent)
{
CustomList *custom_list;
g_return_val_if_fail (parent == NULL || parent->user_data != NULL, FALSE);
/* this is a list, nodes have no children */
if (parent)
return FALSE;
/* parent == NULL is a special case; we need to return the first top-level row */
g_return_val_if_fail (CUSTOM_IS_LIST (tree_model), FALSE);
custom_list = CUSTOM_LIST(tree_model);
/* No rows => no first row */
if (custom_list->num_rows == 0)
return FALSE;
/* Set iter to first item in list */
iter->stamp = custom_list->stamp;
iter->user_data = custom_list->rows[0];
return TRUE;
}
/*****************************************************************************
*
* custom_list_iter_has_child: Returns TRUE or FALSE depending on whether
* the row specified by 'iter' has any children.
* We only have a list and thus no children.
*
*****************************************************************************/
static gboolean
custom_list_iter_has_child (GtkTreeModel *tree_model,
GtkTreeIter *iter)
{
return FALSE;
}
/*****************************************************************************
*
* custom_list_iter_n_children: Returns the number of children the row
* specified by 'iter' has. This is usually 0,
* as we only have a list and thus do not have
* any children to any rows. A special case is
* when 'iter' is NULL, in which case we need
* to return the number of top-level nodes,
* ie. the number of rows in our list.
*
*****************************************************************************/
static gint
custom_list_iter_n_children (GtkTreeModel *tree_model,
GtkTreeIter *iter)
{
CustomList *custom_list;
g_return_val_if_fail (CUSTOM_IS_LIST (tree_model), -1);
g_return_val_if_fail (iter == NULL || iter->user_data != NULL, FALSE);
custom_list = CUSTOM_LIST(tree_model);
/* special case: if iter == NULL, return number of top-level rows */
if (!iter)
return custom_list->num_rows;
return 0; /* otherwise, this is easy again for a list */
}
/*****************************************************************************
*
* custom_list_iter_nth_child: If the row specified by 'parent' has any
* children, set 'iter' to the n-th child and
* return TRUE if it exists, otherwise FALSE.
* A special case is when 'parent' is NULL, in
* which case we need to set 'iter' to the n-th
* row if it exists.
*
*****************************************************************************/
static gboolean
custom_list_iter_nth_child (GtkTreeModel *tree_model,
GtkTreeIter *iter,
GtkTreeIter *parent,
gint n)
{
CustomRecord *record;
CustomList *custom_list;
g_return_val_if_fail (CUSTOM_IS_LIST (tree_model), FALSE);
custom_list = CUSTOM_LIST(tree_model);
/* a list has only top-level rows */
if(parent)
return FALSE;
/* special case: if parent == NULL, set iter to n-th top-level row */
if( n >= custom_list->num_rows )
return FALSE;
record = custom_list->rows[n];
g_assert( record != NULL );
g_assert( record->pos == n );
iter->stamp = custom_list->stamp;
iter->user_data = record;
return TRUE;
}
/*****************************************************************************
*
* custom_list_iter_parent: Point 'iter' to the parent node of 'child'. As
* we have a list and thus no children and no
* parents of children, we can just return FALSE.
*
*****************************************************************************/
static gboolean
custom_list_iter_parent (GtkTreeModel *tree_model,
GtkTreeIter *iter,
GtkTreeIter *child)
{
return FALSE;
}
/*****************************************************************************
*
* custom_list_new: This is what you use in your own code to create a
* new custom list tree model for you to use.
*
*****************************************************************************/
CustomList *
custom_list_new (void)
{
CustomList *newcustomlist;
newcustomlist = (CustomList*) g_object_new (CUSTOM_TYPE_LIST, NULL);
g_assert( newcustomlist != NULL );
return newcustomlist;
}
/*****************************************************************************
*
* custom_list_append_record: Empty lists are boring. This function can
* be used in your own code to add rows to the
* list. Note how we emit the "row-inserted"
* signal after we have appended the row
* internally, so the tree view and other
* interested objects know about the new row.
*
*****************************************************************************/
void
custom_list_append_record (CustomList *custom_list,
const gchar *name,
guint year_born)
{
GtkTreeIter iter;
GtkTreePath *path;
CustomRecord *newrecord;
gulong newsize;
guint pos;
g_return_if_fail (CUSTOM_IS_LIST(custom_list));
g_return_if_fail (name != NULL);
pos = custom_list->num_rows;
custom_list->num_rows++;
newsize = custom_list->num_rows * sizeof(CustomRecord*);
custom_list->rows = g_realloc(custom_list->rows, newsize);
newrecord = g_new0(CustomRecord, 1);
newrecord->name = g_strdup(name);
newrecord->name_collate_key = g_utf8_collate_key(name,-1); /* for fast sorting, used later */
newrecord->year_born = year_born;
custom_list->rows[pos] = newrecord;
newrecord->pos = pos;
/* inform the tree view and other interested objects
* (e.g. tree row references) that we have inserted
* a new row, and where it was inserted */
path = gtk_tree_path_new();
gtk_tree_path_append_index(path, newrecord->pos);
custom_list_get_iter(GTK_TREE_MODEL(custom_list), &iter, path);
gtk_tree_model_row_inserted(GTK_TREE_MODEL(custom_list), path, &iter);
gtk_tree_path_free(path);
}
- custom-list.h
- custom-list.c
- main.c
main.c
[edit | edit source]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.
#include "custom-list.h"
#include <stdlib.h>
void
fill_model (CustomList *customlist)
{
const gchar *firstnames[] = { "Joe", "Jane", "William", "Hannibal", "Timothy", "Gargamel", NULL } ;
const gchar *surnames[] = { "Grokowich", "Twitch", "Borheimer", "Bork", NULL } ;
const gchar **fname, **sname;
for (sname = surnames; *sname != NULL; sname++)
{
for (fname = firstnames; *fname != NULL; fname++)
{
gchar *name = g_strdup_printf ("%s %s", *fname, *sname);
custom_list_append_record (customlist, name, 1900 + (guint) (103.0*rand()/(RAND_MAX+1900.0)));
g_free(name);
}
}
}
GtkWidget *
create_view_and_model (void)
{
GtkTreeViewColumn *col;
GtkCellRenderer *renderer;
CustomList *customlist;
GtkWidget *view;
customlist = custom_list_new();
fill_model(customlist);
view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(customlist));
g_object_unref(customlist); /* destroy store automatically with view */
renderer = gtk_cell_renderer_text_new();
col = gtk_tree_view_column_new();
gtk_tree_view_column_pack_start (col, renderer, TRUE);
gtk_tree_view_column_add_attribute (col, renderer, "text", CUSTOM_LIST_COL_NAME);
gtk_tree_view_column_set_title (col, "Name");
gtk_tree_view_append_column(GTK_TREE_VIEW(view),col);
renderer = gtk_cell_renderer_text_new();
col = gtk_tree_view_column_new();
gtk_tree_view_column_pack_start (col, renderer, TRUE);
gtk_tree_view_column_add_attribute (col, renderer, "text", CUSTOM_LIST_COL_YEAR_BORN);
gtk_tree_view_column_set_title (col, "Year Born");
gtk_tree_view_append_column(GTK_TREE_VIEW(view),col);
return view;
}
int
main (int argc, char **argv)
{
GtkWidget *window, *view, *scrollwin;
gtk_init(&argc,&argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_default_size (GTK_WINDOW(window), 200, 400);
g_signal_connect(window, "delete_event", gtk_main_quit, NULL);
scrollwin = gtk_scrolled_window_new(NULL,NULL);
view = create_view_and_model();
gtk_container_add(GTK_CONTAINER(scrollwin), view);
gtk_container_add(GTK_CONTAINER(window), scrollwin);
gtk_widget_show_all(window);
gtk_main();
return 0;
}