GTK+ 2.0 Tree View Tutorial using Ocaml | ||
---|---|---|
Prev | Chapter 3. GTree.models for Data Storage: GTree.list_store and GTree.tree_store | Next |
There are different ways to refer to a specific row. The two you will have to deal with are Gtk.tree_iter and Gtk.tree_path.
A Gtk.tree_path is a comparatively straight-forward way to describe the logical position of a row in the model. As a GTree.view always displays all rows in a model, a tree path always describes the same row in both model and view.
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 first 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 first 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. Then pick the 4th child of the last row. Continue 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 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 Gtk.row_references 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 clips' would suddenly be the first and only child of 'clips', and be described by the tree path that formerly belonged to 'movie clips', ie. "1:0:0".
You can get a new Gtk.tree_path from a path in string form using GTree.Path.from_string, and you can convert a given Gtk.tree_path into its string notation with GTree.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, Gtk.tree_path uses an integer array internally. You can get the depth (ie. the nesting level) of a tree path with GTree.Path.get_depth (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. GTree.Path.get_indices (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 GTree.Path.up (gtk_tree_path_up), GTree.Path.down (gtk_tree_path_down), GTree.Path.next (gtk_tree_path_next), GTree.Path.prev (gtk_tree_path_prev), or GTree.Path.is_ancestor (gtk_tree_path_is_ancestor). 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 specific model (ie. the row described by the path exists) is to convert the path into an iter using GTree.model#get_iter (gtk_tree_model_get_iter).
Gtk.tree_path is an opaque structure, with its details hidden from the compiler. If you need to make a copy of a tree path, use GTree.Path.copy (gtk_tree_path_copy).
Another way to refer to a row in a list or tree is Gtk.tree_iter. 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 GTree.list_store and GTree.tree_store) must support the GTree.model (GtkTreeModel) functions that operate on tree iters (e.g. get the tree iter for the first child of the row specified by a given tree iter, get the first row in the list/tree, get the n-th child of a given iter etc.). Some of these functions are:
GTree.model#get_iter_first (gtk_tree_model_get_iter_first) - sets the given iter to the first top-level item in the list or tree
GTree.model#iter_next (gtk_tree_model_iter_next) - sets the given iter to the next item at the current level in a list or tree.
GTree.model#iter_children (gtk_tree_model_iter_children) - sets the first given iter to the first child of the row referenced by the second iter (not very useful for lists, mostly useful for trees).
GTree.model#iter_n_children (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.
GTree.model#iter_children (gtk_tree_model_iter_nth_child) - sets the first 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 first iter set to the n-th row of a list.
GTree.model#iter_parent (gtk_tree_model_iter_parent) - returns the iter to the parent of the row referenced by the given iter (does nothing for lists, only useful for trees).
There are more functions that operate on iters. Check out the GTree.model ( GtkTreeModel API reference) for details.
You might notice that there is no GTree.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 GTree.list_store#append or GTree.tree_store#append.
Tree iters are often only valid for a short time, and might become invalid if the store changes with some models. There is a better way to keep track of a row over time: Gtk.row_reference
A Gtk.row_reference 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.
GTree.row_reference class is the wrapper class of Gtk.row_reference.
A new tree row reference GTree.row_reference can be created with GTree.model#get_row_reference (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 GTree.row_reference#path (gtk_tree_row_reference_get_path).
You can check whether the row referenced still exists with GTree.row_reference#valid (gtk_tree_row_reference_valid).
For the curious: internally, the tree row reference connects to the tree model's "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 significant 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 GTree.list_store and GTree.tree_store have persistent iters, so storing iters is possible. However, using tree row references is definitively 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 find 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.
Tree iters can easily be converted into tree paths using GTree.model#get_path (gtk_tree_model_get_path), and tree paths can easily be converted into tree iters using GTree.model#get_iter (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 * * * ************************************************************) (************************************************************ * * on_row_activated: a row has been double-clicked * ************************************************************) let on_row_activated view path col = let model = view#model in let iter = model#get_iter path in let name = model#get iter col_name in Printf.printf "The row containing the name '%s' has been double-clicked.\n" name
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 reference's path first 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 Gtk.tree_iter is just a structure that contains data fields you do not need to know anything about):
(************************************************************ * * * Going through every row in a list store * * * ************************************************************) let traverse_list_store liststore = (* Get first row in list store *) let first = liststore#get_iter_first in match first with | Some iter -> (* ... do something with that row using the iter ... *) (* Here set the col_first_name column *) liststore#set ~row:iter ~column:col_first_name "Joe"; (* Make iter point to the next row in the list store *) while liststore#iter_next iter do (* Again, do something with that row using the iter ... *) (* Again, here set the col_first_name column *) liststore#set ~row:iter ~column:col_first_name "Jane"; done | None -> ()
The code above asks the model to fill the iter structure to make it point to the first row in the list store. If there is a first row and the list store is not empty, the iter will be set, and GTree.model#get_iter_first (gtk_tree_model_get_iter_first) will return Some _. If there is no first row, it will just return None. If a first row exists, the while loop will be entered and we change some of the first row's 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 GTree.model#iter_next (gtk_tree_model_iter_next) returns false. Instead of traversing the list store we could also have used GTree.model#foreach (gtk_tree_model_foreach).