2 * GtkDTree: A directory tree view
4 * Original version by Daniel Lacroix (LACROIX@wanadoo.fr)
6 * Adapted to the Midnight Commander by Miguel.
15 #include <sys/types.h>
19 #include "dir-open.xpm"
20 #include "dir-close.xpm"
22 #include "treestore.h"
24 #include "../vfs/vfs.h"
29 # define mc_opendir opendir
30 # define mc_closedir closedir
32 # define mc_readdir readdir
35 #define TREE_SPACING 3
37 static GtkCTreeClass
*parent_class
= NULL
;
47 static guint gtk_dtree_signals
[LAST_SIGNAL
] = { 0 };
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
);
63 val
= gtk_ctree_node_get_pixtext (
64 GTK_CTREE (dtree
), row
, 0,
65 &node_text
, NULL
, NULL
, NULL
);
70 new_path
= g_concat_dir_and_file (node_text
, path
);
74 row
= GTK_CTREE_ROW (row
)->parent
;
77 if (path
[0] && path
[1]) {
78 int l
= strlen (path
);
80 if (path
[l
- 1] == '/')
87 gtk_dtree_contains (GtkDTree
*dtree
, GtkCTreeNode
*parent
, char *text
)
95 node
= GTK_CTREE_ROW (parent
)->children
;
97 for (; node
&& GTK_CTREE_ROW (node
)->parent
== parent
;) {
100 gtk_ctree_node_get_pixtext (GTK_CTREE (dtree
), node
, 0, &s
, NULL
, NULL
, NULL
);
102 if (strcmp (s
, text
) == 0)
105 node
= GTK_CTREE_ROW (node
)->sibling
;
111 static GtkCTreeNode
*
112 gtk_dtree_insert_node (GtkDTree
*dtree
, GtkCTreeNode
*parent
, char *text
)
118 return gtk_ctree_insert_node (GTK_CTREE (dtree
), parent
, NULL
,
128 gtk_dtree_load_path (GtkDTree
*dtree
, char *path
, GtkCTreeNode
*parent
, int level
)
130 GtkCTreeNode
*phantom
= NULL
;
133 struct stat dir_stat
;
139 if (mc_stat (path
, &dir_stat
)) {
142 if (!S_ISDIR(dir_stat
.st_mode
))
145 dtree
->loading_dir
++;
148 phantom
= gtk_dtree_contains (dtree
, parent
, "PHANTOM");
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
--;
159 dir
= tree_store_opendir (path
);
161 dtree
->loading_dir
--;
165 while ((dirent
= tree_store_readdir (dir
)) != NULL
) {
166 GtkCTreeNode
*sibling
;
169 text
= x_basename (dirent
->name
);
171 /* Do not insert duplicates */
172 sibling
= gtk_dtree_contains (dtree
, parent
, text
);
175 sibling
= gtk_dtree_insert_node (dtree
, parent
, text
);
178 gtk_dtree_load_path (dtree
, dirent
->name
, sibling
, level
-1);
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;
196 scan_begin (GtkDTree
*dtree
)
198 if (++dtree
->scan_level
== 1) {
200 gtk_clist_freeze (GTK_CLIST (dtree
));
202 gtk_signal_emit (GTK_OBJECT (dtree
), gtk_dtree_signals
[SCAN_BEGIN
]);
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
]);
214 gtk_clist_thaw (GTK_CLIST (dtree
));
219 /* Scans a subdirectory in the tree */
221 scan_subtree (GtkDTree
*dtree
, GtkCTreeNode
*row
, char *path
)
223 dtree
->loading_dir
++;
226 gtk_dtree_load_path (dtree
, path
, row
, 1);
229 dtree
->loading_dir
--;
233 gtk_dtree_select_row (GtkCTree
*ctree
, GtkCTreeNode
*row
, gint column
)
238 dtree
= GTK_DTREE (ctree
);
240 if (dtree
->removing_rows
)
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
);
252 (* parent_class
->tree_select_row
) (ctree
, row
, column
);
254 if (row
== dtree
->last_node
) {
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
);
278 static GtkCTreeNode
*
279 gtk_dtree_lookup_dir (GtkDTree
*dtree
, GtkCTreeNode
*parent
, char *dirname
)
287 node
= GTK_CTREE_ROW (parent
)->children
;
292 if (GTK_CTREE_ROW (node
)->parent
== parent
) {
293 gtk_ctree_node_get_pixtext (
294 GTK_CTREE (dtree
), node
, 0, &text
,
297 if (strcmp (dirname
, text
) == 0)
300 node
= GTK_CTREE_NODE_NEXT (node
);
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))
319 s
= alloca (strlen (path
) + 1);
321 current_node
= dtree
->root_node
;
324 npath
= g_strdup ("/");
327 while ((current
= strtok (s
, "/")) != NULL
) {
332 full_path
= g_concat_dir_and_file (npath
, current
);
336 node
= gtk_dtree_lookup_dir (dtree
, current_node
, current
);
338 gtk_dtree_load_path (dtree
, full_path
, current_node
, 1);
340 node
= gtk_dtree_lookup_dir (dtree
, current_node
, current
);
344 gtk_ctree_expand (GTK_CTREE (dtree
), 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
;
374 * gtk_dtree_select_dir:
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
382 * Returns: TRUE if it succeeded, otherwise, FALSE
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
);
393 gtk_dtree_do_select_dir (dtree
, path
);
395 if (dtree
->requested_path
)
396 g_free (dtree
->requested_path
);
397 dtree
->requested_path
= g_strdup (path
);
404 gtk_dtree_size_allocate (GtkWidget
*widget
, GtkAllocation
*allocation
)
406 GtkDTree
*dtree
= GTK_DTREE (widget
);
409 GTK_WIDGET_CLASS (parent_class
)->size_allocate (widget
, allocation
);
411 if (allocation
->width
> 1 && allocation
->height
> 1)
412 dtree
->visible
= TRUE
;
414 dtree
->visible
= FALSE
;
416 if (!(dtree
->visible
&& dtree
->requested_path
))
422 if (!dtree
->requested_path
)
425 if (strcmp (dtree
->current_path
, dtree
->requested_path
) == 0) {
426 g_free (dtree
->requested_path
);
427 dtree
->requested_path
= NULL
;
431 request
= dtree
->requested_path
;
432 dtree
->requested_path
= NULL
;
433 gtk_dtree_do_select_dir (dtree
, request
);
437 /* Our handler for the tree_expand signal */
439 gtk_dtree_expand (GtkCTree
*ctree
, GtkCTreeNode
*node
)
444 dtree
= GTK_DTREE (ctree
);
448 (* parent_class
->tree_expand
) (ctree
, node
);
450 path
= gtk_dtree_get_row_path (dtree
, node
);
451 scan_subtree (dtree
, node
, path
);
457 /* Our handler for the tree_collapse signal */
459 gtk_dtree_collapse (GtkCTree
*ctree
, GtkCTreeNode
*node
)
464 /* Select the node only if it is an ancestor of the currently-selected
470 sel
= GTK_CLIST (ctree
)->selection
;
474 if (node
!= sel
->data
&& gtk_dtree_is_ancestor (GTK_DTREE (ctree
), node
, sel
->data
))
478 (* parent_class
->tree_collapse
) (ctree
, node
);
481 gtk_ctree_select (ctree
, node
);
485 * entry_removed_callback:
487 * Called when an entry is removed by the treestore
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
)
499 copy
= dirname
= g_strdup (tree
->name
);
501 current_node
= dtree
->root_node
;
502 while ((current
= strtok (copy
, "/")) != NULL
) {
503 current_node
= gtk_dtree_lookup_dir (dtree
, current_node
, current
);
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;
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.
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
)
533 dirname
= g_strdup (dirname
);
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
);
543 new_node
= gtk_dtree_lookup_dir (dtree
, current_node
, current
);
545 GtkCTreeNode
*sibling
;
547 sibling
= gtk_dtree_insert_node (dtree
, current_node
, current
);
548 gtk_dtree_load_path (dtree
, full_path
, sibling
, 1);
552 current_node
= new_node
;
559 * Callback routine invoked from the treestore to hint us
560 * about the progress of the freezing
563 tree_set_freeze (int state
, void *data
)
565 GtkDTree
*dtree
= GTK_DTREE (data
);
569 gtk_clist_freeze (GTK_CLIST (dtree
));
571 gtk_clist_thaw (GTK_CLIST (dtree
));
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
);
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
,
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
,
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
,
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
,
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
;
649 gtk_dtree_load_root_tree (GtkDTree
*dtree
)
651 char *root_dir
[1] = { "/" };
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
,
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
));
685 gtk_dtree_load_pixmap (char *pix
[], GdkPixmap
**pixmap
, GdkBitmap
**bitmap
)
687 GdkImlibImage
*image
;
693 image
= gdk_imlib_create_image_from_xpm_data (pix
);
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
);
704 gdk_dtree_load_pixmaps (GtkDTree
*dtree
)
708 gtk_dtree_load_pixmap (
710 &dtree
->pixmap_open
, &dtree
->bitmap_open
);
711 gtk_dtree_load_pixmap (
713 &dtree
->pixmap_close
, &dtree
->bitmap_close
);
716 static int dirty_tag
= -1;
719 gtk_dtree_save_tree (void)
727 * Callback routine invoked by the treestore code when the state
728 * of the treestore has been modified.
731 gtk_dtree_dirty_notify (int state
)
733 if (dirty_tag
!= -1) {
737 gtk_timeout_remove (dirty_tag
);
743 dirty_tag
= gtk_timeout_add (10 * 1000, (GtkFunction
) gtk_dtree_save_tree
, NULL
);
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
);
763 gtk_dtree_construct (GtkDTree
*dtree
)
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
);
795 widget
= gtk_type_new (GTK_TYPE_DTREE
);
796 gtk_dtree_construct (GTK_DTREE (widget
));
802 gtk_dtree_get_type (void)
804 static GtkType dtree_type
= 0;
808 GtkTypeInfo dtree_info
=
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
);
827 * gtk_dtree_is_ancestor:
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
836 * Return value: TRUE if the node is an ancestor of the child, FALSE otherwise.
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
)