Next Previous Contents

12. Tree Widget

The purpose of tree widgets is to display hierarchically-organized data. The GTK_TREE widget itself is a vertical container for widgets of type GTK_TREE_ITEM. GTK_TREE itself is not terribly different from GTK_LIST - both are derived directly from GTK_CONTAINER, and the GTK_CONTAINER methods work in the same way on GTK_TREE widgets as on GTK_LIST widgets. The difference is that GTK_TREE widgets can be nested within other GTK_TREE widgets. We'll see how to do this shortly.

The GTK_TREE widget has its own window, and defaults to a white background, as does GTK_LIST. Also, most of the GTK_TREE methods work in the same way as the corresponding GTK_LIST ones. However, GTK_TREE is not derived from GTK_TREE, so you cannot use them interchangeably.

12.1 Creating a Tree

A GtkTree is created in the usual way, using creation procedure:

make

Like the GTK_LIST widget, a GTK_TREE will simply keep growing as more items are added to it, as well as when subtrees are expanded. For this reason, they are almost always packed into a GTK_SCROLLED_WINDOW. You might want to use set_usize() on the scrolled window to ensure that it is big enough to see the tree's items, as the default size for GTK_SCROLLED_WINDOW is quite small.

Now that you have a tree, you'll probably want to add some items to it. The Tree Item Widget below explains the gory details of GTK_TREE_ITEM. For now, it'll suffice to create one, using creation procedure:

make_with_label( label : STRING );

You can then add it to the tree using one of the following (see Functions and Macros below for more options):

append ( child : GTK_TREE_ITEM )
prepend ( child : GTK_TREE_ITEM )

Note that you must add items to a GTK_TREE one at a time.

12.2 Adding a Subtree

A subtree is created like any other GtkTree widget. A subtree is added to another tree beneath a tree item, using the folowing procedure from GTK_TREE_ITEM:

set_subtree ( subtree : GTK_WIDGET ) is:

You do not need to call show() on a subtree before or after adding it to a GTK_TREE_ITEM. However, you must have added the GTK_TREE_ITEM in question to a parent tree before calling set_subtree(). This is because, technically, the parent of the subtree is not the GTK_TREE_ITEM which "owns" it, but rather the GTK_TREE which holds that GTK_TREE_ITEM.

When you add a subtree to a GTK_TREE_ITEM, a plus or minus sign appears beside it, which the user can click on to "expand" or "collapse" it, meaning, to show or hide its subtree. GtkTreeItems are collapsed by default. Note that when you collapse a GTK_TREE_ITEM, any selected items in its subtree remain selected, which may not be what the user expects.

12.3 Handling the Selection List

As with GTK_LIST, the GTK_TREE type has a selection field, and it is possible to control the behaviour of the tree (somewhat) by setting the selection type using:

set_selection_mode ( mode : GTK_SELECTION_MODE )

The semantics associated with the various selection modes are described in the section on the GTK_LIST widget. As with the GTK_LIST widget, the "select_child", "unselect_child" (not really - see Signals below for an explanation), and "selection_changed" signals are emitted when list items are selected or unselected. However, in order to take advantage of these signals, you need to know which GTK_TREE widget they will be emitted by, and where to find the list of selected items.

This is a source of potential confusion. The best way to explain this is that though all GTK_TREE widgets are created equal, some are more equal than others. All GTK_TREE widgets have their own X window, and can therefore receive events such as mouse clicks (if their GtkTreeItems or their children don't catch them first!). However, to make GTK_SELECTION_SINGLE and GTK_SELECTION_BROWSE selection types behave in a sane manner, the list of selected items is specific to the topmost GTK_TREE widget in a hierarchy, known as the "root tree".

Thus, accessing the selectionfield directly in an arbitrary GTK_TREE widget is not a good idea unless you know it's the root tree. Instead, use the selection function, which gives the root tree's selection list as an ARRAY[GTK_TREE_ITEM]. Of course, this list can include items that are not in the subtree in question if the selection type is GTK_SELECTION_MULTIPLE.

Finally, the "select_child" (and "unselect_child", in theory) signals are emitted by all trees, but the "selection_changed" signal is only emitted by the root tree. Consequently, if you want to handle the "select_child" signal for a tree and all its subtrees, you will have to call signal_connect() for every subtree.

12.4 Tree Widget Internals

Signals

selection_changed;

This signal will be emitted whenever the selection field of a GTK_TREE has changed. This happens when a child of the GTK_TREE is selected or deselected.

select_child(child : GTK_WIDGET);

This signal is emitted when a child of the GtkTree is about to get selected. This happens on calls to select_item(),select_child(), on all button presses and calls to toggle() and. It may sometimes be indirectly triggered on other occasions where children get added to or removed from the GTK_TREE.

unselect_child ( child : GTK_WIDGET);

This signal is emitted when a child of the GTK_TREE is about to get deselected. As of GTK+ 1.0.4, this seems to only occur on calls to unselect_item() or unselect_child(), and perhaps on other occasions, but not when a button press deselects a child, nor on emission of the "toggle" signal by toggle().

Functions and procedures

append ( child : GTK_TREE_ITEM )

Append a tree item to a GtkTree.

prepend ( child : GTK_TREE_ITEM )

Prepend a tree item to a GtkTree.

insert ( child : GTK_TREE_ITEM; position : INTEGER ) is

Insert a tree item into a GtkTree at the position in the list specified by position.

remove_items ( items : ARRAY[GTK_TREE_ITEM] )

Remove a list of items from a GtkTree. Note that removing an item from a tree dereferences (and thus usually) destroys it and its subtree, if it has one, and all subtrees in that subtree. If you want to remove only one item, you can use GTK_CONTAINER::remove().

clear_items ( start : INTEGER; upper : INTEGER )

Remove the items from position start to position end from a GtkTree. The same warning about dereferencing applies here, as clear_items() simply constructs a list and passes it to remove_items().

select_item ( item : INTEGER )

Emits the "select_item" signal for the child at position item, thus selecting the child (unless you unselect it in a signal handler).

unselect_item ( item : INTEGER ) is

Emits the "unselect_item" signal for the child at position item, thus unselecting the child.

select_child ( child : GTK_TREE_ITEM ) is

Emits the "select_item" signal for the child tree_item, thus selecting it.

unselect_child ( child : GTK_TREE_ITEM ) is

Emits the "unselect_item" signal for the child tree_item, thus unselecting it.

child_position ( child : GTK_TREE_ITEM ) : INTEGER

Returns the position in the tree of child, unless child is not in the tree, in which case it returns -1.

set_selection_mode ( mode : GTK_SELECTION_MODE )

Sets the selection mode, which can be one of GTK_SELECTION_SINGLE (the default), GTK_SELECTION_BROWSE, GTK_SELECTION_MULTIPLE, or GTK_SELECTION_EXTENDED. This is only defined for root trees, which makes sense, since the root tree "owns" the selection. Setting it for subtrees has no effect at all; the value is simply ignored.

set_view_mode ( mode : GTK_TREE_VIEW_MODE ) is

Sets the "view mode", which can be either GTK_TREE_VIEW_LINE (the default) or GTK_TREE_VIEW_ITEM. The view mode propagates from a tree to its subtrees, and can't be set exclusively to a subtree (this is not exactly true - see the example code comments).

The term "view mode" is rather ambiguous - basically, it controls the way the highlight is drawn when one of a tree's children is selected. If it's GTK_TREE_VIEW_LINE, the entire GtkTreeItem widget is highlighted, while for GTK_TREE_VIEW_ITEM, only the child widget (i.e. usually the label) is highlighted.

set_view_lines ( flag : BOOLEAN ) is

Controls whether connecting lines between tree items are drawn. flag is either TRUE, in which case they are, or FALSE, in which case they aren't.

12.5 Tree Item Widget

The GTK_TREE_ITEM widget, like GTK_LIST, is derived from GTK_ITEM, which in turn is derived from GTK_BIN. Therefore, the item itself is a generic container holding exactly one child widget, which can be of any type. The GTK_TREE_ITEM widget has a number of extra fields, but the only one we need be concerned with is the subtree field.

Signals

GTK_TREE_ITEM inherits the "select", "deselect", and "toggle" signals from GTK_ITEM. In addition, it adds two signals of its own, "expand" and "collapse".

select

This signal is emitted when an item is about to be selected, either after it has been clicked on by the user, or when the program calls GTK_ITEM::select() or GTK_TREE::select_child().

deselect

This signal is emitted when an item is about to be unselected, either after it has been clicked on by the user, or when the program calls GTK_ITEM::deselect(). In the case of GtkTreeItems, it is also emitted by GTK_TREE::unselect_child(), and sometimes GTK_TREE::select_child().

toggle

This signal is emitted when the program calls GTK_ITEM::toggle(). The effect it has when emitted on a GtkTreeItem is to call select_child() (and never unselect_child()) on the item's parent tree, if the item has a parent tree. If it doesn't, then the highlight is reversed on the item.

expand

This signal is emitted when the tree item's subtree is about to be expanded, that is, when the user clicks on the plus sign next to the item, or when the program calls GTK_TREE_ITEM::expand().

collapse

This signal is emitted when the tree item's subtree is about to be collapsed, that is, when the user clicks on the minus sign next to the item, or when the program calls GTK_TREE_ITEM::collapse().

12.6 Tree Example

This is somewhat like the tree example in testgtk, but a lot less complete (although much better commented). It puts up a window with a tree, and connects all the signals for the relevant objects, so you can see when they are emitted.

-- example-start tree tree.e
class TREE
inherit
   GTK_CONSTANTS
   VEGTK_MAIN
   VEGTK_CALLBACK_HANDLER
creation
   make
feature {ANY} -- Creation
   make is
   local
      window : GTK_WINDOW;
      scrolled_win : GTK_SCROLLED_WINDOW;
      tree : GTK_TREE;
      itemnames : ARRAY [STRING];
      i,j : INTEGER;
      subtree : GTK_TREE;
      item, subitem : GTK_TREE_ITEM;
   do
      vegtk_init

      itemnames := <<"Foo", "Bar", "Baz", "Quux","Maurice">>;
      -- a generic toplevel window
      !!window.make (Gtk_window_toplevel);
      signal_connect (window, "destroy",$destroy);

      window.set_border_width (5);

      -- A generic scrolled window
      !!scrolled_win.make (Void, Void);
      scrolled_win.set_policy (Gtk_policy_automatic,
                               Gtk_policy_automatic);
      scrolled_win.set_usize (150, 200);
      window.add (scrolled_win);
      scrolled_win.show;

      -- Create the root tree
      !!tree.make;
      print ("root tree is ");
      print (tree.to_external);
      print ("%N");
      -- connect all GtkTree:: signals
      signal_connect (tree, "select_child",$cb_select_child);
      signal_connect (tree, "unselect_child",$cb_unselect_child);
      signal_connect (tree, "selection_changed",$cb_selection_changed);

      -- Add it to the scrolled window
      scrolled_win.add_with_viewport (tree);
      -- Set the selection mode
      tree.set_selection_mode (Gtk_selection_multiple);
      -- Show it
      tree.show;
      from
         i := 1;
      until
         i > 5
      loop

         -- Create a tree item
         !!item.make_with_label (itemnames.item(i));
         -- Connect all GtkItem:: and GtkTreeItem:: signals
         signal_connect_with_data (item, "select",$cb_itemsignal, "select");
         signal_connect_with_data (item, "deselect",$cb_itemsignal, "deselect");
         signal_connect_with_data (item, "toggle",$cb_itemsignal, "toggle");
         signal_connect_with_data (item, "expand", $cb_itemsignal, "expand");
         signal_connect_with_data (item, "collapse", $cb_itemsignal, "collapse");
         -- Add it to the parent tree
         tree.append (item);
         -- Show it - this can be done at any time
         item.show;
         -- Create this item's subtree
         !!subtree.make;

         print ("-> item ");
         print (itemnames.item(i));
         print ("->");
         print (item.to_external);
         print (", subtree ")
         print (subtree.to_external);
         print ("%N");

         -- This is still necessary if you want these signals to be called
         -- for the subtree's children.  Note that selection_change will be
         -- signalled for the root tree regardless.
         signal_connect (subtree, "select_child",$cb_select_child);
         signal_connect (subtree, "unselect_child",$cb_unselect_child);

         -- This has absolutely no effect, because it is completely ignored
         --   in subtrees
         subtree.set_selection_mode (Gtk_selection_single);
         -- Neither does this, but for a rather different reason - the
         -- view_mode and view_line values of a tree are propagated to
         -- subtrees when they are mapped.  So, setting it later on would
         -- actually have a (somewhat unpredictable) effect */
         subtree.set_view_mode (Gtk_tree_view_item);
         -- Set this item's subtree - note that you cannot do this until
         --  AFTER the item has been added to its parent tree!
         item.set_subtree (subtree);

         from
            j := 1;
         until
            j > 5
         loop

            -- Create a subtree item, in much the same way
            !!subitem.make_with_label (itemnames.item(j));
            -- Connect all GtkItem:: and GtkTreeItem:: signals
            signal_connect_with_data (subitem, "select",$cb_itemsignal, "select");
            signal_connect_with_data (subitem, "deselect",$cb_itemsignal, "deselect");
            signal_connect_with_data (subitem, "toggle",$cb_itemsignal, "toggle");
            signal_connect_with_data (subitem, "expand",$cb_itemsignal, "expand");
            signal_connect_with_data (subitem, "collapse",$cb_itemsignal, "collapse");

            --g_print ("-> -> item %s->%p\n", itemnames[j], subitem);
            print ("-> -> item ");
            print (itemnames.item(j));
            print ("->");
            print (subitem.to_external);
            print ("%N");

            -- Add it to the parent tree
            subtree.append (subitem);
            -- Show it
            subitem.show;
            j := j+1;
         end -- loop
         i := i+1;
      end -- loop
      -- Show the window and loop endlessly
      window.show;
      gtk_main;
   end -- make

feature {NONE} -- Callbacks

   cb_itemsignal (data : ANY; cb_data : VEGTK_CALLBACK_DATA) is
      -- for all the GtkItem:: and GtkTreeItem:: signals
   local
       item : GTK_TREE_ITEM;
       signame : STRING;
       name : STRING;
       label : GTK_LABEL;
       tree : GTK_TREE;
   do
      item ?= cb_data.get_widget;
      signame ?= data;
      -- It's a GtkBin, so it has one child, which we know to be a
      --   label, so get that */
      label ?= item.child;
      -- Get the text of the label
      name := label.get;
      -- Get the level of the tree which the item is in
      print (signame);
      print (" called for item ");
      print (name);
      print ("->")
      print (item.to_external);
      print (", level ");
      tree ?= item.parent;
      print (tree.level);
      print ("%N");
   end

   cb_unselect_child (data : ANY; cb_data : VEGTK_CALLBACK_DATA) is
      -- Note that this is never called
   local
      root_tree,child,subtree : GTK_TREE;
      args : ARRAY[ANY];
   do
      root_tree ?= cb_data.get_widget;
      args := cb_data.get_args;
      child ?= args.item(1);
      subtree ?= args.item(2);

      print ("unselect_child called for root tree ");
      print (root_tree.to_external);
      print (", subtree ");
      print (subtree.to_external)
      print (", child ");
      print (child.to_external);
      print ("%N");
   end

   cb_select_child  (data : ANY; cb_data : VEGTK_CALLBACK_DATA) is
      -- Note that this is called every time the user clicks on an item,
      -- whether it is already selected or not. */
   local
      root_tree : GTK_TREE;
      args : ARRAY[ANY];
      child : GTK_TREE_ITEM;
   do
      root_tree ?= cb_data.get_widget;
      args := cb_data.get_args;
      child ?= args.item(1);
--      subtree ?= args.item(2);

      print ("select_child called for root tree ");
      print (root_tree.to_external);
--      print (", subtree ");
--      print (subtree.to_external)
      print (", child ");
      print (child.to_external);
      print ("%N");
   end

   cb_selection_changed (data : ANY; cb_data : VEGTK_CALLBACK_DATA) is
   local
      tree,subtree : GTK_TREE;
      selection : ARRAY [GTK_TREE_ITEM];
      i : INTEGER;
      name : STRING;
      label : GTK_LABEL;
      item : GTK_BIN;

   do
      tree ?= cb_data.get_widget;

      print ("selection_change called for tree ");
      print (tree.to_external);
      print ("%N");

      print ("selected objects are:%N");

      selection := tree.selection;

      if selection /= Void then
         from
            i := selection.lower;
         until
            i > selection.upper
         loop
            item  := selection.item(i);
            label ?= item.child;
            name := label.get;
            print ("%T");
            print (name);
            print (" on level ");
            subtree ?= item.parent;
            print (subtree.level);
            print ("%N");
            i := i+1;
         end -- loop
      end -- if
   end -- cb_selection_changed

   destroy is
   do
      gtk_main_quit;
   end
end -- class TREE
-- example-end


Next Previous Contents