De-fuzzyed some msgs...
[midnight-commander.git] / gnome / gtkdtree.c
bloba7fe9144e6c357bf021ed0e63de5f316f51e3e80
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 #include "global.h"
11 #include <gnome.h>
12 #include <sys/types.h>
13 #include <dirent.h>
14 #include <sys/stat.h>
15 #include <unistd.h>
16 #include "dir-open.xpm"
17 #include "dir-close.xpm"
18 #include "main.h"
19 #include "treestore.h"
20 #include "gtkdtree.h"
21 #include "../vfs/vfs.h"
23 #define FREEZE
25 #ifdef HACK
26 # define mc_opendir opendir
27 # define mc_closedir closedir
28 # define mc_stat stat
29 # define mc_readdir readdir
30 #endif
32 #define TREE_SPACING 3
34 static GtkCTreeClass *parent_class = NULL;
36 enum {
37 DIRECTORY_CHANGED,
38 SCAN_BEGIN,
39 SCAN_END,
40 POSSIBLY_UNGRAB,
41 LAST_SIGNAL
44 static guint gtk_dtree_signals[LAST_SIGNAL] = { 0 };
46 char *
47 gtk_dtree_get_row_path (GtkDTree *dtree, GtkCTreeNode *row)
49 char *node_text, *path;
51 g_return_val_if_fail (dtree != NULL, NULL);
52 g_return_val_if_fail (GTK_IS_DTREE (dtree), NULL);
53 g_return_val_if_fail (row != NULL, NULL);
55 path = g_strdup ("");
56 do {
57 char *new_path;
58 int val;
60 val = gtk_ctree_node_get_pixtext (
61 GTK_CTREE (dtree), row, 0,
62 &node_text, NULL, NULL, NULL);
64 if (!val)
65 return path;
67 new_path = g_concat_dir_and_file (node_text, path);
68 g_free (path);
69 path = new_path;
71 row = GTK_CTREE_ROW (row)->parent;
72 } while (row);
74 if (path[0] && path[1]) {
75 int l = strlen (path);
77 if (path[l - 1] == '/')
78 path[l - 1] = 0;
80 return path;
83 static GtkCTreeNode *
84 gtk_dtree_contains (GtkDTree *dtree, GtkCTreeNode *parent, char *text)
86 GtkCTreeNode *node;
88 g_assert (dtree);
89 g_assert (parent);
90 g_assert (text);
92 node = GTK_CTREE_ROW (parent)->children;
94 for (; node && GTK_CTREE_ROW (node)->parent == parent;) {
95 char *s;
97 gtk_ctree_node_get_pixtext (GTK_CTREE (dtree), node, 0, &s, NULL, NULL, NULL);
99 if (strcmp (s, text) == 0)
100 return node;
102 node = GTK_CTREE_ROW (node)->sibling;
105 return NULL;
108 static GtkCTreeNode *
109 gtk_dtree_insert_node (GtkDTree *dtree, GtkCTreeNode *parent, char *text)
111 char *texts[1];
113 texts[0] = text;
115 return gtk_ctree_insert_node (GTK_CTREE (dtree), parent, NULL,
116 texts, TREE_SPACING,
117 dtree->pixmap_close,
118 dtree->bitmap_close,
119 dtree->pixmap_open,
120 dtree->bitmap_open,
121 FALSE, FALSE);
124 static gboolean
125 gtk_dtree_load_path (GtkDTree *dtree, char *path, GtkCTreeNode *parent, int level)
127 GtkCTreeNode *phantom = NULL;
128 tree_scan *dir;
129 tree_entry *dirent;
130 struct stat dir_stat;
132 g_assert (path);
133 g_assert (parent);
134 g_assert (dtree);
136 if (mc_stat (path, &dir_stat)) {
137 return FALSE;
139 if (!S_ISDIR(dir_stat.st_mode))
140 return FALSE;
142 dtree->loading_dir++;
144 #if 0
145 phantom = gtk_dtree_contains (dtree, parent, "PHANTOM");
146 if (!level) {
147 dirent = tree_store_whereis (path);
148 if (!phantom && (!dirent || (dirent && !dirent->scanned)))
149 if (dir_stat.st_nlink > 2 || strncmp(path,"/afs",4)==0)
150 gtk_dtree_insert_node (dtree, parent, "PHANTOM");
151 dtree->loading_dir--;
152 return TRUE;
154 #endif
156 dir = tree_store_opendir (path);
157 if (!dir) {
158 dtree->loading_dir--;
159 return FALSE;
162 while ((dirent = tree_store_readdir (dir)) != NULL) {
163 GtkCTreeNode *sibling;
164 char *text;
166 text = x_basename (dirent->name);
168 /* Do not insert duplicates */
169 sibling = gtk_dtree_contains (dtree, parent, text);
171 if (sibling == NULL)
172 sibling = gtk_dtree_insert_node (dtree, parent, text);
174 if (level)
175 gtk_dtree_load_path (dtree, dirent->name, sibling, level-1);
177 if (!level)
178 break;
181 tree_store_closedir (dir);
182 dtree->loading_dir--;
183 if (phantom != NULL && level) {
184 dtree->removing_rows = 1;
185 gtk_ctree_remove_node (GTK_CTREE (dtree), phantom);
186 dtree->removing_rows = 0;
189 return TRUE;
192 static void
193 scan_begin (GtkDTree *dtree)
195 if (++dtree->scan_level == 1) {
196 #ifdef FREEZE
197 gtk_clist_freeze (GTK_CLIST (dtree));
198 #endif
199 gtk_signal_emit (GTK_OBJECT (dtree), gtk_dtree_signals[SCAN_BEGIN]);
203 static void
204 scan_end (GtkDTree *dtree)
206 g_assert (dtree->scan_level > 0);
208 if (--dtree->scan_level == 0) {
209 gtk_signal_emit (GTK_OBJECT (dtree), gtk_dtree_signals[SCAN_END]);
210 #ifdef FREEZE
211 gtk_clist_thaw (GTK_CLIST (dtree));
212 #endif
216 /* Scans a subdirectory in the tree */
217 static void
218 scan_subtree (GtkDTree *dtree, GtkCTreeNode *row, char *path)
220 dtree->loading_dir++;
221 scan_begin (dtree);
223 gtk_dtree_load_path (dtree, path, row, 1);
225 scan_end (dtree);
226 dtree->loading_dir--;
229 static void
230 gtk_dtree_select_row (GtkCTree *ctree, GtkCTreeNode *row, gint column)
232 GtkDTree *dtree;
233 char *path;
235 dtree = GTK_DTREE (ctree);
237 if (dtree->removing_rows)
238 return;
240 /* Ask for someone to ungrab the mouse, as the stupid clist grabs it on
241 * button press. We cannot do it unconditionally because we don't want
242 * to knock off a DnD grab. We cannot do it in a button_press handler,
243 * either, because the row is selected *inside* the default handler.
245 gtk_signal_emit (GTK_OBJECT (dtree), gtk_dtree_signals[POSSIBLY_UNGRAB], NULL);
247 scan_begin (dtree);
249 (* parent_class->tree_select_row) (ctree, row, column);
251 if (row == dtree->last_node) {
252 scan_end (dtree);
253 return;
256 dtree->last_node = row;
258 /* Set the new current path */
260 path = gtk_dtree_get_row_path (dtree, row);
262 if (dtree->current_path)
263 g_free (dtree->current_path);
265 dtree->current_path = path;
267 scan_subtree (dtree, row, path);
269 if (!dtree->internal)
270 gtk_signal_emit (GTK_OBJECT (dtree), gtk_dtree_signals[DIRECTORY_CHANGED], path);
272 scan_end (dtree);
275 static GtkCTreeNode *
276 gtk_dtree_lookup_dir (GtkDTree *dtree, GtkCTreeNode *parent, char *dirname)
278 GtkCTreeNode *node;
280 g_assert (dtree);
281 g_assert (parent);
282 g_assert (dirname);
284 node = GTK_CTREE_ROW (parent)->children;
286 while (node) {
287 char *text;
289 if (GTK_CTREE_ROW (node)->parent == parent) {
290 gtk_ctree_node_get_pixtext (
291 GTK_CTREE (dtree), node, 0, &text,
292 NULL, NULL, NULL);
294 if (strcmp (dirname, text) == 0)
295 return node;
297 node = GTK_CTREE_NODE_NEXT (node);
300 return NULL;
303 static gboolean
304 gtk_dtree_do_select_dir (GtkDTree *dtree, char *path)
306 GtkCTreeNode *current_node;
307 char *s, *current, *npath;
309 g_return_val_if_fail (dtree != NULL, FALSE);
310 g_return_val_if_fail (GTK_IS_DTREE (dtree), FALSE);
311 g_return_val_if_fail (path != NULL, FALSE);
313 if (dtree->current_path && (strcmp (path, dtree->current_path) == 0))
314 return TRUE;
316 s = alloca (strlen (path) + 1);
317 strcpy (s, path);
318 current_node = dtree->root_node;
320 s++;
321 npath = g_strdup ("/");
323 dtree->internal = 1;
324 while ((current = strtok (s, "/")) != NULL) {
325 char *full_path;
326 GtkCTreeNode *node;
328 s = NULL;
329 full_path = g_concat_dir_and_file (npath, current);
330 g_free (npath);
331 npath = full_path;
333 node = gtk_dtree_lookup_dir (dtree, current_node, current);
334 if (!node) {
335 gtk_dtree_load_path (dtree, full_path, current_node, 1);
337 node = gtk_dtree_lookup_dir (dtree, current_node, current);
340 if (node) {
341 gtk_ctree_expand (GTK_CTREE (dtree), node);
342 current_node = node;
343 } else
344 break;
346 g_free (npath);
348 if (current_node) {
349 gtk_ctree_select (GTK_CTREE (dtree), current_node);
350 if (gtk_ctree_node_is_visible (GTK_CTREE (dtree), current_node)
351 != GTK_VISIBILITY_FULL)
352 gtk_ctree_node_moveto (GTK_CTREE (dtree), current_node, 0, 0.5, 0.0);
355 if (dtree->current_path) {
356 g_free (dtree->current_path);
357 dtree->current_path = g_strdup (path);
360 if (dtree->requested_path) {
361 g_free (dtree->requested_path);
362 dtree->requested_path = NULL;
365 dtree->internal = 0;
367 return TRUE;
371 * gtk_dtree_select_dir:
372 * @dtree: the tree
373 * @path: The path we want loaded into the tree
375 * Attemps to open all of the tree notes until
376 * path is reached. It takes a fully qualified
377 * pathname.
379 * Returns: TRUE if it succeeded, otherwise, FALSE
381 gboolean
382 gtk_dtree_select_dir (GtkDTree *dtree, char *path)
384 g_return_val_if_fail (dtree != NULL, FALSE);
385 g_return_val_if_fail (GTK_IS_DTREE (dtree), FALSE);
386 g_return_val_if_fail (path != NULL, FALSE);
387 g_return_val_if_fail (*path == '/', FALSE);
389 if (dtree->visible)
390 gtk_dtree_do_select_dir (dtree, path);
391 else {
392 if (dtree->requested_path)
393 g_free (dtree->requested_path);
394 dtree->requested_path = g_strdup (path);
397 return TRUE;
400 static void
401 gtk_dtree_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
403 GtkDTree *dtree = GTK_DTREE (widget);
404 char *request;
406 GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation);
408 if (allocation->width > 1 && allocation->height > 1)
409 dtree->visible = TRUE;
410 else
411 dtree->visible = FALSE;
413 if (!(dtree->visible && dtree->requested_path))
414 return;
416 if (!dtree->visible)
417 return;
419 if (!dtree->requested_path)
420 return;
422 if (strcmp (dtree->current_path, dtree->requested_path) == 0) {
423 g_free (dtree->requested_path);
424 dtree->requested_path = NULL;
425 return;
428 request = dtree->requested_path;
429 dtree->requested_path = NULL;
430 gtk_dtree_do_select_dir (dtree, request);
431 g_free (request);
434 /* Our handler for the tree_expand signal */
435 static void
436 gtk_dtree_expand (GtkCTree *ctree, GtkCTreeNode *node)
438 GtkDTree *dtree;
439 char *path;
441 dtree = GTK_DTREE (ctree);
443 scan_begin (dtree);
445 (* parent_class->tree_expand) (ctree, node);
447 path = gtk_dtree_get_row_path (dtree, node);
448 scan_subtree (dtree, node, path);
449 g_free (path);
451 scan_end (dtree);
454 /* Our handler for the tree_collapse signal */
455 static void
456 gtk_dtree_collapse (GtkCTree *ctree, GtkCTreeNode *node)
458 GList *sel;
459 int do_select;
461 /* Select the node only if it is an ancestor of the currently-selected
462 * node.
465 do_select = FALSE;
467 sel = GTK_CLIST (ctree)->selection;
468 if (!sel)
469 do_select = TRUE;
470 else {
471 if (node != sel->data && gtk_dtree_is_ancestor (GTK_DTREE (ctree), node, sel->data))
472 do_select = TRUE;
475 (* parent_class->tree_collapse) (ctree, node);
477 if (do_select)
478 gtk_ctree_select (ctree, node);
482 * entry_removed_callback:
484 * Called when an entry is removed by the treestore
486 static void
487 entry_removed_callback (tree_entry *tree, void *data)
489 GtkCTreeNode *current_node;
490 GtkDTree *dtree = data;
491 char *dirname, *copy, *current;
493 if (dtree->loading_dir)
494 return;
496 copy = dirname = g_strdup (tree->name);
497 copy++;
498 current_node = dtree->root_node;
499 while ((current = strtok (copy, "/")) != NULL) {
500 current_node = gtk_dtree_lookup_dir (dtree, current_node, current);
501 if (!current_node)
502 break;
503 copy = NULL;
505 if (current == NULL && current_node) {
506 dtree->removing_rows = 1;
507 gtk_ctree_remove_node (GTK_CTREE (data), current_node);
508 dtree->removing_rows = 0;
511 g_free (dirname);
515 * entry_added_callback:
517 * Callback invoked by the treestore when a tree_entry has been inserted
518 * into the treestore. We update the gtkdtree with this new information.
520 static void
521 entry_added_callback (char *dirname, void *data)
523 GtkCTreeNode *current_node, *new_node;
524 GtkDTree *dtree = data;
525 char *copy, *current, *npath, *full_path;
527 if (dtree->loading_dir)
528 return;
530 dirname = g_strdup (dirname);
531 copy = dirname;
532 copy++;
533 current_node = dtree->root_node;
534 npath = g_strdup ("/");
535 while ((current = strtok (copy, "/")) != NULL) {
536 full_path = g_concat_dir_and_file (npath, current);
537 g_free (npath);
538 npath = full_path;
540 new_node = gtk_dtree_lookup_dir (dtree, current_node, current);
541 if (!new_node) {
542 GtkCTreeNode *sibling;
544 sibling = gtk_dtree_insert_node (dtree, current_node, current);
545 gtk_dtree_load_path (dtree, full_path, sibling, 1);
546 break;
548 copy = NULL;
549 current_node = new_node;
551 g_free (npath);
552 g_free (dirname);
556 * Callback routine invoked from the treestore to hint us
557 * about the progress of the freezing
559 static void
560 tree_set_freeze (int state, void *data)
562 GtkDTree *dtree = GTK_DTREE (data);
564 #ifdef FREEZE
565 if (state)
566 gtk_clist_freeze (GTK_CLIST (dtree));
567 else
568 gtk_clist_thaw (GTK_CLIST (dtree));
569 #endif
572 static void
573 gtk_dtree_destroy (GtkObject *object)
575 GtkDTree *dtree = GTK_DTREE (object);
577 tree_store_remove_entry_remove_hook (entry_removed_callback);
578 tree_store_remove_entry_add_hook (entry_added_callback);
579 tree_store_remove_freeze_hook (tree_set_freeze);
581 gdk_pixmap_unref (dtree->pixmap_open);
582 gdk_pixmap_unref (dtree->pixmap_close);
583 gdk_bitmap_unref (dtree->bitmap_open);
584 gdk_bitmap_unref (dtree->bitmap_close);
586 if (dtree->current_path)
587 g_free (dtree->current_path);
589 if (dtree->requested_path)
590 g_free (dtree->requested_path);
592 (GTK_OBJECT_CLASS (parent_class))->destroy (object);
595 static void
596 gtk_dtree_class_init (GtkDTreeClass *klass)
598 GtkObjectClass *object_class = (GtkObjectClass *) klass;
599 GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
600 GtkCTreeClass *ctree_class = (GtkCTreeClass *) klass;
602 parent_class = gtk_type_class (GTK_TYPE_CTREE);
604 gtk_dtree_signals[DIRECTORY_CHANGED] =
605 gtk_signal_new ("directory_changed",
606 GTK_RUN_FIRST, object_class->type,
607 GTK_SIGNAL_OFFSET (GtkDTreeClass, directory_changed),
608 gtk_marshal_NONE__POINTER,
609 GTK_TYPE_NONE,
611 GTK_TYPE_POINTER);
612 gtk_dtree_signals[SCAN_BEGIN] =
613 gtk_signal_new ("scan_begin",
614 GTK_RUN_FIRST, object_class->type,
615 GTK_SIGNAL_OFFSET (GtkDTreeClass, scan_begin),
616 gtk_marshal_NONE__NONE,
617 GTK_TYPE_NONE,
619 gtk_dtree_signals[SCAN_END] =
620 gtk_signal_new ("scan_end",
621 GTK_RUN_FIRST, object_class->type,
622 GTK_SIGNAL_OFFSET (GtkDTreeClass, scan_end),
623 gtk_marshal_NONE__NONE,
624 GTK_TYPE_NONE,
626 gtk_dtree_signals[POSSIBLY_UNGRAB] =
627 gtk_signal_new ("possibly_ungrab",
628 GTK_RUN_FIRST, object_class->type,
629 GTK_SIGNAL_OFFSET (GtkDTreeClass, possibly_ungrab),
630 gtk_marshal_NONE__NONE,
631 GTK_TYPE_NONE,
634 gtk_object_class_add_signals (object_class, gtk_dtree_signals, LAST_SIGNAL);
636 object_class->destroy = gtk_dtree_destroy;
638 widget_class->size_allocate = gtk_dtree_size_allocate;
640 ctree_class->tree_select_row = gtk_dtree_select_row;
641 ctree_class->tree_expand = gtk_dtree_expand;
642 ctree_class->tree_collapse = gtk_dtree_collapse;
645 static void
646 gtk_dtree_load_root_tree (GtkDTree *dtree)
648 char *root_dir[1] = { "/" };
650 g_assert (dtree);
652 gtk_clist_freeze (GTK_CLIST (dtree));
653 gtk_clist_clear (GTK_CLIST (dtree));
655 dtree->root_node = gtk_ctree_insert_node (
656 GTK_CTREE (dtree), NULL, NULL,
657 root_dir, TREE_SPACING,
658 dtree->pixmap_close,
659 dtree->bitmap_close,
660 dtree->pixmap_open,
661 dtree->bitmap_open,
662 FALSE, TRUE);
664 gtk_dtree_load_path (dtree, "/", dtree->root_node, 1);
666 dtree->last_node = dtree->root_node;
668 if (dtree->current_path != NULL)
669 g_free (dtree->current_path);
671 /* Set current_path to "/" */
672 dtree->current_path = g_malloc (2);
673 dtree->current_path[0] = '/';
674 dtree->current_path[1] = 0;
676 /* Select root node */
677 gtk_ctree_select (GTK_CTREE (dtree), dtree->root_node);
678 gtk_clist_thaw (GTK_CLIST (dtree));
681 static void
682 gtk_dtree_load_pixmap (char *pix[], GdkPixmap **pixmap, GdkBitmap **bitmap)
684 GdkImlibImage *image;
686 g_assert (pix);
687 g_assert (pixmap);
688 g_assert (bitmap);
690 image = gdk_imlib_create_image_from_xpm_data (pix);
691 *pixmap = NULL;
692 *bitmap = NULL;
693 g_return_if_fail(image);
695 gdk_imlib_render (image, image->rgb_width, image->rgb_height);
696 *pixmap = gdk_imlib_move_image (image);
697 *bitmap = gdk_imlib_move_mask (image);
700 static void
701 gdk_dtree_load_pixmaps (GtkDTree *dtree)
703 g_assert (dtree);
705 gtk_dtree_load_pixmap (
706 DIRECTORY_OPEN_XPM,
707 &dtree->pixmap_open, &dtree->bitmap_open);
708 gtk_dtree_load_pixmap (
709 DIRECTORY_CLOSE_XPM,
710 &dtree->pixmap_close, &dtree->bitmap_close);
713 static int dirty_tag = -1;
715 static int
716 gtk_dtree_save_tree (void)
718 dirty_tag = -1;
719 tree_store_save ();
720 return FALSE;
724 * Callback routine invoked by the treestore code when the state
725 * of the treestore has been modified.
727 static void
728 gtk_dtree_dirty_notify (int state)
730 if (dirty_tag != -1) {
731 if (state)
732 return;
733 else {
734 gtk_timeout_remove (dirty_tag);
735 dirty_tag = -1;
739 if (state)
740 dirty_tag = gtk_timeout_add (10 * 1000, (GtkFunction) gtk_dtree_save_tree, NULL);
743 static void
744 gtk_dtree_init (GtkDTree *dtree)
746 /* HACK: This is to avoid GtkCTree's broken focusing behavior */
747 GTK_WIDGET_UNSET_FLAGS (dtree, GTK_CAN_FOCUS);
749 dtree->current_path = NULL;
750 dtree->auto_expanded_nodes = NULL;
752 tree_store_dirty_notify = gtk_dtree_dirty_notify;
754 tree_store_add_entry_remove_hook (entry_removed_callback, dtree);
755 tree_store_add_entry_add_hook (entry_added_callback, dtree);
756 tree_store_add_freeze_hook (tree_set_freeze, dtree);
759 void
760 gtk_dtree_construct (GtkDTree *dtree)
762 GtkCList *clist;
763 GtkCTree *ctree;
765 g_return_if_fail (dtree != NULL);
766 g_return_if_fail (GTK_IS_DTREE (dtree));
768 clist = GTK_CLIST (dtree);
769 ctree = GTK_CTREE (dtree);
771 gtk_ctree_construct (ctree, 1, 0, NULL);
773 gtk_clist_set_selection_mode(clist, GTK_SELECTION_BROWSE);
774 gtk_clist_set_auto_sort (clist, TRUE);
775 gtk_clist_set_sort_type (clist, GTK_SORT_ASCENDING);
776 gtk_clist_set_column_auto_resize (clist, 0, TRUE);
777 gtk_clist_columns_autosize (clist);
779 /*gtk_ctree_set_line_style (ctree, GTK_CTREE_LINES_DOTTED);*/
780 gtk_clist_set_reorderable (GTK_CLIST (ctree), FALSE);
782 gdk_dtree_load_pixmaps (dtree);
783 gtk_dtree_load_root_tree (dtree);
787 GtkWidget *
788 gtk_dtree_new (void)
790 GtkWidget *widget;
792 widget = gtk_type_new (GTK_TYPE_DTREE);
793 gtk_dtree_construct (GTK_DTREE (widget));
795 return widget;
798 GtkType
799 gtk_dtree_get_type (void)
801 static GtkType dtree_type = 0;
803 if (!dtree_type)
805 GtkTypeInfo dtree_info =
807 "GtkDTree",
808 sizeof (GtkDTree),
809 sizeof (GtkDTreeClass),
810 (GtkClassInitFunc) gtk_dtree_class_init,
811 (GtkObjectInitFunc) gtk_dtree_init,
812 /* reserved_1 */ NULL,
813 /* reserved_2 */ NULL,
814 (GtkClassInitFunc) NULL,
817 dtree_type = gtk_type_unique (GTK_TYPE_CTREE, &dtree_info);
820 return dtree_type;
824 * gtk_dtree_is_ancestor:
825 * @dtree: A tree
826 * @node: The presumed ancestor node
827 * @child: The presumed child node
829 * Tests whether a node is an ancestor of a child node. This does this in
830 * O(height of child), instead of O(number of children in node), like GtkCTree
831 * does.
833 * Return value: TRUE if the node is an ancestor of the child, FALSE otherwise.
835 gboolean
836 gtk_dtree_is_ancestor (GtkDTree *dtree, GtkCTreeNode *node, GtkCTreeNode *child)
838 g_return_val_if_fail (dtree != NULL, FALSE);
839 g_return_val_if_fail (GTK_IS_DTREE (dtree), FALSE);
840 g_return_val_if_fail (node != NULL, FALSE);
841 g_return_val_if_fail (child != NULL, FALSE);
843 for (; child; child = GTK_CTREE_ROW (child)->parent)
844 if (child == node)
845 return TRUE;
847 return FALSE;