* editmenu.c (edit_wrap_cmd): Use g_free() on the result
[midnight-commander.git] / gnome / gtkdtree.c
blob5c87d78d0b0281698fa1ad906f60012355d14259
1 /*
2 * GtkDTree: A directory tree view
4 * Original version by Daniel Lacroix (LACROIX@wanadoo.fr)
6 * Adapted to the Midnight Commander by Miguel.
8 */
9 #include <config.h>
10 #ifdef HAVE_ALLOCA_H
11 #include <alloca.h>
12 #endif
13 #include "global.h"
14 #include <gnome.h>
15 #include <sys/types.h>
16 #include <dirent.h>
17 #include <sys/stat.h>
18 #include <unistd.h>
19 #include "dir-open.xpm"
20 #include "dir-close.xpm"
21 #include "main.h"
22 #include "treestore.h"
23 #include "gtkdtree.h"
24 #include "../vfs/vfs.h"
26 #define FREEZE
28 #ifdef HACK
29 # define mc_opendir opendir
30 # define mc_closedir closedir
31 # define mc_stat stat
32 # define mc_readdir readdir
33 #endif
35 #define TREE_SPACING 3
37 static GtkCTreeClass *parent_class = NULL;
39 enum {
40 DIRECTORY_CHANGED,
41 SCAN_BEGIN,
42 SCAN_END,
43 POSSIBLY_UNGRAB,
44 LAST_SIGNAL
47 static guint gtk_dtree_signals[LAST_SIGNAL] = { 0 };
49 char *
50 gtk_dtree_get_row_path (GtkDTree *dtree, GtkCTreeNode *row)
52 char *node_text, *path;
54 g_return_val_if_fail (dtree != NULL, NULL);
55 g_return_val_if_fail (GTK_IS_DTREE (dtree), NULL);
56 g_return_val_if_fail (row != NULL, NULL);
58 path = g_strdup ("");
59 do {
60 char *new_path;
61 int val;
63 val = gtk_ctree_node_get_pixtext (
64 GTK_CTREE (dtree), row, 0,
65 &node_text, NULL, NULL, NULL);
67 if (!val)
68 return path;
70 new_path = g_concat_dir_and_file (node_text, path);
71 g_free (path);
72 path = new_path;
74 row = GTK_CTREE_ROW (row)->parent;
75 } while (row);
77 if (path[0] && path[1]) {
78 int l = strlen (path);
80 if (path[l - 1] == '/')
81 path[l - 1] = 0;
83 return path;
86 static GtkCTreeNode *
87 gtk_dtree_contains (GtkDTree *dtree, GtkCTreeNode *parent, char *text)
89 GtkCTreeNode *node;
91 g_assert (dtree);
92 g_assert (parent);
93 g_assert (text);
95 node = GTK_CTREE_ROW (parent)->children;
97 for (; node && GTK_CTREE_ROW (node)->parent == parent;) {
98 char *s;
100 gtk_ctree_node_get_pixtext (GTK_CTREE (dtree), node, 0, &s, NULL, NULL, NULL);
102 if (strcmp (s, text) == 0)
103 return node;
105 node = GTK_CTREE_ROW (node)->sibling;
108 return NULL;
111 static GtkCTreeNode *
112 gtk_dtree_insert_node (GtkDTree *dtree, GtkCTreeNode *parent, char *text)
114 char *texts[1];
116 texts[0] = text;
118 return gtk_ctree_insert_node (GTK_CTREE (dtree), parent, NULL,
119 texts, TREE_SPACING,
120 dtree->pixmap_close,
121 dtree->bitmap_close,
122 dtree->pixmap_open,
123 dtree->bitmap_open,
124 FALSE, FALSE);
127 static gboolean
128 gtk_dtree_load_path (GtkDTree *dtree, char *path, GtkCTreeNode *parent, int level)
130 GtkCTreeNode *phantom = NULL;
131 tree_scan *dir;
132 tree_entry *dirent;
133 struct stat dir_stat;
135 g_assert (path);
136 g_assert (parent);
137 g_assert (dtree);
139 if (mc_stat (path, &dir_stat)) {
140 return FALSE;
142 if (!S_ISDIR(dir_stat.st_mode))
143 return FALSE;
145 dtree->loading_dir++;
147 #if 0
148 phantom = gtk_dtree_contains (dtree, parent, "PHANTOM");
149 if (!level) {
150 dirent = tree_store_whereis (path);
151 if (!phantom && (!dirent || (dirent && !dirent->scanned)))
152 if (dir_stat.st_nlink > 2 || strncmp(path,"/afs",4)==0)
153 gtk_dtree_insert_node (dtree, parent, "PHANTOM");
154 dtree->loading_dir--;
155 return TRUE;
157 #endif
159 dir = tree_store_opendir (path);
160 if (!dir) {
161 dtree->loading_dir--;
162 return FALSE;
165 while ((dirent = tree_store_readdir (dir)) != NULL) {
166 GtkCTreeNode *sibling;
167 char *text;
169 text = x_basename (dirent->name);
171 /* Do not insert duplicates */
172 sibling = gtk_dtree_contains (dtree, parent, text);
174 if (sibling == NULL)
175 sibling = gtk_dtree_insert_node (dtree, parent, text);
177 if (level)
178 gtk_dtree_load_path (dtree, dirent->name, sibling, level-1);
180 if (!level)
181 break;
184 tree_store_closedir (dir);
185 dtree->loading_dir--;
186 if (phantom != NULL && level) {
187 dtree->removing_rows = 1;
188 gtk_ctree_remove_node (GTK_CTREE (dtree), phantom);
189 dtree->removing_rows = 0;
192 return TRUE;
195 static void
196 scan_begin (GtkDTree *dtree)
198 if (++dtree->scan_level == 1) {
199 #ifdef FREEZE
200 gtk_clist_freeze (GTK_CLIST (dtree));
201 #endif
202 gtk_signal_emit (GTK_OBJECT (dtree), gtk_dtree_signals[SCAN_BEGIN]);
206 static void
207 scan_end (GtkDTree *dtree)
209 g_assert (dtree->scan_level > 0);
211 if (--dtree->scan_level == 0) {
212 gtk_signal_emit (GTK_OBJECT (dtree), gtk_dtree_signals[SCAN_END]);
213 #ifdef FREEZE
214 gtk_clist_thaw (GTK_CLIST (dtree));
215 #endif
219 /* Scans a subdirectory in the tree */
220 static void
221 scan_subtree (GtkDTree *dtree, GtkCTreeNode *row, char *path)
223 dtree->loading_dir++;
224 scan_begin (dtree);
226 gtk_dtree_load_path (dtree, path, row, 1);
228 scan_end (dtree);
229 dtree->loading_dir--;
232 static void
233 gtk_dtree_select_row (GtkCTree *ctree, GtkCTreeNode *row, gint column)
235 GtkDTree *dtree;
236 char *path;
238 dtree = GTK_DTREE (ctree);
240 if (dtree->removing_rows)
241 return;
243 /* Ask for someone to ungrab the mouse, as the stupid clist grabs it on
244 * button press. We cannot do it unconditionally because we don't want
245 * to knock off a DnD grab. We cannot do it in a button_press handler,
246 * either, because the row is selected *inside* the default handler.
248 gtk_signal_emit (GTK_OBJECT (dtree), gtk_dtree_signals[POSSIBLY_UNGRAB], NULL);
250 scan_begin (dtree);
252 (* parent_class->tree_select_row) (ctree, row, column);
254 if (row == dtree->last_node) {
255 scan_end (dtree);
256 return;
259 dtree->last_node = row;
261 /* Set the new current path */
263 path = gtk_dtree_get_row_path (dtree, row);
265 if (dtree->current_path)
266 g_free (dtree->current_path);
268 dtree->current_path = path;
270 scan_subtree (dtree, row, path);
272 if (!dtree->internal)
273 gtk_signal_emit (GTK_OBJECT (dtree), gtk_dtree_signals[DIRECTORY_CHANGED], path);
275 scan_end (dtree);
278 static GtkCTreeNode *
279 gtk_dtree_lookup_dir (GtkDTree *dtree, GtkCTreeNode *parent, char *dirname)
281 GtkCTreeNode *node;
283 g_assert (dtree);
284 g_assert (parent);
285 g_assert (dirname);
287 node = GTK_CTREE_ROW (parent)->children;
289 while (node) {
290 char *text;
292 if (GTK_CTREE_ROW (node)->parent == parent) {
293 gtk_ctree_node_get_pixtext (
294 GTK_CTREE (dtree), node, 0, &text,
295 NULL, NULL, NULL);
297 if (strcmp (dirname, text) == 0)
298 return node;
300 node = GTK_CTREE_NODE_NEXT (node);
303 return NULL;
306 static gboolean
307 gtk_dtree_do_select_dir (GtkDTree *dtree, char *path)
309 GtkCTreeNode *current_node;
310 char *s, *current, *npath;
312 g_return_val_if_fail (dtree != NULL, FALSE);
313 g_return_val_if_fail (GTK_IS_DTREE (dtree), FALSE);
314 g_return_val_if_fail (path != NULL, FALSE);
316 if (dtree->current_path && (strcmp (path, dtree->current_path) == 0))
317 return TRUE;
319 s = alloca (strlen (path) + 1);
320 strcpy (s, path);
321 current_node = dtree->root_node;
323 s++;
324 npath = g_strdup ("/");
326 dtree->internal = 1;
327 while ((current = strtok (s, "/")) != NULL) {
328 char *full_path;
329 GtkCTreeNode *node;
331 s = NULL;
332 full_path = g_concat_dir_and_file (npath, current);
333 g_free (npath);
334 npath = full_path;
336 node = gtk_dtree_lookup_dir (dtree, current_node, current);
337 if (!node) {
338 gtk_dtree_load_path (dtree, full_path, current_node, 1);
340 node = gtk_dtree_lookup_dir (dtree, current_node, current);
343 if (node) {
344 gtk_ctree_expand (GTK_CTREE (dtree), node);
345 current_node = node;
346 } else
347 break;
349 g_free (npath);
351 if (current_node) {
352 gtk_ctree_select (GTK_CTREE (dtree), current_node);
353 if (gtk_ctree_node_is_visible (GTK_CTREE (dtree), current_node)
354 != GTK_VISIBILITY_FULL)
355 gtk_ctree_node_moveto (GTK_CTREE (dtree), current_node, 0, 0.5, 0.0);
358 if (dtree->current_path) {
359 g_free (dtree->current_path);
360 dtree->current_path = g_strdup (path);
363 if (dtree->requested_path) {
364 g_free (dtree->requested_path);
365 dtree->requested_path = NULL;
368 dtree->internal = 0;
370 return TRUE;
374 * gtk_dtree_select_dir:
375 * @dtree: the tree
376 * @path: The path we want loaded into the tree
378 * Attemps to open all of the tree notes until
379 * path is reached. It takes a fully qualified
380 * pathname.
382 * Returns: TRUE if it succeeded, otherwise, FALSE
384 gboolean
385 gtk_dtree_select_dir (GtkDTree *dtree, char *path)
387 g_return_val_if_fail (dtree != NULL, FALSE);
388 g_return_val_if_fail (GTK_IS_DTREE (dtree), FALSE);
389 g_return_val_if_fail (path != NULL, FALSE);
390 g_return_val_if_fail (*path == '/', FALSE);
392 if (dtree->visible)
393 gtk_dtree_do_select_dir (dtree, path);
394 else {
395 if (dtree->requested_path)
396 g_free (dtree->requested_path);
397 dtree->requested_path = g_strdup (path);
400 return TRUE;
403 static void
404 gtk_dtree_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
406 GtkDTree *dtree = GTK_DTREE (widget);
407 char *request;
409 GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation);
411 if (allocation->width > 1 && allocation->height > 1)
412 dtree->visible = TRUE;
413 else
414 dtree->visible = FALSE;
416 if (!(dtree->visible && dtree->requested_path))
417 return;
419 if (!dtree->visible)
420 return;
422 if (!dtree->requested_path)
423 return;
425 if (strcmp (dtree->current_path, dtree->requested_path) == 0) {
426 g_free (dtree->requested_path);
427 dtree->requested_path = NULL;
428 return;
431 request = dtree->requested_path;
432 dtree->requested_path = NULL;
433 gtk_dtree_do_select_dir (dtree, request);
434 g_free (request);
437 /* Our handler for the tree_expand signal */
438 static void
439 gtk_dtree_expand (GtkCTree *ctree, GtkCTreeNode *node)
441 GtkDTree *dtree;
442 char *path;
444 dtree = GTK_DTREE (ctree);
446 scan_begin (dtree);
448 (* parent_class->tree_expand) (ctree, node);
450 path = gtk_dtree_get_row_path (dtree, node);
451 scan_subtree (dtree, node, path);
452 g_free (path);
454 scan_end (dtree);
457 /* Our handler for the tree_collapse signal */
458 static void
459 gtk_dtree_collapse (GtkCTree *ctree, GtkCTreeNode *node)
461 GList *sel;
462 int do_select;
464 /* Select the node only if it is an ancestor of the currently-selected
465 * node.
468 do_select = FALSE;
470 sel = GTK_CLIST (ctree)->selection;
471 if (!sel)
472 do_select = TRUE;
473 else {
474 if (node != sel->data && gtk_dtree_is_ancestor (GTK_DTREE (ctree), node, sel->data))
475 do_select = TRUE;
478 (* parent_class->tree_collapse) (ctree, node);
480 if (do_select)
481 gtk_ctree_select (ctree, node);
485 * entry_removed_callback:
487 * Called when an entry is removed by the treestore
489 static void
490 entry_removed_callback (tree_entry *tree, void *data)
492 GtkCTreeNode *current_node;
493 GtkDTree *dtree = data;
494 char *dirname, *copy, *current;
496 if (dtree->loading_dir)
497 return;
499 copy = dirname = g_strdup (tree->name);
500 copy++;
501 current_node = dtree->root_node;
502 while ((current = strtok (copy, "/")) != NULL) {
503 current_node = gtk_dtree_lookup_dir (dtree, current_node, current);
504 if (!current_node)
505 break;
506 copy = NULL;
508 if (current == NULL && current_node) {
509 dtree->removing_rows = 1;
510 gtk_ctree_remove_node (GTK_CTREE (data), current_node);
511 dtree->removing_rows = 0;
514 g_free (dirname);
518 * entry_added_callback:
520 * Callback invoked by the treestore when a tree_entry has been inserted
521 * into the treestore. We update the gtkdtree with this new information.
523 static void
524 entry_added_callback (char *dirname, void *data)
526 GtkCTreeNode *current_node, *new_node;
527 GtkDTree *dtree = data;
528 char *copy, *current, *npath, *full_path;
530 if (dtree->loading_dir)
531 return;
533 dirname = g_strdup (dirname);
534 copy = dirname;
535 copy++;
536 current_node = dtree->root_node;
537 npath = g_strdup ("/");
538 while ((current = strtok (copy, "/")) != NULL) {
539 full_path = g_concat_dir_and_file (npath, current);
540 g_free (npath);
541 npath = full_path;
543 new_node = gtk_dtree_lookup_dir (dtree, current_node, current);
544 if (!new_node) {
545 GtkCTreeNode *sibling;
547 sibling = gtk_dtree_insert_node (dtree, current_node, current);
548 gtk_dtree_load_path (dtree, full_path, sibling, 1);
549 break;
551 copy = NULL;
552 current_node = new_node;
554 g_free (npath);
555 g_free (dirname);
559 * Callback routine invoked from the treestore to hint us
560 * about the progress of the freezing
562 static void
563 tree_set_freeze (int state, void *data)
565 GtkDTree *dtree = GTK_DTREE (data);
567 #ifdef FREEZE
568 if (state)
569 gtk_clist_freeze (GTK_CLIST (dtree));
570 else
571 gtk_clist_thaw (GTK_CLIST (dtree));
572 #endif
575 static void
576 gtk_dtree_destroy (GtkObject *object)
578 GtkDTree *dtree = GTK_DTREE (object);
580 tree_store_remove_entry_remove_hook (entry_removed_callback);
581 tree_store_remove_entry_add_hook (entry_added_callback);
582 tree_store_remove_freeze_hook (tree_set_freeze);
584 gdk_pixmap_unref (dtree->pixmap_open);
585 gdk_pixmap_unref (dtree->pixmap_close);
586 gdk_bitmap_unref (dtree->bitmap_open);
587 gdk_bitmap_unref (dtree->bitmap_close);
589 if (dtree->current_path)
590 g_free (dtree->current_path);
592 if (dtree->requested_path)
593 g_free (dtree->requested_path);
595 (GTK_OBJECT_CLASS (parent_class))->destroy (object);
598 static void
599 gtk_dtree_class_init (GtkDTreeClass *klass)
601 GtkObjectClass *object_class = (GtkObjectClass *) klass;
602 GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
603 GtkCTreeClass *ctree_class = (GtkCTreeClass *) klass;
605 parent_class = gtk_type_class (GTK_TYPE_CTREE);
607 gtk_dtree_signals[DIRECTORY_CHANGED] =
608 gtk_signal_new ("directory_changed",
609 GTK_RUN_FIRST, object_class->type,
610 GTK_SIGNAL_OFFSET (GtkDTreeClass, directory_changed),
611 gtk_marshal_NONE__POINTER,
612 GTK_TYPE_NONE,
614 GTK_TYPE_POINTER);
615 gtk_dtree_signals[SCAN_BEGIN] =
616 gtk_signal_new ("scan_begin",
617 GTK_RUN_FIRST, object_class->type,
618 GTK_SIGNAL_OFFSET (GtkDTreeClass, scan_begin),
619 gtk_marshal_NONE__NONE,
620 GTK_TYPE_NONE,
622 gtk_dtree_signals[SCAN_END] =
623 gtk_signal_new ("scan_end",
624 GTK_RUN_FIRST, object_class->type,
625 GTK_SIGNAL_OFFSET (GtkDTreeClass, scan_end),
626 gtk_marshal_NONE__NONE,
627 GTK_TYPE_NONE,
629 gtk_dtree_signals[POSSIBLY_UNGRAB] =
630 gtk_signal_new ("possibly_ungrab",
631 GTK_RUN_FIRST, object_class->type,
632 GTK_SIGNAL_OFFSET (GtkDTreeClass, possibly_ungrab),
633 gtk_marshal_NONE__NONE,
634 GTK_TYPE_NONE,
637 gtk_object_class_add_signals (object_class, gtk_dtree_signals, LAST_SIGNAL);
639 object_class->destroy = gtk_dtree_destroy;
641 widget_class->size_allocate = gtk_dtree_size_allocate;
643 ctree_class->tree_select_row = gtk_dtree_select_row;
644 ctree_class->tree_expand = gtk_dtree_expand;
645 ctree_class->tree_collapse = gtk_dtree_collapse;
648 static void
649 gtk_dtree_load_root_tree (GtkDTree *dtree)
651 char *root_dir[1] = { "/" };
653 g_assert (dtree);
655 gtk_clist_freeze (GTK_CLIST (dtree));
656 gtk_clist_clear (GTK_CLIST (dtree));
658 dtree->root_node = gtk_ctree_insert_node (
659 GTK_CTREE (dtree), NULL, NULL,
660 root_dir, TREE_SPACING,
661 dtree->pixmap_close,
662 dtree->bitmap_close,
663 dtree->pixmap_open,
664 dtree->bitmap_open,
665 FALSE, TRUE);
667 gtk_dtree_load_path (dtree, "/", dtree->root_node, 1);
669 dtree->last_node = dtree->root_node;
671 if (dtree->current_path != NULL)
672 g_free (dtree->current_path);
674 /* Set current_path to "/" */
675 dtree->current_path = g_malloc (2);
676 dtree->current_path[0] = '/';
677 dtree->current_path[1] = 0;
679 /* Select root node */
680 gtk_ctree_select (GTK_CTREE (dtree), dtree->root_node);
681 gtk_clist_thaw (GTK_CLIST (dtree));
684 static void
685 gtk_dtree_load_pixmap (char *pix[], GdkPixmap **pixmap, GdkBitmap **bitmap)
687 GdkImlibImage *image;
689 g_assert (pix);
690 g_assert (pixmap);
691 g_assert (bitmap);
693 image = gdk_imlib_create_image_from_xpm_data (pix);
694 *pixmap = NULL;
695 *bitmap = NULL;
696 g_return_if_fail(image);
698 gdk_imlib_render (image, image->rgb_width, image->rgb_height);
699 *pixmap = gdk_imlib_move_image (image);
700 *bitmap = gdk_imlib_move_mask (image);
703 static void
704 gdk_dtree_load_pixmaps (GtkDTree *dtree)
706 g_assert (dtree);
708 gtk_dtree_load_pixmap (
709 DIRECTORY_OPEN_XPM,
710 &dtree->pixmap_open, &dtree->bitmap_open);
711 gtk_dtree_load_pixmap (
712 DIRECTORY_CLOSE_XPM,
713 &dtree->pixmap_close, &dtree->bitmap_close);
716 static int dirty_tag = -1;
718 static int
719 gtk_dtree_save_tree (void)
721 dirty_tag = -1;
722 tree_store_save ();
723 return FALSE;
727 * Callback routine invoked by the treestore code when the state
728 * of the treestore has been modified.
730 static void
731 gtk_dtree_dirty_notify (int state)
733 if (dirty_tag != -1) {
734 if (state)
735 return;
736 else {
737 gtk_timeout_remove (dirty_tag);
738 dirty_tag = -1;
742 if (state)
743 dirty_tag = gtk_timeout_add (10 * 1000, (GtkFunction) gtk_dtree_save_tree, NULL);
746 static void
747 gtk_dtree_init (GtkDTree *dtree)
749 /* HACK: This is to avoid GtkCTree's broken focusing behavior */
750 GTK_WIDGET_UNSET_FLAGS (dtree, GTK_CAN_FOCUS);
752 dtree->current_path = NULL;
753 dtree->auto_expanded_nodes = NULL;
755 tree_store_dirty_notify = gtk_dtree_dirty_notify;
757 tree_store_add_entry_remove_hook (entry_removed_callback, dtree);
758 tree_store_add_entry_add_hook (entry_added_callback, dtree);
759 tree_store_add_freeze_hook (tree_set_freeze, dtree);
762 void
763 gtk_dtree_construct (GtkDTree *dtree)
765 GtkCList *clist;
766 GtkCTree *ctree;
768 g_return_if_fail (dtree != NULL);
769 g_return_if_fail (GTK_IS_DTREE (dtree));
771 clist = GTK_CLIST (dtree);
772 ctree = GTK_CTREE (dtree);
774 gtk_ctree_construct (ctree, 1, 0, NULL);
776 gtk_clist_set_selection_mode(clist, GTK_SELECTION_BROWSE);
777 gtk_clist_set_auto_sort (clist, TRUE);
778 gtk_clist_set_sort_type (clist, GTK_SORT_ASCENDING);
779 gtk_clist_set_column_auto_resize (clist, 0, TRUE);
780 gtk_clist_columns_autosize (clist);
782 /*gtk_ctree_set_line_style (ctree, GTK_CTREE_LINES_DOTTED);*/
783 gtk_clist_set_reorderable (GTK_CLIST (ctree), FALSE);
785 gdk_dtree_load_pixmaps (dtree);
786 gtk_dtree_load_root_tree (dtree);
790 GtkWidget *
791 gtk_dtree_new (void)
793 GtkWidget *widget;
795 widget = gtk_type_new (GTK_TYPE_DTREE);
796 gtk_dtree_construct (GTK_DTREE (widget));
798 return widget;
801 GtkType
802 gtk_dtree_get_type (void)
804 static GtkType dtree_type = 0;
806 if (!dtree_type)
808 GtkTypeInfo dtree_info =
810 "GtkDTree",
811 sizeof (GtkDTree),
812 sizeof (GtkDTreeClass),
813 (GtkClassInitFunc) gtk_dtree_class_init,
814 (GtkObjectInitFunc) gtk_dtree_init,
815 /* reserved_1 */ NULL,
816 /* reserved_2 */ NULL,
817 (GtkClassInitFunc) NULL,
820 dtree_type = gtk_type_unique (GTK_TYPE_CTREE, &dtree_info);
823 return dtree_type;
827 * gtk_dtree_is_ancestor:
828 * @dtree: A tree
829 * @node: The presumed ancestor node
830 * @child: The presumed child node
832 * Tests whether a node is an ancestor of a child node. This does this in
833 * O(height of child), instead of O(number of children in node), like GtkCTree
834 * does.
836 * Return value: TRUE if the node is an ancestor of the child, FALSE otherwise.
838 gboolean
839 gtk_dtree_is_ancestor (GtkDTree *dtree, GtkCTreeNode *node, GtkCTreeNode *child)
841 g_return_val_if_fail (dtree != NULL, FALSE);
842 g_return_val_if_fail (GTK_IS_DTREE (dtree), FALSE);
843 g_return_val_if_fail (node != NULL, FALSE);
844 g_return_val_if_fail (child != NULL, FALSE);
846 for (; child; child = GTK_CTREE_ROW (child)->parent)
847 if (child == node)
848 return TRUE;
850 return FALSE;