The purpose of tree widgets is to display hierarchically-organized data. The Tree widget itself is a vertical container for widgets of type TreeItem. Tree itself is not terribly different from CList - both are derived directly from Container, and the Container methods work in the same way on Tree widgets as on CList widgets. The difference is that Tree widgets can be nested within other Tree widgets. We'll see how to do this shortly.
The Tree widget has its own window, and defaults to a white background, as does CList. Also, most of the Tree methods work in the same way as the corresponding CList ones. However, Tree is not derived from CList, so you cannot use them interchangeably.
Warning: GtkTree is doubly deprecated. It is known to be buggy, so it had been replaced by GtkCtree, but it still works in FPC. GtkCtree, in turn, is also deprecated by GtkTreeView collection of widgets. Unfortunately, GtkTreeView (as well as GtkCTree) is not documented in the tutorial (yet?). |
A Tree is created in the usual way, using:
function gtk_tree_new() : PGtkWidget;
Like the CList widget, a 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 ScrolledWindow. You might want to use
gtk_widget_set_size_request
() on the scrolled window to ensure
that it is big enough to see the tree's items, as the default size
for ScrolledWindow 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 TreeItem. For now, it'll suffice to create one, using:
function gtk_tree_item_new_with_label (label : pgchar) : PGtkWidget;
You can then add it to the tree using one of the following (see Functions and Macros below for more options):
procedure gtk_tree_append (tree : PGtkTree; tree_item : PGtkWidget); procedure gtk_tree_prepend (tree : PGtkTree ; tree_item : PGtkWidget);
Note that you must add items to a Tree one at a time - there is no
equivalent to gtk_list_*_items
().
A subtree is created like any other Tree widget. A subtree is added to another tree beneath a tree item, using:
procedure gtk_tree_item_set_subtree (tree_item : PGtkTreeItem; subtree : PGtkWidget);
You do not need to call gtk_widget_show
() on a subtree before
or after adding it to a TreeItem. However, you must have added
the TreeItem in question to a parent tree before calling
gtk_tree_item_set_subtree
(). This is because, technically,
the parent of the subtree is not the GtkTreeItem which "owns" it, but
rather the GtkTree which holds that GtkTreeItem.
When you add a subtree to a TreeItem, 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. TreeItems are collapsed by default. Note that when you collapse a TreeItem, any selected items in its subtree remain selected, which may not be what the user expects.
As with CList, the Tree type has a selection field, and it is possible to control the behaviour of the tree (somewhat) by setting the selection type using:
procedure gtk_tree_set_selection_mode (tree : PGtkTree; mode : GtkSelectionMode);
The semantics associated with the various selection modes are described in the section on the CList widget. As with the CList 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 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 Tree widgets are created equal, some are more equal than
others. All Tree widgets have their own X window, and can therefore receive
events such as mouse clicks (if their TreeItems 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 Tree widget
in a hierarchy, known as the "root tree".
Thus, accessing the selection field directly in an arbitrary Tree widget
is not a good idea unless you know it's the root tree. Instead, use
the GTK_TREE_SELECTION(Tree)
macro, which gives the root tree's
selection list as a GList pointer. 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
g_signal_connect
() for every subtree.
The Tree's type definition looks like this:
type TGtkTree = record container : TGtkContainer; children : PGList; root_tree : PGtkTree; { owner of selection list } tree_owner : PGtkWidget; selection : PGList; level : guint; indent_value : guint; current_indent : guint; selection_mode : guint; view_mode : guint; view_line : guint; end;
The perils associated with accessing the selection field directly have
already been mentioned. The other important fields of the record can also
be accessed with handy macros or class functions.
GTK_IS_ROOT_TREE(Tree)
returns a boolean value which indicates
whether a tree is the root tree in a Tree hierarchy, while
GTK_TREE_ROOT_TREE(Tree)
returns the root tree, an object of
type GtkTree (so, remember to cast it using GTK_WIDGET(Tree)
if you want to use one of the gtk_widget_*
() functions on it).
Instead of directly accessing the children field of a Tree widget, it's
probably best to cast it using GTK_CONTAINER(Tree)
, and pass it
to the gtk_container_children
() function. This creates a duplicate
of the original list, so it's advisable to free it up using
g_list_free
() after you're done with it, or to iterate on it
destructively, like this:
children := gtk_container_children(GTK_CONTAINER(tree)); while (children = true) do begin do_something_nice(GTK_TREE_ITEM(children^.data)); children := g_list_remove_link(children, children); end;
The tree_owner
field is defined only in subtrees, where it
points to the TreeItem widget which holds the tree in question.
The level
field indicates how deeply nested a particular tree is; root trees have level
0, and each successive level of subtrees has a level one greater than
the parent level. This field is set only after a Tree widget is actually
mapped (i.e. drawn on the screen).
procedure selection_changed (tree : PGtkTree);
This signal will be emitted whenever the selection
field of
a Tree has changed. This happens when a child of the Tree is selected
or deselected.
procedure select_child (tree : PGtkTree; child : PGtkWidget);
This signal is emitted when a child of the Tree is about to get selected.
This happens on calls to gtk_tree_select_item
(),
gtk_tree_select_child
(), on all button presses and calls
to gtk_tree_item_toggle
() and gtk_item_toggle
().
It may sometimes be indirectly triggered on other occasions where children
get added to or removed from the Tree.
procedure unselect_child (tree : PGtkTree; child : PGtkWidget);
This signal is emitted when a child of the Tree is about to get
deselected. As of GTK 1.0.4, this seems to only occur on calls to
gtk_tree_unselect_item
() or
gtk_tree_unselect_child
(), and perhaps on other occasions,
but not when a button press deselects a child, nor on emission of the
"toggle" signal by gtk_item_toggle
().
gtk_tree_get_type() : guint;
Returns the GtkTree
type identifier.
gtk_tree_new() : PGtkWidget;
Create a new Tree object. The new widget is returned as a pointer to
a GtkWidget object. nil
is returned on failure.
procedure gtk_tree_append (tree : PGtkTree; tree_item : PGtkWidget);
Append a tree item to a Tree.
procedure gtk_tree_prepend (tree : PGtkTree; tree_item : PGtkWidget);
Prepend a tree item to a Tree.
procedure gtk_tree_insert (tree : PGtkTree; tree_item : PGtkWidget; position : gint);
Insert a tree item into a Tree at the position in the list specified
by position
.
procedure gtk_tree_remove_items (tree : PGtkTree; items : pGList);
Remove a list of items (in the form of a GList pointer) from a Tree. 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
().
procedure gtk_tree_clear_items (tree : PGtkTree; start, end : gint);
Remove the items from position start
to position
end
from a Tree. The same warning about dereferencing applies
here, as gtk_tree_clear_items
() simply constructs a list
and passes it to gtk_tree_remove_items
().
procedure gtk_tree_select_item (tree : PGtkTree; item : gint);
Emits the "select_item" signal for the child at position
item
, thus selecting the child (unless you unselect it
in a signal handler).
procedure gtk_tree_unselect_item (tree : PGtkTree; item : gint);
Emits the "unselect_item" signal for the child at position
item
, thus unselecting the child.
procedure gtk_tree_select_child (tree : PGtkTree; tree_item : PGtkWidget);
Emits the "select_item" signal for the child tree_item
,
thus selecting it.
procedure gtk_tree_unselect_child (tree : PGtkTree; tree_item : PGtkWidget);
Emits the "unselect_item" signal for the child tree_item
,
thus unselecting it.
function gtk_tree_child_position (tree : PGtkTree; child : PGtkWidget) : gint;
Returns the position in the tree of child
, unless
child
is not in the tree, in which case it returns -1.
procedure gtk_tree_set_selection_mode (tree : PGtkTree; mode : TGtkSelectionMode);
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.
procedure gtk_tree_set_view_mode (tree : PGtkTree; mode : TGtkTreeViewMode);
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 TreeItem widget
is highlighted, while for GTK_TREE_VIEW_ITEM
, only the child
widget (i.e., usually the label) is highlighted.
procedure gtk_tree_set_view_lines (tree : PGtkTree; flag : gboolean);
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.
function GTK_TREE (obj : gpointer) : PGtkTree;
Cast a generic pointer to GtkTree pointer.
function GTK_TREE_CLASS (class : gpointer) : PGtkTreeClass;
Cast a generic pointer to GtkTreeClass pointer.
function GTK_IS_TREE (obj : gpointer) : gint;
Determine if a generic pointer refers to a GtkTree
object.
function GTK_IS_ROOT_TREE (obj : gpointer) : gint;
Determine if a generic pointer refers to a GtkTree
object
and is a root tree. Though this will accept any pointer, the results
of passing it a pointer that does not refer to a Tree are undefined and
possibly harmful.
function GTK_TREE_ROOT_TREE (obj : gpointer) : PGtkTree;
Return the root tree of a pointer to a GtkTree
object.
The above warning applies.
function GTK_TREE_SELECTION (obj : gpointer) : PGList;
Return the selection list of the root tree of a GtkTree
object. The above warning applies here, too.
The TreeItem widget, like CListItem, is derived from Item, which in turn is derived from Bin. Therefore, the item itself is a generic container holding exactly one child widget, which can be of any type. The TreeItem widget has a number of extra fields, but the only one we need be concerned with is the subtree field.
The definition for the TreeItem type looks like this:
type TGtkTreeItem = record item : TGtkItem; subtree : PGtkWidget; pixmaps_box : GtkWidget; plus_pix_widget, minus_pix_widget : PGtkWidget; { pixmap node for this item's color depth } pixmaps : PGList; expanded : guint; end;
The pixmaps_box
field is an EventBox which catches clicks
on the plus/minus symbol which controls expansion and collapsing.
The pixmaps
field points to an internal data structure.
Since you can always obtain the subtree of a TreeItem in a (relatively)
type-safe manner with the GTK_TREE_ITEM_SUBTREE(Item)
macro,
it's probably advisable never to touch the insides of a TreeItem unless you
really know what you're doing.
Since it is directly derived from an Item it can be treated as such
by using the GTK_ITEM(TreeItem)
macro. A TreeItem usually holds
a label, so the convenience function gtk_list_item_new_with_label
()
is provided. The same effect can be achieved using code like the following,
which is actually copied verbatim from
gtk_tree_item_new_with_label
():
tree_item := gtk_tree_item_new(); label_widget := gtk_label_new(label); gtk_misc_set_alignment(GTK_MISC(label_widget), 0.0, 0.5); gtk_container_add(GTK_CONTAINER(tree_item), label_widget); gtk_widget_show(label_widget);
As one is not forced to add a Label to a TreeItem, you could also add an HBox or an Arrow, or even a Notebook (though your app will likely be quite unpopular in this case) to the TreeItem.
If you remove all the items from a subtree, it will be destroyed and unparented, unless you reference it beforehand, and the TreeItem which owns it will be collapsed. So, if you want it to stick around, do something like the following:
gtk_widget_ref(tree); owner := GTK_TREE(tree)^.tree_owner; gtk_container_remove(GTK_CONTAINER(tree), item); if (tree^.parent = nil) then begin gtk_tree_item_expand(GTK_TREE_ITEM(owner)); gtk_tree_item_set_subtree(GTK_TREE_ITEM(owner), tree); end else gtk_widget_unref(tree);
Finally, drag-n-drop does work with TreeItems. You just have
to make sure that the TreeItem you want to make into a drag item or a drop
site has not only been added to a Tree, but that each successive parent widget
has a parent itself, all the way back to a toplevel or dialog window, when
you call gtk_widget_dnd_drag_set
() or
gtk_widget_dnd_drop_set
(). Otherwise, strange things will
happen.
TreeItem inherits the "select", "deselect", and "toggle" signals from Item. In addition, it adds two signals of its own, "expand" and "collapse".
procedure select (tree_item : PGtkItem);
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_tree_item_select
(), gtk_item_select
(), or
gtk_tree_select_child
().
procedure deselect (tree_item : PGtkItem);
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_tree_item_deselect
() or gtk_item_deselect
().
In the case of TreeItems, it is also emitted by
gtk_tree_unselect_child
(), and sometimes
gtk_tree_select_child
().
procedure toggle (tree_item : PGtkItem);
This signal is emitted when the program calls
gtk_item_toggle
(). The effect it has when emitted on a TreeItem
is to call gtk_tree_select_child
() (and never
gtk_tree_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.
procedure expand (tree_item : PGtkTreeItem);
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
().
procedure collapse (tree_item : PGtkTreeItem);
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
().
function gtk_tree_item_get_type () : guint;
Returns the GtkTreeItem
type identifier (which is a
Longint
.
function gtk_tree_item_new () : PGtkWidget;
Create a new TreeItem object. The new widget is returned as a pointer
to a GtkWidget object. nil
is returned on failure.
function gtk_tree_item_new_with_label (label : pgchar) : PGtkWidget;
Create a new TreeItem object, having a single GtkLabel
as
the sole child. The new widget is returned as a pointer to a
GtkWidget
object. nil
is returned on failure.
procedure gtk_tree_item_select (tree_item : PGtkTreeItem);
This procedure is basically a wrapper around a call
to gtk_item_select(GTK_ITEM(tree_item))
which will emit
the "select" signal.
procedure gtk_tree_item_deselect (tree_item : PGtkTreeItem);
This procedure is basically a wrapper around a call to
gtk_item_deselect(GTK_ITEM(tree_item))
which will emit
the "deselect" signal.
procedure gtk_tree_item_set_subtree (tree_item : PGtkTreeItem; subtree : PGtkWidget);
This function adds a subtree to tree_item
, showing it
if tree_item
is expanded, or hiding it if tree_item
is collapsed. Again, remember that the tree_item
must have
already been added to a tree for this to work.
procedure gtk_tree_item_remove_subtree (tree_item : PGtkTreeItem);
This removes all of tree_item
's subtree's children
(thus unreferencing and destroying it, any of its children's subtrees, and
so on...), then removes the subtree itself, and hides the plus/minus sign.
procedure gtk_tree_item_expand (tree_item : PGtkTreeItem);
This emits the "expand" signal on tree_item
, which expands
it.
procedure gtk_tree_item_collapse (tree_item : PGtkTreeItem);
This emits the "collapse" signal on tree_item
, which
collapses it.
function GTK_TREE_ITEM (obj : gpointer) : PGtkTreeItem;
Cast a generic pointer to GtkTreeItem pointer.
function GTK_TREE_ITEM_CLASS (obj : gpointer) : PGtkTreeItemClass;
Cast a generic pointer to a GtkTreeItemClass pointer.
function GTK_IS_TREE_ITEM (obj : gpointer) : gint;
Determine if a generic pointer refers to a GtkTreeItem object.
function GTK_TREE_ITEM_SUBTREE (obj : gpointer) : PGtkWidget;
Returns a tree item's subtree (obj should point to
a GtkTreeItem
object).
This example puts up a window with a tree, and connects all the signals for the relevant objects, so you can see when they are emitted.
program TreeExample; uses gtk2, glib2, gdk2; { For all the GtkItem:: and GtkTreeItem:: signal } procedure cb_itemsignal (item : PGtkWidget; signame : pchar); cdecl; var name : pchar; a_label : PGtkLabel; level : Integer; begin { It's a Bin, so it has one child, which we know to be a label, so get that } a_label := GTK_LABEL(GTK_BIN(item)^.child); { Get the text of the label } name := gtk_label_get_text(a_label); { Get the level of the tree which the item is in } level := GTK_TREE(item^.parent)^.level; g_print('%s called for item %s->%p, level %d'#10, signame, name, item, level); end; { Note that this is never called } procedure cb_unselect_child (root_tree : PGtkWidget; child : PGtkWidget; subtree : PGtkWidget); cdecl; begin g_print('unselect_child called for root tree %p, subtree %p, child %p'#10, root_tree, subtree, child); end; { Note that this is called every time the user clicks on an item, whether it is already selected or not } procedure cb_select_child (root_tree : PGtkWidget; child : PGtkWidget; subtree : PGtkWidget); cdecl; begin g_print('select_child called for root tree %p, subtree %p, child %p'#10, root_tree, subtree, child); end; procedure cb_selection_changed (tree : PGtkWidget); cdecl; var i : pGList; name : pchar; a_label : PGtkLabel; item : PGtkWidget; begin g_print('selection_change called for tree %p'#10, tree); writeln('selected objects are:'); i := GTK_TREE_SELECTION_OLD(GTK_TREE(tree)); while (i <> nil) do begin { Get a GtkWidget pointer from the list node } item := GTK_WIDGET(i^.data); a_label := GTK_LABEL(GTK_BIN(item)^.child); name := gtk_label_get_text(a_label); g_print('\t%s on level %d'#10, name, GTK_TREE(item^.parent)^.level); i := i^.next; end; end; const itemnames : array [0..4] of pgchar = ('Foo', 'Bar', 'Baz', 'Quux', 'Maurice'); var window, scrolled_win, tree, subtree, item, subitem : PGtkWidget; i, j : Integer; begin gtk_init(@argc, @argv); { A generic toplevel window } window := gtk_window_new(GTK_WINDOW_TOPLEVEL); g_signal_connect(G_OBJECT(window), 'delete_event', G_CALLBACK(@gtk_main_quit), nil); gtk_container_set_border_width(GTK_CONTAINER(window), 5); { A generic scrolled window } scrolled_win := gtk_scrolled_window_new(nil, nil); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_win), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_widget_set_size_request(scrolled_win, 150, 200); gtk_container_add(GTK_CONTAINER(window), scrolled_win); gtk_widget_show(scrolled_win); { Create the root tree } tree := gtk_tree_new(); g_print('root tree is %p'#10, tree); { Connect all GtkTree:: signals } g_signal_connect(G_OBJECT(tree), 'select_child', G_CALLBACK(@cb_select_child), @tree); g_signal_connect(G_OBJECT(tree), 'unselect_child', G_CALLBACK(@cb_unselect_child), @tree); g_signal_connect(G_OBJECT(tree), 'selection_changed', G_CALLBACK(@cb_selection_changed), @tree); { Add it to the scrolled window } gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_win), tree); { Set the selection mode } gtk_tree_set_selection_mode(GTK_TREE(tree), GTK_SELECTION_MULTIPLE); { Show it } gtk_widget_show(tree); for i := 0 to 4 do begin { Create a tree item } item := gtk_tree_item_new_with_label(itemnames[i]); { Connect all GtkItem:: and GtkTreeItem:: signals } g_signal_connect(G_OBJECT(item), 'select', G_CALLBACK(@cb_itemsignal), pchar('select')); g_signal_connect(G_OBJECT(item), 'deselect', G_CALLBACK(@cb_itemsignal), pchar('deselect')); g_signal_connect(G_OBJECT(item), 'toggle', G_CALLBACK(@cb_itemsignal), pchar('toggle')); g_signal_connect(G_OBJECT(item), 'expand', G_CALLBACK(@cb_itemsignal), pchar('expand')); g_signal_connect(G_OBJECT(item), 'collapse', G_CALLBACK(@cb_itemsignal), pchar('collapse')); { Add it to the parent tree } gtk_tree_append(GTK_TREE(tree), item); { Show it - this can be done at any time} gtk_widget_show(item); { Create this item's subtree } subtree := gtk_tree_new(); g_print('-> item %s->%p, subtree %p'#10, itemnames[i], item, subtree); { 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.} g_signal_connect(G_OBJECT(subtree), 'select_child', G_CALLBACK(@cb_select_child), @subtree); g_signal_connect(G_OBJECT(subtree), 'unselect_child', G_CALLBACK(@cb_unselect_child), @subtree); { This has absolutely no effect, because it is completely ignored in subtrees } gtk_tree_set_selection_mode(GTK_TREE(subtree), 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} gtk_tree_set_view_mode(GTK_TREE(subtree), 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!} gtk_tree_item_set_subtree(GTK_TREE_ITEM(item), subtree); for j := 0 to 4 do begin { Create a subtree item, in much the same way } subitem := gtk_tree_item_new_with_label(itemnames[j]); { Connect all GtkItem:: and GtkTreeItem:: signals} g_signal_connect(G_OBJECT(subitem), 'select', G_CALLBACK(@cb_itemsignal), pchar('select')); g_signal_connect(G_OBJECT(subitem), 'deselect', G_CALLBACK(@cb_itemsignal), pchar('deselect')); g_signal_connect(G_OBJECT(subitem), 'toggle', G_CALLBACK(@cb_itemsignal), pchar('toggle')); g_signal_connect(G_OBJECT(subitem), 'expand', G_CALLBACK(@cb_itemsignal), pchar('expand')); g_signal_connect(G_OBJECT(subitem), 'collapse', G_CALLBACK(@cb_itemsignal), pchar('collapse')); g_print('-> -> item %s->%p'#10, itemnames[j], subitem); { Add it to its parent tree } gtk_tree_append(GTK_TREE(subtree), subitem); { Show it } gtk_widget_show(subitem); end; end; { Show the window and loop endlessly } gtk_widget_show(window); gtk_main(); end.