Chapter 6. Selections, Double-Clicks and Context Menus

6.1. Handling Selections

One of the most basic features of a list or tree view is that rows can be selected or unselected. Selections are handled using the GTree.selection (GtkTreeSelection) object of a tree view. Every tree view automatically has a GTree.selection associated with it, and you can get it using GTree.view#selection method (gtk_tree_view_get_selection). Selections are handled completely on the tree view side, which means that the model knows nothing about which rows are selected or not. There is no particular reason why selection handling could not have been implemented with functions that access the tree view widget directly, but for reasons of API cleanliness and code clarity the Gtk+ developers decided to create this special GTree.selection object that then internally deals with the tree view widget. You will never need to create a tree selection object, it will be created for you automatically when you create a new tree view. You only need to use said selection method to get the selection object.

There are three ways to deal with tree view selections: either you get a list of the currently selected rows whenever you need it, for example within a context menu function, or you keep track of all select and unselect actions and keep a list of the currently selected rows around for whenever you need them; as a last resort, you can also traverse your list or tree and check each single row for whether it is selected or not (which you need to do if you want all rows that are not selected for example).

6.1.1. Selection Modes

You can use set_mode method of the GTree.selection object to influence the way that selections are handled. There are four selection modes:

  • `NONE - no items can be selected

  • `SINGLE - no more than one item can be selected

  • `BROWSE - exactly one item is always selected

  • `MULTIPLE - anything between no item and all items can be selected

6.1.2. Getting the Currently Selected Rows

You can get a Gtk.tree_path list of the selected rows using get_selected_rows method (gtk_tree_selection_get_selected_rows).

It is used like this:


...
let cols = new GTree.column_list
let col_name = cols#add Gobject.Data.string
let col_age = cols#add Gobject.Data.int
...

let selection_changed (model:#GTree.model) selection () =
  let pr path =
    let row = model#get_iter path in
    let name = model#get ~row ~column:col_name in
    let age = model#get ~row ~column:col_age in
    Printf.printf "(%s, %d)\n" name age;
    flush stdout
  in
  List.iter pr selection#get_selected_rows
  
let create_view ~model ~packing () =
  ...
  view#selection#set_mode `MULTIPLE;
  view#selection#connect#changed ~callback:(selection_changed model view#selection);
  ...

One thing you need to be aware of is that you need to take care when looping through the list that 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 first 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.

6.1.3. Using Selection Functions

You can set up a custom selection function with GTree.selection#set_select_function (gtk_tree_selection_set_select_function). This function will then be called every time a row is going to be selected or unselected (meaning: it will be called before the selection status of that row is changed). Selection functions are commonly used for the following things:

  1. ... to keep track of the currently selected items (then you maintain a list of selected items yourself). In this case, note again that your selection function is called before the row's selection status is changed. In other words: if the row is going to be selected, then the boolean parameter that is passed to the selection function is still false. Also note that the selection function might not always be called when a row is removed, so you either have to unselect a row before you remove it to make sure your selection function is called and removes the row from your list, or check the validity of a row when you process the selection list you keep. You should not store tree paths in your self-maintained list of of selected rows, because whenever rows are added or removed or the model is resorted the paths might point to other rows. Use tree row references or other unique means of identifying a row instead.

  2. ... to tell Gtk+ whether it is allowed to select or unselect that specific row (you should make sure though that it is otherwise obvious to a user whether a row can be selected or not, otherwise the user will be confused if she just cannot select or unselect a row). This is done by returning true or false in the selection function.

  3. ... to take additional action whenever a row is selected or unselected.

Yet another simple example:


let view_selection_func (model:#GTree.model) path currently_selected =
  let row = model#get_iter path in
  let name = model#get ~row ~column:col_name in
  if not currently_selected
  then Printf.printf "%s is going to be selected.\n" name
  else Printf.printf "%s is going to be unselected.\n" name;
  flush stdout;
  true (* allow selection state to change *)

let create_view ~model ~packing () =
  let view = GTree.view ~model ~packing () in
  ..
  view#selection#set_select_function (view_selection_func model);
  ...

6.1.4. Checking Whether a Row is Selected

You can check whether a given row is selected or not using the functions GTree.selection#iter_is_selected (gtk_tree_selection_iter_is_selected) or GTree.selection#path_is_selected (gtk_tree_selection_path_is_selected). If you want to know all rows that are not selected, for example, you could just traverse the whole list or tree, and use the above functions to check for each row whether it is selected or not.