/* * GtkDTree: A directory tree view * * Original version by Daniel Lacroix (LACROIX@wanadoo.fr) * * Adapted to the Midnight Commander by Miguel. * */ #include #include "global.h" #include #include #include #include #include #include "dir-open.xpm" #include "dir-close.xpm" #include "main.h" #include "treestore.h" #include "gtkdtree.h" #ifdef HACK # define mc_opendir opendir # define mc_closedir closedir # define mc_stat stat # define mc_readdir readdir #endif #define TREE_SPACING 3 static GtkCTreeClass *parent_class = NULL; enum { DIRECTORY_CHANGED, SCAN_BEGIN, SCAN_END, LAST_SIGNAL }; static guint gtk_dtree_signals [LAST_SIGNAL] = { 0 }; char * gtk_dtree_get_row_path (GtkDTree *dtree, GtkCTreeNode *row, gint column) { char *node_text, *path; g_return_val_if_fail (dtree != NULL, NULL); g_return_val_if_fail (GTK_IS_DTREE (dtree), NULL); g_return_val_if_fail (row != NULL, NULL); path = g_strdup (""); do { char *new_path; int val; val = gtk_ctree_node_get_pixtext ( GTK_CTREE (dtree), row, column, &node_text, NULL, NULL, NULL); if (!val) return path; new_path = g_concat_dir_and_file (node_text, path); g_free (path); path = new_path; row = GTK_CTREE_ROW (row)->parent; } while (row); if (path [0] && path [1]){ int l = strlen (path); if (path [l-1] == '/') path [l-1] = 0; } return path; } static GtkCTreeNode * gtk_dtree_contains (GtkDTree *dtree, GtkCTreeNode *parent, char *text) { GtkCTreeNode *node; g_assert (dtree); g_assert (parent); g_assert (text); node = GTK_CTREE_ROW (parent)->children; for (; node && GTK_CTREE_ROW (node)->parent == parent;){ char *s; gtk_ctree_node_get_pixtext (GTK_CTREE (dtree), node, 0, &s, NULL, NULL, NULL); if (strcmp (s, text) == 0) return node; node = GTK_CTREE_ROW (node)->sibling; } return NULL; } static GtkCTreeNode * gtk_dtree_insert_node (GtkDTree *dtree, GtkCTreeNode *parent, char *text) { char *texts [1]; texts [0] = text; return gtk_ctree_insert_node (GTK_CTREE (dtree), parent, NULL, texts, TREE_SPACING, dtree->pixmap_close, dtree->bitmap_close, dtree->pixmap_open, dtree->bitmap_open, FALSE, FALSE); } static gboolean gtk_dtree_load_path (GtkDTree *dtree, char *path, GtkCTreeNode *parent, int level) { tree_scan *dir; tree_entry *dirent; g_assert (path); g_assert (parent); g_assert (dtree); dtree->loading_dir++; dir = tree_store_opendir (path); if (!dir){ dtree->loading_dir--; return FALSE; } for (; (dirent = tree_store_readdir (dir)) != NULL; ){ GtkCTreeNode *sibling; char *text; text = x_basename (dirent->name); /* Do not insert duplicates */ sibling = gtk_dtree_contains (dtree, parent, text); if (sibling == NULL) sibling = gtk_dtree_insert_node (dtree, parent, text); if (level) gtk_dtree_load_path (dtree, dirent->name, sibling, level-1); if (!level) break; } tree_store_closedir (dir); dtree->loading_dir--; return TRUE; } static void scan_begin (GtkDTree *dtree) { if (++dtree->scan_level == 1) { gtk_clist_freeze (GTK_CLIST (dtree)); gtk_signal_emit (GTK_OBJECT (dtree), gtk_dtree_signals[SCAN_BEGIN]); } } static void scan_end (GtkDTree *dtree) { g_assert (dtree->scan_level > 0); if (--dtree->scan_level == 0) { gtk_signal_emit (GTK_OBJECT (dtree), gtk_dtree_signals[SCAN_END]); gtk_clist_thaw (GTK_CLIST (dtree)); } } static void gtk_dtree_select_row (GtkCTree *ctree, GtkCTreeNode *row, gint column) { GtkDTree *dtree = GTK_DTREE (ctree); char *path; if (dtree->removing_rows) return; parent_class->tree_select_row (ctree, row, column); if (row == dtree->last_node) return; dtree->loading_dir++; dtree->last_node = row; scan_begin (dtree); path = gtk_dtree_get_row_path (GTK_DTREE (ctree), row, 0); if (dtree->current_path) g_free (dtree->current_path); dtree->current_path = path; if (!dtree->internal) gtk_signal_emit (GTK_OBJECT (ctree), gtk_dtree_signals [DIRECTORY_CHANGED], path); gtk_dtree_load_path (dtree, path, row, 1); #if 0 last_node = GTK_CTREE_ROW (row)->children; for (; last_node; last_node = GTK_CTREE_ROW (last_node)->sibling){ char *np, *text; gtk_ctree_node_get_pixtext ( ctree, last_node, column, &text, NULL, NULL, NULL); np = g_concat_dir_and_file (path, text); gtk_dtree_load_path (dtree, np, last_node, 0); g_free (np); } #endif dtree->loading_dir--; scan_end (dtree); } static GtkCTreeNode * gtk_dtree_lookup_dir (GtkDTree *dtree, GtkCTreeNode *parent, char *dirname) { GtkCTreeNode *node; g_assert (dtree); g_assert (parent); g_assert (dirname); node = GTK_CTREE_ROW (parent)->children; while (node){ char *text; if (GTK_CTREE_ROW (node)->parent == parent){ gtk_ctree_node_get_pixtext ( GTK_CTREE (dtree), node, 0, &text, NULL, NULL, NULL); if (strcmp (dirname, text) == 0) return node; } node = GTK_CTREE_NODE_NEXT (node); } return NULL; } static gboolean gtk_dtree_do_select_dir (GtkDTree *dtree, char *path) { GtkCTreeNode *current_node; char *s, *current, *npath; g_return_val_if_fail (dtree != NULL, FALSE); g_return_val_if_fail (GTK_IS_DTREE (dtree), FALSE); g_return_val_if_fail (path != NULL, FALSE); if (dtree->current_path && (strcmp (path, dtree->current_path) == 0)) return TRUE; s = alloca (strlen (path)+1); strcpy (s, path); current_node = dtree->root_node; s++; npath = g_strdup ("/"); dtree->internal = 1; while ((current = strtok (s, "/")) != NULL){ char *full_path; GtkCTreeNode *node; s = NULL; full_path = g_concat_dir_and_file (npath, current); g_free (npath); npath = full_path; node = gtk_dtree_lookup_dir (dtree, current_node, current); if (!node){ gtk_dtree_load_path (dtree, full_path, current_node, 1); node = gtk_dtree_lookup_dir (dtree, current_node, current); } if (node){ gtk_ctree_expand (GTK_CTREE (dtree), node); current_node = node; } else break; } g_free (npath); if (current_node){ gtk_ctree_select (GTK_CTREE (dtree), current_node); if (gtk_ctree_node_is_visible (GTK_CTREE (dtree), current_node) != GTK_VISIBILITY_FULL){ gtk_ctree_node_moveto (GTK_CTREE (dtree), current_node, 0, 0.5, 0.0); } } if (dtree->current_path){ g_free (dtree->current_path); dtree->current_path = g_strdup (path); } if (dtree->requested_path){ g_free (dtree->requested_path); dtree->requested_path = NULL; } dtree->internal = 0; return TRUE; } /** * gtk_dtree_select_dir: * @dtree: the tree * @path: The path we want loaded into the tree * * Attemps to open all of the tree notes until * path is reached. It takes a fully qualified * pathname. * * Returns: TRUE if it succeeded, otherwise, FALSE */ gboolean gtk_dtree_select_dir (GtkDTree *dtree, char *path) { g_return_val_if_fail (dtree != NULL, FALSE); g_return_val_if_fail (GTK_IS_DTREE (dtree), FALSE); g_return_val_if_fail (path != NULL, FALSE); g_return_val_if_fail (*path == '/', FALSE); if (dtree->visible) gtk_dtree_do_select_dir (dtree, path); else { if (dtree->requested_path) g_free (dtree->requested_path); dtree->requested_path = g_strdup (path); } return TRUE; } static void gtk_dtree_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { GtkDTree *dtree = GTK_DTREE (widget); char *request; GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation); if (allocation->width != 0 && allocation->height != 0) dtree->visible = TRUE; else dtree->visible = FALSE; if (!(dtree->visible && dtree->requested_path)) return; if (!dtree->visible) return; if (!dtree->requested_path) return; if (strcmp (dtree->current_path, dtree->requested_path) == 0){ g_free (dtree->requested_path); dtree->requested_path = NULL; return; } request = dtree->requested_path; dtree->requested_path = NULL; gtk_dtree_do_select_dir (dtree, request); g_free (request); } static void gtk_dtree_expand (GtkCTree *ctree, GtkCTreeNode *node) { scan_begin (GTK_DTREE (ctree)); parent_class->tree_expand (ctree, node); gtk_ctree_select (ctree, node); scan_end (GTK_DTREE (ctree)); } static void gtk_dtree_collapse (GtkCTree *ctree, GtkCTreeNode *node) { parent_class->tree_collapse (ctree, node); gtk_ctree_select (ctree, node); } /* * entry_removed_callback: * * Called when an entry is removed by the treestore */ static void entry_removed_callback (tree_entry *tree, void *data) { GtkCTreeNode *current_node; GtkDTree *dtree = data; char *dirname, *copy, *current ; if (dtree->loading_dir) return; copy = dirname = g_strdup (tree->name); copy++; current_node = dtree->root_node; while ((current = strtok (copy, "/")) != NULL){ current_node = gtk_dtree_lookup_dir (dtree, current_node, current); if (!current_node) break; copy = NULL; } if (current == NULL && current_node){ dtree->removing_rows = 1; gtk_ctree_remove_node (GTK_CTREE (data), current_node); dtree->removing_rows = 0; } g_free (dirname); } /* * entry_added_callback: * * Callback invoked by the treestore when a tree_entry has been inserted * into the treestore. We update the gtkdtree with this new information. */ static void entry_added_callback (char *dirname, void *data) { GtkCTreeNode *current_node, *new_node; GtkDTree *dtree = data; char *copy, *current, *npath, *full_path; if (dtree->loading_dir) return; dirname = g_strdup (dirname); copy = dirname; copy++; current_node = dtree->root_node; npath = g_strdup ("/"); while ((current = strtok (copy, "/")) != NULL){ full_path = g_concat_dir_and_file (npath, current); g_free (npath); npath = full_path; new_node = gtk_dtree_lookup_dir (dtree, current_node, current); if (!new_node){ GtkCTreeNode *sibling; sibling = gtk_dtree_insert_node (dtree, current_node, current); gtk_dtree_load_path (dtree, full_path, sibling, 1); break; } copy = NULL; current_node = new_node; } g_free (npath); g_free (dirname); } /* * Callback routine invoked from the treestore to hint us * about the progress of the freezing */ static void tree_set_freeze (int state, void *data) { GtkDTree *dtree = GTK_DTREE (data); if (state) gtk_clist_freeze (GTK_CLIST (dtree)); else gtk_clist_thaw (GTK_CLIST (dtree)); } static void gtk_dtree_destroy (GtkObject *object) { GtkDTree *dtree = GTK_DTREE (object); tree_store_remove_entry_remove_hook (entry_removed_callback); tree_store_remove_entry_add_hook (entry_added_callback); tree_store_remove_freeze_hook (tree_set_freeze); gdk_pixmap_unref (dtree->pixmap_open); gdk_pixmap_unref (dtree->pixmap_close); gdk_bitmap_unref (dtree->bitmap_open); gdk_bitmap_unref (dtree->bitmap_close); (GTK_OBJECT_CLASS (parent_class))->destroy (object); } static void gtk_dtree_class_init (GtkDTreeClass *klass) { GtkObjectClass *object_class = (GtkObjectClass *) klass; GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; GtkCTreeClass *ctree_class = (GtkCTreeClass *) klass; parent_class = gtk_type_class (GTK_TYPE_CTREE); gtk_dtree_signals[DIRECTORY_CHANGED] = gtk_signal_new ("directory_changed", GTK_RUN_FIRST, object_class->type, GTK_SIGNAL_OFFSET (GtkDTreeClass, directory_changed), gtk_marshal_NONE__POINTER, GTK_TYPE_NONE, 1, GTK_TYPE_POINTER); gtk_dtree_signals[SCAN_BEGIN] = gtk_signal_new ("scan_begin", GTK_RUN_FIRST, object_class->type, GTK_SIGNAL_OFFSET (GtkDTreeClass, scan_begin), gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0); gtk_dtree_signals[SCAN_END] = gtk_signal_new ("scan_end", GTK_RUN_FIRST, object_class->type, GTK_SIGNAL_OFFSET (GtkDTreeClass, scan_end), gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0); gtk_object_class_add_signals (object_class, gtk_dtree_signals, LAST_SIGNAL); object_class->destroy = gtk_dtree_destroy; widget_class->size_allocate = gtk_dtree_size_allocate; ctree_class->tree_select_row = gtk_dtree_select_row; ctree_class->tree_expand = gtk_dtree_expand; ctree_class->tree_collapse = gtk_dtree_collapse; } static void gtk_dtree_load_root_tree (GtkDTree *dtree) { char *root_dir [1] = { "/" }; g_assert (dtree); gtk_clist_freeze (GTK_CLIST (dtree)); gtk_clist_clear (GTK_CLIST (dtree)); dtree->root_node = gtk_ctree_insert_node ( GTK_CTREE (dtree), NULL, NULL, root_dir, TREE_SPACING, dtree->pixmap_close, dtree->bitmap_close, dtree->pixmap_open, dtree->bitmap_open, FALSE, TRUE); gtk_dtree_load_path (dtree, "/", dtree->root_node, 1); dtree->last_node = dtree->root_node; if (dtree->current_path != NULL) g_free (dtree->current_path); /* Set current_path to "/" */ dtree->current_path = g_malloc (2); dtree->current_path [0] = '/'; dtree->current_path [1] = 0; /* Select root node */ gtk_ctree_select (GTK_CTREE (dtree), dtree->root_node); gtk_clist_thaw (GTK_CLIST (dtree)); } static void gtk_dtree_load_pixmap (char *pix [], GdkPixmap **pixmap, GdkBitmap **bitmap) { GdkImlibImage *image; g_assert (pix); g_assert (pixmap); g_assert (bitmap); image = gdk_imlib_create_image_from_xpm_data (pix); gdk_imlib_render (image, image->rgb_width, image->rgb_height); *pixmap = gdk_imlib_move_image (image); *bitmap = gdk_imlib_move_mask (image); } /* * FIXME: This uses the current colormap and pixmap. * This means that at widget *creation* time the proper * colormap and visual should be set, dont wait till * realize for that */ static void gdk_dtree_load_pixmaps (GtkDTree *dtree) { g_assert (dtree); gtk_dtree_load_pixmap ( DIRECTORY_OPEN_XPM, &dtree->pixmap_open, &dtree->bitmap_open); gtk_dtree_load_pixmap ( DIRECTORY_CLOSE_XPM, &dtree->pixmap_close, &dtree->bitmap_close); } static int dirty_tag = -1; static int gtk_dtree_save_tree (void) { dirty_tag = -1; mc_tree_store_save (); return FALSE; } /* * Callback routine invoked by the treestore code when the state * of the treestore has been modified. */ static void gtk_dtree_dirty_notify (int state) { if (dirty_tag != -1){ if (state) return; else { gtk_timeout_remove (dirty_tag); dirty_tag = -1; } } if (state) dirty_tag = gtk_timeout_add (10 * 1000, (GtkFunction) gtk_dtree_save_tree, NULL); } static void gtk_dtree_init (GtkDTree *dtree) { dtree->current_path = NULL; dtree->auto_expanded_nodes = NULL; tree_store_dirty_notify = gtk_dtree_dirty_notify; tree_store_add_entry_remove_hook (entry_removed_callback, dtree); tree_store_add_entry_add_hook (entry_added_callback, dtree); tree_store_add_freeze_hook (tree_set_freeze, dtree); } void gtk_dtree_construct (GtkDTree *dtree) { GtkCList *clist; GtkCTree *ctree; g_return_if_fail (dtree != NULL); g_return_if_fail (GTK_IS_DTREE (dtree)); clist = GTK_CLIST (dtree); ctree = GTK_CTREE (dtree); gtk_ctree_construct (ctree, 1, 0, NULL); gtk_clist_set_selection_mode(clist, GTK_SELECTION_BROWSE); gtk_clist_set_auto_sort (clist, TRUE); gtk_clist_set_sort_type (clist, GTK_SORT_ASCENDING); gtk_clist_set_selection_mode (clist, GTK_SELECTION_BROWSE); gtk_clist_set_column_auto_resize (clist, 0, TRUE); gtk_clist_columns_autosize (clist); gtk_ctree_set_line_style (ctree, GTK_CTREE_LINES_DOTTED); gtk_clist_set_reorderable (GTK_CLIST (ctree), FALSE); gdk_dtree_load_pixmaps (dtree); gtk_dtree_load_root_tree (dtree); } GtkWidget * gtk_dtree_new (void) { GtkWidget *widget; widget = gtk_type_new (GTK_TYPE_DTREE); gtk_dtree_construct (GTK_DTREE (widget)); return widget; } GtkType gtk_dtree_get_type (void) { static GtkType dtree_type = 0; if (!dtree_type) { GtkTypeInfo dtree_info = { "GtkDTree", sizeof (GtkDTree), sizeof (GtkDTreeClass), (GtkClassInitFunc) gtk_dtree_class_init, (GtkObjectInitFunc) gtk_dtree_init, /* reserved_1 */ NULL, /* reserved_2 */ NULL, (GtkClassInitFunc) NULL, }; dtree_type = gtk_type_unique (GTK_TYPE_CTREE, &dtree_info); } return dtree_type; }