/* GNU Midnight Commander -- GNOME edition
 *
 * Directory display routines
 *
 * Copyright (C) 1997 The Free Software Foundation
 *
 * Authors: Miguel de Icaza
 *          Federico Mena
 */

#include <config.h>
#include <string.h>
#include <stdlib.h>		/* atoi */
#include "fs.h"
#include "x.h"
#include "dir.h"
#include "panel.h"
#include "command.h"
#include "panel.h"		/* current_panel */
#include "command.h"		/* cmdline */
#include "main.h"
#include "color.h"
#include "mouse.h"
#include "layout.h"		/* get_panel_widget */
#include "ext.h"		/* regex_command */
#include "cmd.h"		/* copy_cmd, ren_cmd, delete_cmd, ... */
#include "gscreen.h"
#include "dir.h"
#include "dialog.h"
#include "gdesktop.h"
#include "gpageprop.h"
#include "gcliplabel.h"
#include "gblist.h"
#include "../vfs/vfs.h"
#include <gdk/gdkprivate.h>

/* The pixmaps */
#include "directory.xpm"
#include "link.xpm"
#include "dev.xpm"
#include "listing-list.xpm"
#include "listing-iconic.xpm"
/* This is used to initialize our pixmaps */
static int pixmaps_ready;
GdkPixmap *icon_directory_pixmap;
GdkBitmap *icon_directory_mask;
GdkPixmap *icon_link_pixmap;
GdkBitmap *icon_link_mask;
GdkPixmap *icon_dev_pixmap;
GdkBitmap *icon_dev_mask;

/* These are big images used in the Icon View,  for the gnome_icon_list */
static GdkImlibImage *icon_view_directory;
static GdkImlibImage *icon_view_executable;
static GdkImlibImage *icon_view_symlink;
static GdkImlibImage *icon_view_device;
static GdkImlibImage *icon_view_regular;
static GdkImlibImage *icon_view_core;
static GdkImlibImage *icon_view_sock;

static char *drag_types [] = { "text/plain", "file:ALL", "url:ALL" };
static char *drop_types [] = { "url:ALL" };

#define ELEMENTS(x) (sizeof (x) / sizeof (x[0]))

/* GtkWidgets with the shaped windows for dragging */
GtkWidget *drag_directory    = NULL;
GtkWidget *drag_directory_ok = NULL;
GtkWidget *drag_multiple     = NULL;
GtkWidget *drag_multiple_ok  = NULL;

typedef void (*context_menu_callback)(GtkWidget *, void *);

/*
 * Flags for the context-sensitive popup menus
 */
#define F_ALL         1
#define F_REGULAR     2
#define F_SYMLINK     4
#define F_SINGLE      8
#define F_NOTDIR     16
#define F_DICON      32		/* Only applies to desktop_icon_t */
#define F_PANEL      64         /* Only applies to WPanel */

static void panel_file_list_configure_contents (GtkWidget *file_list, WPanel *panel, int main_width, int height);

void
repaint_file (WPanel *panel, int file_index, int move, int attr, int isstatus)
{
}

/*
 * Invoked by the generic code:  show current working directory
 */
void
show_dir (WPanel *panel)
{
	assign_text (panel->current_dir, panel->cwd);
	update_input (panel->current_dir, 1);
	gtk_window_set_title (GTK_WINDOW (panel->xwindow), panel->cwd);
}

/*
 * Utility routine:  Try to load a bitmap for a file_entry
 */
static void
panel_file_list_set_type_bitmap (GtkCList *cl, int row, int column, int color, file_entry *fe)
{
	/* Here, add more icons */
	switch (color){
	case DIRECTORY_COLOR:
		gtk_clist_set_pixmap (cl, row, column, icon_directory_pixmap, icon_directory_mask);
		break;
	case LINK_COLOR:
		gtk_clist_set_pixmap (cl, row, column, icon_link_pixmap, icon_link_mask);
		break;
	case DEVICE_COLOR:
		gtk_clist_set_pixmap (cl, row, column, icon_dev_pixmap, icon_dev_mask);
		break;
	}
}

/*
 * Sets the color attributes for a given row.
 */
static void
panel_file_list_set_row_colors (GtkCList *cl, int row, int color_pair)
{
	gtk_clist_set_foreground (cl, row, gmc_color_pairs [color_pair].fore);
	gtk_clist_set_background (cl, row, gmc_color_pairs [color_pair].back);
}

/*
 * Update the status of the back and forward history buttons.
 * Called from the generic code 
 */
void
x_panel_update_marks (WPanel *panel)
{
	int ff = panel->dir_history->next ? 1 : 0;
	int bf = panel->dir_history->prev ? 1 : 0;

	if (!panel->fwd_b)
		return;
	
	gtk_widget_set_sensitive (panel->fwd_b, ff);
	gtk_widget_set_sensitive (panel->back_b, bf);
}

/*
 * Listing view: Load the contents
 */
static void
panel_fill_panel_list (WPanel *panel)
{
	const int top       = panel->count;
	const int items     = panel->format->items;
	const int selected  = panel->selected;
	GtkCList *cl        = GTK_CLIST (panel->list);
	int i, col, type_col, color;
	char  **texts;

	texts = malloc (sizeof (char *) * items);

	gtk_clist_freeze (GTK_CLIST (cl));
	gtk_clist_clear (GTK_CLIST (cl));

	/* which column holds the type information */
	type_col = -1;
	
	for (i = 0; i < top; i++){
		file_entry *fe = &panel->dir.list [i];
		format_e *format = panel->format;
		
		for (col = 0; format; format = format->next){
			if (!format->use_in_gui)
			    continue;

			if (type_col == -1)
				if (strcmp (format->id, "type") == 0)
					type_col = col;
			
			if (!format->string_fn)
				texts [col] = "";
			else
				texts [col] = (*format->string_fn)(fe, 10);
			col++;
		}
		gtk_clist_append (cl, texts);

		color = file_compute_color (fe->f.marked ? MARKED : NORMAL, fe);
		panel_file_list_set_row_colors (cl, i, color);
		if (type_col != -1)
			panel_file_list_set_type_bitmap (cl, i, type_col, color, fe);
	}
	/* This is needed as the gtk_clist_append changes selected under us :-( */
	panel->selected = selected;
	select_item (panel);
	gtk_clist_thaw (GTK_CLIST (cl));
	free (texts);
}

/*
 * Icon view: load the panel contents
 */
static void
panel_fill_panel_icons (WPanel *panel)
{
	GnomeIconList *icons = GNOME_ICON_LIST (panel->icons);
	const int top       = panel->count;
	const int selected  = panel->selected;
	int i;
	GdkImlibImage *image;
	
	gnome_icon_list_freeze (icons);
	gnome_icon_list_clear (icons);
	
	for (i = 0; i < top; i++){
		file_entry *fe = &panel->dir.list [i];

		
		switch (file_entry_color (fe)){
		case DIRECTORY_COLOR:
			image = icon_view_directory;
			break;
			
		case LINK_COLOR:
			image = icon_view_symlink;
			break;
			
		case DEVICE_COLOR:
			image = icon_view_device;
			break;
			
		case SPECIAL_COLOR:
			image = icon_view_sock;
			break;
			
		case EXECUTABLE_COLOR:
			image = icon_view_executable;
			break;
				
		case CORE_COLOR:
			image = icon_view_core;
			break;
			
		case STALLED_COLOR:
		case NORMAL_COLOR:
		default:
			image = icon_view_regular;
		}
		gnome_icon_list_append_imlib (icons, image, fe->fname);
	}
	/* This is needed as the gtk_clist_append changes selected under us :-( */
	panel->selected = selected;
	select_item (panel);
	gnome_icon_list_thaw (icons);
}

/*
 * Invoked from the generic code to fill the display 
 */
void
x_fill_panel (WPanel *panel)
{
	if (panel->list_type == list_icons)
		panel_fill_panel_icons (panel);
	else
		panel_fill_panel_list (panel);
}

static void
gmc_panel_set_size (int index, int boot)
{
	Widget *w;
	WPanel *p;
	
	w = (Widget *) get_panel_widget (index);
	p = (WPanel *) w;
	w->cols = 40;
	w->lines = 25;
	set_panel_formats (p);
	paint_panel (p);

	if (!boot)
		paint_frame (p);

	x_fill_panel (p);
}

void
x_panel_set_size (int index)
{
	printf ("WARNING: set size called\n");
	gmc_panel_set_size (index, 1);
}

/*
 * Invoked when the f.mark field of a file item changes
 */
void
x_panel_select_item (WPanel *panel, int index, int value)
{
	int color;

	color = file_compute_color (value ? MARKED : NORMAL, &panel->dir.list[index]);
	panel_file_list_set_row_colors (GTK_CLIST (panel->list), index, color);
}

void
x_select_item (WPanel *panel)
{
	if (panel->list_type == list_icons){
		GnomeIconList *list = GNOME_ICON_LIST (panel->icons);
		
		do_file_mark (panel, panel->selected, 1);
		display_mini_info (panel);
		gnome_icon_list_select_icon (list, panel->selected);

		if (list->icon_rows){
			if (gnome_icon_list_icon_is_visible (list, panel->selected) != GTK_VISIBILITY_FULL)
				gnome_icon_list_moveto (list, panel->selected, 0.5);
		}
	} else {
		GtkCList *clist = GTK_CLIST (panel->list);
		int color, marked;
		
		if (panel->dir.list [panel->selected].f.marked)
			marked = 1;
		else
			marked = 0;
		
		color = file_compute_color (marked ? MARKED_SELECTED : SELECTED, &panel->dir.list [panel->selected]);
		panel_file_list_set_row_colors (GTK_CLIST (panel->list), panel->selected, color);
		
		/* Make it visible */
		if (gtk_clist_row_is_visible (clist, panel->selected) != GTK_VISIBILITY_FULL)
			gtk_clist_moveto (clist, panel->selected, 0, 0.5, 0.0);
	}
}

void
x_unselect_item (WPanel *panel)
{
	if (panel->list_type == list_icons){
	        int selected = panel->selected;

		/* This changes the panel->selected */
		gnome_icon_list_unselect_all (GNOME_ICON_LIST (panel->icons), NULL, NULL);
		panel->selected = selected;
	} else {
		int color;
		int val;

		val = panel->dir.list [panel->selected].f.marked ? MARKED : NORMAL;
		color = file_compute_color (val, &panel->dir.list [panel->selected]);
		panel_file_list_set_row_colors (GTK_CLIST (panel->list), panel->selected, color);
	}
}

void
x_filter_changed (WPanel *panel)
{
	assign_text (panel->filter_w, panel->filter ? panel->filter : "");
	update_input (panel->filter_w, 1);
}

void
x_adjust_top_file (WPanel *panel)
{
/*	gtk_clist_moveto (GTK_CLIST (panel->list), panel->top_file, 0, 0.0, 0.0); */
}

/*
 * These two constants taken from Gtk sources, hack to figure out how much
 * of the clist is visible
 */
#define COLUMN_INSET 3
#define CELL_SPACING 1

/*
 * Configures the columns title sizes for the panel->list CList widget
 */
static void
panel_file_list_configure_contents (GtkWidget *file_list, WPanel *panel, int main_width, int height)
{
	format_e *format = panel->format;
	int i, used_columns, expandables, items;
	int char_width, usable_pixels, extra_pixels, width;
	int total_columns, extra_columns;
	int expand_space, extra_space, shrink_space;
	int lost_pixels, display_the_mini_info;
	
	/* Pass 1: Count minimum columns,
	 * set field_len to default to the requested_field_len
	 * and compute how much space we lost to the column decorations
	 */
	lost_pixels = used_columns = expandables = items = 0;
	for (format = panel->format; format; format = format->next){
		format->field_len = format->requested_field_len;
		if (!format->use_in_gui)
			continue;
		
		used_columns += format->field_len;
		items++;
		if (format->expand)
			expandables++;
		lost_pixels += CELL_SPACING + (2 * COLUMN_INSET);
	}

	/* The left scrollbar might take some space from us, use this information */
	if (GTK_WIDGET_VISIBLE (GTK_CLIST (file_list)->vscrollbar)){
		int scrollbar_width = GTK_WIDGET (GTK_CLIST (file_list)->vscrollbar)->requisition.width;
		int scrollbar_space = GTK_CLIST_CLASS (GTK_OBJECT (file_list)->klass)->scrollbar_spacing;

		lost_pixels += scrollbar_space + scrollbar_width;
	}
	char_width = gdk_string_width (file_list->style->font, "xW") / 2;
	width = main_width - lost_pixels;
	extra_pixels  = width % char_width;
	usable_pixels = width - extra_pixels;
	total_columns = usable_pixels / char_width;
	extra_columns = total_columns - used_columns;
	if (extra_columns > 0){
		expand_space  = extra_columns / expandables;
		extra_space   = extra_columns % expandables;
	} else
		extra_space = expand_space = 0;

	/* Hack: the default mini-info display only gets displayed
	 * if panel->estimated_total is not zero, ie, if this has been
	 * initialized for the first time.
	 */
	
	display_the_mini_info = (panel->estimated_total == 0);
	panel->estimated_total = total_columns;

	if (display_the_mini_info)
		display_mini_info (panel);
	
	/* If we dont have enough space, shorten the fields */
	if (used_columns > total_columns){
		expand_space = 0;
		shrink_space = (used_columns - total_columns) / items;
	} else
		shrink_space = 0;
	
	gtk_clist_freeze (GTK_CLIST (file_list));
	for (i = 0, format = panel->format; format; format = format->next){
		if (!format->use_in_gui)
			continue;

		format->field_len += (format->expand ? expand_space : 0) - shrink_space;
		gtk_clist_set_column_width (GTK_CLIST (file_list), i, format->field_len * char_width);
		i++;
	}
	gtk_clist_thaw (GTK_CLIST (file_list));
}

static void
panel_action_open_with (GtkWidget *widget, WPanel *panel)
{
	char *command;
	
	command = input_expand_dialog (_(" Open with..."),
				       _("Enter extra arguments:"), panel->dir.list [panel->selected].fname);
	if (!command)
		return;
	execute (command);
	free (command);
}

static void
panel_action_open (GtkWidget *widget, WPanel *panel)
{
	if (do_enter (panel))
		return;
	panel_action_open_with (widget, panel);
}

void
panel_action_view (GtkWidget *widget, WPanel *panel)
{
	view_cmd (panel);
}

void
panel_action_view_unfiltered (GtkWidget *widget, WPanel *panel)
{
	view_simple_cmd (panel);
}

void
panel_action_properties (GtkWidget *widget, WPanel *panel)
{
	file_entry *fe = &panel->dir.list [panel->selected];
	char *full_name = concat_dir_and_file (panel->cwd, fe->fname);
	
	if (item_properties (GTK_WIDGET (panel->list), full_name, NULL) != 0){
		reread_cmd ();
	}
	free (full_name);
}

/*
 * The context menu: text displayed, condition that must be met and
 * the routine that gets invoked upon activation.
 */
static struct {
	char *text;
	int  flags;
	context_menu_callback callback;
} file_actions [] = {
	{ N_("Properties"),      F_SINGLE | F_PANEL,   	  	 (context_menu_callback) panel_action_properties },
	{ N_("Properties"),      F_SINGLE | F_DICON,  	  	 (context_menu_callback) desktop_icon_properties },
	{ "",                    F_SINGLE,   	    	  	 NULL },
	{ N_("Open"),            F_PANEL | F_ALL,      	  	 (context_menu_callback) panel_action_open },
	{ N_("Open"),            F_DICON | F_ALL, 	  	 (context_menu_callback) desktop_icon_execute },
	{ N_("Open with"),       F_PANEL | F_ALL,      	  	 (context_menu_callback) panel_action_open_with },
	{ N_("View"),            F_PANEL | F_NOTDIR,      	 (context_menu_callback) panel_action_view },
	{ N_("View unfiltered"), F_PANEL | F_NOTDIR,      	 (context_menu_callback) panel_action_view_unfiltered },  
	{ "",                    0,          	                 NULL },
	{ N_("Link..."),         F_PANEL | F_REGULAR | F_SINGLE, (context_menu_callback) link_cmd },
	{ N_("Symlink..."),      F_PANEL | F_SINGLE,             (context_menu_callback) symlink_cmd },
	{ N_("Edit symlink..."), F_PANEL | F_SYMLINK,            (context_menu_callback) edit_symlink_cmd },
	{ NULL, 0, NULL },
};

typedef struct {
	char *text;
	context_menu_callback callback;
} common_menu_t;
	
/*
 * context menu, constant entries
 */
common_menu_t common_panel_actions [] = {
	{ N_("Copy..."),         (context_menu_callback) copy_cmd },
	{ N_("Rename/move.."),   (context_menu_callback) ren_cmd },
	{ N_("Delete..."),       (context_menu_callback) delete_cmd },
	{ NULL, NULL }
};

common_menu_t common_dicon_actions [] = {
	{ N_("Delete"),          (context_menu_callback) desktop_icon_delete },
	{ NULL, NULL }
};
      
static GtkWidget *
create_popup_submenu (WPanel *panel, desktop_icon_t *di, int row, char *filename)
{
	static int submenu_translated;
	GtkWidget *menu;
	int i;
	void *closure;

	closure = (panel != 0 ? (void *) panel : (void *)di);
	
	if (!submenu_translated){
		/* FIXME translate it */
		submenu_translated = 1;
	}
	
	menu = gtk_menu_new ();
	for (i = 0; file_actions [i].text; i++){
		GtkWidget *item;

		/* First, try F_PANEL and F_DICON flags */
		if (di && (file_actions [i].flags & F_PANEL))
			continue;

		if (panel && (file_actions [i].flags & F_DICON))
			continue;
		
		/* Items with F_ALL bypass any other condition */
		if (!(file_actions [i].flags & F_ALL)){

			/* Items with F_SINGLE require that ONLY ONE marked files exist */
			if (panel && file_actions [i].flags & F_SINGLE){
				if (panel->marked > 1)
					continue;
			}

			/* Items with F_NOTDIR requiere that the selection is not a directory */
			if (panel && file_actions [i].flags & F_NOTDIR){
				struct stat *s = &panel->dir.list [row].buf;

				if (panel->dir.list [row].f.link_to_dir)
					continue;
				
				if (S_ISDIR (s->st_mode))
					continue;
			}
			
			/* Items with F_REGULAR do not accept any strange file types */
			if (panel && file_actions [i].flags & F_REGULAR){
				struct stat *s = &panel->dir.list [row].buf;
				
				if (S_ISLNK (panel->dir.list [row].f.link_to_dir))
					continue;
				if (S_ISSOCK (s->st_mode) || S_ISCHR (s->st_mode) ||
				    S_ISFIFO (s->st_mode) || S_ISBLK (s->st_mode))
					continue;
			}

			/* Items with F_SYMLINK only operate on symbolic links */
			if (panel && file_actions [i].flags & F_SYMLINK){
				if (!S_ISLNK (panel->dir.list [row].buf.st_mode))
					continue;
			}
		}
		if (*file_actions [i].text)
			item = gtk_menu_item_new_with_label (_(file_actions [i].text));
		else
			item = gtk_menu_item_new ();

		gtk_widget_show (item);
		if (file_actions [i].callback){
			gtk_signal_connect (GTK_OBJECT (item), "activate",
					    GTK_SIGNAL_FUNC(file_actions [i].callback), closure);
		}

		gtk_menu_append (GTK_MENU (menu), item);
	}
	return menu;
}

/*
 * Ok, this activates a menu popup action for a filename
 * it is kind of hackish, it gets the desired action from the
 * item, so it has to peek inside the item to retrieve the label
 */
static void
popup_activate_by_string (GtkMenuItem *item, WPanel *panel)
{
	char *filename = panel->dir.list [panel->selected].fname;
	char *action;
	int movedir;
	
	g_return_if_fail (GTK_IS_MENU_ITEM (item));
	g_return_if_fail (GTK_IS_LABEL (GTK_BIN (item)->child));

	action = GTK_LABEL (GTK_BIN (item)->child)->label;

	regex_command (filename, action, NULL, &movedir);
}

static void
popup_activate_desktop_icon (GtkMenuItem *item, char *filename)
{
	char *action;
	int movedir;
	
	action = GTK_LABEL (GTK_BIN (item)->child)->label;

	regex_command (filename, action, NULL, &movedir);
}

static void
file_popup_add_context (GtkMenu *menu, WPanel *panel, desktop_icon_t *di, char *filename)
{
	GtkWidget *item;
	char *p, *q;
	int c, i;
	void *closure, *regex_closure;
	common_menu_t *menu_p;
	GtkSignalFunc regex_func;
	
	if (panel){
		menu_p        = common_panel_actions;
		closure       = panel;
		regex_func    = GTK_SIGNAL_FUNC (popup_activate_by_string);
		regex_closure = panel;
	} else {
		menu_p        = common_dicon_actions;
		closure       = di;
		regex_func    = GTK_SIGNAL_FUNC (popup_activate_desktop_icon);
		regex_closure = di->dentry->exec [0];
	}
	
	for (i = 0; menu_p [i].text; i++){
		GtkWidget *item;

		item = gtk_menu_item_new_with_label (_(menu_p [i].text));
		gtk_widget_show (item);
		gtk_signal_connect (GTK_OBJECT (item), "activate",
				    GTK_SIGNAL_FUNC (menu_p [i].callback), closure);
		gtk_menu_append (GTK_MENU (menu), item);
	}
	
	p = regex_command (filename, NULL, NULL, NULL);
	if (!p)
		return;
	
	item = gtk_menu_item_new ();
	gtk_widget_show (item);
	gtk_menu_append (menu, item);
	
	for (;;){
		while (*p == ' ' || *p == '\t')
			p++;
		if (!*p)
			break;
		q = p;
		while (*q && *q != '=' && *q != '\t')
			q++;
		c = *q;
		*q = 0;
		
		item = gtk_menu_item_new_with_label (p);
		gtk_widget_show (item);

		gtk_signal_connect (GTK_OBJECT(item), "activate", regex_func, regex_closure);
		gtk_menu_append (menu, item);
		if (!c)
			break;
		p = q + 1;
	}
}

/*
 * Create a context menu
 * It can take either a WPanel or a GnomeDesktopEntry.  One of them should
 * be set to NULL.
 */
void
file_popup (GdkEventButton *event, void *WPanel_pointer, void *desktop_icon_t_pointer, int row, char *filename)
{
	GtkWidget *menu = gtk_menu_new ();
	GtkWidget *submenu;
	GtkWidget *item;
	WPanel      *panel = WPanel_pointer;
	desktop_icon_t *di = desktop_icon_t_pointer;
	char *str;
		
	g_return_if_fail (((panel != NULL) ^ (di != NULL)));

	if (panel)
		str = (panel->marked > 1) ? "..." : filename;
	else
		str = filename;
	
	if (panel){
		item = gtk_menu_item_new_with_label (str);
		gtk_widget_show (item);
		gtk_menu_append (GTK_MENU (menu), item);

		submenu = create_popup_submenu (panel, di, row, filename);
		gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
	} else 
		menu = create_popup_submenu (panel, di, row, filename);

	file_popup_add_context (GTK_MENU (menu), panel, di, filename);

	gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, 3, event->time);
}

static void
internal_select_item (GtkWidget *file_list, WPanel *panel, int row)
{
	unselect_item (panel);
	panel->selected = row;

	gtk_signal_handler_block_by_data (GTK_OBJECT (file_list), panel);
	select_item (panel);
	gtk_signal_handler_unblock_by_data (GTK_OBJECT (file_list), panel);
}

static void
panel_file_list_select_row (GtkWidget *file_list, int row, int column, GdkEvent *event, WPanel *panel)
{
	int current_selection = panel->selected;
	
	if (!event) {
		internal_select_item (file_list, panel, row);
		return;
	}
	
	switch (event->type) {
	case GDK_BUTTON_PRESS:
		gtk_clist_unselect_row (GTK_CLIST (panel->list), row, 0);
		internal_select_item (file_list, panel, row);

		switch (event->button.button) {
		case 1:
			if (!(event->button.state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)))
				break;
			/* fallback if shift-click is pressed */
			do_file_mark_range (panel, row, current_selection);
			break;
			
		case 2:
			do_file_mark (panel, row, !panel->dir.list[row].f.marked);
			break;

		case 3:
			file_popup (&event->button, panel, NULL, row, panel->dir.list[row].fname);
			break;
		}

		break;

	case GDK_2BUTTON_PRESS:
		gtk_clist_unselect_row (GTK_CLIST (panel->list), row, 0);
		if (event->button.button == 1)
			do_enter (panel);
		break;

	default:
		break;
	}
}

/* Figure out the number of visible lines in the panel */
static void
panel_file_list_compute_lines (GtkCList *file_list, WPanel *panel, int height)
{
	int lost_pixels = 0;
	
	if (GTK_WIDGET_VISIBLE (file_list->hscrollbar)){
		int scrollbar_width = GTK_WIDGET (file_list->hscrollbar)->requisition.width;
		int scrollbar_space = GTK_CLIST_CLASS (GTK_OBJECT (file_list)->klass)->scrollbar_spacing;

		lost_pixels = scrollbar_space + scrollbar_width;
	}
	panel->widget.lines = (height-lost_pixels) /
		(GTK_CLIST (file_list)->row_height + CELL_SPACING);
}

static void
panel_file_list_size_allocate_hook (GtkWidget *file_list, GtkAllocation *allocation, WPanel *panel)
{
	gtk_signal_handler_block_by_data (GTK_OBJECT (file_list), panel);
	panel_file_list_configure_contents (file_list, panel, allocation->width, allocation->height);
	gtk_signal_handler_unblock_by_data (GTK_OBJECT (file_list), panel);
	
	panel_file_list_compute_lines (GTK_CLIST (file_list), panel, allocation->height);
}

static void
panel_file_list_column_callback (GtkWidget *widget, int col, WPanel *panel)
{
	format_e *format;
	int i;
	
	for (i = 0, format = panel->format; format; format = format->next){
		if (!format->use_in_gui)
			continue;
		if (i == col){
			sortfn *sorting_routine;
			
			sorting_routine = get_sort_fn (format->id);
			if (!sorting_routine)
				return;

			if (sorting_routine == panel->sort_type)
				panel->reverse = !panel->reverse;
			panel->sort_type = sorting_routine;

			do_re_sort (panel);
			return;
		}
		i++;
	}
}

static void
panel_create_pixmaps (GtkWidget *parent)
{
	GdkColor color = gtk_widget_get_style (parent)->bg [GTK_STATE_NORMAL];

	pixmaps_ready = 1;
	icon_directory_pixmap = gdk_pixmap_create_from_xpm_d (parent->window, &icon_directory_mask, &color, directory_xpm);
	icon_link_pixmap      = gdk_pixmap_create_from_xpm_d (parent->window, &icon_link_mask,      &color, link_xpm);
	icon_dev_pixmap       = gdk_pixmap_create_from_xpm_d (parent->window, &icon_dev_mask,       &color, dev_xpm);
}

static void
panel_file_list_scrolled (GtkAdjustment *adj, WPanel *panel)
{
	if (!GTK_IS_ADJUSTMENT (adj)) {
		fprintf (stderr, "file_list_is_scrolled is called and there are not enough boats!\n");
		exit (1);
	}
}

static void
panel_configure_file_list (WPanel *panel, GtkWidget *file_list)
{
	format_e *format = panel->format;
	GtkCList *cl = GTK_CLIST (file_list);
	GtkObject *adjustment;
	int i;

	/* Set sorting callback */
	gtk_signal_connect (GTK_OBJECT (file_list), "click_column",
			    GTK_SIGNAL_FUNC (panel_file_list_column_callback), panel);

	/* Configure the CList */

	gtk_clist_set_selection_mode (cl, GTK_SELECTION_SINGLE);
	gtk_clist_set_policy (cl, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
	
	for (i = 0, format = panel->format; format; format = format->next){
		GtkJustification just;

		if (!format->use_in_gui)
			continue;

		/* Set desired justification */
		if (format->just_mode == J_LEFT)
			just = GTK_JUSTIFY_LEFT;
		else
			just = GTK_JUSTIFY_RIGHT;
		
		gtk_clist_set_column_justification (cl, i, just);
		i++;
	}

	/* Configure the scrolbars */
	adjustment = GTK_OBJECT (gtk_range_get_adjustment (GTK_RANGE (cl->vscrollbar)));
	gtk_signal_connect_after (GTK_OBJECT(adjustment), "value_changed", 
				  GTK_SIGNAL_FUNC (panel_file_list_scrolled), panel);
}

static void *
panel_build_selected_file_list (WPanel *panel, int *file_list_len)
{
	if (panel->marked){
		char *data, *copy;
		int i, total_len = 0;
		int cwdlen = strlen (panel->cwd) + 1;
		
		/* first pass, compute the length */
		for (i = 0; i < panel->count; i++)
			if (panel->dir.list [i].f.marked)
				total_len += (cwdlen + panel->dir.list [i].fnamelen + 1);

		data = copy = xmalloc (total_len, "build_selected_file_list");
		for (i = 0; i < panel->count; i++)
			if (panel->dir.list [i].f.marked){
				strcpy (copy, panel->cwd);
				copy [cwdlen-1] = '/';
				strcpy (&copy [cwdlen], panel->dir.list [i].fname);
				copy += panel->dir.list [i].fnamelen + 1 + cwdlen;
			}
		*file_list_len = total_len;
		return data;
	} else {
		char *fullname = concat_dir_and_file (panel->cwd, panel->dir.list [panel->selected].fname);
		
		*file_list_len = strlen (fullname) + 1;
		return fullname;
	}
}
/*
 * Handler for text/plain and url:ALL drag types
 *
 * Sets the drag information to the filenames selected on the panel
 */
static void
panel_transfer_file_names (GtkWidget *widget, GdkEventDragRequest *event, WPanel *panel, int *len, char **data)
{
	*data = panel_build_selected_file_list (panel, len);
}

/*
 * Handler for file:ALL type (target application only understands local pathnames)
 *
 * Makes local copies of the files and transfers the filenames.
 */
static void
panel_make_local_copies_and_transfer (GtkWidget *widget, GdkEventDragRequest *event, WPanel *panel,
				      int *len, char **data)
{
	char *filename, *localname;
	int i;
	
	if (panel->marked){
		char **local_names_array, *p;
		int j, total_len;

		/* First assemble all of the filenames */
		local_names_array = malloc (sizeof (char *) * panel->marked);
		total_len = j = 0;
		for (i = 0; i < panel->count; i++){
			char *filename;

			if (!panel->dir.list [i].f.marked)
				continue;
			
			filename = concat_dir_and_file (panel->cwd, panel->dir.list [i].fname);
			localname = mc_getlocalcopy (filename);
			total_len += strlen (localname) + 1;
			local_names_array [j++] = localname;
			free (filename);
		}
		*len = total_len;
		*data = p = malloc (total_len);
		for (i = 0; i < j; i++){
			strcpy (p, local_names_array [i]);
			g_free (local_names_array [i]);
			p += strlen (p) + 1;
		}
	} else {
		filename = concat_dir_and_file (panel->cwd, panel->dir.list [i].fname);
		localname = mc_getlocalcopy (filename);
		free (filename);
		*data = localname;
		*len = strlen (localname + 1);
	}
}

static void
panel_drag_request (GtkWidget *widget, GdkEventDragRequest *event, WPanel *panel, int *len, char **data)
{
	*len = 0;
	*data = 0;
	
	if ((strcmp (event->data_type, "text/plain") == 0) ||
	    (strcmp (event->data_type, "url:ALL") == 0)){
		panel_transfer_file_names (widget, event, panel, len, data);
	} else if (strcmp (event->data_type, "file:ALL") == 0){
		if (vfs_file_is_local (panel->cwd))
			panel_transfer_file_names (widget, event, panel, len, data);
		else
			panel_make_local_copies_and_transfer (widget, event, panel, len, data);
	}
}

/*
 * Listing mode: drag request handler
 */
static void
panel_clist_drag_request (GtkWidget *widget, GdkEventDragRequest *event, WPanel *panel)
{
	GdkWindowPrivate *clist_window = (GdkWindowPrivate *) (GTK_WIDGET (widget)->window);
	GdkWindowPrivate *clist_areaw  = (GdkWindowPrivate *) (GTK_CLIST (widget)->clist_window);
	char *data;
	int len;

	panel_drag_request (widget, event, panel, &len, &data);
	
	/* Now transfer the DnD information */
	if (len && data){
		if (clist_window->dnd_drag_accepted)
			gdk_window_dnd_data_set ((GdkWindow *)clist_window, (GdkEvent *) event, data, len);
		else
			gdk_window_dnd_data_set ((GdkWindow *)clist_areaw, (GdkEvent *) event, data, len);
		free (data);
	}
}

/*
 * Invoked when a drop has happened on the panel
 */
static void
panel_clist_drop_data_available (GtkWidget *widget, GdkEventDropDataAvailable *data, WPanel *panel)
{
	gint winx, winy;
	gint dropx, dropy;
	gint row;
	char *drop_dir;
	
	gdk_window_get_origin (GTK_CLIST (widget)->clist_window, &winx, &winy);
	dropx = data->coords.x - winx;
	dropy = data->coords.y - winy;

	if (dropx < 0 || dropy < 0)
		return;

	if (gtk_clist_get_selection_info (GTK_CLIST (widget), dropx, dropy, &row, NULL) == 0)
		drop_dir = panel->cwd;
	else {
		g_assert (row < panel->count);

		if (S_ISDIR (panel->dir.list [row].buf.st_mode))
			drop_dir = concat_dir_and_file (panel->cwd, panel->dir.list [row].fname);
		else 
			drop_dir = panel->cwd;
	}
	drop_on_directory (data, drop_dir, 0);

	if (drop_dir != panel->cwd)
		free (drop_dir);

	update_one_panel_widget (panel, 0, UP_KEEPSEL);
	panel_update_contents (panel);
}

static void
panel_drag_begin (GtkWidget *widget, GdkEvent *event, WPanel *panel)
{
	GdkPoint hotspot = { 15, 15 };

	if (panel->marked > 1){
		if (drag_multiple && drag_multiple_ok){
			gdk_dnd_set_drag_shape (drag_multiple->window, &hotspot,
						drag_multiple_ok->window, &hotspot);
			gtk_widget_show (drag_multiple);
			gtk_widget_show (drag_multiple_ok);
		}
			
	} else {
		if (drag_directory && drag_directory_ok)
			gdk_dnd_set_drag_shape (drag_directory->window, &hotspot,
						drag_directory_ok->window, &hotspot);	
			gtk_widget_show (drag_directory_ok);
			gtk_widget_show (drag_directory);
	}

}

static void
panel_icon_list_drag_begin (GtkWidget *widget, GdkEvent *event, WPanel *panel)
{
	GnomeIconList *icons = GNOME_ICON_LIST (panel->icons);
	
	icons->last_clicked = -1;
	panel_drag_begin (widget, event, panel);
}

static void
panel_artificial_drag_start (GtkCList *window, GdkEventMotion *event)
{
	artificial_drag_start (window->clist_window, event->x, event->y);
}

static GtkWidget *
load_transparent_image (char *base)
{
	char *f = concat_dir_and_file (ICONDIR, base);
	GtkWidget *w;

	w = make_transparent_window (f);
	g_free (f);
	return w;
}

static void
load_dnd_icons (void)
{
	GdkPoint hotspot = { 5, 5 };

	if (!drag_directory)
		drag_directory = gnome_stock_transparent_window (GNOME_STOCK_PIXMAP_NOT, NULL);
	
	if (!drag_directory_ok)
		drag_directory_ok = gnome_stock_transparent_window (GNOME_STOCK_PIXMAP_NEW, NULL);
	
	if (!drag_multiple)
		drag_multiple = gnome_stock_transparent_window (GNOME_STOCK_PIXMAP_NOT, NULL);
	
	if (!drag_multiple_ok)
		drag_multiple_ok = gnome_stock_transparent_window (GNOME_STOCK_PIXMAP_MULTIPLE, NULL);

	if (drag_directory && drag_directory_ok)
		gdk_dnd_set_drag_shape (drag_directory->window, &hotspot,
					drag_directory_ok->window, &hotspot);	
}

/*
 * Pixmaps can only be loaded once the window has been realized, so
 * this is why this hook is here
 */
static void
panel_realized (GtkWidget *file_list, WPanel *panel)
{
	GtkObject *obj = GTK_OBJECT (file_list);

	load_dnd_icons ();
	
	/* DND: Drag setup */
	gtk_signal_connect (obj, "drag_request_event", GTK_SIGNAL_FUNC (panel_clist_drag_request), panel);
	gtk_signal_connect (obj, "drag_begin_event",   GTK_SIGNAL_FUNC (panel_drag_begin), panel);

	gdk_window_dnd_drag_set (GTK_CLIST (file_list)->clist_window, TRUE, drag_types, ELEMENTS (drag_types));
 
	/* DND: Drop setup */
	gtk_signal_connect (obj, "drop_data_available_event", GTK_SIGNAL_FUNC (panel_clist_drop_data_available), panel);

	/* Artificial way of getting drag to start without leaving the widget boundary */
	gtk_signal_connect (obj, "motion_notify_event",
			    GTK_SIGNAL_FUNC (panel_artificial_drag_start), panel);
	gdk_window_dnd_drop_set (GTK_CLIST (file_list)->clist_window, TRUE, drop_types, ELEMENTS (drop_types), FALSE);
}

/*
 * Create, setup the file listing display.
 */
static GtkWidget *
panel_create_file_list (WPanel *panel)
{
	const int items = panel->format->items;
	format_e  *format = panel->format;
	GtkWidget *file_list;
	GtkCList  *clist;
	gchar     **titles;
	int       i;

	titles = g_new (char *, items);
	for (i = 0; i < items; format = format->next)
		if (format->use_in_gui)
			titles [i++] = format->title;

	file_list = gtk_blist_new_with_titles (items, titles);
	clist = GTK_CLIST (file_list);
	panel_configure_file_list (panel, file_list);
	free (titles);

	gtk_signal_connect_after (GTK_OBJECT (file_list), "size_allocate",
				  GTK_SIGNAL_FUNC (panel_file_list_size_allocate_hook),
				  panel);
	gtk_signal_connect (GTK_OBJECT (file_list), "realize",
			    GTK_SIGNAL_FUNC (panel_realized),
			    panel);

	gtk_signal_connect (GTK_OBJECT (file_list), "select_row",
			    GTK_SIGNAL_FUNC (panel_file_list_select_row),
			    panel);
	return file_list;
}

/*
 * Callback: icon selected
 */
static void
panel_icon_list_select_icon (GtkWidget *widget, int index, GdkEvent *event, WPanel *panel)
{
	panel->selected = index;
	do_file_mark (panel, index, 1);
	display_mini_info (panel);
	execute_hooks (select_file_hook);

	if (!event)
		return;
	
	switch (event->type){
	case GDK_BUTTON_PRESS:
		if (event->button.button == 3){
			file_popup (&event->button, panel, NULL, index, panel->dir.list [index].fname);
			return;
		}
		break;

	case GDK_2BUTTON_PRESS:
		if (event->button.button == 1)
			do_enter (panel);
		break;

	default:
		break;
	}
}

static void
panel_icon_list_unselect_icon (GtkWidget *widget, int index, GdkEvent *event, WPanel *panel)
{
	do_file_mark (panel, index, 0);
	display_mini_info (panel);
	if (panel->marked == 0)
		panel->selected = 0;
}

static GdkImlibImage *
load_image_icon_view (char *base)
{
	GdkImlibImage *im;
	char *f = concat_dir_and_file (ICONDIR, base);

	im = gdk_imlib_load_image (f);
	g_free (f);
	return im;
}

void
load_imlib_icons (void)
{
	static int loaded;

	if (loaded)
		return;
	
	icon_view_directory  = load_image_icon_view ("i-directory.png");
	icon_view_executable = load_image_icon_view ("i-executable.png");
	icon_view_symlink    = load_image_icon_view ("i-symlink.png");
	icon_view_device     = load_image_icon_view ("i-device.png");
	icon_view_regular    = load_image_icon_view ("i-regular.png");
	icon_view_core       = load_image_icon_view ("i-core.png");
	icon_view_sock       = load_image_icon_view ("i-sock.png");

	loaded = 1;
}
		  
static void
panel_icon_list_artificial_drag_start (GtkObject *obj, GdkEventMotion *event)
{
	GnomeIconList *ilist = GNOME_ICON_LIST (obj);

	artificial_drag_start (ilist->ilist_window, event->x, event->y);
}

/*
 * Icon view drag request handler
 */
static void
panel_icon_list_drag_request (GtkWidget *widget, GdkEventDragRequest *event, WPanel *panel)
{
	GnomeIconList *ilist = GNOME_ICON_LIST (widget);
	char *data;
	int len;

	panel_drag_request (widget, event, panel, &len, &data);

	if (len && data){
		gdk_window_dnd_data_set (ilist->ilist_window, (GdkEvent *) event, data, len);
		free (data);
	}
}

static void
panel_icon_list_drop_data_available (GtkWidget *widget, GdkEventDropDataAvailable *data, WPanel *panel)
{
	GnomeIconList *ilist = GNOME_ICON_LIST (widget);
	gint winx, winy;
	gint dropx, dropy;
	gint item;
	char *drop_dir;
	
	gdk_window_get_origin (ilist->ilist_window, &winx, &winy);
	dropx = data->coords.x - winx;
	dropy = data->coords.y - winy;

	if (dropx < 0 || dropy < 0)
		return;

	item = gnome_icon_list_get_icon_at (ilist, dropx, dropy);
	if (item == -1)
		drop_dir = panel->cwd;
	else {
		g_assert (item < panel->count);

		if (S_ISDIR (panel->dir.list [item].buf.st_mode))
			drop_dir = concat_dir_and_file (panel->cwd, panel->dir.list [item].fname);
		else 
			drop_dir = panel->cwd;
	}
	drop_on_directory (data, drop_dir, 0);

	if (drop_dir != panel->cwd)
		free (drop_dir);

	update_one_panel_widget (panel, 0, UP_KEEPSEL);
	panel_update_contents (panel);
}

/*
 * Setup for the icon view
 */
static void
panel_icon_list_realized (GtkObject *obj, WPanel *panel)
{
	GnomeIconList *icon = GNOME_ICON_LIST (obj);
	
	load_imlib_icons ();
	load_dnd_icons ();

	/* DND: Drag setup */
	gtk_signal_connect (obj, "drag_request_event", GTK_SIGNAL_FUNC (panel_icon_list_drag_request), panel);
	gtk_signal_connect (obj, "drag_begin_event", GTK_SIGNAL_FUNC (panel_icon_list_drag_begin), panel);
	gdk_window_dnd_drag_set (icon->ilist_window, TRUE, drag_types, ELEMENTS (drag_types));
	
	/* DND: Drop setup */
	gtk_signal_connect (obj, "drop_data_available_event",
			    GTK_SIGNAL_FUNC (panel_icon_list_drop_data_available), panel);
	
	gtk_signal_connect (obj, "motion_notify_event",
			    GTK_SIGNAL_FUNC (panel_icon_list_artificial_drag_start), panel);
	gdk_window_dnd_drop_set (icon->ilist_window, TRUE, drop_types, ELEMENTS (drop_types), FALSE);
}

/*
 * Create and setup the icon field display
 */
static GtkWidget *
panel_create_icon_display (WPanel *panel)
{
	GnomeIconList *icon_field;

	icon_field = GNOME_ICON_LIST (gnome_icon_list_new ());
	gnome_icon_list_set_separators (icon_field, " /-_.");
	
	gnome_icon_list_set_selection_mode (icon_field, GTK_SELECTION_MULTIPLE);

	gtk_signal_connect (GTK_OBJECT (icon_field), "select_icon",
			    GTK_SIGNAL_FUNC (panel_icon_list_select_icon), panel);
	gtk_signal_connect (GTK_OBJECT (icon_field), "unselect_icon",
			    GTK_SIGNAL_FUNC (panel_icon_list_unselect_icon), panel);

	gtk_signal_connect (GTK_OBJECT (icon_field), "realize",
			    GTK_SIGNAL_FUNC (panel_icon_list_realized), panel);
	return GTK_WIDGET (icon_field);
}

void
panel_switch_new_display_mode (WPanel *panel)
{
	GtkWidget *old_list = panel->list;

	if (!old_list)
		return;
	
	panel->list = panel_create_file_list (panel);
	gtk_widget_destroy (old_list);
	gtk_table_attach (GTK_TABLE (panel->table), panel->list, 0, 1, 1, 2,
			  GTK_EXPAND | GTK_FILL | GTK_SHRINK, 
			  GTK_EXPAND | GTK_FILL | GTK_SHRINK,
			  0, 0);
	gtk_widget_show (panel->list);
	panel_update_contents (panel);
}

static GtkWidget *
panel_create_cwd (Dlg_head *h, WPanel *panel, void **entry)
{
	WInput *in;

	in = input_new (0, 0, 0, 10, "", "cwd");
	add_widget (h, in);

	/* Force the creation of the gtk widget */
	send_message_to (h, (Widget *) in, WIDGET_INIT, 0);

	*entry = in;
	return GTK_WIDGET (in->widget.wdata);
}

/* FIXME: for now, this list is hardcoded.  We want a way to let the user configure it. */

static struct filter_item {
	char *text;
	char *glob;
} filter_items [] = {
	{ N_("All files"),
	  "*" },
	{ N_("Archives and compressed files"),
	  "*.(tar|gz|tgz|taz|zip|lha|zoo|pak|sit|arc|arj|rar|huf|lzh)" },
	{ N_("RPM/DEB files"),
	  "*.(rpm|deb)" },
	{ N_("Text/Document files"),
	  "*.(txt|tex|doc|rtf)" },
	{ N_("HTML and SGML files"),
	  "*.(html|htm|sgml|sgm)" },
	{ N_("Postscript and PDF files"),
	  "*.(ps|pdf)" },
	{ N_("Spreadsheet files"),
	  "*.(xls|wks|wk1)" },
	{ N_("Image files"),
	  "*.(png|jpg|jpeg|xcf|gif|tif|tiff|xbm|xpm|pbm|pgm|ppm|tga|rgb|iff|lbm|ilbm|"
	  "bmp|pcx|pic|pict|psd|gbr|pat|ico|fig|cgm|rle|fits)" },
	{ N_("Video/animation files"),
	  "*.(mpg|mpeg|mov|avi|fli|flc|flh|flx|dl)" },
	{ N_("Audio files"),
	  "*.(au|wav|mp3|snd|mod|s3m|ra)" },
	{ N_("C program files"),
	  "*.[ch]" },
	{ N_("C++ program files"),
	  "*.(cc|C|cpp|cxx|h|H)" },
	{ N_("Objective-C program files"),
	  "*.[mh]" },
	{ N_("Scheme program files"),
	  "*.scm" },
	{ N_("Assembler program files"),
	  "*.(s|S|asm)" },
	{ N_("Misc. program files"),
	  "*.(awk|sed|lex|l|y|sh|idl|pl|py|am|in|f|el|bas|pas|java|sl|p|m4|tcl|pov)" },
	{ N_("Font files"),
	  "*.(pfa|pfb|afm|ttf|fon|pcf|pcf.gz|spd)" }
};

static GtkWidget *filter_menu;

static void
filter_item_select (GtkWidget *widget, gpointer data)
{
	/* FIXME: the hintbar resizes horribly and screws the panel */
#if 0
	struct filter_item *fi = gtk_object_get_user_data (GTK_OBJECT (widget));

	set_hintbar (easy_patterns ? fi->glob : fi->regexp);
#endif
}

static void
filter_item_deselect (GtkWidget *widget, gpointer data)
{
/*	set_hintbar (""); */
}

static void
filter_item_activate (GtkWidget *widget, gpointer data)
{
	struct filter_item *fi = gtk_object_get_user_data (GTK_OBJECT (widget));
	WPanel *panel = data;
	char *pattern;

	if (easy_patterns)
		pattern = g_strdup (fi->glob);
	else {
		/* This is sort of a hack to force convert_pattern() to actually convert the thing */

		easy_patterns = 1;
		pattern = convert_pattern (fi->glob, match_file, 0);
		easy_patterns = 0;
	}

	set_panel_filter_to (panel, pattern);
}

static void
build_filter_menu (WPanel *panel, GtkWidget *button)
{
	GtkWidget *item;
	int i;

	if (filter_menu)
		return;

	/* FIXME: the filter menu is global, and it is never destroyed */

	filter_menu = gtk_menu_new ();

	gtk_object_set_user_data (GTK_OBJECT (filter_menu), button);

	for (i = 0; i < ELEMENTS (filter_items); i++) {
		item = gtk_menu_item_new_with_label (_(filter_items[i].text));
		gtk_object_set_user_data (GTK_OBJECT (item), &filter_items[i]);

		gtk_signal_connect (GTK_OBJECT (item), "select",
				    (GtkSignalFunc) filter_item_select,
				    panel);
		gtk_signal_connect (GTK_OBJECT (item), "deselect",
				    (GtkSignalFunc) filter_item_deselect,
				    panel);
		gtk_signal_connect (GTK_OBJECT (item), "activate",
				    (GtkSignalFunc) filter_item_activate,
				    panel);

		gtk_widget_show (item);
		gtk_menu_append (GTK_MENU (filter_menu), item);
	}
}

static void
position_filter_popup (GtkMenu *menu, gint *x, gint *y, gpointer data)
{
	int screen_width, screen_height;
	GtkWidget *wmenu = GTK_WIDGET (menu);
	GtkWidget *button = GTK_WIDGET (data);

	/* This code is mostly ripped off from gtkmenu.c - Federico */

	screen_width = gdk_screen_width ();
	screen_height = gdk_screen_height ();

	gdk_window_get_origin (button->window, x, y);

	*y += button->allocation.height;

	if ((*x + wmenu->requisition.width) > screen_width)
		*x -= (*x + wmenu->requisition.width) - screen_width;

	if ((*y + wmenu->requisition.height) > screen_height)
		*y -= (*y + wmenu->requisition.height) - screen_height;

	if (*y < 0)
		*y = 0;
}

static void
show_filter_popup (GtkWidget *button, gpointer data)
{
	WPanel *panel;

	panel = data;

	build_filter_menu (panel, button);

	gtk_menu_popup (GTK_MENU (filter_menu), NULL, NULL,
			position_filter_popup,
			button,
			1,
			GDK_CURRENT_TIME);
}

void
display_mini_info (WPanel *panel)
{
	GtkLabel *label = GTK_LABEL (panel->ministatus);

	if (panel->searching){
		char *str = copy_strings (N_("Search: "), panel->search_buffer, NULL);
		
		gtk_clip_label_set (label, str);
		free (str);
		return;
	}

	if (panel->marked){
		char buffer [120];
		
		sprintf (buffer, N_(" %s bytes in %d file%s"),
			 size_trunc_sep (panel->total), panel->marked,
			 panel->marked == 1 ? "" : "s");
		
		gtk_clip_label_set (label, buffer);
		return;
	}

	if (S_ISLNK (panel->dir.list [panel->selected].buf.st_mode)){
		char *link, link_target [MC_MAXPATHLEN];
		int  len;
		
		link = concat_dir_and_file (panel->cwd, panel->dir.list [panel->selected].fname);
		len = mc_readlink (link, link_target, MC_MAXPATHLEN);
		free (link);
		
		if (len > 0){
			char *str;

			link_target [len] = 0;
			str = copy_strings ("-> ", link_target, NULL);
			gtk_clip_label_set (label, str);
			free (str);
		} else 
			gtk_clip_label_set (label, N_("<readlink failed>"));
		return;
	}

	if (panel->estimated_total){
		int  len = panel->estimated_total;
		char *buffer;

		buffer = xmalloc (len + 2, "display_mini_info");
		format_file (buffer, panel, panel->selected, panel->estimated_total-2, 0, 1);
		buffer [len] = 0;
		gtk_clip_label_set (label, buffer);
		free (buffer);
	}
	if (panel->list_type == list_icons){
		if (panel->marked == 0){
			gtk_clip_label_set (label, "");
		}
	}
}

static GtkWidget *
panel_create_filter (Dlg_head *h, WPanel *panel, void **filter_w)
{
	GtkWidget *fhbox;
	GtkWidget *button;
	GtkWidget *arrow;
	GtkWidget *label;
	GtkWidget *ihbox;
	WInput *in;

	fhbox = gtk_hbox_new (FALSE, 0);

	/* Filter popup button */

	button = gtk_button_new ();
	gtk_signal_connect (GTK_OBJECT (button), "clicked",
			    (GtkSignalFunc) show_filter_popup,
			    panel);
	GTK_WIDGET_UNSET_FLAGS (button, GTK_CAN_FOCUS);
	gtk_box_pack_start (GTK_BOX (fhbox), button, FALSE, FALSE, 0);
	gtk_widget_show (button);

	ihbox = gtk_hbox_new (FALSE, 3);
	gtk_container_add (GTK_CONTAINER (button), ihbox);
	gtk_widget_show (ihbox);

	arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_OUT);
	gtk_box_pack_start (GTK_BOX (ihbox), arrow, TRUE, TRUE, 0);
	gtk_widget_show (arrow);

	label = gtk_label_new (N_("Filter"));
	gtk_box_pack_start (GTK_BOX (ihbox), label, TRUE, TRUE, 0);
	gtk_widget_show (label);

	/* Filter input line */

	in = input_new (0, 0, 0, 10, "", "filter");
	add_widget (h, in);

	/* Force the creation of the gtk widget */
	send_message_to (h, (Widget *) in, WIDGET_INIT, 0);
	*filter_w = in;

	gtk_box_pack_start (GTK_BOX (fhbox), GTK_WIDGET (in->widget.wdata), TRUE, TRUE, 0);

	return fhbox;
}

static void
panel_back (GtkWidget *button, WPanel *panel)
{
	directory_history_prev (panel);
}

static void
panel_fwd (GtkWidget *button, WPanel *panel)
{
	directory_history_next (panel);
}

static void
panel_up (GtkWidget *button, WPanel *panel)
{
	do_panel_cd (panel, "..", cd_exact);
}

static GtkWidget *
button_switch_to (char **icon, GtkSignalFunc fn, void *closure)
{
	GtkWidget *button, *pix;

	button = gtk_button_new ();
	pix = gnome_pixmap_new_from_xpm_d (icon);
	gtk_container_add (GTK_CONTAINER (button), pix);
	gtk_signal_connect (GTK_OBJECT (button), "clicked", fn, closure);
			    
	return button;
}

static void
do_switch_to_iconic (GtkWidget *widget, WPanel *panel)
{
	if (panel->list_type == list_icons)
		return;
	panel->list_type = list_icons;
	set_panel_formats (panel);
	paint_panel (panel);
	do_refresh ();
}

static void
do_switch_to_listing (GtkWidget *widget, WPanel *panel)
{
	if (panel->list_type != list_icons)
		return;
	panel->list_type = list_full;
	set_panel_formats (panel);
	paint_panel (panel);
	do_refresh ();
}

static GtkWidget *
button_switch_to_icon (WPanel *panel)
{
	return button_switch_to (listing_iconic_xpm, GTK_SIGNAL_FUNC (do_switch_to_iconic), panel);
}

static GtkWidget *
button_switch_to_listing (WPanel *panel)
{
	return button_switch_to (listing_list_xpm, GTK_SIGNAL_FUNC (do_switch_to_listing), panel);
}

void
x_create_panel (Dlg_head *h, widget_data parent, WPanel *panel)
{
	GtkWidget *status_line, *filter, *vbox;
	GtkWidget *frame, *cwd, *back_p, *fwd_p;
	GtkWidget *display;

	panel->xwindow = gtk_widget_get_toplevel (GTK_WIDGET (panel->widget.wdata));
	
	panel->table = gtk_table_new (2, 1, 0);

	panel->icons = panel_create_icon_display (panel);
	panel->list  = panel_create_file_list (panel);
	gtk_widget_ref (panel->icons);
	gtk_widget_ref (panel->list);

	if (panel->list_type == list_icons)
		display = panel->icons;
	else
		display = panel->list;

	filter = panel_create_filter (h, panel, &panel->filter_w);
	cwd = panel_create_cwd (h, panel, &panel->current_dir);
	
	/* We do not want the focus by default  (and the previos add_widget just gave it to us) */
	h->current = h->current->prev;

	/* buttons */
	back_p = gnome_stock_pixmap_widget_new (panel->xwindow, GNOME_STOCK_MENU_BACK);
	fwd_p  = gnome_stock_pixmap_widget_new (panel->xwindow, GNOME_STOCK_MENU_FORWARD);
	
	panel->up_b    = gtk_button_new_with_label ("up");
	panel->back_b   = gtk_button_new ();
	panel->fwd_b    = gtk_button_new ();
	gtk_container_add (GTK_CONTAINER (panel->back_b), back_p);
	gtk_container_add (GTK_CONTAINER (panel->fwd_b), fwd_p);
	gtk_signal_connect (GTK_OBJECT (panel->back_b), "clicked", GTK_SIGNAL_FUNC(panel_back), panel);
	gtk_signal_connect (GTK_OBJECT (panel->fwd_b), "clicked", GTK_SIGNAL_FUNC(panel_fwd), panel);
	gtk_signal_connect (GTK_OBJECT (panel->up_b), "clicked", GTK_SIGNAL_FUNC(panel_up), panel);
	panel_update_marks (panel);
	
	/* ministatus */
	panel->ministatus = gtk_clip_label_new ("");
	gtk_misc_set_alignment (GTK_MISC (panel->ministatus), 0.0, 0.0);
	gtk_misc_set_padding (GTK_MISC (panel->ministatus), 3, 0);
	gtk_widget_show (panel->ministatus);
	
	status_line = gtk_hbox_new (0, 0);
	gtk_container_border_width (GTK_CONTAINER (status_line), 3);
	
	gtk_label_set_justify (GTK_LABEL (panel->ministatus), GTK_JUSTIFY_LEFT);
	gtk_box_pack_start (GTK_BOX (status_line), panel->back_b, 0, 0, 2);
	gtk_box_pack_start (GTK_BOX (status_line), panel->up_b, 0, 0, 2);
	gtk_box_pack_start (GTK_BOX (status_line), panel->fwd_b, 0, 0, 2);
	gtk_box_pack_start (GTK_BOX (status_line), cwd, 1, 1, 5);
	gtk_box_pack_start (GTK_BOX (status_line), button_switch_to_icon (panel), 0, 0, 2);
	gtk_box_pack_start (GTK_BOX (status_line), button_switch_to_listing (panel), 0, 0, 2);
#if 0
	/* Remove the filter for now, until I add another toolbar */
	gtk_box_pack_end   (GTK_BOX (status_line), filter, 0, 0, 0);
#endif
	gtk_widget_show_all (status_line);
	
	/* The statusbar */
	frame = gtk_frame_new (NULL);
	gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
	gtk_container_border_width (GTK_CONTAINER (frame), 3);

	panel->status = gtk_clip_label_new ("");
	gtk_misc_set_alignment (GTK_MISC (panel->status), 0.0, 0.5);
	gtk_misc_set_padding (GTK_MISC (panel->status), 3, 0);
	gtk_container_add (GTK_CONTAINER (frame), panel->status);
	gtk_label_set_justify (GTK_LABEL (panel->status), GTK_JUSTIFY_LEFT);
	gtk_widget_show_all (frame);

	/* Add both the icon view and the listing view */
	gtk_table_attach (GTK_TABLE (panel->table), panel->icons, 0, 1, 1, 2,
			  GTK_EXPAND | GTK_FILL | GTK_SHRINK, 
			  GTK_EXPAND | GTK_FILL | GTK_SHRINK,
			  0, 0);
	gtk_table_attach (GTK_TABLE (panel->table), panel->list, 0, 1, 1, 2,
			  GTK_EXPAND | GTK_FILL | GTK_SHRINK, 
			  GTK_EXPAND | GTK_FILL | GTK_SHRINK,
			  0, 0);
	gtk_widget_show (display);
	
	gtk_table_attach (GTK_TABLE (panel->table), status_line, 0, 1, 0, 1,
			  GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);

	gtk_table_attach (GTK_TABLE (panel->table), panel->ministatus, 0, 1, 2, 3,
			  GTK_EXPAND | GTK_FILL | GTK_SHRINK,
			  0, 0, 0);
	gtk_table_attach (GTK_TABLE (panel->table), frame, 0, 1, 3, 4,
			  GTK_EXPAND | GTK_FILL,
			  0, 0, 0);
	

	/* Ultra nasty hack: pull the vbox from wdata */
	vbox =  GTK_WIDGET (panel->widget.wdata);
	
	panel->widget.wdata = (widget_data) panel->table;
	
	/* Now, insert our table in our parent */
	gtk_container_add (GTK_CONTAINER (vbox), panel->table);
	gtk_widget_show (vbox);
	gtk_widget_show (panel->table);

	if (!(panel->widget.options & W_PANEL_HIDDEN))
		gtk_widget_show (gtk_widget_get_toplevel (panel->table));

	/* This is a bug workaround for the icon list, as the icon */
	gtk_widget_queue_resize (panel->icons);
			     
	if (!pixmaps_ready){
		if (!GTK_WIDGET_REALIZED (panel->list))
			gtk_widget_realize (panel->list);
		panel_create_pixmaps (panel->list);
	}

	/* In GNOME the panel wants to have the cursor, to avoid "auto" focusing the
	 * filter input line
	 */
	panel->widget.options |= W_WANT_CURSOR;
	panel->estimated_total = 0;
}

void
panel_update_cols (Widget *panel, int frame_size)
{
	panel->cols  = 60;
	panel->lines = 20;
}

char *
get_nth_panel_name (int num)
{
    static char buffer [20];
    
    if (!num)
        return "New Left Panel";
    else if (num == 1)
        return "New Right Panel";
    else {
        sprintf (buffer, "%ith Panel", num);
        return buffer;
    }
}

void
load_hint (void)
{
	char *hint;
	
	if ((hint = get_random_hint ())){
		if (*hint)
			set_hintbar (hint);
		free (hint);
	} else
		set_hintbar ("The GNOME File Manager " VERSION);
	
}

void
paint_frame (WPanel *panel)
{
}

void
x_reset_sort_labels (WPanel *panel)
{
	if (panel->list_type == list_icons){
		if (panel->icons)
			gtk_widget_show (panel->icons);
		if (panel->list)
			gtk_widget_hide (panel->list);
	} else {
		panel_switch_new_display_mode (panel);
		if (panel->list)
			gtk_widget_show (panel->list);
		if (panel->icons)
			gtk_widget_hide (panel->icons);
	}
}

/* Releases all of the X resources allocated */
void
x_panel_destroy (WPanel *panel)
{
	gtk_widget_destroy (GTK_WIDGET (panel->xwindow));
}



