3.2. Refiriendose a las filas: GtkTreeIter, GtkTreePath, GtkTreeRowReference

Existen distintos modos de referirse a una fila específica. Los dos modos con los cuales es necesario tratar son GtkTreeIter y GtkTreePath.

3.2.1. GtkTreePath

Describiendo una fila 'geográficamente'

Un GtkTreePath es un modo comparativamente avanzado de describir la posición lógica de una fila en el modelo. Como un GtkTreeView siempre despliega todas las filas en un modelo, un tree path siempre describe la misma fila tanto en el modelo como en la vista.

Figura 3-1. Tree Paths

El gráfico muestra el tree path en forma de string junto a la etiqueta. Básicamente, cuenta los hijos desde la raíz imaginaria de la vista de árbol. Un string de un tree path vacío especificaría esa raíz imaginaria invisible. Ahora 'Songs' es el primer hijo (desde la raíz) y entonces su tree path es tan solo "0". 'Videos' es el segundo hijo desde la raíz, y su tree path es "1". 'oggs' es el segundo hijo del primer item desde la raiz, por lo que su tree path es "0:1". Entonces, solo debe contarse desde la raiz hasta la fila en cuestión, y se obtendrá el respectivo tree path.

Para aclarar esto, un tree path de "3:9:4:1" basicamente significaría en lenguaje humano (atención - ¡esto no es lo realmente significa!) algo como lo siguiente: ir a la 3a fila del nivel superior. Ahora ir al noveno hijo de esa fila. Procedase al cuarto hijo de la fila anterior. Continuese entonces al primer hijo de esa. Ahora nos encontramos en la fila descrita por este tree path. Esto no es lo que significa para GTK+ though. Mientras los humanos cuentan desde 1, los computadores usualmente comienzan contando desde 0. Por lo que realmente significa el tree path "3:9:4:1" es: Ir a la 4a fila del nivel superior. Entonces ir al décimo hijo de esa fila. Entonces tomar el quinto hijo de la fila anterior. Sígase hasta el segundo hijo de la fila previa. Ahora nos encontramos en la fila que éste tree path describe. :)

Este modo de referirse a las filas implica lo siguiente: Si se insertan o eliminan filas en el medio o las filas son reordenadas, un tree path puede repentinamente referirse a una fila completamete distinta de la que se refería antes de la inserción/eliminación/reordenamiento. Esto es algo que debe tenerse en mente. (Ver la sección sobre GtkTreeRowReference más adelante para ver un tree path que se mantiene actualizado para asegurarse de que siemprese refiere a la misma fila cuando el modelo cambia).

Este efecto se vuelve aparente si se imagina lo que podría suceder si se eliminan las filas tituladas 'funny clips' del arbol en la figura de arriba. La fila 'movie trailers' repentinamente podrían ser las primeras y únicas hijas de 'clips', y serían descritas por el tree path que anteriormente perteneciera a 'movie clips', ie. "1:0:0".

Se puede obtener un nuevo GtkTreePath desde una path en forma de string usando gtk_tree_path_new_from_string, y se puede convertir un GtkTreePath dado en su notación de string con gtk_tree_path_to_string. Es muy poco usual tener que manejar la notación de string, pero es descrita aquí meramente para demostrar el concepto de tree paths.

En vez de la notación de string, GtkTreePath usa un arreglo de enteros internamente. Se puede obtener la profundidad (ie. el nivel mas anidado) de un tree path con gtk_tree_path_get_depth. Una profundidad de 0 es el nodo raiz invisible imaginario de la vista y el modelo del árbol. Una profundidad d e 1 significa que el tree path describe una fila del nivel superior. Como las listas son sólo árboles sin nodos hijos, todas las filas en una lista siempre tienen tree paths de profundidad 1. gtk_tree_path_get_indices retorna el arreglo de enteros internos de un tree path. De todos modos, raramente se necesita operar con estos.

Si se opera con tree paths, es preferible usar un tree path dado y funciones como 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. !Nótese que de este modo se puede construir y operar en tree paths que hacen referencia a filas que no existen en el módelo o vista! 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 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

Refering to a row in model-speak

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 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:

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 model's flags, and check whether the GTK_TREE_MODEL_ITERS_PERSIST flag 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

3.2.3. GtkTreeRowReference

Keeping track of rows even when the model changes

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 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 GtkListStore and GtkTreeStore 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.

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))
  {
    gchar *name;

    gtk_tree_model_get(model, &iter, COL_NAME, &name, -1);

    g_print ("The row containing the name '%s' has been double-clicked.\n", name);

    g_free(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 heap, as in the following example (keep in mind that GtkTreeIter is just a structure that contains data fields 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 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 gtk_tree_model_get_iter_first will return TRUE. If there is no first row, it will just return FALSE. 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 gtk_tree_model_iter_next returns FALSE. Instead of traversing the list store we could also have used gtk_tree_model_foreach