mc/gnome/gtkdtree.c

671 lines
14 KiB
C

/*
* GtkDTree: A directory tree view
*
* Original version by Daniel Lacroix (LACROIX@wanadoo.fr)
*
* Adapted to the Midnight Commander by Miguel.
*
*/
#include <config.h>
#include "global.h"
#include <gnome.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
#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,
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
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;
gtk_clist_freeze (GTK_CLIST (ctree));
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--;
gtk_clist_thaw (GTK_CLIST (ctree));
}
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)
{
parent_class->tree_expand (ctree, node);
gtk_dtree_select_row (ctree, node, 0);
}
/*
* 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);
}
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);
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_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;
}
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);
}
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;
}