Create  Edit  FrontPage  Index  Search  Changes  History  RSS  Back  Login

tut-gtk2-treev-rr Diff - Ruby-GNOME2 Project Website

  • Added parts are displayed like this.
  • Deleted parts are displayed like this.

= The Tree View Widget
{{link "tut-gtk2-treev-trees", "tut-gtk2-treev", "tut-gtk", "tut-gtk2-treev-addrnhs"}}


== Referencing Rows

Three objects are available for referring to a specific row within a tree model, each with its own unique advantages. They are  Gtk::TreePath, Gtk::TreeIter, and Gtk::TreeRowReference.

=== Tree Path

{{image_right("treev-rr-01.png")}}

Instances of Gtk::TreePath class are a very convenient objects for referring rows within a tree model, because it can be easily represented as a human-readable string. It can also be represented as an array of integers. For instance, if you are presented with the string 3:7:5, you would start at the fourth element (recall that indexing begins at zero) on the first root level. You would next proceed to the eighth child on the second level in the root element there. The row in question is the sixth child of the root element on the third level.

You can get a new Gtk::TreePath from a path in string form using Gtk::TreePath.new(path_in_string_notation). But if you already have a Gtk::TreePath object you can convert it into its string notation with Gtk::TreePath#to_str.

Internally Gtk::TreePath does not save a string notation, instead it uses an integer array. You can get the depth (ie. the nesting level) of a tree path with Gtk::TreePath#depth. A depth of 0 is the imaginary invisible root node, this, by the way, is the only level where you initialize Gtk::TreeStore#append method with the((*nil*))argument, namely if a child has children, to obtain their respective root iterator, you'd pass to the Gtk::TreeStore#append the child which is the parent of those children.

:Imaginary, invisible root node:

    Pay attention to((*imaginary, invisible*))here. If you do not, you will finally notice this point when inserting rows into a tree view by double clicking the row, under which, you'd like to insert a "sub-item". Namely, there is no row for the((*imaginary, invisible row.*))That is why it is called invisible. True, there is no physical row there, but the top level on which the top root node exists is anything but imaginary! We, will discus this problem in the second version of the 'treev-MultiDim-load-asci-table.rb' example (- remember we announced issues for programming the insertion of new and deletion of existing rows).


At this point in addition to Gtk::TreeStore, we also need to address the Gtk::ListStore. As lists are just trees without child nodes, all rows in a list always have tree paths of depth 1. Gtk::TreePath#indices returns the internal integer array of a tree path. You will rarely operate with paths, but if you do, you most likely will use methods like Gtk::TreePath#up!, Gtk::TreePath#down!, Gtk::TreePath#next!, Gtk::TreePath#prev!, Gtk::TreePath#ancestor?, Gtk::TreePath#decendent?. Note that this way you can operate on tree paths which refer to rows that do not exist in model. A far more coomon way to refer to a row in a list or tree model is Gtk::TreeIter.





((*------------- FIXME, FIXME, FIXME, FIXME, FIXME, FIXME ---------- (start) ------*))

:PATH NAVIGATION METHODS (up!, down!, prev! next!) DO NOT WORK AS EXPECTED:

    As of September 2012, path navigation methods do not work when you reach the last level, i.e. when the path depth reaches 0 (ZERO). But you can bypass the problem if you handle path as if it were a string. Following are two code snippets demonstrating what was just said. (1) the first showing the code that does not work, utilizing Gtk::TreePath navigation methods. And (2) the second showing how you can accomplish the desired effect by processing path as a string.
    
    For instance the following code does not work:

     # GTk::TreePath#up! DOES NOT WORK

     def fix_parent_row_prices(treeview, sel_path, added_price)
       model = treeview.model
       path = sel_path
       while path
         puts "DEBUG: path.class=#{path.class} to_str=#{path.to_str} path=#{path}"
         iter = model.get_iter(path)
         iter[PRICE_COLUMN] += added_price
         path = nil if !(path.up!)
       end
     end

    The following code processing path as a string works:

     def fix_parent_row_prices(treeview, sel_path, added_price)
       model = treeview.model
       path = sel_path
       while path
         iter = model.get_iter(path)
         iter[PRICE_COLUMN] += added_price
         path = /(.+)(:\d+)/ =~ path ? $1 : $2
       end
     end

((*------------- FIXME, FIXME, FIXME, FIXME, FIXME, FIXME ---------- (end) ------*))








{{br}}


--- Gtk::TreePath.new(path = nil)

    Creates a new Gtk::TreePath initialized to path. path is expected to be a colon separated list of numbers. For example, the string "10:4:0" would create a path of depth 3 pointing to the 11th child of the root node, the 5th child of that 11th child, and the 1st child of that 5th child. If an invalid path string is passed in, error is occured.
    * path: The string representation of a path.
    * Returns: A newly created Gtk::TreePath.

For example one could write:

path = Gtk::TreePath.new("1:2")


You can convert a path object to string with the usual to_str method:

puts "Path=#{path.to_str}"  # => Path=1:2


You may want to convert a path to an array of indices:

--- indices

    Returns the current indices of path. This is an array of integers, each representing a node in a tree. This value should not be freed.
    * Returns : The current indices, or nil.

puts "Array=#{path.indices}"  # => Array=[1, 2]

=== Tree Iterators


GTK+ providess the Gtk::TreeIter object, which can be used to reference a specific row within a Gtk::TreeModel. These iterators are used internally by models, which means you never directly alter the contents of an iterator.


A Gtk::TreeIter is a reference to a specific node on a specific model. These are filled in by the model in a model-specific way. One can convert a path to an iterator by calling Gtk::TreeModel#get_iter. These iterators are the primary way of accessing a model and are similar to the iterators used by Gtk::TextBuffer. They are generally statically allocated on the heap and only used for a short time. The model interfaces define a sets of operations for navigating the model:



:In Gtk::ListStore we have methods:

    Gtk::ListStore#insert(position), Gtk::ListStore#insert_before(sibling), Gtk::ListStore#insert_after(sibling), Gtk::ListStore#prepend, Gtk::ListStore#append, Gtk::ListStore#swap(a, b), Gtk::ListStore#move_before(iter, position), Gtk::ListStore#move_after(iter, position) and Gtk::ListStore#each; Gtk::ListStore#iter_first.



:In Gtk::TreeStore we have methods:

    Gtk::TreeStore#insert(parent, position), Gtk::TreeStore#insert(parent, position, values), Gtk::TreeStore#insert_before(parent, sibling), Gtk::TreeStore#insert_after(parent, sibling), Gtk::TreeStore#prepend(parent), Gtk::TreeStore#append(parent), Gtk::TreeStore#swap(a, b), Gtk::TreeStore#move_before(iter, position), Gtk::TreeStore#move_after(iter, position) and Gtk::TreeStore#each; Gtk::TreeStore#iter_first.

:See also Gtk::TreeModel:
    Gtk::TreeModel#each



But only in  Gtk::TreeIter we can find the method Gtk::TreeIter#next!:






def traverse_list_store (liststore)
   # get first row in list store
   return unless iter = liststore.iter_first
   begin  
     # ... do something with that row using the iter ...
   end while iter.next!
end

The same can of course be accomplished by either Gtk::ListStore#each or Gtk::TreeStore#each:

liststore.each { |model,path,iter| # ... do something with that row using the iter ...
}





=== Converting Tree Iterators to Paths or Paths to Iterators

Tree iters can easily be converted into tree paths using Gtk::TreeIter#path, and tree paths can easily be converted into tree iters using Gtk::TreeModel#get_iter. For example:

path = an_iter.path
store = treeview.model
iter = store.get_iter(path)    # where path : The Gtk::TreePath or a String representation of a Gtk::TreePath.

Converting a path to the coresponding iterator is obvious:

path_string = "2:0:3:6"
iter = treeview.model.get_iter(path_string)

# or using iter object would also work:
iter = treeview.model.get_iter(Gtk::TreePath.new(path_string))






{{br}}
=== Tree Row References

Gtk::TreeRowReference objects are used to watch a tree model for changes. Internally they connect to the((*row-inserted, row-deleted,*))and((*row-reordered*)) signals, updating the stored path based on the changes.

New tree row references are created with Gtk::TreeRowReference.new:

--- Gtk::TreeRowReference.new(model, path)

    Creates a row reference based on the path. This reference will keep pointing to the node pointed to by the path, so long as it exists. It listens to all signals emitted by the model, and updates it's path appropriately. If the path isn't a valid path in the model, then an error occurs.
    * model : A Gtk::TreeModel
    * path : A valid Gtk::TreePath to monitor
    * Returns : A new Gtk::TreeRowReference

If you need to retrieve a path, you can use Gtk::TreeRowReference#path instance method, which will return nil if the row no longer exists within the model. As you can see from the API document segment above, the tree references are able to update the tree path based on changes within the tree model, but if you remove all the elements from the same level as the tree path's row, it will no longer have a row to point to.

You should be aware that tree row references do add a bit of a processing overhead, when adding, removing, or sorting rows within a tree model, since the references will have to handle all signals emitted by these actions.



=== TreeRowReference to the rescue:

Before continuing let us underline that removing a row from a tree model, also removes it from the associated tree vieews. However we here have a much bigger fish to fry: while traversing a store with a Gtk::TreeModel#each loop, it can be dangerous to add or remove rows from the store. Though, we have not yet been talking about removing rows from tree model and view, we should mention here that if you remove a parent row that has a hierarchy of children, it too will be removed, i.e. removing a parent that has children, and grand children, all these descendants, as well as any of their descendants will also be removed. It is easy to to remove rows with either Gtk::ListStore#remove or Gtk::TreeStore#remove. The removed row and all its children and any children of children, and so on ..., will also be removed from the tree view as well.

As stated, removing rows in a loop can be a problem, however. Following is an example showing, how to avoid such a problem, namely, it is not possible to traverse a store with Gtk::TreeModel#each, to check whether the given row should be removed and then simply remove it by calling one of the models' remove methods. This will not work, because when the model is changed from within a 'foreach loop', this invalidates 'tree iters' in the each method, and causes unpredictable behaviour.

In events like this Gtk::TreeRowReference will help us:

underage = []
liststore.each do |model,path,iter|
   (iter[0] == false) and underage.push(Gtk::TreeRowReference.new(model,path))
end

underage.each do |rowref|
   (path = rowref.path) and liststore.remove(liststore.get_iter(path))
end

If you want to remove all rows you can use Gtk::ListStore#clear and Gtk::TreeStore#clear.

{{br}}
{{br}}

=== A Brief Summary And Test Of Tree View Concepts Up To This Point

{{image_right "TreeStore-n-Refs.png"}}

The following is the extended version of the earlier spaghetti code example demonstrating some of the points we learned so far here. (1) We manually created three levels deep tree view. (2) implemented the 'set_cell_data_func' code block for 'Age' tree view column which maps to model's column three, however, this is not explicitly stated when column is instantiated. Nevertheless, we obtain the year of birth value from model via((*year_born=iter[2],*))and generate the age of the person if the year is a valid value. More interesting feature is how we handle foreground colour with the Gtk::CellRendererText#forefround_set  and Gtk::CellRendererText#forgeground= methods. (3) Use virtual tree view column with title or header 'Path & Depth', which has no corresponding model column. In it we display depth and path values for each row. And lastly: (4) in the button we test tree view references. Namely, we wish to see if we can access a row in the tree view by providing a string value "2:0:1" for its path value, passing its path object to the Gtk::TreeRowReference.new constructor, and finally retrieve its iter from the obtained tree row reference. (We could have converted the path string directly to an iterator, but we wanted to test the tree row reference in the process.)


((*manually-build-multilevel-tview-summary.rb*))
{{br}}

#!/usr/bin/env ruby
require 'gtk2'

treestore = Gtk::TreeStore.new(String, String, Integer)

# Append a toplevel row and fill in some data
parent = treestore.append(nil)
parent[0] = "Maria"
parent[1] = "Incognito"
# Append another toplevel row and fill in some data
parent = treestore.append(nil)
parent[0] = "Tony"
parent[1] = "Zus"
parent[2] = 1949
# Append a second toplevel row and fill in some data
parent = treestore.append(nil)
parent[0] = "Jane"
parent[1] = "Average"
parent[2] = 1962
# Append a child to the second toplevel row and fill in some data
child = treestore.append(parent)
child[0] = "Janinita"
child[1] = "Average"
child[2] = 1985
# Append a grandchild to the third level row and fill in some data
grandchild = treestore.append(child)
grandchild[0] = "Janinitica"
grandchild[1] = "Average"
grandchild[2] = 2002
# Append the 2nd grandchild to the third level row and fill in some data
grandchild = treestore.append(child)
grandchild[0] = "Janinitica2"
grandchild[1] = "Average"
grandchild[2] = 2003
# Append again a second toplevel row and fill in some data
parent = treestore.append(nil)
parent[0] = "Gustav"
parent[1] = "Crocky"
parent[2] = 1951
# Append a child to the second toplevel row and fill in some data
child = treestore.append(parent)
child[0] = "Gustlek"
child[1] = "Crocky"
child[2] = 1970
# Append a child to the second toplevel row and fill in some data
child = treestore.append(parent)
child[0] = "Zyla"
child[1] = "Crocky"
child[2] = 1973


view = Gtk::TreeView.new(treestore)
view.selection.mode = Gtk::SELECTION_NONE

# Create a renderer
renderer = Gtk::CellRendererText.new
# Add column using our renderer
col = Gtk::TreeViewColumn.new("First Name", renderer, :text => 0)
view.append_column(col)

# Create another renderer and set the weight property
renderer = Gtk::CellRendererText.new
renderer.weight = Pango::FontDescription::WEIGHT_BOLD
# Add column using the second renderer
col = Gtk::TreeViewColumn.new("Last Name", renderer, :text => 1)
view.append_column(col)

# Create one more renderer and set the foreground color to red
renderer = Gtk::CellRendererText.new
renderer.foreground = "red"
# Add column using the third renderer
col = Gtk::TreeViewColumn.new("Age", renderer)
view.append_column(col)

# Create a cell data function to calculate age
col.set_cell_data_func(renderer) do |col, renderer, model, iter|
   year_now = 2012 # To save code not relevant to the example
   year_born = iter[2]

   if (year_born <= year_now) && (year_born > 0)

     ### - NOTE: the following doesn't work {{ iter[2] === Integer }} !!!
     # iter[2] = sprintf("%i years old", year_now - year_born)
     ### - You must employ:  renderer.text = ...
     renderer.text = sprintf("%i years old", year_now - year_born)


     # render in default foreground color if we know the age
     renderer.foreground_set = false
   else
     renderer.text = "age unknown"
     # render with foreground color we set earlier if we don't know the age
     renderer.foreground_set = true
   end
end

# - add another (virtual) VIEW column to TreeView
# Create one last renderer and set its display value to row's path
renderer = Gtk::CellRendererText.new
# Add column using the third renderer
col = Gtk::TreeViewColumn.new("Path & Depth", renderer)
view.append_column(col)

# Create a cell data function to display row path and depth.
col.set_cell_data_func(renderer) do |col, renderer, model, iter|
  renderer.text = "P:#{iter.path.to_str} \tDepth:#{iter.path.depth}"
end

vbox = Gtk::VBox.new(homogeneous=false, spacing=nil)
button = Gtk::Button.new("Click to Test TreeReference")
button.signal_connect("clicked") do
   # pretend we have an arbitrary TreeReference
   row_ref = Gtk::TreeRowReference.new(treestore, Gtk::TreePath.new("2:0:1"))
   iter = row_ref.model.get_iter(row_ref.path)
   puts "Iter from Ref: First Name: #{iter[0]}"
end

vbox.pack_start_defaults(view)
vbox.pack_start_defaults(button)

window = Gtk::Window.new("Test Obtaining Iters from TreeReference")
window.signal_connect("destroy") { Gtk.main_quit }
window.add(vbox)
window.show_all
Gtk.main