Treeview Tutorial
Treeview Tutorial
Tim-Philipp Mller
GTK+ 2.0 Tree View Tutorial by Tim-Philipp Mller This is a tutorial on how to use the GTK (the GIMP Toolkit) GtkTreeView widget through its C interface. Please mail all comments and suggestions to <tim at centricular dot net> A tarball of the tutorial for off-line reading including the example source codes is available here: treeview-tutorial.tar.gz. There is also a version in PDF format (for easier printing) and the raw docbook XML source document. This tutorial is work-in-progress. The latest version can be found at http://scentric.net/tutorial/. Some sections are a bit outdated (e.g. GtkTreeModelFilter has been in Gtk since 2.4), just havent gotten around to rewrite them or update them. Sorry! Last updated: September 29th, 2006
Table of Contents
1. Lists and Trees: the GtkTreeView Widget ...................................................................................................................... 1 1.1. Hello World .............................................................................................................................................................. 1 2. Components: Model, Renderer, Column, View ............................................................................................................ 4 3. GtkTreeModels for Data Storage: GtkListStore and GtkTreeStore .......................................................................... 5 3.1. How Data is Organised in a Store......................................................................................................................... 5 3.2. Refering to Rows: GtkTreeIter, GtkTreePath, GtkTreeRowReference .............................................................. 6 3.2.1. GtkTreePath ................................................................................................................................................. 6 3.2.2. GtkTreeIter ................................................................................................................................................... 7 3.2.3. GtkTreeRowReference................................................................................................................................ 8 3.2.4. Usage ............................................................................................................................................................ 8 3.3. Adding Rows to a Store.......................................................................................................................................... 9 3.3.1. Adding Rows to a List Store...................................................................................................................... 9 3.3.2. Adding Rows to a Tree Store................................................................................................................... 10 3.3.3. Speed Issues when Adding a Lot of Rows............................................................................................ 10 3.4. Manipulating Row Data ....................................................................................................................................... 11 3.5. Retrieving Row Data............................................................................................................................................. 12 3.5.1. Freeing Retrieved Row Data ................................................................................................................... 13 3.6. Removing Rows..................................................................................................................................................... 14 3.7. Removing Multiple Rows .................................................................................................................................... 15 3.8. Storing GObjects (Pixbufs etc.) ............................................................................................................................ 16 3.9. Storing Data Structures: of Pointers, GBoxed Types, and GObject (TODO)................................................. 17 4. Creating a Tree View ........................................................................................................................................................ 18 4.1. Connecting Tree View and Model....................................................................................................................... 18 4.1.1. Reference counting ................................................................................................................................... 18 4.2. Tree View Look and Feel ...................................................................................................................................... 18 5. Mapping Data to the Screen: GtkTreeViewColumn and GtkCellRenderer.......................................................... 20 5.1. Cell Renderers........................................................................................................................................................ 20 5.2. Attributes................................................................................................................................................................ 24 5.3. Cell Data Functions ............................................................................................................................................... 25 5.4. GtkCellRendererText and Integer, Boolean and Float Types .......................................................................... 26 5.5. GtkCellRendererText, UTF8, and pango markup............................................................................................. 26 5.6. A Working Example .............................................................................................................................................. 28 5.7. How to Make a Whole Row Bold or Coloured ................................................................................................. 30 5.8. How to Pack Icons into the Tree View................................................................................................................ 31 6. Selections, Double-Clicks and Context Menus .......................................................................................................... 33 6.1. Handling Selections .............................................................................................................................................. 33 6.1.1. Selection Modes ........................................................................................................................................ 33 6.1.2. Getting the Currently Selected Rows..................................................................................................... 33 6.1.3. Using Selection Functions........................................................................................................................ 34 6.1.4. Checking Whether a Row is Selected..................................................................................................... 36 6.1.5. Selecting and Unselecting Rows............................................................................................................. 36 6.1.6. Getting the Number of Selected Rows................................................................................................... 36 6.2. Double-Clicks on a Row ....................................................................................................................................... 36 6.3. Context Menus on Right Click ............................................................................................................................ 37 7. Sorting ................................................................................................................................................................................. 40 7.1. GtkTreeSortable ..................................................................................................................................................... 40 7.2. GtkTreeModelSort ................................................................................................................................................. 42 7.3. Sorting and Tree View Column Headers ........................................................................................................... 43 7.4. Case-insensitive String Comparing .................................................................................................................... 43 8. Editable Cells..................................................................................................................................................................... 45 8.1. Editable Text Cells ................................................................................................................................................. 45 8.1.1. Setting the cursor to a specic cell.......................................................................................................... 45 8.2. Editable Toggle and Radio Button Cells ............................................................................................................ 46 8.3. Editable Spin Button Cells.................................................................................................................................... 46
iii
9. Miscellaneous .................................................................................................................................................................... 47 9.1. Getting the Column Number from a Tree View Column Widget .................................................................. 47 9.2. Column Expander Visibility ................................................................................................................................ 48 9.2.1. Hiding the Column Expander ................................................................................................................ 48 9.2.2. Forcing Column Expander Visibility ..................................................................................................... 48 9.3. Getting the Cell Renderer a Click Event Happened On .................................................................................. 48 9.4. Glade and Tree Views ........................................................................................................................................... 49 10. DragnDrop (DnD) **** needs revision *** .............................................................................................................. 51 10.1. DragnDropping Row-Unrelated Data to and from a Tree View from other Windows or Widgets ...... 51 10.2. Dragging Rows Around Within a Tree **** TODO *** ................................................................................... 53 10.3. Dragging Rows from One Tree to Another **** TODO *** ............................................................................ 54 11. Writing Custom Models................................................................................................................................................. 55 11.1. When is a Custom Model Useful?..................................................................................................................... 55 11.2. What Does Writing a Custom Model Involve? ............................................................................................... 55 11.3. Example: A Simple Custom List Model ........................................................................................................... 55 11.3.1. custom-list.h ............................................................................................................................................ 56 11.3.2. custom-list.c ............................................................................................................................................. 56 11.4. From a List to a Tree ............................................................................................................................................ 58 11.5. Additional interfaces, here: the GtkTreeSortable interface............................................................................ 59 11.6. Working Example: Custom List Model Source Code..................................................................................... 64 11.6.1. custom-list.h ............................................................................................................................................ 64 11.6.2. custom-list.c ............................................................................................................................................. 65 11.6.3. main.c........................................................................................................................................................ 75 12. Writing Custom Cell Renderers ................................................................................................................................... 77 12.1. Working Example: a Progress Bar Cell Renderer............................................................................................ 77 12.1.1. custom-cell-renderer-progressbar.h ..................................................................................................... 77 12.1.2. custom-cell-renderer-progressbar.c ...................................................................................................... 78 12.1.3. main.c........................................................................................................................................................ 83 12.2. Cell Renderers Others Have Written ................................................................................................................ 84 13. Other Resources .............................................................................................................................................................. 86 14. Copyright, License, Credits, and Revision History.................................................................................................. 87 14.1. Copyright and License........................................................................................................................................ 87 14.2. Credits ................................................................................................................................................................... 87 14.3. Revision History .................................................................................................................................................. 87
iv
much more powerful and exible that most application developers will not want to miss it once they have come to know it. The purpose of this chapter is not to provide an exhaustive documentation of GtkTreeView - that is what the API documentation is for, which should be read alongside with this tutorial. The goal is rather to present an introduction to the most commonly-used aspects of GtkTreeView, and to demonstrate how the various GtkTreeView components and concepts work together. Furthermore, an attempt has been made to shed some light on custom tree models and custom cell renderers, which seem to be often-mentioned, but rarely explained. Developers looking for a quick and dirty introduction that teaches them everything they need to know in less than ve paragraphs will not nd it here. In the authors experience, developers who do not understand how the tree view and the models work together will run into problems once they try to modify the given examples, whereas developers who have worked with other toolkits that employ the Model/View/Controller-design will nd that the API reference provides all the information they need to know in more condensed form anyway. Those who disagree may jump straight to the working example code of course. Please note that the code examples in the following sections do not necessarily demonstrate how GtkTreeView is used best in a particular situation. There are different ways to achieve the same result, and the examples merely show those different ways, so that developers are able to decide which one is most suitable for the task at hand.
static GtkTreeModel * create_and_fill_model (void) { GtkListStore *store; GtkTreeIter iter; store = gtk_list_store_new (NUM_COLS, G_TYPE_STRING, G_TYPE_UINT); /* Append a row and fill in some data */ gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, COL_NAME, "Heinz El-Mann", COL_AGE, 51, -1); /* append another row and fill in some data */ gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, COL_NAME, "Jane Doe", COL_AGE, 23, -1); /* ... and a third row */
view = gtk_tree_view_new (); /* --- Column #1 --- */ renderer = gtk_cell_renderer_text_new (); gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), -1, "Name", renderer, "text", COL_NAME, NULL); /* --- Column #2 --- */ renderer = gtk_cell_renderer_text_new (); gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), -1, "Age", renderer, "text", COL_AGE, NULL); model = create_and_fill_model (); gtk_tree_view_set_model (GTK_TREE_VIEW (view), model); /* The tree view has acquired its own reference to the * model, so we can drop ours. That way the model will * be freed automatically when the tree view is destroyed */ g_object_unref (model); return view; }
int main (int argc, char **argv) { GtkWidget *window; GtkWidget *view; gtk_init (&argc, &argv); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); g_signal_connect (window, "delete_event", gtk_main_quit, NULL); /* dirty */ view = create_view_and_model (); gtk_container_add (GTK_CONTAINER (window), view); gtk_widget_show_all (window); gtk_main ();
with exibility in mind. If you plan to store a lot of data, or have a large number of rows, you should consider implementing your own custom model that stores and manipulates data your own way and implements the GtkTreeModel interface. This will not only be more efcient, but probably also lead to saner code in the long run, and give you more control over your data. See below for more details on how to implement custom models. Tree model implementations like GtkListStore and GtkTreeStore will take care of the view side for you once you have congured the GtkTreeView to display what you want. If you change data in the store, the model will notify the tree view and your data display will be updated. If you add or remove rows, the model will also notify the store, and your row will appear in or disappear from the view as well.
sions)
G_TYPE_FLOAT, G_TYPE_DOUBLE G_TYPE_STRING
- stores a string in the store (makes a copy of the original string) - stores a pointer value (does not copy any data into the store, just stores the pointer value!) - stores a GdkPixbuf in the store (increases the pixbufs refcount, see below)
G_TYPE_POINTER
GDK_TYPE_PIXBUF
You do not need to understand the type system, it will usually sufce to know the above types, so you can tell a list store or tree store what kind of data you want to store. Advanced users can derive their own types from the fundamental GLib types. For simple structures you could register a new boxed type for example, but that is usually not necessary. G_TYPE_POINTER will often do as well, you will just need to take care of memory allocation and freeing yourself then. 5
Chapter 3. GtkTreeModels for Data Storage: GtkListStore and GtkTreeStore Storing GObject-derived types (most GDK_TYPE_FOO and GTK_TYPE_FOO) is a special case that is dealt with further below. Here is an example of how to create a list store:
GtkListStore *list_store; list_store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_UINT);
This creates a new list store with two columns. Column 0 stores a string and column 1 stores an unsigned integer for each row. At this point the model has no rows yet of course. Before we start to add rows, lets have a look at the different ways used to refer to a particular row.
3.2.1. GtkTreePath
A GtkTreePath is a comparatively straight-forward way to describe the logical position of a row in the model. As a GtkTreeView always displays all rows in a model, a tree path always describes the same row in both model and view.
Figure 3-1. Tree Paths The picture shows the tree path in string form next to the label. Basically, it just counts the children from the imaginary root of the tree view. An empty tree path string would specify that imaginary invisible root. Now Songs is the rst child (from the root) and thus its tree path is just "0". Videos is the second child from the root, and its tree path is "1". oggs is the second child of the rst item from the root, so its tree path is "0:1". So you just count your way down from the root to the row in question, and you get your tree path. To clarify this, a tree path of "3:9:4:1" would basically mean in human language (attention - this is not what it really means!) something along the lines of: go to the 3rd top-level row. Now go to the 9th child of that row. Proceed to the 4th child of the previous row. Then continue to the 1st child of that. Now you are at the row this tree path describes. This is not what it means for Gtk+ though. While humans start counting at 1, computers usually start counting at 0. So the real meaning of the tree path "3:9:4:1" is: Go to the 4th top-level row. Then go to the 10th child of that row. Pick the 5th child of that row. Then proceed to the 2nd child of the previous row. Now you are at the row this tree path describes. :) The implication of this way of refering to rows is as follows: if you insert or delete rows in the middle or if the rows are resorted, a tree path might suddenly refer to a completely different row than it refered to before the insertion/deletion/resorting. This is important to keep in mind. (See the section on GtkTreeRowReferences 6
Chapter 3. GtkTreeModels for Data Storage: GtkListStore and GtkTreeStore below for a tree path that keeps updating itself to make sure it always refers to the same row when the model changes). This effect becomes apparent if you imagine what would happen if we were to delete the row entitled funny clips from the tree in the above picture. The row movie trailers would suddenly be the rst and only child of clips, and be described by the tree path that formerly belonged to funny clips, ie. "1:0:0". You can get a new GtkTreePath from a path in string form using gtk_tree_path_new_from_string, and you can convert a given GtkTreePath into its string notation with gtk_tree_path_to_string. Usually you will rarely have to handle the string notation, it is described here merely to demonstrate the concept of tree paths. Instead of the string notation, GtkTreePath uses an integer array internally. You can get the depth (ie. the nesting level) of a tree path with gtk_tree_path_get_depth. A depth of 0 is the imaginary invisible root node of the tree view and model. A depth of 1 means that the tree path describes a top-level row. As lists are just trees without child nodes, all rows in a list always have tree paths of depth 1. gtk_tree_path_get_indices returns the internal integer array of a tree path. You will rarely need to operate with those either. If you operate with tree paths, you are most likely to use a given tree path, and use functions like gtk_tree_path_up, gtk_tree_path_down, gtk_tree_path_next, gtk_tree_path_prev, gtk_tree_path_is_ancestor, or gtk_tree_path_is_descendant. Note that this way you can construct and operate on tree paths that refer to rows that do not exist in model or view! The only way to check whether a path is valid for a specic model (ie. the row described by the path exists) is to convert the path into an iter using gtk_tree_model_get_iter.
GtkTreePath is an opaque structure, with its details hidden from the compiler. If you need to make a copy of a tree path, use gtk_tree_path_copy.
3.2.2. GtkTreeIter
Another way to refer to a row in a list or tree is GtkTreeIter. A tree iter is just a structure that contains a couple of pointers that mean something to the model you are using. Tree iters are used internally by models, and they often contain a direct pointer to the internal data of the row in question. You should never look at the content of a tree iter and you must not modify it directly either. All tree models (and therefore also GtkListStore and GtkTreeStore) must support the GtkTreeModel functions that operate on tree iters (e.g. get the tree iter for the rst child of the row specied by a given tree iter, get the rst row in the list/tree, get the n-th child of a given iter etc.). Some of these functions are:
gtk_tree_model_get_iter_first gtk_tree_model_iter_next
- sets the given iter to the rst top-level item in the list or tree
- sets the given iter to the next item at the current level in a list or tree.
gtk_tree_model_iter_children - sets the rst given iter to the rst child of the row referenced by the second
iter (not very useful for lists, mostly useful for trees).
gtk_tree_model_iter_n_children
- returns the number of children the row referenced by the provided iter has. If you pass NULL instead of a pointer to an iter structure, this function will return the number of top-level rows. You can also use this function to count the number of items in a list store.
gtk_tree_model_iter_nth_child
- sets the rst iter to the n-th child of the row referenced by the second iter. If you pass NULL instead of a pointer to an iter structure as the second iter, you can get the rst iter set to the n-th row of a list. - sets the rst iter to the parent of the row referenced by the second iter (does nothing for lists, only useful for trees).
gtk_tree_model_iter_parent
Almost all of those functions return TRUE if the requested operation succeeded, and return FALSE otherwise. There are more functions that operate on iters. Check out the GtkTreeModel API reference for details. You might notice that there is no gtk_tree_model_iter_prev. This is unlikely to be implemented for a variety of reasons. It should be fairly simple to write a helper function that provides this functionality though once you have read this section. Tree iters are used to retrieve data from the store, and to put data into the store. You also get a tree iter as result if you add a new row to the store using gtk_list_store_append or gtk_tree_store_append. Tree iters are often only valid for a short time, and might become invalid if the store changes with some models. It is therefore usually a bad idea to store tree iters, unless you really know what you are doing. You can use gtk_tree_model_get_flags to get a models ags, and check whether the GTK_TREE_MODEL_ITERS_PERSIST ag is set (in which case a tree iter will be valid as long as a row exists), yet still it is not advisable to store iter structures unless you really mean to do that. There is a better way to keep track of a row over time: GtkTreeRowReference 7
3.2.3. GtkTreeRowReference
A GtkTreeRowReference is basically an object that takes a tree path, and watches a model for changes. If anything changes, like rows getting inserted or removed, or rows getting re-ordered, the tree row reference object will keep the given tree path up to date, so that it always points to the same row as before. In case the given row is removed, the tree row reference will become invalid. A new tree row reference can be created with gtk_tree_row_reference_new, given a model and a tree path. After that, the tree row reference will keep updating the path whenever the model changes. The current tree path of the row originally refered to when the tree row reference was created can be retrieved with gtk_tree_row_reference_get_path. If the row has been deleted, NULL will be returned instead of of a tree path. The tree path returned is a copy, so it will need to be freed with gtk_tree_path_free when it is no longer needed. You can check whether the row referenced still exists with gtk_tree_row_reference_valid, and free it with when no longer needed. For the curious: internally, the tree row reference connects to the tree models "row-inserted", "row-deleted", and "rows-reordered" signals and updates its internal tree path whenever something happened to the model that affects the position of the referenced row. Note that using tree row references entails a small overhead. This is hardly signicant for 99.9% of all applications out there, but when you have multiple thousands of rows and/or row references, this might be something to keep in mind (because whenever rows are inserted, removed, or reordered, a signal will be sent out and processed for each row reference). If you have read the tutorial only up to here so far, it is hard to explain really what tree row references are good for. An example where tree row references come in handy can be found further below in the section on removing multiple rows in one go. In practice, a programmer can either use tree row references to keep track of rows over time, or store tree iters directly (if, and only if, the model has persistent iters). Both GtkListStore and GtkTreeStore have persistent iters, so storing iters is possible. However, using tree row references is denitively the Right Way(tm) to do things, even though it comes with some overhead that might impact performance in case of trees that have a very large number of rows (in that case it might be preferable to write a custom model anyway though). Especially beginners might nd it easier to handle and store tree row references than iters, because tree row references are handled by pointer value, which you can easily add to a GList or pointer array, while it is easy to store tree iters in a wrong way.
3.2.4. Usage
Tree iters can easily be converted into tree paths using gtk_tree_model_get_path, and tree paths can easily be converted into tree iters using gtk_tree_model_get_iter. Here is an example that shows how to get the iter from the tree path that is passed to us from the tree view in the "row-activated" signal callback. We need the iter here to retrieve data from the store
/************************************************************ * * * Converting a GtkTreePath into a GtkTreeIter * * * ************************************************************/ /************************************************************ * * onTreeViewRowActivated: a row has been double-clicked * ************************************************************/ void onTreeViewRowActivated (GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *col, gpointer userdata) { GtkTreeIter iter; GtkTreeModel *model; model = gtk_tree_view_get_model(view); if (gtk_tree_model_get_iter(model, &iter, path)) {
Tree row references reveal the current path of a row with gtk_tree_row_reference_get_path. There is no direct way to get a tree iter from a tree row reference, you have to retrieve the tree row references path rst and then convert that into a tree iter. As tree iters are only valid for a short time, they are usually allocated on the stack, as in the following example (keep in mind that GtkTreeIter is just a structure that contains data elds you do not need to know anything about):
/************************************************************ * * * Going through every row in a list store * * * ************************************************************/ void traverse_list_store (GtkListStore *liststore) { GtkTreeIter iter; gboolean valid; g_return_if_fail ( liststore != NULL ); /* Get first row in list store */ valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(liststore), &iter); while (valid) { /* ... do something with that row using the iter ... */ /* (Here column 0 of the list store is of type G_TYPE_STRING) */ gtk_list_store_set(liststore, &iter, 0, "Joe", -1); /* Make iter point to the next row in the list store */ valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(liststore), &iter); } }
The code above asks the model to ll the iter structure to make it point to the rst row in the list store. If there is a rst row and the list store is not empty, the iter will be set, and gtk_tree_model_get_iter_first will return TRUE. If there is no rst row, it will just return FALSE. If a rst row exists, the while loop will be entered and we change some of the rst rows data. Then we ask the model to make the given iter point to the next row, until there are no more rows, which is when gtk_tree_model_iter_next returns FALSE. Instead of traversing the list store we could also have used gtk_tree_model_foreach
liststore = gtk_list_store_new(1, G_TYPE_STRING); /* Append an empty row to the list store. Iter will point to the new row */ gtk_list_store_append(liststore, &iter); /* Append an empty row to the list store. Iter will point to the new row */ gtk_list_store_append(liststore, &iter); /* Append an empty row to the list store. Iter will point to the new row */ gtk_list_store_append(liststore, &iter);
This in itself is not very useful yet of course. We will add data to the rows in the next section.
10
model = gtk_tree_view_get_model(GTK_TREE_VIEW(view)); g_object_ref(model); /* Make sure the model stays with us after the tree view unrefs it */ gtk_tree_view_set_model(GTK_TREE_VIEW(view), NULL); /* Detach model from view */ ... insert a couple of thousand rows ... gtk_tree_view_set_model(GTK_TREE_VIEW(view), model); /* Re-attach model to view */ g_object_unref(model); ...
Secondly, you should make sure that sorting is disabled while you are doing your mass insertions, otherwise your store might be resorted after each and every single row insertion, which is going to be everything but fast. Thirdly, you should not keep around a lot of tree row references if you have so many rows, because with each insertion (or removal) every single tree row reference will check whether its path needs to be updated or not.
familiar with GLibs GValue system. Both gtk_list_store_set and gtk_tree_store_set take a variable number of arguments, and must be terminated with a -1 argument. The rst two arguments are a pointer to the model, and the iter pointing to the row whose data we want to change. They are followed by a variable number of (column, data) argument pairs, terminated by a -1. The column refers to the model column number and is usually an enum value (to make the code more readable and to make changes easier). The data should be of the same data type as the model column. Here is an example where we create a store that stores two strings and one integer for each row:
enum { COL_FIRST_NAME = 0, COL_LAST_NAME, COL_YEAR_BORN, NUM_COLS }; GtkListStore *liststore; GtkTreeIter iter; liststore = gtk_list_store_new(NUM_COLS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT); /* Append an empty row to the list store. Iter will point to the new row */ gtk_list_store_append(liststore, &iter); /* Fill fields with some data */ gtk_list_store_set (liststore, &iter, COL_FIRST_NAME, "Joe", COL_LAST_NAME, "Average", COL_YEAR_BORN, (guint) 1970, -1);
You do not need to worry about allocating and freeing memory for the data to store. The model (or more precisely: the GLib/GObject GType and GValue system) will take care of that for you. If you store a string, for example, the model will make a copy of the string and store that. If you then set the eld to a new string later on, the model will automatically free the old string and again make a copy of the new string and store the copy. This applies to almost all types, be it G_TYPE_STRING or GDK_TYPE_PIXBUF. The exception to note is G_TYPE_POINTER. If you allocate a chunk of data or a complex structure and store it in a G_TYPE_POINTER eld, only the pointer value is stored. The model does not know anything about the size or content of the data your pointer refers to, so it could not even make a copy if it wanted to, so you need to 11
Chapter 3. GtkTreeModels for Data Storage: GtkListStore and GtkTreeStore allocate and free the memory yourself in this case. However, if you do not want to do that yourself and want the model to take care of your custom data for you, then you need to register your own type and derive it from one of the GLib fundamental types (usually G_TYPE_BOXED). See the GObject GType reference manual for details. Making a copy of data involves memory allocation and other overhead of course, so one should consider the performance implications of using a custom GLib type over a G_TYPE_POINTER carefully before taking that approach. Again, a custom model might be the better alternative, depending on the overall amount of data to be stored (and retrieved).
12
Note that when a new row is created, all elds of a row are set to a default NIL value appropriate for the data type in question. A eld of type G_TYPE_INT will automatically contain the value 0 until it is set to a different value, and strings and all kind of pointer types will be NULL until set to something else. Those are valid contents for the model, and if you are not sure that row contents have been set to something, you need to be prepared to handle NULL pointers and the like in your code. Run the above program with an additional empty row and look at the output to see this in effect.
13
Similarly, GBoxed-derived types retrieved from a model need to be freed with g_boxed_free when done with them (dont worry if you have never heard of GBoxed). If the model column is of type G_TYPE_POINTER, gtk_tree_model_get will simply copy the pointer value, but not the data (even if if it wanted to, it couldnt copy the data, because it would not know how to copy it or what to copy exactly). If you store pointers to objects or strings in a pointer column (which you should not do unless you really know what you are doing and why you are doing it), you do not need to unref or free the returned values as described above, because gtk_tree_model_get would not know what kind of data they are and therefore wont ref or copy them on retrieval.
g_print ("Row has been double-clicked. Removing row.\n"); model = gtk_tree_view_get_model(view); if (!gtk_tree_model_get_iter(model, &iter, path)) return; /* path describes a non-existing row - should not happen */ gtk_list_store_remove(GTK_LIST_STORE(model), &iter); }
Note: gtk_list_store_remove and gtk_tree_store_remove both have slightly different semantics in Gtk+2.0 and Gtk+-2.2 and later. In Gtk+-2.0, both functions do not return a value, while in later Gtk+ versions those functions return either TRUE or FALSE to indicate whether the iter given has been set to the next valid row (or invalidated if there is no next row). This is important to keep in mind when writing code that is supposed to work with all Gtk+-2.x versions. In that case you should just ignore the value returned (as in the call above) and check the iter with gtk_list_store_iter_is_valid if you need it. If you want to remove the n-th row from a list (or the n-th child of a tree node), you have two approaches: either you rst create a GtkTreePath that describes that row and then turn it into an iter and remove it; or you take the 14
Chapter 3. GtkTreeModels for Data Storage: GtkListStore and GtkTreeStore iter of the parent node and use gtk_tree_model_iter_nth_child (which will also work for list stores if you use NULL as the parent iter. Of course you could also start with the iter of the rst top-level row, and then step-by-step move it to the row you want, although that seems a rather awkward way of doing it. The following code snippet will remove the n-th row of a list if it exists:
/****************************************************************** * * list_store_remove_nth_row * * Removes the nth row of a list store if it exists. * * Returns TRUE on success or FALSE if the row does not exist. * ******************************************************************/ gboolean list_store_remove_nth_row (GtkListStore *store, gint n) { GtkTreeIter iter; g_return_val_if_fail (GTK_IS_LIST_STORE(store), FALSE); /* NULL means the parent is the virtual root node, so the * n-th top-level element is returned in iter, which is * the n-th row in a list store (as a list store only has * top-level elements, and no children) */ if (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(store), &iter, NULL, n)) { gtk_list_store_remove(store, &iter); return TRUE; } return FALSE; }
15
iter;
if (gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, path)) { gtk_list_store_remove(store, &iter); } /* FIXME/CHECK: Do we need to free the path here? */ } } g_list_foreach(rr_list, (GFunc) gtk_tree_row_reference_free, NULL); g_list_free(rr_list); } ... gtk_list_store_clear and gtk_tree_store_clear come in handy if you want to remove all rows.
16
Having learned how to add, manipulate, and retrieve data from a store, the next step is to get that data displayed in a GtkTreeView widget.
3.9. Storing Data Structures: of Pointers, GBoxed Types, and GObject (TODO)
Unnished chapter.
17
larly useful in callbacks where you only get passed the tree view widget (after all, we do not want to go down the road of global variables, which will inevitably lead to the Dark Side, do we?).
setting is only a hint; in the end it depends on the active Gtk+ theme engine if the tree view shows ruled lines or not. Users seem to have strong feelings about rules in tree views, so it is probably a good idea to provide an option somewhere to disable rule hinting if you set it on tree views (but then, people also seem to have strong feelings about options abundance and sensible default options, so whatever you do will probably upset someone at some point). The expander column can be set with gtk_tree_view_set_expander_column. This is the column where child elements are indented with respect to their parents, and where rows with children have an expander arrow with which a nodes children can be collapsed (hidden) or expanded (shown). By default, this is the rst column. 18
Notes
1. Reference counting means that an object has a counter that can be increased or decreased (ref-ed and unrefed). If the counter is unref-ed to 0, the object is automatically destroyed. This is useful, because other objects or application programmers only have to think about whether they themselves are still using that object or not, without knowing anything about others also using it. The object is simply automatically destroyed when no one is using it any more. 2. Rules means that every second line of the tree view has a shaded background, which makes it easier to see which cell belongs to which row in tree views that have a lot of columns.
19
Figure 5-1. Cell Renderer Properties In the above diagram, both Country and Representative are tree view columns, where the Country and Representative labels are the column headers. The Country column contains two cell renderers, one to display the ag icons, and one to display the country name. The Representative column only contains one cell renderer to display the representatives name.
Chapter 5. Mapping Data to the Screen: GtkTreeViewColumn and GtkCellRenderer row to be rendererd, so that you can manually set the properties of the cell renderer before it is rendered. Both approaches can be used at the same time as well. Lastly, you can set a cell renderer property when you create the cell renderer. That way it will be used for all rows/cells to be rendered (unless it is changed later of course). Different cell renderers exist for different purposes:
GtkCellRendererText
GtkCellRendererPixbuf is used to display images; either user-dened images, or one of the stock icons that
is a special cell that implements editable cells (ie. GtkEntry or GtkSpinbutton in a treeview). This is not a cell renderer! If you want to have editable text cells, use GtkCellRendererText and make sure the "editable" property is set. GtkCellEditable is only used by implementations of editable cells and widgets that can be inside of editable cells. You are unlikely to ever need it.
Contrary to what one may think, a cell renderer does not render just one single cell, but is responsible for rendering part or whole of a tree view column for each single row. It basically starts in the rst row and renders its part of the column there. Then it proceeds to the next row and renders its part of the column there again. And so on. How does a cell renderer know what to render? A cell renderer object has certain properties that are documented in the API reference (just like most other objects, and widgets). These properties determine what the cell renderer is going to render and how it is going to be rendered. Whenever the cell renderer is called upon to render a certain cell, it looks at its properties and renders the cell accordingly. This means that whenever you set a property or change a property of the cell renderer, this will affect all rows that are rendered after the change, until you change the property again. Here is a diagram (courtesy of Owen Taylor) that tries to show what is going on when rows are rendered:
Chapter 5. Mapping Data to the Screen: GtkTreeViewColumn and GtkCellRenderer The above diagram shows the process when attributes are used. In the example, a text cell renderers "text" property has been linked to the rst model column. The "text" property contains the string to be rendered. The "foreground" property, which contains the colour of the text to be shown, has been linked to the second model column. Finally, the "strikethrough" property, which determines whether the text should be with a horizontal line that strikes through the text, has been connected to the third model column (of type G_TYPE_BOOLEAN). With this setup, the cell renderers properties are loaded from the model before each cell is rendered. Here is a silly and utterly useless little example that demonstrates this behaviour, and introduces some of the most commonly used properties of GtkCellRendererText:
#include <gtk/gtk.h> enum { COL_FIRST_NAME = 0, COL_LAST_NAME, NUM_COLS } ; static GtkTreeModel * create_and_fill_model (void) { GtkTreeStore *treestore; GtkTreeIter toplevel, child; treestore = gtk_tree_store_new(NUM_COLS, G_TYPE_STRING, G_TYPE_STRING); /* Append a top level row and leave it empty */ gtk_tree_store_append(treestore, &toplevel, NULL); /* Append a second top level row, and fill it with some data */ gtk_tree_store_append(treestore, &toplevel, NULL); gtk_tree_store_set(treestore, &toplevel, COL_FIRST_NAME, "Joe", COL_LAST_NAME, "Average", -1); /* Append a child to the second top level row, and fill in some data */ gtk_tree_store_append(treestore, &child, &toplevel); gtk_tree_store_set(treestore, &child, COL_FIRST_NAME, "Jane", COL_LAST_NAME, "Average", -1); return GTK_TREE_MODEL(treestore); } static GtkWidget * create_view_and_model { GtkTreeViewColumn GtkCellRenderer GtkWidget GtkTreeModel
view = gtk_tree_view_new(); /* --- Column #1 --- */ col = gtk_tree_view_column_new(); gtk_tree_view_column_set_title(col, "First Name"); /* pack tree view column into tree view */ gtk_tree_view_append_column(GTK_TREE_VIEW(view), col); renderer = gtk_cell_renderer_text_new(); /* pack cell renderer into tree view column */ gtk_tree_view_column_pack_start(col, renderer, TRUE);
22
/* set text property of the cell renderer */ g_object_set(renderer, "text", "Boooo!", NULL);
/* --- Column #2 --- */ col = gtk_tree_view_column_new(); gtk_tree_view_column_set_title(col, "Last Name"); /* pack tree view column into tree view */ gtk_tree_view_append_column(GTK_TREE_VIEW(view), col); renderer = gtk_cell_renderer_text_new(); /* pack cell renderer into tree view column */ gtk_tree_view_column_pack_start(col, renderer, TRUE); /* set cell-background property of the cell renderer */ g_object_set(renderer, "cell-background", "Orange", "cell-background-set", TRUE, NULL); model = create_and_fill_model(); gtk_tree_view_set_model(GTK_TREE_VIEW(view), model); g_object_unref(model); /* destroy model automatically with view */ gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(view)), GTK_SELECTION_NONE); return view; }
int main (int argc, char **argv) { GtkWidget *window; GtkWidget *view; gtk_init(&argc, &argv); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); g_signal_connect(window, "delete_event", gtk_main_quit, NULL); /* dirty */ view = create_view_and_model(); gtk_container_add(GTK_CONTAINER(window), view); gtk_widget_show_all(window); gtk_main(); return 0; }
23
Figure 5-3. Persistent Cell Renderer Properties It looks like the tree view display is partly correct and partly incomplete. On the one hand the tree view renders the correct number of rows (note how there is no orange on the right after row 3), and it displays the hierarchy correctly (on the left), but it does not display any of the data that we have stored in the model. This is because we have made no connection between what the cell renderers should render and the data in the model. We have simply set some cell renderer properties on start-up, and the cell renderers adhere to those set properties meticulously. There are two different ways to connect cell renderers to data in the model: attributes and cell data functions.
5.2. Attributes
An attribute is a connection between a cell renderer property and a eld/column in the model. Whenever a cell is to be rendered, a cell renderer property will be set to the values of the specied model column of the row that is to be rendered. It is very important that the columns data type is the same type that a property takes according to the API reference manual. Here is some code to look at:
... col = gtk_tree_view_column_new(); renderer = gtk_cell_renderer_text_new(); gtk_tree_view_column_pack_start(col, renderer, TRUE); gtk_tree_view_column_add_attribute(col, renderer, "text", COL_FIRST_NAME); ...
This means that the text cell renderer property "text" will be set to the string in model column COL_FIRST_NAME of each row to be drawn. It is important to internalise the difference between gtk_tree_view_column_add_attribute and g_object_set: g_object_set sets a property to a certain value, while gtk_tree_view_column_add_attribute sets a property to whatever is in the specied _model column_ at the time of rendering. Again, when setting attributes it is very important that the data type stored in a model column is the same as the data type that a property requires as argument. Check the API reference manual to see the data type that is required for each property. When reading through the example a bit further above, you might have noticed that we set the "cell-background" property of a GtkCellRendererText, even though the API documentation does not list such a property. We can do this, because GtkCellRendererText is derived from GtkCellRenderer, which does in fact have such a property. Derived classes inherit the properties of their parents. This is the same as with widgets that you can cast into one of their ancestor classes. The API reference has an object hierarchy that shows you which classes a widget or some other object is derived from. There are two more noteworthy things about GtkCellRenderer properties: one is that sometimes there are different properties which do the same, but take different arguments, such as the "foreground" and "foreground-gdk" properties of GtkCellRendererText (which specify the text colour). The "foreground" property take a colour in string form, such as "Orange" or "CornowerBlue", whereas "foreground-gdk" takes a GdkColor argument. It is up to you to decide which one to use - the effect will be the same. The other thing worth mentioning is that most properties have a "foo-set" property taking a boolean value as argument, such as "foreground-set". This is useful when you want to have a certain setting have an effect or not. If you set 24
Chapter 5. Mapping Data to the Screen: GtkTreeViewColumn and GtkCellRenderer the "foreground" property, but set "foreground-set" to FALSE, then your foreground color setting will be disregarded. This is useful in cell data functions (see below), or, for example, if you want set the foreground colour to a certain value at start-up, but only want this to be in effect in some columns, but not in others (in which case you could just connect the "foreground-set" property to a model column of type G_TYPE_BOOLEAN with gtk_tree_view_column_add_attribute. Setting column attributes is the most straight-forward way to get your model data to be displayed. This is usually used whenever you want the data in the model to be displayed exactly as it is in the model. Another way to get your model data displayed on the screen is to set up cell data functions.
for each row to be rendered by this particular cell renderer, the cell data function is going to be called, which then retrieves the oat from the model, and turns it into a string where the oat has only one digit after the colon/comma, and renders that with the text cell renderer. 25
Chapter 5. Mapping Data to the Screen: GtkTreeViewColumn and GtkCellRenderer This is only a simple example, you can make cell data functions a lot more complicated if you want to. As always, there is a trade-off to keep in mind though. Your cell data function is going to be called every single time a cell in that (renderer) column is going to be rendered. Go and check how often this function is called in your program if you ever use one. If you do time-consuming operations within a cell data function, things are not going to be fast, especially if you have a lot of rows. The alternative in this case would have been to make an additional column COLUMN_AGE_FLOAT_STRING of type G_TYPE_STRING, and to set the oat in string form whenever you set the oat itself in a row, and then hook up the string column to a text cell renderer using attributes. This way the oat to string conversion would only need to be done once. This is a cpu cycles / memory trade-off, and it depends on your particular case which one is more suitable. Things you should probably not do is to convert long strings into UTF8 format in a cell data function, for example. You might notice that your cell data function is called at times even for rows that are not visible at the moment. This is because the tree view needs to know its total height, and in order to calculate this it needs to know the height of each and every single row, and it can only know that by having it measured, which is going to be slow when you have a lot of rows with different heights (if your rows all have the same height, there should not be any visible delay though).
Even though the "text" property would require a string value, we use a model column of an integer type when setting attributes. The integer will then automatically be converted into a string before the cell renderer property is set 1. If you are using a oating point type, ie. G_TYPE_FLOAT or G_TYPE_DOUBLE, there is no way to tell the text cell renderer how many digits after the oating point (or comma) should be rendered. If you only want a certain amount of digits after the point/comma, you will need to use a cell data function.
Chapter 5. Mapping Data to the Screen: GtkTreeViewColumn and GtkCellRenderer rst invalid character). Filenames are especially hard, because there is no indication whatsoever what character encoding a lename is in (it might have been created when the user was using a different locale, so lename encoding is basically unreliable and broken). You may want to convert to UTF8 with fallback characters in that case. You can check whether a string is valid UTF8 with g_utf8_validate. You should, in this authors opinion at least, put these checks into your code at crucial places wherever it is not affecting performance, especially if you are an English-speaking programmer that has little experience with non-English locales. It will make it easier for others and yourself to spot problems with non-English locales later on. In addition to the "text" property, GtkCellRendererText also has a "markup" property that takes text with pango markup as input. Pango markup allows you to place special tags into a text string that affect the style the text is rendered (see the pango documentation). Basically you can achieve everything you can achieve with the other properties also with pango markup (only that using properties is more efcient and less messy). Pango markup has one distinct advantage though that you cannot achieve with text cell renderer properties: with pango markup, you can change the text style in the middle of the text, so you could, for example, render one part of a text string in bold print, and the rest of the text in normal. Here is an example of a string with pango markup:
"You can have text in <b>bold</b> or in a <span color=Orange>different color</span>"
When using the "markup" property, you need to take into account that the "markup" and "text" properties do not seem to be mutually exclusive (I suppose this could be called a bug). In other words: whenever you set "markup" (and have used the "text" property before), set the "text" property to NULL, and vice versa. Example:
... void foo_cell_data_function ( ... ) { ... if (foo->is_important) g_object_set(renderer, "markup", "<b>important</b>", "text", NULL, NULL); else g_object_set(renderer, "markup", NULL, "text", "not important", NULL); ... } ...
Another thing to keep in mind when using pango markup text is that you might need to escape text if you construct strings with pango markup on the y using random input data. For example:
... void foo_cell_data_function ( ... ) { gchar *markuptxt; ... /* This might be problematic if artist_string or title_string contain markup characters/entities: */ * markuptxt = g_strdup_printf("<b>%s</b> - <i>%s</i>", artist_string, title_string); ... g_object_set(renderer, "markup", markuptxt, "text", NULL, NULL); ... g_free(markuptxt); } ...
The above example will not work if artist_string is "Simon & Garfunkel" for example, because the & character is one of the characters that is special. They need to be escaped, so that pango knows that they do not refer to any pango markup, but are just characters. In this case the string would need to be "Simon & Garfunkel" in order to make sense in between the pango markup in which it is going to be pasted. You can escape a string with g_markup_escape (and you will need to free the resulting newly-allocated string again with g_free). It is possible to combine both pango markup and text cell renderer properties. Both will be added together to render the string in question, only that the text cell renderer properties will be applied to the whole string. If you set the "markup" property to normal text without any pango markup, it will render as normal text just as if you 27
Chapter 5. Mapping Data to the Screen: GtkTreeViewColumn and GtkCellRenderer had used the "text" property. However, as opposed to the "text" property, special characters in the "markup" property text would still need to be escaped, even if you do not use pango markup in the text.
28
view = gtk_tree_view_new(); /* --- Column #1 --- */ col = gtk_tree_view_column_new(); gtk_tree_view_column_set_title(col, "First Name"); /* pack tree view column into tree view */ gtk_tree_view_append_column(GTK_TREE_VIEW(view), col); renderer = gtk_cell_renderer_text_new(); /* pack cell renderer into tree view column */ gtk_tree_view_column_pack_start(col, renderer, TRUE); /* connect text property of the cell renderer to * model column that contains the first name */ gtk_tree_view_column_add_attribute(col, renderer, "text", COL_FIRST_NAME);
/* --- Column #2 --- */ col = gtk_tree_view_column_new(); gtk_tree_view_column_set_title(col, "Last Name"); /* pack tree view column into tree view */ gtk_tree_view_append_column(GTK_TREE_VIEW(view), col); renderer = gtk_cell_renderer_text_new(); /* pack cell renderer into tree view column */ gtk_tree_view_column_pack_start(col, renderer, TRUE); /* connect text property of the cell renderer to * model column that contains the last name */ gtk_tree_view_column_add_attribute(col, renderer, "text", COL_LAST_NAME); /* set weight property of the cell renderer to * bold print (we want all last names in bold) */ g_object_set(renderer, "weight", PANGO_WEIGHT_BOLD,
29
/* --- Column #3 --- */ col = gtk_tree_view_column_new(); gtk_tree_view_column_set_title(col, "Age"); /* pack tree view column into tree view */ gtk_tree_view_append_column(GTK_TREE_VIEW(view), col); renderer = gtk_cell_renderer_text_new(); /* pack cell renderer into tree view column */ gtk_tree_view_column_pack_start(col, renderer, TRUE); /* connect a cell data function */ gtk_tree_view_column_set_cell_data_func(col, renderer, age_cell_data_func, NULL, NULL);
model = create_and_fill_model(); gtk_tree_view_set_model(GTK_TREE_VIEW(view), model); g_object_unref(model); /* destroy model automatically with view */ gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(view)), GTK_SELECTION_NONE); return view; }
int main (int argc, char **argv) { GtkWidget *window; GtkWidget *view; gtk_init(&argc, &argv); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); g_signal_connect(window, "delete_event", gtk_main_quit, NULL); /* dirty */ view = create_view_and_model(); gtk_container_add(GTK_CONTAINER(window), view); gtk_widget_show_all(window); gtk_main(); return 0; }
Chapter 5. Mapping Data to the Screen: GtkTreeViewColumn and GtkCellRenderer just set the rules hint on the tree view as described in the here, and everything will be done automatically, in colours that conform to the chosen theme even (unless the theme disables rule hints, that is). Otherwise, the most suitable approach for most cases is that you add two columns to your model, one for the property itself (e.g. a column COL_ROW_COLOR of type G_TYPE_STRING), and one for the boolean ag of the property (e.g. a column COL_ROW_COLOR_SET of type G_TYPE_BOOLEAN). You would then connect these columns with the "foreground" and "foreground-set" properties of each renderer. Now, whenever you set a rows COL_ROW_COLOR eld to a colour, and set that rows COL_ROW_COLOR_SET eld to TRUE, then this column will be rendered in the colour of your choice. If you only want either the default text colour or one special other colour, you could even achieve the same thing with just one extra model column: in this case you could just set all renderers "foreground" property to whatever special color you want, and only connect the COL_ROW_COLOR_SET column to all renderers "foreground-set" property using attributes. This works similar with any other attribute, only that you need to adjust the data type for the property of course (e.g. "weight" would take a G_TYPE_INT, in form of a PANGO_WEIGHT_FOO dene in this case). As a general rule, you should not change the text colour or the background colour of a cell unless you have a really good reason for it. To quote Havoc Pennington: Because colors in GTK+ represent a theme the user has chosen, you should never set colors purely for aesthetic reasons. If users dont like GTK+ gray, they can change it themselves to their favorite shade of orange.
31
model = GTK_TREE_MODEL(create_liststore()); view = gtk_tree_view_new_with_model(model); col = gtk_tree_view_column_new(); gtk_tree_view_column_set_title(col, "Title"); renderer = gtk_cell_renderer_pixbuf_new(); gtk_tree_view_column_pack_start(col, renderer, FALSE); gtk_tree_view_column_set_attributes(col, renderer, "pixbuf", COL_ICON, NULL); renderer = gtk_cell_renderer_text_new(); gtk_tree_view_column_pack_start(col, renderer, TRUE); gtk_tree_view_column_set_attributes(col, renderer, "text", COL_TEXT, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(view), col); gtk_widget_show_all(view); return view; }
Note that the tree view will not resize icons for you, but displays them in their original size. If you want to display stock icons instead of GdkPixbufs loaded from le, you should have a look at the "stock-id" property of GtkCellRendererPixbuf (and your model column should be of type G_TYPE_STRING, as all stock IDs are just strings by which to identify the stock icon).
Notes
1. For those interested, the conversion actually takes place within g_object_set_property. Before a certain cell is rendered, the tree view column will call gtk_tree_model_get_value to set the cell renderer properties according to values stored in the tree model (if any are mapped via gtk_tree_view_column_add_attribute or one of the convenience functions that do the same thing), and then pass on the GValue retrieved to g_object_set_property.
32
- no items can be selected - no more than one item can be selected - exactly one item is always selected - anything between no item and all items can be selected
GTK_SELECTION_SINGLE GTK_SELECTION_BROWSE
GTK_SELECTION_MULTIPLE
which means that you cant use it or need to reimplement it if you want your application to work with older installations. If the selection mode you are using is either GTK_SELECTION_SINGLE or GTK_SELECTION_BROWSE, the most convenient way to get the selected row is the function gtk_tree_selection_get_selected, which will return TRUE and ll in the specied tree iter with the selected row (if a row is selected), and return FALSE otherwise. It is used like this:
... GtkTreeSelection *selection; GtkTreeModel *model; GtkTreeIter iter; /* This will only work in single or browse selection mode! */ selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view)); if (gtk_tree_selection_get_selected(selection, &model, &iter)) { gchar *name; gtk_tree_model_get (model, &iter, COL_NAME, &name, -1); g_print ("selected row is: %s\n", name); g_free(name); } else
33
One thing you need to be aware of is that you need to take care when removing rows from the model in a gtk_tree_selection_selected_foreach callback, or when looping through the list that gtk_tree_selection_get_selected_rows returns (because it contains paths, and when you remove rows in the middle, then the old paths will point to either a non-existing row, or to another row than the one selected). You have two ways around this problem: one way is to use the solution to removing multiple rows that has been described above, ie. to get tree row references for all selected rows and then remove the rows one by one; the other solution is to sort the list of selected tree paths so that the last rows come rst in the list, so that you remove rows from the end of the list or tree. You cannot remove rows from within a foreach callback in any case, that is simply not allowed. Here is an example of how to use gtk_tree_selection_selected_foreach:
... gboolean view_selected_foreach_func (GtkTreeModel GtkTreePath GtkTreeIter gpointer { gchar *name;
gtk_tree_model_get (model, iter, COL_NAME, &name, -1); g_print ("%s is selected\n", name); }
void do_something_with_all_selected_rows (GtkWidget *treeview) { GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview)); gtk_tree_selection_selected_foreach(selection, view_selected_foreach_func, NULL); }
*view; *selection;
34
*view; *selection;
35
You
can
check
whether
given
36
g_print ("A row has been double-clicked!\n"); model = gtk_tree_view_get_model(treeview); if (gtk_tree_model_get_iter(model, &iter, path)) { gchar *name; gtk_tree_model_get(model, &iter, COLUMN_NAME, &name, -1); g_print ("Double-clicked row contains name %s\n", name); g_free(name); } } void create_view (void) { GtkWidget *view; view = gtk_tree_view_new(); ... g_signal_connect(view, "row-activated", (GCallback) view_onRowActivated, NULL); ... }
37
void view_popup_menu (GtkWidget *treeview, GdkEventButton *event, gpointer userdata) { GtkWidget *menu, *menuitem; menu = gtk_menu_new(); menuitem = gtk_menu_item_new_with_label("Do something"); g_signal_connect(menuitem, "activate", (GCallback) view_popup_menu_onDoSomething, treeview); gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); gtk_widget_show_all(menu); /* Note: event can be NULL here when called from view_onPopupMenu; * gdk_event_get_time() accepts a NULL argument */ gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, (event != NULL) ? event->button : 0, gdk_event_get_time((GdkEvent*)event)); }
gboolean view_onButtonPressed (GtkWidget *treeview, GdkEventButton *event, gpointer userdata) { /* single click with the right mouse button? */ if (event->type == GDK_BUTTON_PRESS && event->button == 3) { g_print ("Single right click on the tree view.\n"); /* optional: select row if no row is selected or only * one other row is selected (will only do something * if you set a tree selection mode as described later * in the tutorial) */ if (1) { GtkTreeSelection *selection; selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview)); /* Note: gtk_tree_selection_count_selected_rows() does not exist in gtk+-2.0, only in gtk+ >= v2.2 ! */ * if (gtk_tree_selection_count_selected_rows(selection) <= 1) { GtkTreePath *path; /* Get tree path for row that was clicked */ if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(treeview), (gint) event->x, (gint) event->y, &path, NULL, NULL, NULL)) { gtk_tree_selection_unselect_all(selection); gtk_tree_selection_select_path(selection, path); gtk_tree_path_free(path); } } } /* end of optional bit */ view_popup_menu(treeview, event, userdata); return TRUE; /* we handled this */ } return FALSE; /* we did not handle this */
38
gboolean view_onPopupMenu (GtkWidget *treeview, gpointer userdata) { view_popup_menu(treeview, NULL, userdata); return TRUE; /* we handled this */ }
void create_view (void) { GtkWidget *view; view = gtk_tree_view_new(); ... g_signal_connect(view, "button-press-event", (GCallback) view_onButtonPressed, NULL); g_signal_connect(view, "popup-menu", (GCallback) view_onPopupMenu, NULL); ... }
39
Chapter 7. Sorting
Lists and trees are meant to be sorted. This is done using the GtkTreeSortable interface that can be implemented by tree models. Interface means that you can just cast a GtkTreeModel into a GtkTreeSortable with GTK_TREE_SORTABLE(model) and use the documented tree sortable functions on it, just like we did before when we cast a list store to a tree model and used the gtk_tree_model_foo family of functions. Both GtkListStore and GtkTreeStore implement the tree sortable interface. The most straight forward way to sort a list store or tree store is to directly use the tree sortable interface on them. This will sort the store in place, meaning that rows will actually be reordered in the store if required. This has the advantage that the position of a row in the tree view will always be the same as the position of a row in the model, in other words: a tree path refering to a row in the view will always refer to the same row in the model, so you can get a rows iter easily with gtk_tree_model_get_iter using a tree path supplied by the tree view. This is not only convenient, but also sufcient for most scenarios. However, there are cases when sorting a model in place is not desirable, for example when several tree views display the same model with different sortings, or when the unsorted state of the model has some special meaning and needs to be restored at some point. This is where GtkTreeModelSort comes in, which is a special model that maps the unsorted rows of a child model (e.g. a list store or tree store) into a sorted state without changing the child model.
7.1. GtkTreeSortable
The tree sortable interface is fairly simple and should be easy to use. Basically you dene a sort column ID integer for every criterion you might want to sort by and tell the tree sortable which function should be called to compare two rows (represented by two tree iters) for every sort ID with gtk_tree_sortable_set_sort_func. Then you sort the model by setting the sort column ID and sort order with gtk_tree_sortable_set_sort_column_id, and the model will be re-sorted using the compare function you have set up. Your sort column IDs can correspond to your model columns, but they do not have to (you might want to sort according to a criterion that is not directly represented by the data in one single model column, for example). Some code to illustrate this:
enum { COL_NAME = 0, COL_YEAR_BORN };
GtkTreeModel
*liststore = NULL;
void toolbar_onSortByYear (void) { GtkTreeSortable *sortable; GtkSortType order; gint sortid; sortable = GTK_TREE_SORTABLE(liststore); /* If we are already sorting by year, reverse sort order, * otherwise set it to year in ascending order */ if (gtk_tree_sortable_get_sort_column_id(sortable, &sortid, &order) == TRUE && sortid == SORTID_YEAR) { GtkSortType neworder; neworder = (order == GTK_SORT_ASCENDING) ? GTK_SORT_DESCENDING : GTK_SORT_ASCENDING;
40
Chapter 7. Sorting
gtk_tree_sortable_set_sort_column_id(sortable, SORTID_YEAR, neworder); } else { gtk_tree_sortable_set_sort_column_id(sortable, SORTID_YEAR, GTK_SORT_ASCENDING); } }
/* This is not pretty. Of course you can also use a * separate compare function for each sort ID value */ gint sort_iter_compare_func (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer userdata) { gint sortcol = GPOINTER_TO_INT(userdata); gint ret = 0; switch (sortcol) { case SORTID_NAME: { gchar *name1, *name2; gtk_tree_model_get(model, a, COL_NAME, &name1, -1); gtk_tree_model_get(model, b, COL_NAME, &name2, -1); if (name1 == NULL || name2 == NULL) { if (name1 == NULL && name2 == NULL) break; /* both equal => ret = 0 */ ret = (name1 == NULL) ? -1 : 1; } else { ret = g_utf8_collate(name1,name2); } g_free(name1); g_free(name2); } break; case SORTID_YEAR: { guint year1, year2; gtk_tree_model_get(model, a, COL_YEAR_BORN, &year1, -1); gtk_tree_model_get(model, b, COL_YEAR_BORN, &year2, -1); if (year1 != year2) { ret = (year1 > year2) ? 1 : -1; } /* else both equal => ret = 0 */ } break; default: g_return_val_if_reached(0); } return ret; }
void
41
Chapter 7. Sorting
create_list_and_view (void) { GtkTreeSortable *sortable; ... liststore = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_UINT); sortable = GTK_TREE_SORTABLE(liststore); gtk_tree_sortable_set_sort_func(sortable, SORTID_NAME, sort_iter_compare_func, GINT_TO_POINTER(SORTID_NAME), NULL); gtk_tree_sortable_set_sort_func(sortable, SORTID_YEAR, sort_iter_compare_func, GINT_TO_POINTER(SORTID_YEAR), NULL); /* set initial sort order */ gtk_tree_sortable_set_sort_column_id(sortable, SORTID_NAME, GTK_SORT_ASCENDING); ... view = gtk_tree_view_new_with_model(liststore); ... }
Usually things are a bit easier if you make use of the tree view column headers for sorting, in which case you only need to assign sort column IDs and your compare functions, but do not need to set the current sort column ID or order yourself (see below). Your tree iter compare function should return a negative value if the row specied by iter a comes before the row specied by iter b, and a positive value if row b comes before row a. It should return 0 if both rows are equal according to your sorting criterion (you might want to use a second sort criterion though to avoid jumping of equal rows when the store gets resorted). Your tree iter compare function should not take the sort order into account, but assume an ascending sort order (otherwise bad things will happen).
7.2. GtkTreeModelSort
GtkTreeModelSort is a wrapper tree model. It takes another tree model such as a list store or a tree store as child
model, and presents the child model to the outside (ie. a tree view or whoever else is accessing it via the tree model interface) in a sorted state. It does that without changing the order of the rows in the child model. This is useful if you want to display the same model in different tree views with different sorting criteria for each tree view, for example, or if you need to restore the original unsorted state of your store again at some point.
GtkTreeModelSort implements the GtkTreeSortable interface, so you can treat it just as if it was your data store
for sorting purposes. Here is the basic setup with a tree view:
... void create_list_and_view (void) { ... liststore = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_UINT); sortmodel = gtk_tree_model_sort_new_with_model(liststore); gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(sortmodel), SORTID_NAME, sort_func, GINT_TO_POINTER(SORTID_NAME), NULL); gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(sortmodel), SORTID_YEAR, sort_func, GINT_TO_POINTER(SORTID_YEAR), NULL); /* set initial sort order */ gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(sortmodel),
42
Chapter 7. Sorting
SORTID_NAME, GTK_SORT_ASCENDING); ... view = gtk_tree_view_new_with_model(sortmodel); ... } ...
However, when using the sort tree model, you need to be careful when you use iters and paths with the model. This is because a path pointing to a row in the view (and the sort tree model here) does probably not point to the same row in the child model which is your original list store or tree store, because the row order in the child model is probably different from the sorted order. Similarly, an iter that is valid for the sort tree model is not valid for the child model, and vice versa. You can convert paths and iters from and to the child model using gtk_tree_model_sort_convert_child_path_to_path, gtk_tree_model_sort_convert_child_iter_to_iter, gtk_tree_model_sort_convert_path_to_child_path, and gtk_tree_model_sort_convert_iter_to_child_iter. You are unlikely to need these functions frequently though, as you can still directly use gtk_tree_model_get on the sort tree model with a path supplied by the tree view. For the tree view, the sort tree model is the real model - it knows nothing about the sort tree models child model at all, which means that any path or iter that you get passed from the tree view in a callback or otherwise will refer to the sort tree model, and that you need to pass a path or iter refering to the sort tree model as well if you call tree view functions.
at least half-way correct linguistic case-insensitive sorting, we need to take a two-step approach. For example, we could use g_utf8_casefold to convert the strings to compare into a form that is independent of case, and then use g_utf8_collate to compare those two strings (note that the strings returned by g_utf8_casefold will not resemble the original string in any recognisable way; they will work ne for comparisons though). Alternatively, one could use g_utf8_strdown on both strings and then compare the results again with g_utf8_collate. Obviously, all this is not going to be very fast, and adds up if you have a lot of rows. To speed things up, you can create a collation key with g_utf8_collate_key and store that in your model as well. A collation key is just a string that does not mean anything to us, but can be used with strcmp for string comparison purposes (which is a lot faster than g_utf8_collate). It should be noted that the way g_utf8_collate sorts is dependent on the current locale. Make sure you are not working in the C locale (=default, none specied) before you are wondering about weird sorting orders. Check with echo $LANG on a command line what you current locale is set to. 43
Chapter 7. Sorting Check out the "Unicode Manipulation" section in the GLib API Reference for more details.
44
... when you create the renderer, which sets all rows in that particular renderer column to be editable. Now that our cells are editable, we also want to be notied when a cell has been edited. This can be achieved by connecting to the cell renderers "edited" signal:
g_signal_connect(renderer, "edited", (GCallback) cell_edited_callback, NULL);
This callback is then called whenever a cell has been edited. Instead of NULL we could have passed a pointer to the model as user data for convenience, as we probably want to store the new value in the model. The callback for the "edited" signal looks like this (the API reference is a bit lacking in this particular case):
void cell_edited_callback (GtkCellRendererText *cell, gchar *path_string, gchar *new_text, gpointer user_data);
The tree path is passed to the "edited" signal callback in string form. You can convert this into a GtkTreePath with gtk_tree_path_new_from_string, or convert it into an iter with gtk_tree_model_get_iter_from_string. Note that the cell renderer will not change the data for you in the store. After a cell has been edited, you will only receive an "edited" signal. If you do not change the data in the store, the old text will be rendered again as if nothing had happened. If you have multiple (renderer) columns with editable cells, it is not necessary to have a different callback for each renderer, you can use the same callback for all renderers, and attach some data to each renderer, which you can later retrieve again in the callback to know which renderer/column has been edited. This is done like this, for example:
renderer = gtk_cell_renderer_text_new(); ... g_object_set_data(G_OBJECT(renderer), "my_column_num", GUINT_TO_POINTER(COLUMN_NAME)); ... renderer = gtk_cell_renderer_text_new(); ... g_object_set_data(G_OBJECT(renderer), "my_column_num", GUINT_TO_POINTER(COLUMN_YEAR_OF_BIRTH)); ...
where COLUMN_NAME and COLUMN_YEAR_OF_BIRTH are enum values. In your callback you can then get the column number with
guint column_number = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(renderer), "my_column_num"));
You can use this mechanism to attach all kinds of custom data to any object or widget, with a string identier to your liking. A good example for editable cells is in gtk-demo, which is part of the Gtk+ source code tree (in gtk+-2.x.y/demos/gtk-demo).
45
Just like with the "edited" signal of the text cell renderer, the tree path is passed to the "toggled" signal callback in string form. You can convert this into a GtkTreePath with gtk_tree_path_new_from_string, or convert it into an iter with gtk_tree_model_get_iter_from_string.
46
Chapter 9. Miscellaneous
This section deals with issues and questions that did not seem to t in anywhere else. If you can think of something else that should be dealt with here, do not hesitate to send a mail to <tim at centricular dot net>.
9.1. Getting the Column Number from a Tree View Column Widget
Signal callbacks often only get passed a pointer to a GtkTreeViewColumn when the application programmer really just wants to know which column number was affected. There are two ways to nd out the position of a column within the tree view. One way is to write a small helper function that looks up the column number from a given tree view column object, like this for example: 1.
/* Returns column number or -1 if not found or on error */ gint get_col_number_from_tree_view_column (GtkTreeViewColumn *col) { GList *cols; gint num; g_return_val_if_fail ( col != NULL, -1 ); g_return_val_if_fail ( col->tree_view != NULL, -1 ); cols = gtk_tree_view_get_columns(GTK_TREE_VIEW(col->tree_view)); num = g_list_index(cols, (gpointer) col); g_list_free(cols); return num; }
Alternatively, it is possible to use g_object_set_data and g_object_get_data on the tree view column in order to identify which column it is. This also has the advantage that you can still keep track of your columns even if the columns get re-ordered within the tree view (a feature which is usually disabled though). Use like this:
... enum { COL_FIRSTNAME, COL_SURNAME, }; ... void some_callback (GtkWidget *treeview, ..., GtkTreeViewColumn *col, ...) { guint colnum = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(col), "columnnum")); ... } void create_view(void) { ... col = gtk_tree_view_column_new(); g_object_set_data(G_OBJECT(col), "columnnum", GUINT_TO_POINTER(COL_FIRSTNAME)); ... col = gtk_tree_view_column_new(); g_object_set_data(G_OBJECT(col), "columnnum", GUINT_TO_POINTER(COL_SURNAME)); ... } "columnnum" is a random string in the above example - you can use whatever string you want instead, or store multiple bits of data (with different string identiers of course). Of course you can also combine both approaches,
47
Chapter 9. Miscellaneous as they do slightly different things (the rst tracks the physical position of a column within the tree view, the second tracks the meaning of a column to you, independent of its position within the view).
48
Chapter 9. Miscellaneous
{ GtkTreeViewColumn *checkcol = (GtkTreeViewColumn*) node->data; if (x >= colx && x < (colx + checkcol->width)) col = checkcol; else colx += checkcol->width; } g_list_free(columns); if (col == NULL) return FALSE; /* not found */ /* (2) find the cell renderer within the column */ cells = gtk_tree_view_column_get_cell_renderers(col); for (node = cells; node != NULL; node = node->next) { GtkCellRenderer *checkcell = (GtkCellRenderer*) node->data; guint width = 0, height = 0; /* Will this work for all packing modes? doesnt that * return a random width depending on the last content * rendered? */ gtk_cell_renderer_get_size(checkcell, GTK_WIDGET(view), NULL, NULL, NULL, &width, NULL); if (x >= colx && x < (colx + width)) { *cell = checkcell; g_list_free(cells); return TRUE; } colx += width; } g_list_free(cells); return FALSE; /* not found */ } static gboolean onButtonPress (GtkWidget *view, GdkEventButton *bevent, gpointer data) { GtkCellRenderer *renderer = NULL; if (tree_view_get_cell_from_pos(GTK_TREE_VIEW(view), bevent->x, bevent->y, &renderer)) g_print ("Renderer found\n"); else g_print ("Renderer not found!\n"); }
49
Chapter 9. Miscellaneous
Notes
1. This function has been inspired by this mailing list message (thanks to Ken Rastatter for the link and the topic suggestion). 2. Do not use Glade to generate code for you. Use Glade to create the interface. It will save the interface into a .glade le in XML format. You can then use libglade2 to construct your interface (windows etc.) from that .glade le. See this mailing list message for a short discussion about why you should avoid Glade code generation.
50
10.1. DragnDropping Row-Unrelated Data to and from a Tree View from other Windows or Widgets
DragnDropping general information from or to a tree view widget works just like it works with any other widget and involves the standard Gtk+ Drag and Drop mechanisms. If you use this, you can receive drops to or initiate drags from anywhere in your tree view (including empty sections). This is not row- or column-specic and is most likely not want you want. Nevertheless, here is a small example of a tree view in which you can dragndrop URIs from other applications (browsers, for example), with the dropped URIs just being appended to the list (note that usually you would probably rather want to set up your whole window as a target then and not just the tree view widget):
#include <gtk/gtk.h> enum { COL_URI = 0, NUM_COLS } ;
void view_onDragDataReceived(GtkWidget *wgt, GdkDragContext *context, int x, int y, GtkSelectionData *seldata, guint info, guint time, gpointer userdata) { GtkTreeModel *model; GtkTreeIter iter; model = GTK_TREE_MODEL(userdata); gtk_list_store_append(GTK_LIST_STORE(model), &iter); gtk_list_store_set(GTK_LIST_STORE(model), &iter, COL_URI, (gchar*)seldata->data, -1); } static GtkWidget * create_view_and_model { GtkTreeViewColumn GtkCellRenderer GtkListStore GtkWidget
liststore = gtk_list_store_new(NUM_COLS, G_TYPE_STRING); view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(liststore)); g_object_unref(liststore); /* destroy model with view */ col = gtk_tree_view_column_new(); renderer = gtk_cell_renderer_text_new(); gtk_tree_view_column_set_title(col, "URI"); gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
51
If you are receiving drops into a tree view, you can connect to the views "drag-motion" signal to track the mouse pointer while it is in a drag and drop operation over the tree view. This is useful for example if you want to expand a collapsed node in a tree when the mouse hovers above the node for a certain amount of time during a dragndrop operation. Here is an example of how to achieve this:
/*************************************************************************** * onDragMotion_expand_timeout * * Timeout used to make sure that we expand rows only * after hovering about them for a certain amount * of time while doing DragnDrop *
52
/*************************************************************************** * view_onDragMotion: we dont want to expand unexpanded nodes * immediately when the mouse pointer passes across * them during DnD. Instead, we only want to expand * the node if the pointer has been hovering above the * node for at least 1.5 seconds or so. To achieve this, * we use a timeout that is removed whenever the row * in focus changes. * * ***************************************************************************/ static gboolean view_onDragMotion (GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, gpointer data) { static GtkTreePath *lastpath; /* NULL */ GtkTreePath *path = NULL; if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), x, y, &path, NULL, NULL, NULL)) { if (!lastpath || ((lastpath) && gtk_tree_path_compare(lastpath, path) != 0)) { (void) g_source_remove_by_user_data(&lastpath); if (!gtk_tree_view_row_expanded(GTK_TREE_VIEW(widget), path)) { /* 1500 = 1.5 secs */ g_timeout_add(1500, (GSourceFunc) onDragMotion_expand_timeout, &lastpath); } } } else { g_source_remove_by_user_data(&lastpath); } if (lastpath) gtk_tree_path_free(lastpath); lastpath = path; return TRUE; }
Connect to the views "drag-drop" signal to be called when the drop happens. You can translate the coordinates provided into a tree path with gtk_tree_view_get_path_at_pos.
53
10.3. Dragging Rows from One Tree to Another **** TODO ***
****** TODO (is this possible at all in Gtk+ <= 2.2?)
54
- tells the outside that your model has certain special characterstics, like persistent iters.
get_n_columns - how many data elds per row are visible to the outside that uses gtk_tree_model_get, e.g. cell
renderer attributes
get_column_type get_iter get_path
- what type of data is stored in a data eld (model column) that is visible to the outside
- take a tree path and ll an iter structure so that you know which row it refers to - take an iter and convert it into a tree path, ie. the physical position within the model - retrieve data from a row - take an iter structure and make it point to the next row - tell whether the row represented by a given iter has any children or not - tell how many children a row represented by a given iter has - set a given iter structure to the n-th child of a given parent iter
get_value iter_next
iter_children
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 specic 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). 55
11.3.1. custom-list.h
The header le for our custom list model denes 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-specic data. You can add additional elds 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 dened in the header le. 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-specic functions to modify the model as you extend it to suit your needs.
11.3.2. 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 dene 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 56
Chapter 11. Writing Custom Models we can also register additional interfaces (e.g. GtkTreeSortable or one of the DragnDrop 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 benecial 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. Lets have a look at the heart of the object type registration:
GType custom_list_get_type (void) { static GType custom_list_type = 0; if (custom_list_type) return custom_list_type; /* Some boilerplate type registration stuff */ if (1) { static const GTypeInfo custom_list_info = { sizeof (CustomListClass), NULL, NULL, (GClassInitFunc) custom_list_class_init, NULL, NULL, sizeof (CustomList), 0, (GInstanceInitFunc) custom_list_init };
custom_list_type = g_type_register_static (G_TYPE_OBJECT, "CustomList", &custom_list_info, (GTypeFlags)0); } /* Here we register our GtkTreeModel interface with the type system */ if (1) { static const GInterfaceInfo tree_model_info = { (GInterfaceInitFunc) custom_list_tree_model_init, NULL, NULL }; 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 ll 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;
57
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_ags 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 specic 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 rows 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)
58
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 le:
/* custom-list.c */ ... /* -- GtkTreeSortable interface functions -- */ static gboolean custom_list_sortable_get_sort_column_id (GtkTreeSortable *sortable, gint *sort_col_id, GtkSortType *order); custom_list_sortable_set_sort_column_id (GtkTreeSortable *sortable, gint sort_col_id, GtkSortType order); custom_list_sortable_set_sort_func (GtkTreeSortable *sortable, gint sort_col_id, GtkTreeIterCompareFunc sort_func, gpointer user_data, GtkDestroyNotify destroy_func);
static void
static void
static void
custom_list_sortable_set_default_sort_func (GtkTreeSortable *sortable, GtkTreeIterCompareFunc sort_func, gpointer user_data, GtkDestroyNotify destroy_func custom_list_sortable_has_default_sort_func (GtkTreeSortable *sortable); custom_list_resort (CustomList *custom_list);
Next, lets extend our CustomList structure with a eld 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,
59
n_columns; column_types[CUSTOM_LIST_N_COLUMNS]; sort_id; sort_order; stamp; /* Random integer to check whether an iter belongs to our model */
Now, we make sure we initialise the new elds in custom_list_new, and add our new interface:
... static void ... void custom_list_init (CustomList *custom_list) { ... custom_list->sort_id = SORT_ID_NONE; custom_list->sort_order = GTK_SORT_ASCENDING; ... } custom_list_sortable_init (GtkTreeSortableIface *iface);
GType custom_list_get_type (void) { ... /* Add GtkTreeSortable interface */ if (1) { 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; iface->set_default_sort_func = custom_list_sortable_set_default_sort_func; iface->has_default_sort_func = custom_list_sortable_has_default_sort_func; }
60
Now that we have nally 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__); }
61
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 GLibs 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 "rowsreordered" 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; }
62
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);
63
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.
11.6.1. custom-list.h
#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 #define #define #define #define #define CUSTOM_TYPE_LIST CUSTOM_LIST(obj) CUSTOM_LIST_CLASS(klass) CUSTOM_IS_LIST(obj) CUSTOM_IS_LIST_CLASS(klass) CUSTOM_LIST_GET_CLASS(obj) (custom_list_get_type ()) (G_TYPE_CHECK_INSTANCE_CAST ((obj), (G_TYPE_CHECK_CLASS_CAST ((klass), (G_TYPE_CHECK_INSTANCE_TYPE ((obj), (G_TYPE_CHECK_CLASS_TYPE ((klass), (G_TYPE_INSTANCE_GET_CLASS ((obj),
/* 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, } ;
64
/* 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; guint CustomRecord num_rows; **rows;
*/
/* this MUST be the first member */ /* number of rows that we have */ /* 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;
custom_list_get_type (void); *custom_list_new (void); custom_list_append_record (CustomList const gchar guint *custom_list, *name, year_born);
#endif /* _custom_list_h_included_ */
65
11.6.2. custom-list.c
#include "custom-list.h"
/* boring declarations of local functions */ static void static void static void static void custom_list_init custom_list_class_init (CustomList *pkg_tree);
(CustomListClass *klass);
custom_list_tree_model_init (GtkTreeModelIface *iface); custom_list_finalize (GObject (GtkTreeModel (GtkTreeModel *object); *tree_model); *tree_model); *tree_model, index); *tree_model, *iter, *path); *tree_model, *iter); *tree_model, *iter, column, *value); *tree_model, *iter); *tree_model, *iter, *parent); *tree_model, *iter); *tree_model, *iter); *tree_model, *iter, *parent, n); *tree_model, *iter, *child);
custom_list_get_column_type (GtkTreeModel gint custom_list_get_iter (GtkTreeModel GtkTreeIter GtkTreePath (GtkTreeModel GtkTreeIter (GtkTreeModel GtkTreeIter gint GValue (GtkTreeModel GtkTreeIter (GtkTreeModel GtkTreeIter GtkTreeIter (GtkTreeModel GtkTreeIter
static gboolean
static gboolean
custom_list_iter_next
static gboolean
custom_list_iter_children
static gboolean
custom_list_iter_has_child
static gint
custom_list_iter_n_children (GtkTreeModel GtkTreeIter custom_list_iter_nth_child (GtkTreeModel GtkTreeIter GtkTreeIter gint (GtkTreeModel GtkTreeIter GtkTreeIter
static gboolean
static gboolean
custom_list_iter_parent
/***************************************************************************** * * 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. * * *****************************************************************************/
66
custom_list_type = g_type_register_static (G_TYPE_OBJECT, "CustomList", &custom_list_info, (GTypeFlags)0); } /* Here we register our GtkTreeModel interface with the type system */ if (1) { static const GInterfaceInfo tree_model_info = { (GInterfaceInitFunc) custom_list_tree_model_init, NULL, NULL }; 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. * *
67
/***************************************************************************** * * 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 structures 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->column_types[1] = G_TYPE_STRING; custom_list->column_types[2] = G_TYPE_UINT; g_assert (CUSTOM_LIST_N_COLUMNS == 3); custom_list->num_rows = 0; custom_list->rows = NULL; custom_list->stamp = g_random_int(); } /* CUSTOM_LIST_COL_RECORD */ /* CUSTOM_LIST_COL_NAME */ /* CUSTOM_LIST_COL_YEAR_BORN */
/***************************************************************************** * * 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, *
68
/***************************************************************************** * * 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);
69
/***************************************************************************** * * 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 rows 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) {
70
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;
71
/***************************************************************************** * * 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. * *
72
/***************************************************************************** * * 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. *
73
/***************************************************************************** * * 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 const gchar guint { GtkTreeIter iter; GtkTreePath *path; CustomRecord *newrecord; gulong newsize; guint pos; *custom_list, *name, year_born)
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;
74
11.6.3. 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.
#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 { GtkTreeViewColumn GtkCellRenderer CustomList GtkWidget
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 */
75
76
Register some new properties that your renderer needs with the type system and write your own set_property and get_property functions to set and get your new renderers properties. Write your own cell_renderer_get_size function and override the parent objects function (usually the parent is of type GtkCellRenderer. Note that you should honour the standard properties for padding and cell alignment of the parent object here. Write your own cell_renderer_render function and override the parent objects function. This function does the actual rendering.
The GObject type system stuff of writing a new cell renderer is similar to what we have done above when writing a custom tree model, and is relatively straight forward in this case. Copy and paste and modify according to your own needs. Good examples of cell renderer code to look at or even modify are GtkCellRendererPixbuf and GtkCellRendererToggle in the Gtk+ source code tree. Both cases are less than ve hundred lines of code to look at and thus should be fairly easy to digest.
12.1.1. custom-cell-renderer-progressbar.h
The header le consists of the usual GObject type cast and type check denes and our
CustomCellRendererProgress structure. As the type of the parent indicates, we derive from GtkCellRenderer.
The parent object must always be the rst item in the structure (note also that it is not a pointer to an object, but the parent object structure itself embedded in our structure). Our CustomCellRendererProgress structure is fairly uneventful and contains only a double precision oat variable in which we store our new "percentage" property (which will determine how long the progressbar is going to be).
#ifndef _custom_cell_renderer_progressbar_included_ #define _custom_cell_renderer_progressbar_included_ #include <gtk/gtk.h> /* Some boilerplate GObject type check and type cast macros. * klass is used here instead of class, because class * is a c++ keyword */ #define #define #define #define #define #define CUSTOM_TYPE_CELL_RENDERER_PROGRESS CUSTOM_CELL_RENDERER_PROGRESS(obj) CUSTOM_CELL_RENDERER_PROGRESS_CLASS(klass) CUSTOM_IS_CELL_PROGRESS_PROGRESS(obj) CUSTOM_IS_CELL_PROGRESS_PROGRESS_CLASS(klass) CUSTOM_CELL_RENDERER_PROGRESS_GET_CLASS(obj)
(custom_cell_renderer_progress_get_type()) (G_TYPE_CHECK_INSTANCE_CAST((obj), CUSTOM_TY (G_TYPE_CHECK_CLASS_CAST ((klass), CUSTOM_TY (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CUSTOM_TY (G_TYPE_CHECK_CLASS_TYPE ((klass), CUSTOM_TY (G_TYPE_INSTANCE_GET_CLASS ((obj), CUSTOM_TY
77
/* CustomCellRendererProgress: Our custom cell renderer structure. Extend according to need */ * struct _CustomCellRendererProgress { GtkCellRenderer parent; gdouble }; progress;
GType GtkCellRenderer
#endif /* _custom_cell_renderer_progressbar_included_ */
12.1.2. custom-cell-renderer-progressbar.c
The code contains everything as described above, so lets jump right into it:
#include "custom-cell-renderer-progressbar.h" /* This is based mainly on GtkCellRendererProgress * in GAIM, written and (c) 2002 by Sean Egan * (Licensed under the GPL), which in turn is * based on Gtks GtkCellRenderer[Text|Toggle|Pixbuf] * implementation by Jonathan Blandford */ /* Some boring function declarations: GObject type system stuff */ static void static void static void custom_cell_renderer_progress_init (CustomCellRendererProgress
*cellprogres
custom_cell_renderer_progress_class_init (CustomCellRendererProgressClass *klass); custom_cell_renderer_progress_get_property (GObject guint GValue GParamSpec (GObject guint const GValue GParamSpec *object, param_id, *value, *pspec); *object, param_id, *value, *pspec);
static void
custom_cell_renderer_progress_set_property
static void
/* These functions are the heart of our custom cell renderer: */ static void custom_cell_renderer_progress_get_size (GtkCellRenderer GtkWidget GdkRectangle gint gint gint gint (GtkCellRenderer GdkWindow GtkWidget *cell, *widget, *cell_area, *x_offset, *y_offset, *width, *height); *cell, *window, *widget,
static void
custom_cell_renderer_progress_render
78
/*************************************************************************** * * custom_cell_renderer_progress_get_type: here we register our type with the GObject type system if we * havent done so yet. Everything * else is done in the callbacks. * * ***************************************************************************/ GType custom_cell_renderer_progress_get_type (void) { static GType cell_progress_type = 0; if (cell_progress_type) return cell_progress_type; if (1) { static const GTypeInfo cell_progress_info = { sizeof (CustomCellRendererProgressClass), NULL, /* NULL, /* (GClassInitFunc) custom_cell_renderer_progress_class_init, NULL, /* NULL, /* sizeof (CustomCellRendererProgress), 0, /* (GInstanceInitFunc) custom_cell_renderer_progress_init, };
/* Derive from GtkCellRenderer */ cell_progress_type = g_type_register_static (GTK_TYPE_CELL_RENDERER, "CustomCellRendererProgress", &cell_progress_info, 0); } return cell_progress_type; }
/*************************************************************************** * * custom_cell_renderer_progress_init: set some default properties of the parent (GtkCellRenderer). * * ***************************************************************************/ static void custom_cell_renderer_progress_init (CustomCellRendererProgress *cellrendererprogress) { GTK_CELL_RENDERER(cellrendererprogress)->mode = GTK_CELL_RENDERER_MODE_INERT; GTK_CELL_RENDERER(cellrendererprogress)->xpad = 2; GTK_CELL_RENDERER(cellrendererprogress)->ypad = 2; }
79
/*************************************************************************** * * custom_cell_renderer_progress_class_init: * * set up our own get_property and set_property functions, and * override the parents functions that we need to implement. * And make our new "percentage" property known to the type system. * If you want cells that can be activated on their own (ie. not * just the whole row selected) or cells that are editable, you * will need to override activate and start_editing as well. * ***************************************************************************/ static void custom_cell_renderer_progress_class_init (CustomCellRendererProgressClass *klass) { GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); parent_class = g_type_class_peek_parent (klass); object_class->finalize = custom_cell_renderer_progress_finalize; /* Hook up functions to set and get our custom cell renderer properties */ * object_class->get_property = custom_cell_renderer_progress_get_property; object_class->set_property = custom_cell_renderer_progress_set_property; /* Override the two crucial functions that are the heart of a cell renderer in the parent class */ * cell_class->get_size = custom_cell_renderer_progress_get_size; cell_class->render = custom_cell_renderer_progress_render; /* Install our very own properties */ g_object_class_install_property (object_class, PROP_PERCENTAGE, g_param_spec_double ("percentage", "Percentage", "The fractional progress to display", 0, 1, 0, G_PARAM_READWRITE)); }
/*************************************************************************** * * custom_cell_renderer_progress_finalize: free any resources here * ***************************************************************************/ static void custom_cell_renderer_progress_finalize (GObject *object) { /* CustomCellRendererProgress *cellrendererprogress = CUSTOM_CELL_RENDERER_PROGRESS(object); */ /* Free any dynamically allocated resources here */ (* G_OBJECT_CLASS (parent_class)->finalize) (object); }
80
/*************************************************************************** * * custom_cell_renderer_progress_set_property: as it says * ***************************************************************************/ static void custom_cell_renderer_progress_set_property (GObject *object, guint param_id, const GValue *value, GParamSpec *pspec) { CustomCellRendererProgress *cellprogress = CUSTOM_CELL_RENDERER_PROGRESS (object); switch (param_id) { case PROP_PERCENTAGE: cellprogress->progress = g_value_get_double(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec); break; } } /*************************************************************************** * * custom_cell_renderer_progress_new: return a new cell renderer instance * ***************************************************************************/ GtkCellRenderer * custom_cell_renderer_progress_new (void) { return g_object_new(CUSTOM_TYPE_CELL_RENDERER_PROGRESS, NULL); }
/*************************************************************************** * * custom_cell_renderer_progress_get_size: crucial - calculate the size of our cell, taking into account * padding and alignment properties * of parent. * * ***************************************************************************/ #define FIXED_WIDTH #define FIXED_HEIGHT 100 10
81
/*************************************************************************** * * custom_cell_renderer_progress_render: crucial - do the rendering. * ***************************************************************************/ static void custom_cell_renderer_progress_render (GtkCellRenderer *cell, GdkWindow *window, GtkWidget *widget, GdkRectangle *background_area, GdkRectangle *cell_area, GdkRectangle *expose_area, guint flags) { CustomCellRendererProgress *cellprogress = CUSTOM_CELL_RENDERER_PROGRESS (cell); GtkStateType state; gint width, height; gint x_offset, y_offset; custom_cell_renderer_progress_get_size (cell, widget, cell_area, &x_offset, &y_offset, &width, &height); if (GTK_WIDGET_HAS_FOCUS (widget)) state = GTK_STATE_ACTIVE; else state = GTK_STATE_NORMAL; width -= cell->xpad*2; height -= cell->ypad*2; gtk_paint_box (widget->style,
82
12.1.3. main.c
And here is a little test that makes use of our new CustomCellRendererProgress:
#include "custom-cell-renderer-progressbar.h" static GtkListStore static gboolean enum { COL_PERCENTAGE = 0, COL_TEXT, NUM_COLS }; #define STEP 0.01 *liststore; increasing = TRUE; /* direction of progress bar change */
gboolean increase_progress_timeout (GtkCellRenderer *renderer) { GtkTreeIter iter; gfloat perc = 0.0; gchar buf[20]; gtk_tree_model_get_iter_first(GTK_TREE_MODEL(liststore), &iter); /* first and only row */ gtk_tree_model_get (GTK_TREE_MODEL(liststore), &iter, COL_PERCENTAGE, &perc, -1); if ( perc > (1.0-STEP) || (perc < STEP && perc > 0.0) ) { increasing = (!increasing); } if (increasing) perc = perc + STEP; else perc = perc - STEP; g_snprintf(buf, sizeof(buf), "%u %%", (guint)(perc*100)); gtk_list_store_set (liststore, &iter, COL_PERCENTAGE, perc, COL_TEXT, buf, -1); return TRUE; /* Call again */ }
83
liststore = gtk_list_store_new(NUM_COLS, G_TYPE_FLOAT, G_TYPE_STRING); gtk_list_store_append(liststore, &iter); gtk_list_store_set (liststore, &iter, COL_PERCENTAGE, 0.5, -1); /* start at 50% */ view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(liststore)); g_object_unref(liststore); /* 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", COL_TEXT); gtk_tree_view_column_set_title (col, "Progress"); gtk_tree_view_append_column(GTK_TREE_VIEW(view),col); renderer = custom_cell_renderer_progress_new(); col = gtk_tree_view_column_new(); gtk_tree_view_column_pack_start (col, renderer, TRUE); gtk_tree_view_column_add_attribute (col, renderer, "percentage", COL_PERCENTAGE); gtk_tree_view_column_set_title (col, "Progress"); gtk_tree_view_append_column(GTK_TREE_VIEW(view),col); g_timeout_add(50, (GSourceFunc) increase_progress_timeout, NULL); return view; }
int main (int argc, char **argv) { GtkWidget *window, *view; gtk_init(&argc,&argv); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_default_size (GTK_WINDOW(window), 150, 100); g_signal_connect(window, "delete_event", gtk_main_quit, NULL); view = create_view_and_model(); gtk_container_add(GTK_CONTAINER(window), view); gtk_widget_show_all(window); gtk_main(); return 0; }
Progress bar cell renderer (gaim) Date cell renderer (mrproject) (is this one easy to re-use?) List/combo cell renderer (mrproject) (is this one easy to re-use?) Pop-up cell renderer (mrproject) (what does this do?) 84
85
Gtk+ API Reference Manual Gdk API Reference Manual Pango API Reference Manual GLib API Reference Manual gtk-app-devel mailing list archives - search them! gtk-demo - part of the Gtk+ source code (look in gtk+-2.x.y/demos/gtk-demo), especially list_store.c, tree_store.c, and stock_browser.c TreeView tutorial using Gtks C++ interface (gtkmm) TreeView tutorial using Gtks python interface Some slides from Owen Taylors GUADEC 2003 tutorial (postscript, pdf, see pages 13-15) Existing applications - yes, they exist, and you can look at their source code. SourceForges WebCVS browse feature is quite useful, and the same goes for GNOME as well. If your intention is to display external data (from a database, or in XML form) as a list or tree or table, you might also be interested GnomeDB, especially libgda and libgnomedb (e.g. the GnomeDBGrid widget). See also this PDF presentation (page 24ff). your link here!
86
14.2. Credits
Thanks to Axel C. for proof-reading the rst drafts, for many suggestions, and for introducing me to the tree view widget in the rst place (back then when I was still convinced that porting to Gtk+-2.x was unnecessary, Gtk+-1.2 applications looked nice, and Aristotle had already said everything about politics that needs to be said). Harring Figueiredo shed some light on how GtkListStore and GtkTreeStore deal with pixbufs. Ken Rastatter suggested some additional topics (with complete references even). Both Andrej Prsa and Alan B. Canon sent me a couple of suggestions, and taf2, Massimo Mangoni and others spotted some typos. Many thanks to all of them, and of course also to kris and everyone else in #gtk+.
Remove unnecessary col = gtk_tree_view_column_new() im hello world code (leftover from migration to convenience functions).
Point out that GObjects such as GdkPixbufs retrieved with gtk_tree_model_get() need to be g_object_unref()ed after use, as gtk_tree_model_get() adds a reference. Added explicit (gint) event->x double to int conversion to code snippet using gtk_tree_view_get_path_at_pos() to avoid compiler warnings.
Fixed another mistake in tree path explanation: text did not correspond picture (s/movie clips/movie trailers/); (thanks to Benjamin Brandt for spotting it).
Fixed mistake in tree path explanation (s/4th/5th/) (thanks to both Andrew Kirillov and Benjamin Brandt for spotting it).
Fixed fatal typo in custom list code: g_assert() in custom_list_init() should be ==, not != (spotted by mmc). Added link to Owen Taylors mail on the GtkTreeView DragnDrop API.
Fixed typo in code example (remove n-th row example) (Thanks to roel for spotting it). Changed Context menus section title
Added tiny section on Glade and treeviews Added more detail to the section describing GtkTreePath, GtkTreeIter et.al. Reformatted document structure: instead of one single chapter with lots of sections, have multiple chapters (this tutorial is way to big to become part of the Gtk+ tutorial anyway); enumerate chapters and sections. Expanded the section on tree view columns and cell renderers, with help of two diagrams by Owen Taylor (from the GUADEC 2003 Gtk+ tutorial slides).
Added more information about how to remove a single row, or more specically, the n-th row of a list store Added a short example about how to pack icons into the tree view.
Editable cells will work ne even if selection is set to GTK_SELECTION_NONE. Removed sentences that say otherwise.
x jumpy selections in custom model GtkTreeSortable interface implementation. gtk_tree_model_rows_reordered() does not seem to work like the API reference implies (see bug #124790) added section about how to get the cell renderer a button click happened on added section about editable cells with spin buttons (and a CellRendererSpin implementation to the examples)
make custom model GtkTreeSortable implementation emit "sort-column-changed" signal when sortid is changed xed code typo in selection function section; added a paragraph about rule hint to make whole row coloured or bold section
Reformatted source code to make it t on pages when generating ps/pdf output Added link to PDF and docbook XML versions.
88