/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/* e-tree-example-2.c - Test program for directory reading in the GNOME
   Virtual File System.

   Copyright (C) 1999 Free Software Foundation

   The Gnome Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public License as
   published by the Free Software Foundation; either version 2 of the
   License, or (at your option) any later version.

   The Gnome Library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with the Gnome Library; see the file COPYING.LIB.  If not,
   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.

   Author: Ettore Perazzoli <ettore@comm2000.it> */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <gnome.h>
#include <libgnomevfs/gnome-vfs.h>
#include <libgnomevfs/gnome-vfs-mime-handlers.h>
#include "e-hpaned.h"
#include "e-util/e-cursors.h"
#include "e-table-header.h"
#include "e-table-header-item.h"
#include "e-table-item.h"
#include "e-cell-text.h"
#include "e-cell-tree.h"
#include "e-cell-checkbox.h"
#include "e-table-scrolled.h"
#include "e-tree-simple.h"
#include <gdk-pixbuf/gdk-pixbuf.h>

#include "art/tree-expanded.xpm"
#include "art/tree-unexpanded.xpm"

GdkPixbuf *tree_expanded_pixbuf;
GdkPixbuf *tree_unexpanded_pixbuf;

#define MINI_ICON_SIZE 16

#define RIGHT_COLS 4
#define LEFT_COLS 4

#define RIGHT_E_TABLE_SPEC \
"<ETableSpecification>                    	                       \
	<columns-shown>                  			       \
		<column> 0 </column>     			       \
		<column> 1 </column>     			       \
		<column> 2 </column>     			       \
		<column> 3 </column>     			       \
	</columns-shown>                 			       \
	<grouping></grouping>                                          \
</ETableSpecification>"

#define LEFT_E_TABLE_SPEC \
"<ETableSpecification no-header=\"1\">         	                       \
	<columns-shown>                  			       \
		<column> 0 </column>     			       \
	</columns-shown>                 			       \
	<grouping></grouping>                                          \
</ETableSpecification>"

char *left_headers [LEFT_COLS] = {
  "Folder"
};

char *right_headers [RIGHT_COLS] = {
  "Name",
  "Size",
  "Type",
  "Mime Type"
};



GnomeVFSDirectoryFilter *left_filter;
GnomeVFSDirectoryFilter *right_filter;
GHashTable *mime_type_to_pixbuf;
GnomeVFSDirectoryList *right_list = NULL;
ETreePath *right_root;
ETreeModel *right_model = NULL;
ETreeModel *left_model = NULL;

typedef struct {
	char *node_uri;
	GnomeVFSFileInfo *info;

	/* next two used only if the node is a directory */
	/* used if the node is expanded */
	GnomeVFSDirectoryList *list;
	/* used if the node is collapsed */
	ETreePath *placeholder;
} VFSInfo;

/*
 * ETreeSimple callbacks
 * These are the callbacks that define the behavior of our custom model.
 */

static gchar *
type_to_string (GnomeVFSFileType type)
{
	switch (type) {
	case GNOME_VFS_FILE_TYPE_UNKNOWN:
		return "Unknown";
	case GNOME_VFS_FILE_TYPE_REGULAR:
		return "Regular";
	case GNOME_VFS_FILE_TYPE_DIRECTORY:
		return "Directory";
	case GNOME_VFS_FILE_TYPE_SYMBOLIC_LINK:
		return "Symbolic Link";
	case GNOME_VFS_FILE_TYPE_FIFO:
		return "FIFO";
	case GNOME_VFS_FILE_TYPE_SOCKET:
		return "Socket";
	case GNOME_VFS_FILE_TYPE_CHARACTER_DEVICE:
		return "Character device";
	case GNOME_VFS_FILE_TYPE_BLOCK_DEVICE:
		return "Block device";
	default:
		return "???";
	}
}

/* This function returns the value at a particular point in our ETreeModel. */
static void *
tree_value_at (ETreeModel *etm, ETreePath *path, int col, void *model_data)
{
	VFSInfo *vfs_info = e_tree_model_node_get_data (etm, path);

	switch (col) {
	case 0: /* filename */ 
		if (vfs_info->info) 
			return vfs_info->info->name;
		else
			return vfs_info->node_uri;
	case 1: /* size */ {
		static char buf[15];
		if (vfs_info->info) {
			if (vfs_info->info->type == GNOME_VFS_FILE_TYPE_DIRECTORY)
				return NULL;
			else {
				g_snprintf (buf, sizeof(buf), "%ld", (glong) vfs_info->info->size);
				return buf;
			}
		}
		else
			return NULL;
	}
	case 2: /* file type */
		if (vfs_info->info)
			return type_to_string (vfs_info->info->type);
		else
			return NULL;
	case 3: /* mime type */ 
		if (vfs_info->info) {
			const char *mime_type = gnome_vfs_file_info_get_mime_type (vfs_info->info);
			if (mime_type == NULL)
				mime_type = "(Unknown)";
			return (void*)mime_type;
		}
		else {
			return NULL;
		}
	default: return NULL;
	}
}

static GdkPixbuf *
tree_icon_at (ETreeModel *etm, ETreePath *path, void *model_data)
{
	VFSInfo *vfs_info = e_tree_model_node_get_data (etm, path);
	const char *mime_type;
	GdkPixbuf *scaled_pixbuf = NULL;

	if (vfs_info->info == NULL)
		return NULL;

	mime_type = gnome_vfs_file_info_get_mime_type (vfs_info->info);
	if (mime_type == NULL)
		mime_type = "(Unknown)";

	scaled_pixbuf = g_hash_table_lookup (mime_type_to_pixbuf, mime_type);
	if (!scaled_pixbuf) {
		const char *icon_filename = gnome_vfs_mime_get_icon (mime_type);
		if (icon_filename) {
			GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file (icon_filename);

			if (pixbuf) {
				scaled_pixbuf = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (pixbuf),
								gdk_pixbuf_get_has_alpha (pixbuf),
								gdk_pixbuf_get_bits_per_sample (pixbuf),
								MINI_ICON_SIZE, MINI_ICON_SIZE);

				gdk_pixbuf_scale (pixbuf, scaled_pixbuf,
						  0, 0, MINI_ICON_SIZE, MINI_ICON_SIZE,
						  0.0, 0.0,
						  (double) MINI_ICON_SIZE / gdk_pixbuf_get_width (pixbuf),
						  (double) MINI_ICON_SIZE / gdk_pixbuf_get_height (pixbuf),
						  GDK_INTERP_HYPER);
				
				g_hash_table_insert (mime_type_to_pixbuf, (char*)mime_type, scaled_pixbuf);

				gdk_pixbuf_unref (pixbuf);
			}
		}
	}

	return scaled_pixbuf;
}

/* This function sets the value at a particular point in our ETreeModel. */
static void
tree_set_value_at (ETreeModel *etm, ETreePath *path, int col, const void *val, void *model_data)
{
}

/* This function returns whether a particular cell is editable. */
static gboolean
tree_is_editable (ETreeModel *etm, ETreePath *path, int col, void *model_data)
{
	return FALSE;
}

static void
sort_list (GnomeVFSDirectoryList *list)
{
	GnomeVFSDirectorySortRule rules[] = {
		GNOME_VFS_DIRECTORY_SORT_DIRECTORYFIRST,
		GNOME_VFS_DIRECTORY_SORT_BYNAME,
		GNOME_VFS_DIRECTORY_SORT_NONE
	};
		

	gnome_vfs_directory_list_sort (list, FALSE, rules);
}



static void
node_collapsed (ETreeModel *etm, ETreePath *path, void *data)
{
	VFSInfo *vfs_info = e_tree_model_node_get_data (etm, path);
	char *name;
	ETreePath **paths;
	int num_children, i;

	if (vfs_info->info)
		name = vfs_info->info->name;
	else
		name = vfs_info->node_uri;

	gnome_vfs_directory_list_destroy (vfs_info->list);

	/* remove the children of this node, and replace the placeholder */
	num_children = e_tree_model_node_get_children (etm, path, &paths);
	for (i = 0; i < num_children; i ++)
		e_tree_model_node_remove (etm, paths[i]);
	vfs_info->placeholder = e_tree_model_node_insert (etm, path, 0, NULL);
}

static void
node_expanded (ETreeModel *etm, ETreePath *path, gboolean *allow_expand, void *data)
{
	VFSInfo *vfs_info = e_tree_model_node_get_data (etm, path);
	GnomeVFSFileInfo *info;
	GnomeVFSResult result;
	char *name;

	if (vfs_info->info)
		name = vfs_info->info->name;
	else
		name = vfs_info->node_uri;

	/* Load with no filters and without requesting any metadata.  */
	result = gnome_vfs_directory_list_load
		(&vfs_info->list, vfs_info->node_uri,
		 (GNOME_VFS_FILE_INFO_GET_MIME_TYPE
		  | GNOME_VFS_FILE_INFO_FORCE_FAST_MIME_TYPE
		  | GNOME_VFS_FILE_INFO_FOLLOW_LINKS),
		 left_filter);

	*allow_expand = (result == GNOME_VFS_OK);

	if (!*allow_expand) {
		char *buf = g_strdup_printf ("Cannot open directory %s : %s\n",
					     vfs_info->info->name, gnome_vfs_result_to_string (result));
		gnome_error_dialog (buf);
		g_free (buf);
		return;
	}

	sort_list (vfs_info->list);

	/* remove the placeholder and insert all the children of this node */
	e_tree_model_node_remove (etm, vfs_info->placeholder);

	info = gnome_vfs_directory_list_first (vfs_info->list);

	if (info == NULL) {
		/* no files */
		return;
	}

	while (info != NULL) {
		if (info->type == GNOME_VFS_FILE_TYPE_DIRECTORY) {
			ETreePath *new_node;
			VFSInfo *new_vfs_info = g_new0(VFSInfo, 1);
			new_vfs_info->info = info;

			new_node = e_tree_model_node_insert (etm, path, -1, new_vfs_info);
			new_vfs_info->placeholder = e_tree_model_node_insert (etm, new_node, -1, NULL);
			new_vfs_info->node_uri = g_strdup_printf("%s%s/", vfs_info->node_uri, info->name);
		}

		info = gnome_vfs_directory_list_next (vfs_info->list);
	}
}

static void
on_cursor_change (ETable *table,
		  int row,
		  gpointer user_data)
{
	int num_children, i;
	GnomeVFSFileInfo *info;
	GnomeVFSResult result;
	ETreePath **paths;
	ETreePath *left_path = e_tree_model_node_at_row (left_model, row);
	VFSInfo *vfs_info = e_tree_model_node_get_data (left_model, left_path);

	if (right_list) {
		gnome_vfs_directory_list_destroy (right_list);
		right_list = NULL;
	}

	/* remove the children of this node, and replace the placeholder */
	num_children = e_tree_model_node_get_children (right_model, right_root, &paths);
	for (i = 0; i < num_children; i ++)
		e_tree_model_node_remove (right_model, paths[i]);

	/* Load with no filters and without requesting any metadata.  */
	result = gnome_vfs_directory_list_load
		(&right_list, vfs_info->node_uri,
		 (GNOME_VFS_FILE_INFO_GET_MIME_TYPE
		  | GNOME_VFS_FILE_INFO_FORCE_FAST_MIME_TYPE
		  | GNOME_VFS_FILE_INFO_FOLLOW_LINKS),
		 right_filter);

	if (result != GNOME_VFS_OK)
		return;

	info = gnome_vfs_directory_list_first (right_list);
	if (!info)
		return;

	while (info != NULL) {
		ETreePath *new_node;
		VFSInfo *new_vfs_info = g_new0(VFSInfo, 1);
		new_vfs_info->info = info;

		new_node = e_tree_model_node_insert (right_model, right_root, -1, new_vfs_info);

		new_vfs_info->node_uri = g_strdup_printf("%s%s", vfs_info->node_uri, info->name);
			
		info = gnome_vfs_directory_list_next (right_list);
	}
	
}

static void
on_double_click (ETable *etable,
		 int row,
		 void *data)
{
	GnomeVFSMimeApplication *app;
	ETreePath *path = e_tree_model_node_at_row (right_model, row);
	VFSInfo *vfs_info = e_tree_model_node_get_data (right_model, path);
	const char *mime_type = gnome_vfs_file_info_get_mime_type (vfs_info->info);
	if (mime_type == NULL)
		mime_type = "(Unknown)";

	app = gnome_vfs_mime_get_default_application (mime_type);

	if (app)
		printf ("exec %s\n", app->command);
	else
		printf ("no command for mime type %s\n", mime_type);
}

/* create the table on the right */
static GtkWidget*
create_right_tree(GtkWidget *paned)
{
	GtkWidget *e_table;
	GtkWidget *frame;
	ECell *cell_left_just;
	ECell *cell_tree;
	ETableHeader *e_table_header;
	int i;

	right_filter = gnome_vfs_directory_filter_new (GNOME_VFS_DIRECTORY_FILTER_NONE,
						      GNOME_VFS_DIRECTORY_FILTER_NODIRS,
						      NULL);

	/* here we create our model.  This uses the functions we defined
	   earlier. */
	right_model = e_tree_simple_new (tree_icon_at,
					  tree_value_at,
					  tree_set_value_at,
					  tree_is_editable,
					  NULL);

	/* create the unexpanded root node and it's placeholder child. */
	right_root = e_tree_model_node_insert (right_model, NULL,
					       0, NULL);
	e_tree_model_root_node_set_visible (right_model, FALSE);

	/*
	 * Next we create a header.  The ETableHeader is used in two
	 * different way.  The first is the full_header.  This is the
	 * list of possible columns in the view.  The second use is
	 * completely internal.  Many of the ETableHeader functions are
	 * for that purpose.  The only functions we really need are
	 * e_table_header_new and e_table_header_add_col.
	 *
	 * First we create the header.
	 */
	e_table_header = e_table_header_new ();
	
	/*
	 * Next we have to build renderers for all of the columns.
	 * Since all our columns are text columns, we can simply use
	 * the same renderer over and over again.  If we had different
	 * types of columns, we could use a different renderer for
	 * each column.
	 */
	cell_left_just = e_cell_text_new (E_TABLE_MODEL(right_model), NULL, GTK_JUSTIFY_LEFT);

	/* 
	 * This renderer is used for the tree column (the leftmost one), and
	 * has as its subcell renderer the text renderer.  this means that
	 * text is displayed to the right of the tree pipes.
	 */
	cell_tree = e_cell_tree_new (E_TABLE_MODEL(right_model),
				     tree_expanded_pixbuf, tree_unexpanded_pixbuf,
				     TRUE, cell_left_just);

	/*
	 * Next we create a column object for each view column and add
	 * them to the header.  We don't create a column object for
	 * the importance column since it will not be shown.
	 */
	for (i = 0; i < RIGHT_COLS; i++) {
		/* Create the column. */
		ETableCol *ecol = e_table_col_new (
						   i, right_headers [i],
						   80, 20,
						   i == 0 ? cell_tree
						   : cell_left_just,
						   g_str_compare, TRUE);
		/* Add it to the header. */
		e_table_header_add_column (e_table_header, ecol, i);
	}

	/* This frame is simply to get a bevel around our table. */
	frame = gtk_frame_new (NULL);

	/*
	 * Here we create the table.  We give it the three pieces of
	 * the table we've created, the header, the model, and the
	 * initial layout.  It does the rest.
	 */
	e_table = e_table_scrolled_new (e_table_header, E_TABLE_MODEL(right_model), RIGHT_E_TABLE_SPEC);

	gtk_signal_connect (GTK_OBJECT (e_table), "double_click", GTK_SIGNAL_FUNC (on_double_click), NULL);

	/* Build the gtk widget hierarchy. */
	gtk_container_add (GTK_CONTAINER (frame), e_table);
	gtk_container_add (GTK_CONTAINER (paned), frame);

	return e_table;
}

/* We create a window containing our new tree. */
static GtkWidget *
create_left_tree (GtkWidget *paned, char *root_uri)
{
	GtkWidget *scrolled;
	GtkWidget *e_table;
	GtkWidget *frame;
	ECell *cell_left_just;
	ECell *cell_tree;
	ETableHeader *e_table_header;
	ETreePath *root_node;
	VFSInfo *root_vfs_info;
	ETableCol *ecol;

	left_filter = gnome_vfs_directory_filter_new (GNOME_VFS_DIRECTORY_FILTER_NONE,
						      /* putting DIRSONLY doesn't work here, so we filter
							 files out in node_expanded. */
						      (GNOME_VFS_DIRECTORY_FILTER_NOSELFDIR
						       | GNOME_VFS_DIRECTORY_FILTER_NOPARENTDIR), 
						      NULL);


	/* here we create our model.  This uses the functions we defined
	   earlier. */
	left_model = e_tree_simple_new (tree_icon_at,
					  tree_value_at,
					  tree_set_value_at,
					  tree_is_editable,
					  NULL);

	/* catch collapsed/expanded signals */
	gtk_signal_connect (GTK_OBJECT (left_model), "node_expanded",
			    GTK_SIGNAL_FUNC (node_expanded), NULL);

	gtk_signal_connect (GTK_OBJECT (left_model), "node_collapsed",
			    GTK_SIGNAL_FUNC (node_collapsed), NULL);

	/* create the unexpanded root node and it's placeholder child. */
	root_vfs_info = g_new0 (VFSInfo, 1);
	root_vfs_info->node_uri = g_strdup (root_uri);
	root_vfs_info->info = gnome_vfs_file_info_new();

	gnome_vfs_get_file_info (root_uri, root_vfs_info->info,
				 GNOME_VFS_FILE_INFO_GET_MIME_TYPE
				 | GNOME_VFS_FILE_INFO_FORCE_FAST_MIME_TYPE
				 | GNOME_VFS_FILE_INFO_FOLLOW_LINKS);

	root_node = e_tree_model_node_insert (left_model, NULL,
					      0,
					      root_vfs_info);
	e_tree_model_node_set_expanded (left_model, root_node, FALSE);

	root_vfs_info->placeholder = e_tree_model_node_insert (left_model, root_node, 0, NULL);

	e_tree_model_root_node_set_visible (left_model, TRUE);

	/*
	 * Next we create a header.  The ETableHeader is used in two
	 * different way.  The first is the full_header.  This is the
	 * list of possible columns in the view.  The second use is
	 * completely internal.  Many of the ETableHeader functions are
	 * for that purpose.  The only functions we really need are
	 * e_table_header_new and e_table_header_add_col.
	 *
	 * First we create the header.
	 */
	e_table_header = e_table_header_new ();
	
	/*
	 * Next we have to build renderers for all of the columns.
	 * Since all our columns are text columns, we can simply use
	 * the same renderer over and over again.  If we had different
	 * types of columns, we could use a different renderer for
	 * each column.
	 */
	cell_left_just = e_cell_text_new (E_TABLE_MODEL(left_model), NULL, GTK_JUSTIFY_LEFT);

	/* 
	 * This renderer is used for the tree column (the leftmost one), and
	 * has as its subcell renderer the text renderer.  this means that
	 * text is displayed to the right of the tree pipes.
	 */
	cell_tree = e_cell_tree_new (E_TABLE_MODEL(left_model),
				     tree_expanded_pixbuf, tree_unexpanded_pixbuf,
				     TRUE, cell_left_just);

	/* Create the column. */
	ecol = e_table_col_new (0, left_headers [0],
				80, 20,
				cell_tree,
				g_str_compare, TRUE);
	
	e_table_header_add_column (e_table_header, ecol, 0);

	/* This frame is simply to get a bevel around our table. */
	frame = gtk_frame_new (NULL);

	/*
	 * Here we create the table.  We give it the three pieces of
	 * the table we've created, the header, the model, and the
	 * initial layout.  It does the rest.
	 */
	e_table = e_table_new (e_table_header, E_TABLE_MODEL(left_model), LEFT_E_TABLE_SPEC);

	gtk_object_set (GTK_OBJECT (e_table),
			"cursor_mode", E_TABLE_CURSOR_LINE,
			NULL);

	scrolled = gtk_scrolled_window_new (NULL, NULL);
	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
					GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);

	/* Build the gtk widget hierarchy. */
	gtk_container_add (GTK_CONTAINER (scrolled), e_table);
	gtk_container_add (GTK_CONTAINER (frame), scrolled);
	gtk_container_add (GTK_CONTAINER (paned), frame);

	return e_table;
}

static void
create_window(char *root_uri)
{
	GtkWidget *paned;
	GtkWidget *window, *left_tree, *right_tree;

	window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
	paned = e_hpaned_new ();

	gtk_container_add (GTK_CONTAINER (window), paned);

	left_tree = create_left_tree (paned, root_uri);
	right_tree = create_right_tree (paned);

	/* Show it all. */
	gtk_widget_show_all (window);

	gtk_signal_connect (GTK_OBJECT (left_tree), "cursor_change", GTK_SIGNAL_FUNC (on_cursor_change), NULL);
	gtk_signal_connect (GTK_OBJECT (window), "delete-event", gtk_main_quit, NULL);

	/* kick things off by selecting the root node */
	e_table_set_cursor_row (E_TABLE (left_tree), 0);
	on_cursor_change (E_TABLE(left_tree), 0, NULL);
}


int
main (int argc, char **argv)
{
	gchar *root_uri;

	if (argv[1] == NULL)
		root_uri = "file:///";
	else
		root_uri = argv[1];

	gnome_init ("TableExample", "TableExample", argc, argv);
	e_cursors_init ();
	gnome_vfs_init ();

	mime_type_to_pixbuf = g_hash_table_new (g_str_hash, g_str_equal);

	gtk_widget_push_visual (gdk_rgb_get_visual ());
	gtk_widget_push_colormap (gdk_rgb_get_cmap ());

	/*
	 * Create our pixbuf for expanding/unexpanding
	 */
	tree_expanded_pixbuf = gdk_pixbuf_new_from_xpm_data((const char**)tree_expanded_xpm);
	tree_unexpanded_pixbuf = gdk_pixbuf_new_from_xpm_data((const char**)tree_unexpanded_xpm);

	create_window (root_uri);

	gtk_main ();

	e_cursors_shutdown ();
	return 0;
}