/* Desktop management for the Midnight Commander
 *
 * Copyright (C) 1998 The Free Software Foundation
 *
 * Authors: Federico Mena <federico@nuclecu.unam.mx>
 *          Miguel de Icaza <miguel@nuclecu.unam.mx>
 */

#include <config.h>
#include "global.h"
#include <gdk/gdkx.h>
#include <gtk/gtkinvisible.h>
#include <gnome.h>
#include "dialog.h"
#define DIR_H_INCLUDE_HANDLE_DIRENT /* bleah */
#include "dir.h"
#include "ext.h"
#include "file.h"
#include "fileopctx.h"
#include "gconf.h"
#include "gdesktop.h"
#include "gdesktop-icon.h"
#include "gicon.h"
#include "gmain.h"
#include "gmetadata.h"
#include "gdnd.h"
#include "gpopup.h"
#include "../vfs/vfs.h"
#include "main.h"

/* Name of the user's desktop directory (i.e. ~/desktop) */
#define DESKTOP_DIR_NAME "desktop"


struct layout_slot {
	int num_icons;		      /* Number of icons in this slot */
	GList *icons;		      /* The list of icons in this slot */
};


/* Configuration options for the desktop */

int desktop_use_shaped_icons = TRUE;
int desktop_auto_placement = FALSE;
int desktop_snap_icons = FALSE;

/* The computed name of the user's desktop directory */
static char *desktop_directory;

/* Layout information:  number of rows/columns for the layout slots, and the array of slots.  Each
 * slot is an integer that specifies the number of icons that belong to that slot.
 */
static int layout_screen_width;
static int layout_screen_height;
static int layout_cols;
static int layout_rows;
static struct layout_slot *layout_slots;

#define l_slots(u, v) (layout_slots[(u) * layout_rows + (v)])

/* The last icon to be selected */
static DesktopIconInfo *last_selected_icon;

/* Drag and drop sources and targets */

static GtkTargetEntry dnd_icon_sources[] = {
	{ TARGET_MC_DESKTOP_ICON_TYPE, 0, TARGET_MC_DESKTOP_ICON },
	{ TARGET_URI_LIST_TYPE, 0, TARGET_URI_LIST },
	{ TARGET_TEXT_PLAIN_TYPE, 0, TARGET_TEXT_PLAIN },
	{ TARGET_URL_TYPE, 0, TARGET_URL }
};

static GtkTargetEntry dnd_icon_targets[] = {
	{ TARGET_MC_DESKTOP_ICON_TYPE, 0, TARGET_MC_DESKTOP_ICON },
	{ TARGET_URI_LIST_TYPE, 0, TARGET_URI_LIST },
	{ TARGET_URL_TYPE, 0, TARGET_URL }
};

static GtkTargetEntry dnd_desktop_targets[] = {
	{ TARGET_MC_DESKTOP_ICON_TYPE, 0, TARGET_MC_DESKTOP_ICON },
	{ TARGET_URI_LIST_TYPE, 0, TARGET_URI_LIST },
	{ TARGET_URL_TYPE, 0, TARGET_URL }
};

static int dnd_icon_nsources = sizeof (dnd_icon_sources) / sizeof (dnd_icon_sources[0]);
static int dnd_icon_ntargets = sizeof (dnd_icon_targets) / sizeof (dnd_icon_targets[0]);
static int dnd_desktop_ntargets = sizeof (dnd_desktop_targets) / sizeof (dnd_desktop_targets[0]);

/* Proxy window for DnD and clicks on the desktop */
static GtkWidget *proxy_invisible;

/* Offsets for the DnD cursor hotspot */
static int dnd_press_x, dnd_press_y;

/* Whether a call to select_icon() is pending because the initial click on an
 * icon needs to be resolved at button release time.  Also, we store the
 * event->state.
 */
static int dnd_select_icon_pending;
static guint dnd_select_icon_pending_state;

/* Whether the button release signal on a desktop icon should be stopped due to
 * the icon being selected by clicking on the text item.
 */
static int icon_select_on_text;

/* Proxy window for clicks on the root window */
static GdkWindow *click_proxy_gdk_window;

/* GC for drawing the rubberband rectangle */
static GdkGC *click_gc;

/* Starting click position and event state for rubberbanding on the desktop */
static int click_start_x;
static int click_start_y;
static int click_start_state;

/* Current mouse position for rubberbanding on the desktop */
static int click_current_x;
static int click_current_y;

static int click_dragging;


static DesktopIconInfo *desktop_icon_info_new (char *filename, char *url, char *caption,
					       int xpos, int ypos);


/* Convenience function to figure out the slot corresponding to an (x, y) position */
static void
get_slot_from_pos (int x, int y, int *u, int *v)
{
	*u = (x + DESKTOP_SNAP_X / 2) / DESKTOP_SNAP_X;
	*v = (y + DESKTOP_SNAP_Y / 2) / DESKTOP_SNAP_Y;
}

/* Looks for a free slot in the layout_slots array and returns the coordinates
 * that coorespond to it.  "Free" means it either has zero icons in it, or it
 * has the minimum number of icons of all the slots.  Returns the number of
 * icons in the sought spot (ideally 0).
 */
static int
auto_pos (int start_slot, int end_slot, int *slot)
{
	int min, min_slot;
	int i;
	int val;

	min = layout_slots[start_slot].num_icons;
	min_slot = start_slot;

	for (i = start_slot; i < end_slot; i++) {
		val = layout_slots[i].num_icons;

		if (val < min || val == 0) {
			min = val;
			min_slot = i;
			if (val == 0)
				break;
		}
	}

	*slot = min_slot;
	return min;
}

/* Looks for free space in the icon grid, scanning it in column-wise order */
static void
get_icon_auto_pos (int *x, int *y)
{
	int start, end;
	int u, v;
	int val1, val2;
	int slot1, slot2;
	int slot;

	get_slot_from_pos (*x, *y, &u, &v);
	start = u * layout_rows + v;
	end = layout_rows * layout_cols;

	/* Look forwards until the end of the grid.  If we could not find an
	 * empty spot, find the second best.
	 */

	val1 = auto_pos (start, end, &slot1);

	if (val1 == 0)
		slot = slot1;
	else {
		val2 = auto_pos (0, start, &slot2);
		if (val2 < val1)
			slot = slot2;
	}

	*x = (slot / layout_rows) * DESKTOP_SNAP_X;
	*y = (slot % layout_rows) * DESKTOP_SNAP_Y;
}

/* Snaps the specified position to the icon grid.  It looks for the closest free spot on the grid,
 * or the closest one that has the least number of icons in it.
 */
static void
get_icon_snap_pos (int *x, int *y)
{
	int min, min_x, min_y;
	int min_dist;
	int u, v;
	int val, dist;
	int dx, dy;

	min = l_slots (0, 0).num_icons;
	min_x = min_y = 0;
	min_dist = INT_MAX;

	for (u = 0; u < layout_cols; u++)
		for (v = 0; v < layout_rows; v++) {
			val = l_slots (u, v).num_icons;

			dx = *x - u * DESKTOP_SNAP_X;
			dy = *y - v * DESKTOP_SNAP_Y;
			dist = dx * dx + dy * dy;

			if ((val == min && dist < min_dist) || (val < min)) {
				min = val;
				min_dist = dist;
				min_x = u;
				min_y = v;
			}
		}

	*x = min_x * DESKTOP_SNAP_X;
	*y = min_y * DESKTOP_SNAP_Y;
}

/* Removes an icon from the slot it is in, if any */
static void
remove_from_slot (DesktopIconInfo *dii)
{
	if (dii->slot == -1)
		return;

	g_assert (layout_slots[dii->slot].num_icons >= 1);
	g_assert (layout_slots[dii->slot].icons != NULL);

	layout_slots[dii->slot].num_icons--;
	layout_slots[dii->slot].icons = g_list_remove (layout_slots[dii->slot].icons, dii);

	dii->slot = -1;
	dii->x = 0;
	dii->y = 0;
}

/* Places a desktop icon on the specified position */
static void
desktop_icon_info_place (DesktopIconInfo *dii, int xpos, int ypos)
{
	int u, v;
	char *filename;

	remove_from_slot (dii);

	if (xpos < 0)
		xpos = 0;
	else if (xpos > layout_screen_width)
		xpos = layout_screen_width - DESKTOP_SNAP_X;

	if (ypos < 0)
		ypos = 0;
	else if (ypos > layout_screen_height)
		ypos = layout_screen_height - DESKTOP_SNAP_Y;

	/* Increase the number of icons in the corresponding slot */

	get_slot_from_pos (xpos, ypos, &u, &v);

	dii->slot = u * layout_rows + v;
	layout_slots[dii->slot].num_icons++;
	layout_slots[dii->slot].icons = g_list_append (layout_slots[dii->slot].icons, dii);

	/* Move the icon */
	
	dii->x = xpos;
	dii->y = ypos;
	gtk_widget_set_uposition (dii->dicon, xpos, ypos);

	/* Save the information */

	filename = g_concat_dir_and_file (desktop_directory, dii->filename);
	gmeta_set_icon_pos (filename, dii->x, dii->y);
	g_free (filename);
}

/* Destroys all the current desktop icons */
static void
destroy_desktop_icons (void)
{
	int i;
	GList *l;
	DesktopIconInfo *dii;

	for (i = 0; i < (layout_cols * layout_rows); i++) {
		l = layout_slots[i].icons;

		while (l) {
			dii = l->data;
			l = l->next;

			desktop_icon_info_destroy (dii);
		}
	}
}

/* Returns a list with all of the icons on the desktop */
static GList *
get_all_icons (void)
{
	GList *l, *res;
	int i;

	res = NULL;

	for (i = 0; i < (layout_cols * layout_rows); i++)
		for (l = layout_slots [i].icons; l; l = l->next)
			res = g_list_prepend (res, l->data);

	return res;
}

/* Returns the node in the list of DesktopIconInfo structures that contains the
 * icon for the specified filename.  If no icon is found, then returns NULL.
 */
static GList *
icon_exists_in_list (GList *list, char *filename)
{
	GList *l;
	DesktopIconInfo *dii;

	for (l = list; l; l = l->next) {
		dii = l->data;

		if (strcmp (filename, dii->filename) == 0)
			return l;
	}

	return NULL;
}

typedef struct {
	char *filename;
	char *url;
	char *caption;
} file_and_url_t;

/* Reloads the desktop icons efficiently.  If there are "new" files for which no
 * icons have been created, then icons for them will be created started at the
 * specified position if user_pos is TRUE.  If it is FALSE, the icons will be
 * auto-placed.
 */
static void
reload_desktop_icons (int user_pos, int xpos, int ypos)
{
	struct dirent *dirent;
	DIR *dir;
	char *full_name;
	int have_pos, x, y, size;
	DesktopIconInfo *dii;
	GSList *need_position_list, *sl;
	GList *all_icons, *l;
	char *desktop_url, *caption;
	int orig_xpos, orig_ypos;

	dir = mc_opendir (desktop_directory);
	if (!dir) {
		message (FALSE,
			 _("Warning"),
			 _("Could not open %s; will not have desktop icons"),
			 desktop_directory);
		return;
	}

	gnome_metadata_lock ();

	/* Read the directory.  For each file for which we do have an existing
	 * icon, do nothing.  Otherwise, if the file has its metadata for icon
	 * position set, create an icon for it.  Otherwise, store it in a list
	 * of new icons for which positioning is pending.
	 */

	need_position_list = NULL;
	all_icons = get_all_icons ();
	
	while ((dirent = mc_readdir (dir)) != NULL) {
		/* Skip . and .. */

		if ((dirent->d_name[0] == '.' && dirent->d_name[1] == 0)
		    || (dirent->d_name[0] == '.' && dirent->d_name[1] == '.'
			&& dirent->d_name[2] == 0))
			continue;

		l = icon_exists_in_list (all_icons, dirent->d_name);
		if (l) {
			GdkImlibImage *im;
			file_entry *fe;

			/* Reload the icon for this file, as it may have changed */

			full_name = g_concat_dir_and_file (desktop_directory, dirent->d_name);
			fe = file_entry_from_file (full_name);
			im = gicon_get_icon_for_file_speed (desktop_directory, fe, FALSE);
			file_entry_free (fe);
			g_free (full_name);

			dii = l->data;
			desktop_icon_set_icon (DESKTOP_ICON (dii->dicon), im);

			/* Leave the icon in the desktop by removing it from the list */

			all_icons = g_list_remove_link (all_icons, l); 
			continue;
		}

		full_name = g_concat_dir_and_file (desktop_directory, dirent->d_name);
		have_pos = gmeta_get_icon_pos (full_name, &x, &y);

		if (gnome_metadata_get (full_name, "desktop-url", &size, &desktop_url) != 0)
			desktop_url = NULL;

		caption = NULL;
		gnome_metadata_get (full_name, "icon-caption", &size, &caption);
		
		if (have_pos) {
			dii = desktop_icon_info_new (dirent->d_name, desktop_url, caption, x, y);
			gtk_widget_show (dii->dicon);

			g_free (full_name);
		} else {
			file_and_url_t *fau;

			fau = g_new0 (file_and_url_t, 1);
			fau->filename = g_strdup (dirent->d_name);
			if (desktop_url)
				fau->url      = g_strdup (desktop_url);
			if (caption)
				fau->caption  = g_strdup (caption);
			need_position_list = g_slist_prepend (need_position_list, fau);
		}

		if (desktop_url)
			g_free (desktop_url);

		if (caption)
			g_free (caption);
	}

	mc_closedir (dir);

	/* all_icons now contains a list of all of the icons that were not found
	 * in the ~/desktop directory, remove them.  To be paranoically tidy,
	 * also delete the icon position information -- someone may have deleted
	 * a file under us.
	 */

	for (l = all_icons; l; l = l->next) {
		dii = l->data;
		full_name = g_concat_dir_and_file (desktop_directory, dii->filename);
		gmeta_del_icon_pos (full_name);
		g_free (full_name);
		desktop_icon_info_destroy (dii);
	}

	g_list_free (all_icons);

	/* Now create the icons for all the files that did not have their
	 * position set.  This makes auto-placement work correctly without
	 * overlapping icons.
	 */

	need_position_list = g_slist_reverse (need_position_list);

	orig_xpos = orig_ypos = 0;

	for (sl = need_position_list; sl; sl = sl->next) {
		file_and_url_t *fau = sl->data;

		if (user_pos && sl == need_position_list) {
			/* If we are on the first icon, place it "by hand".
			 * Else, use automatic placement based on the position
			 * of the first icon of the series.
			 */
			if (desktop_auto_placement) {
				xpos = ypos = 0;
				get_icon_auto_pos (&xpos, &ypos);
			} else if (desktop_snap_icons)
				get_icon_snap_pos (&xpos, &ypos);

			orig_xpos = xpos;
			orig_ypos = ypos;
		} else {
			xpos = orig_xpos;
			ypos = orig_ypos;
			get_icon_auto_pos (&xpos, &ypos);
		}

		dii = desktop_icon_info_new (fau->filename, fau->url, fau->caption, xpos, ypos);
		gtk_widget_show (dii->dicon);

		if (fau->url)
			g_free (fau->url);
		if (fau->caption)
			g_free (fau->caption);
		g_free (fau->filename);
		g_free (fau);
	}

	g_slist_free (need_position_list);
	gnome_metadata_unlock ();

	/* Flush events to make the icons paint themselves */
	x_flush_events ();
}

/* Perform automatic arrangement of the desktop icons */
static void
arrange_desktop_icons (void)
{
	GList *icons, *l;
	int xpos, ypos;

	icons = g_list_reverse (get_all_icons ());

	for (l = icons; l; l = l->next)
		remove_from_slot (l->data);

	for (l = icons; l; l = l->next) {
		get_icon_auto_pos (&xpos, &ypos);
		desktop_icon_info_place (l->data, xpos, ypos);
	}

	g_list_free (icons);
}

/* Unselects all the desktop icons except the one in exclude */
static void
unselect_all (DesktopIconInfo *exclude)
{
	int i;
	GList *l;
	DesktopIconInfo *dii;

	for (i = 0; i < (layout_cols * layout_rows); i++)
		for (l = layout_slots[i].icons; l; l = l->next) {
			dii = l->data;

			if (dii->selected && dii != exclude) {
				desktop_icon_select (DESKTOP_ICON (dii->dicon), FALSE);
				dii->selected = FALSE;
			}
		}
}

/* Sets the selection state of a range to the specified value.  The range starts at the
 * last_selected_icon and ends at the specified icon.
 */
static void
select_range (DesktopIconInfo *dii, int sel)
{
	int du, dv, lu, lv;
	int min_u, min_v;
	int max_u, max_v;
	int u, v;
	GList *l;
	DesktopIconInfo *ldii;

	/* Find out the selection range */

	if (!last_selected_icon)
		last_selected_icon = dii;

	du = dii->slot / layout_rows;
	dv = dii->slot % layout_rows;
	lu = last_selected_icon->slot / layout_rows;
	lv = last_selected_icon->slot % layout_rows;

	if (du < lu) {
		min_u = du;
		max_u = lu;
	} else {
		min_u = lu;
		max_u = du;
	}

	if (dv < lv) {
		min_v = dv;
		max_v = lv;
	} else {
		min_v = lv;
		max_v = dv;
	}

	/* Select all the icons in the rectangle */

	for (u = min_u; u <= max_u; u++)
		for (v = min_v; v <= max_v; v++)
			for (l = l_slots (u, v).icons; l; l = l->next) {
				ldii = l->data;

				desktop_icon_select (DESKTOP_ICON (ldii->dicon), sel);
				ldii->selected = sel;
			}
}

/*
 * Handles icon selection and unselection due to button presses.  The
 * event_state is the state field of the event.
 */
static void
select_icon (DesktopIconInfo *dii, int event_state)
{
	int range;
	int additive;

	range = ((event_state & GDK_SHIFT_MASK) != 0);
	additive = ((event_state & GDK_CONTROL_MASK) != 0);

	if (!additive)
		unselect_all (NULL);

	if (!range) {
		if (additive) {
			desktop_icon_select (DESKTOP_ICON (dii->dicon), !dii->selected);
			dii->selected = !dii->selected;
		} else if (!dii->selected) {
			desktop_icon_select (DESKTOP_ICON (dii->dicon), TRUE);
			dii->selected = TRUE;
		}

		last_selected_icon = dii;

		if (dii->selected)
			gdk_window_raise (dii->dicon->window);
	} else
		select_range (dii, TRUE);
}

/* Creates a file entry structure and fills it with information appropriate to the specified file.  */
file_entry *
file_entry_from_file (char *filename)
{
	file_entry *fe;
	struct stat s;

	if (mc_lstat (filename, &s) == -1) {
		g_warning ("Could not stat %s, bad things will happen", filename);
		return NULL;
	}

	fe = g_new (file_entry, 1);
	fe->fname = g_strdup (x_basename (filename));
	fe->fnamelen = strlen (fe->fname);
	fe->buf = s;
	fe->f.marked = FALSE;
	fe->f.link_to_dir = FALSE;
	fe->f.stalled_link = FALSE;

	if (S_ISLNK (s.st_mode)) {
		struct stat s2;

		if (mc_stat (filename, &s2) == 0)
			fe->f.link_to_dir = S_ISDIR (s2.st_mode) != 0;
		else
			fe->f.stalled_link = TRUE;
	}

	return fe;
}

/* Frees a file entry structure */
void
file_entry_free (file_entry *fe)
{
	if (fe->fname)
		g_free (fe->fname);

	g_free (fe);
}

/*
 * Callback used when an icon's text changes.  We must validate the
 * rename and return the appropriate value.  The desktop icon info
 * structure is passed in the user data.
 */
static int
text_changed (GnomeIconTextItem *iti, gpointer data)
{
	DesktopIconInfo *dii;
	char *new_name;
	char *source;
	char *dest;
	int retval;

	dii = data;

	source = g_concat_dir_and_file (desktop_directory, dii->filename);
	new_name = gnome_icon_text_item_get_text (iti);
	dest = g_concat_dir_and_file (desktop_directory, new_name);

	if (mc_rename (source, dest) == 0) {
		g_free (dii->filename);
		dii->filename = g_strdup (new_name);
		retval = TRUE;
	} else
		retval = FALSE; /* FIXME: maybe pop up a warning/query dialog? */

	g_free (source);
	g_free (dest);

	return retval;
}

/*
 * Callback when an icon's text changes and the icon reprensents an
 * URL
 */
static int
text_changed_url (GnomeIconTextItem *iti, gpointer data)
{
	DesktopIconInfo *dii = data;
	char *fullname;
	char *new_text;
	
	fullname = g_concat_dir_and_file (desktop_directory, dii->filename);
	new_text = gnome_icon_text_item_get_text (iti);
	gnome_metadata_set (fullname, "icon-caption", strlen (new_text) + 1, new_text);

	return TRUE;
}

/* Sets up the mouse grab for when a desktop icon is being edited */
static void
setup_editing_grab (DesktopIconInfo *dii)
{
	GdkCursor *ibeam;

	ibeam = gdk_cursor_new (GDK_XTERM);
	gdk_pointer_grab (dii->dicon->window,
			  TRUE,
			  (GDK_BUTTON_PRESS_MASK
			   | GDK_BUTTON_RELEASE_MASK
			   | GDK_POINTER_MOTION_MASK
			   | GDK_ENTER_NOTIFY_MASK
			   | GDK_LEAVE_NOTIFY_MASK),
			  NULL,
			  ibeam,
			  GDK_CURRENT_TIME);
	gdk_cursor_destroy (ibeam);
}

/*
 * Callback used when the user begins editing the icon text item in a
 * desktop icon.  It installs the mouse and keyboard grabs that are
 * required while an icon is being edited.
 */
static void
editing_started (GnomeIconTextItem *iti, gpointer data)
{
	DesktopIconInfo *dii;

	dii = data;

	/* Disable drags from this icon until editing is finished */

	gtk_drag_source_unset (DESKTOP_ICON (dii->dicon)->canvas);

	/* Unselect all icons but this one */
	unselect_all (dii);

	gtk_grab_add (dii->dicon);
	setup_editing_grab (dii);
	gdk_keyboard_grab (GTK_LAYOUT (DESKTOP_ICON (dii->dicon)->canvas)->bin_window,
			   FALSE, GDK_CURRENT_TIME);
}

/* Sets up the specified icon as a drag source, but does not connect the signals */
static void
setup_icon_dnd_actions (DesktopIconInfo *dii)
{
	gtk_drag_source_set (DESKTOP_ICON (dii->dicon)->canvas,
			     GDK_BUTTON1_MASK | GDK_BUTTON2_MASK,
			     dnd_icon_sources,
			     dnd_icon_nsources,
			     GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK | GDK_ACTION_ASK);
}

/*
 * Callback used when the user finishes editing the icon text item in
 * a desktop icon.  It removes the mouse and keyboard grabs.
 */
static void
editing_stopped (GnomeIconTextItem *iti, gpointer data)
{
	DesktopIconInfo *dii;

	dii = data;

	gtk_grab_remove (dii->dicon);
	gdk_pointer_ungrab (GDK_CURRENT_TIME);
	gdk_keyboard_ungrab (GDK_CURRENT_TIME);

	/* Re-enable drags from this icon */

	setup_icon_dnd_actions (dii);
}

/* Callback used when the user stops selecting text in a desktop icon.  This function
 * restores the mouse grab that we had set up initially (the selection process changes
 * the grab and then removes it, so we need to restore the initial grab).
 */
static void
selection_stopped (GnomeIconTextItem *iti, gpointer data)
{
	DesktopIconInfo *dii;

	dii = data;
	setup_editing_grab (dii);
}

/**
 * desktop_icon_info_open:
 * @dii: The desktop icon to open.
 * 
 * Opens the specified desktop icon when the user double-clicks on it.
 **/
void
desktop_icon_info_open (DesktopIconInfo *dii)
{
	char *filename;
	file_entry *fe;

	if (dii->url){
		gnome_url_show (dii->url);
		return;
	}
	filename = g_concat_dir_and_file (desktop_directory, dii->filename);

	fe = file_entry_from_file (filename);

	if (S_ISDIR (fe->buf.st_mode) || link_isdir (fe))
		new_panel_at (filename);
	else {
		if (is_exe (fe->buf.st_mode) && if_link_is_exe (fe))
			my_system (EXECUTE_AS_SHELL, shell, filename);
		else
			gmc_open_filename (filename, NULL);
	}

	file_entry_free (fe);
	g_free (filename);
}

void
desktop_icon_info_delete (DesktopIconInfo *dii)
{
	char *full_name;
	struct stat s;
	FileOpContext *ctx;
	long progress_count = 0;
	double progress_bytes = 0;

	/* 1. Delete the file */
	ctx = file_op_context_new ();
	file_op_context_create_ui (ctx, OP_DELETE, TRUE);
	x_flush_events ();
	
	full_name = g_concat_dir_and_file (desktop_directory, dii->filename);

	if (lstat (full_name, &s) != -1) {
		if (S_ISLNK (s.st_mode))
			erase_file (ctx, full_name, &progress_count, &progress_bytes, TRUE);
		else {
			if (S_ISDIR (s.st_mode))
				erase_dir (ctx, full_name, &progress_count, &progress_bytes);
			else
				erase_file (ctx, full_name, &progress_count, &progress_bytes, TRUE);
		}

		gmeta_del_icon_pos (full_name);
	}

	g_free (full_name);
	file_op_context_destroy (ctx);

	/* 2. Destroy the dicon */
	desktop_icon_info_destroy (dii);
}

/* Used to execute the popup menu for desktop icons */
static void
do_popup_menu (DesktopIconInfo *dii, GdkEventButton *event)
{
	char *filename;

	filename = g_concat_dir_and_file (desktop_directory, dii->filename);

	if (gpopup_do_popup (event, NULL, dii, 0, filename) != -1)
		reload_desktop_icons (FALSE, 0, 0);

	g_free (filename);
}

/* Idle handler that opens a desktop icon.  See below for information on why we
 * do things this way.
 */
static gint
idle_open_icon (gpointer data)
{
	desktop_icon_info_open (data);
	return FALSE;
}

/* Event handler for desktop icons.  Button events are ignored when the icon is
 * being edited; these will be handled either by the icon's text item or the
 * icon_event_after() fallback.
 */
static gint
icon_event (GnomeCanvasItem *item, GdkEvent *event, gpointer data)
{
	DesktopIconInfo *dii;
	GnomeIconTextItem *iti;
	int on_text;
	int retval;

	dii = data;
	iti = GNOME_ICON_TEXT_ITEM (DESKTOP_ICON (dii->dicon)->text);

	on_text = item == GNOME_CANVAS_ITEM (iti);
	retval = FALSE;

	switch (event->type) {
	case GDK_BUTTON_PRESS:
		if (event->button.button == 1) {
			/* If se are editing, do not handle the event ourselves
			 * -- either let the text item handle it, or wait until
			 * we fall back to the icon_event_after() callback.
			 */
			if (iti->editing)
				break;

			/* Save the mouse position for DnD */

			dnd_press_x = event->button.x;
			dnd_press_y = event->button.y;

			/* Handle icon selection if we are not on the text item
			 * or if the icon is not selected in the first place.
			 * Otherwise, if there are modifier keys pressed, handle
			 * icon selection instead of starting editing.
			 */
			if (!on_text
			    || !dii->selected
			    || (event->button.state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK))) {
				/* If click on text, and the icon was not
				 * selected in the first place or shift is down,
				 * save this flag.
				 */
				if (on_text
				    && (!dii->selected || (event->button.state & GDK_SHIFT_MASK)))
					icon_select_on_text = TRUE;

				if ((dii->selected
				     && !(event->button.state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)))
				    || ((event->button.state & GDK_CONTROL_MASK)
					&& !(event->button.state & GDK_SHIFT_MASK))) {
					dnd_select_icon_pending = TRUE;
					dnd_select_icon_pending_state = event->button.state;
				} else
					select_icon (dii, event->button.state);

				retval = TRUE;
			}
		} else if (event->button.button == 3) {
			do_popup_menu (dii, (GdkEventButton *) event);
			retval = TRUE;
		}
		break;

	case GDK_2BUTTON_PRESS:
		if (event->button.button != 1 || iti->editing)
			break;

		/* We have an interesting race condition here.  If we open the
		 * desktop icon here instead of doing it in the idle handler,
		 * the icon thinks it must begin editing itself, even when the
		 * icon_select_on_text flag tries to prevent it.  I have no idea
		 * why this happens :-( - Federico
		 */
		gtk_idle_add (idle_open_icon, dii);
/*  		desktop_icon_info_open (dii); */
		icon_select_on_text = TRUE;
		retval = TRUE;
		break;

	case GDK_BUTTON_RELEASE:
		if (event->button.button != 1)
			break;

		if (on_text && icon_select_on_text) {
			icon_select_on_text = FALSE;
			retval = TRUE;
		}

		if (dnd_select_icon_pending) {
			select_icon (dii, dnd_select_icon_pending_state);
			dnd_select_icon_pending = FALSE;
			dnd_select_icon_pending_state = 0;
			retval = TRUE;
		}
		break;

	default:
		break;
	}

	/* If the click was on the text and we actually did something, then we
	 * need to stop the text item's event handler from executing.
	 */
	if (on_text && retval)
		gtk_signal_emit_stop_by_name (GTK_OBJECT (iti), "event");

	return retval;
}

/* Fallback handler for when the icon is being edited and the user clicks
 * outside the icon's text item.  This indicates that editing should be accepted
 * and terminated.
 */
static gint
icon_event_after (GtkWidget *widget, GdkEventButton *event, gpointer data)
{
	DesktopIconInfo *dii;
	GnomeIconTextItem *iti;

	dii = data;
	iti = GNOME_ICON_TEXT_ITEM (DESKTOP_ICON (dii->dicon)->text);

	if (event->type != GDK_BUTTON_PRESS)
		return FALSE;

	g_return_val_if_fail (iti->editing, FALSE); /* sanity check for dropped events */

	gnome_icon_text_item_stop_editing (iti, TRUE);
	return TRUE;
}

/* Callback used when a drag from the desktop icons is started.  We set the drag icon to the proper
 * pixmap.
 */
static void
drag_begin (GtkWidget *widget, GdkDragContext *context, gpointer data)
{
	DesktopIconInfo *dii;
	DesktopIcon *dicon;
	GtkArg args[3];
	GdkImlibImage *im;
	GdkPixmap *pixmap;
	GdkBitmap *mask;
	int x, y;

	dii = data;
	dicon = DESKTOP_ICON (dii->dicon);

	/* See if the icon was pending to be selected */

	if (dnd_select_icon_pending) {
		if (!dii->selected)
			select_icon (dii, dnd_select_icon_pending_state);

		dnd_select_icon_pending = FALSE;
		dnd_select_icon_pending_state = 0;
	}

	/* FIXME: see if it is more than one icon and if so, use a multiple-files icon. */

	args[0].name = "image";
	args[1].name = "x";
	args[2].name = "y";
	gtk_object_getv (GTK_OBJECT (dicon->icon), 3, args);
	im = GTK_VALUE_BOXED (args[0]);
	x = GTK_VALUE_DOUBLE (args[1]);
	y = GTK_VALUE_DOUBLE (args[2]);

	gdk_imlib_render (im, im->rgb_width, im->rgb_height);
	pixmap = gdk_imlib_copy_image (im);
	mask = gdk_imlib_copy_mask (im);

	gtk_drag_set_icon_pixmap (context,
				  gtk_widget_get_colormap (dicon->canvas),
				  pixmap,
				  mask,
				  dnd_press_x - x,
				  dnd_press_y - y);

	gdk_pixmap_unref (pixmap);
	gdk_bitmap_unref (mask);
}

/* Builds a string with the URI-list of the selected desktop icons */
static char *
build_selected_icons_uri_list (int *len)
{
	int i;
	GList *l;
	DesktopIconInfo *dii;
	char *filelist, *p;
	int desktop_dir_len;

	/* First, count the number of selected icons and add up their filename lengths */

	*len = 0;
	desktop_dir_len = strlen (desktop_directory);

	for (i = 0; i < (layout_cols * layout_rows); i++)
		for (l = layout_slots[i].icons; l; l = l->next) {
			dii = l->data;

			/* "file:" + desktop_directory + "/" + dii->filename + "\r\n" */

			if (dii->selected)
				*len += 5 + desktop_dir_len + 1 + strlen (dii->filename) + 2;
		}

	/* Second, create the file list string */

	filelist = g_new (char, *len + 1); /* plus 1 for null terminator */
	p = filelist;

	for (i = 0; i < (layout_cols * layout_rows); i++)
		for (l = layout_slots[i].icons; l; l = l->next) {
			dii = l->data;

			if (dii->selected) {
				strcpy (p, "file:");
				p += 5;

				strcpy (p, desktop_directory);
				p += desktop_dir_len;

				*p++ = '/';

				strcpy (p, dii->filename);
				p += strlen (dii->filename);

				strcpy (p, "\r\n");
				p += 2;
			}
		}

	*p = 0;

	return filelist;
}

/* Callback used to get the drag data from the desktop icons */
static void
drag_data_get (GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection_data,
	       guint info, guint32 time, gpointer data)
{
	DesktopIconInfo *dii;
	char *filelist;
	int len;
	GList *files;

	dii = data;
	filelist = build_selected_icons_uri_list (&len);

	switch (info) {
	case TARGET_MC_DESKTOP_ICON:
	case TARGET_URI_LIST:
	case TARGET_TEXT_PLAIN:
		gtk_selection_data_set (selection_data,
					selection_data->target,
					8,
					filelist,
					len);
		break;

	case TARGET_URL:
		if (dii->url)
			gtk_selection_data_set (selection_data,
						selection_data->target,
						8,
						dii->url,
						strlen (dii->url));
		else {
			files = gnome_uri_list_extract_uris (filelist);
			if (files)
				gtk_selection_data_set (selection_data,
							selection_data->target,
							8,
							files->data,
							strlen (files->data));

			gnome_uri_list_free_strings (files);
		}

		break;

	default:
		g_assert_not_reached ();
	}

	g_free (filelist);
}

/* Callback used when a drag from the desktop is finished.  We need to reload
 * the desktop.
 */
static void
drag_end (GtkWidget *widget, GdkDragContext *context, gpointer data)
{
	reload_desktop_icons (FALSE, 0, 0);
}

/* Set up a desktop icon as a DnD source */
static void
setup_icon_dnd_source (DesktopIconInfo *dii)
{
	setup_icon_dnd_actions (dii);

	gtk_signal_connect (GTK_OBJECT (DESKTOP_ICON (dii->dicon)->canvas), "drag_begin",
			    (GtkSignalFunc) drag_begin,
			    dii);
	gtk_signal_connect (GTK_OBJECT (DESKTOP_ICON (dii->dicon)->canvas), "drag_data_get",
			    GTK_SIGNAL_FUNC (drag_data_get),
			    dii);
	gtk_signal_connect (GTK_OBJECT (DESKTOP_ICON (dii->dicon)->canvas), "drag_end",
			    GTK_SIGNAL_FUNC (drag_end),
			    dii);
}

/* Callback used when we get a drag_motion event from a desktop icon.  We have
 * to decide which operation to perform based on the type of the data the user
 * is dragging.
 */
static gboolean
icon_drag_motion (GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time,
		  gpointer data)
{
	DesktopIconInfo *dii;
	char *filename;
	file_entry *fe;
	GdkDragAction action;
	GtkWidget *source_widget;
	int is_desktop_icon;

	dii = data;
	filename = g_concat_dir_and_file (desktop_directory, dii->filename);
	fe = file_entry_from_file (filename);
	g_free (filename);
	if (!fe)
		return 0; /* eeek */

	gdnd_find_panel_by_drag_context (context, &source_widget);
	is_desktop_icon = gdnd_drag_context_has_target (context, TARGET_MC_DESKTOP_ICON);

	action = gdnd_validate_action (context,
				       TRUE,
				       source_widget != NULL,
				       source_widget && is_desktop_icon,
				       fe,
				       dii->selected);

	gdk_drag_status (context, action, time);
	return TRUE;
}

/*
 * Returns the desktop icon that started the drag from the specified
 * context
 */
static DesktopIconInfo *
find_icon_by_drag_context (GdkDragContext *context)
{
	GtkWidget *source;
	int i;
	GList *l;
	DesktopIconInfo *dii;

	source = gtk_drag_get_source_widget (context);
	if (!source)
		return NULL;

	source = gtk_widget_get_toplevel (source);

	for (i = 0; i < (layout_cols * layout_rows); i++)
		for (l = layout_slots[i].icons; l; l = l->next) {
			dii = l->data;

			if (dii->dicon == source)
				return dii;
		}

	return NULL;
}

/*
 * Performs a drop of desktop icons onto the desktop.  It basically
 * moves the icons from their original position to the new
 * coordinates.
 */
static void
drop_desktop_icons (GdkDragContext *context, GtkSelectionData *data, int x, int y)
{
	DesktopIconInfo *source_dii, *dii;
	int dx, dy;
	int i;
	GList *l;
	GSList *sel_icons, *sl;

	/* Find the icon that the user is dragging */

	source_dii = find_icon_by_drag_context (context);
	if (!source_dii) {
		g_warning ("Eeeeek, could not find the icon that started the drag!");
		return;
	}

	/* Compute the distance to move icons */

	if (desktop_snap_icons)
		get_icon_snap_pos (&x, &y);

	dx = x - source_dii->x - dnd_press_x;
	dy = y - source_dii->y - dnd_press_y;

	/* Build a list of selected icons */

	sel_icons = NULL;

	for (i = 0; i < (layout_cols * layout_rows); i++)
		for (l = layout_slots[i].icons; l; l = l->next) {
			dii = l->data;
			if (dii->selected)
				sel_icons = g_slist_prepend (sel_icons, l->data);
		}

	/* Move the icons.  FIXME: handle auto-placement by reinserting the
	 * icons in the proper place.
	 */

	if (!desktop_auto_placement)
		for (sl = sel_icons; sl; sl = sl->next) {
			dii = sl->data;
			desktop_icon_info_place (dii, dii->x + dx, dii->y + dy);
		}

	/* Clean up */

	g_slist_free (sel_icons);
}

/* Handler for drag_data_received for desktop icons */
static void
icon_drag_data_received (GtkWidget *widget, GdkDragContext *context, gint x, gint y,
			 GtkSelectionData *data, guint info, guint time, gpointer user_data)
{
	DesktopIconInfo *dii;

	dii = user_data;

	if (gdnd_drag_context_has_target (context, TARGET_MC_DESKTOP_ICON) && dii->selected)
		drop_desktop_icons (context, data, x + dii->x, y + dii->y);
	else {
		char *full_name;
		file_entry *fe;

		full_name = g_concat_dir_and_file (desktop_directory, dii->filename);
		fe = file_entry_from_file (full_name);
		if (!fe)
			return; /* eeeek */

		if (gdnd_perform_drop (context, data, fe, full_name))
			reload_desktop_icons (FALSE, 0, 0);

		file_entry_free (fe);
	}
}

/* Set up a desktop icon as a DnD destination */
static void
setup_icon_dnd_dest (DesktopIconInfo *dii)
{
	gtk_drag_dest_set (DESKTOP_ICON (dii->dicon)->canvas,
			   GTK_DEST_DEFAULT_DROP,
			   dnd_icon_targets,
			   dnd_icon_ntargets,
			   GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK | GDK_ACTION_ASK);

	gtk_signal_connect (GTK_OBJECT (DESKTOP_ICON (dii->dicon)->canvas), "drag_motion",
			    (GtkSignalFunc) (icon_drag_motion),
			    dii);
	gtk_signal_connect (GTK_OBJECT (DESKTOP_ICON (dii->dicon)->canvas), "drag_data_received",
			    (GtkSignalFunc) icon_drag_data_received,
			    dii);
}

/* Creates a new desktop icon.  The filename is the pruned filename inside the
 * desktop directory.  It does not show the icon.
 */
static DesktopIconInfo *
desktop_icon_info_new (char *filename, char *url, char *caption, int xpos, int ypos)
{
	DesktopIconInfo *dii;
	file_entry *fe;
	char *full_name;
	GdkImlibImage *icon_im;
	GtkSignalFunc text_changed_func;
	
	/* Create the icon structure */

	full_name = g_concat_dir_and_file (desktop_directory, filename);
	fe = file_entry_from_file (full_name);

	dii = g_new (DesktopIconInfo, 1);
	dii->x = 0;
	dii->y = 0;
	dii->slot = -1;

	if (url) {
		dii->url = g_strdup (url);
		if (!caption)
			caption = url;
	} else {
		dii->url = NULL;
		caption = filename;
	}
	
	icon_im = gicon_get_icon_for_file_speed (desktop_directory, fe, FALSE);
	dii->dicon = desktop_icon_new (icon_im, caption);
	dii->filename = g_strdup (filename);
	dii->selected = FALSE;
	
	file_entry_free (fe);
	g_free (full_name);

	/* Connect to the icon's signals.  We connect to the image/stipple and
	 * text items separately so that the callback can distinguish between
	 * them.  This is not a hack.
	 */

	gtk_signal_connect (GTK_OBJECT (DESKTOP_ICON (dii->dicon)->icon), "event",
			    (GtkSignalFunc) icon_event,
			    dii);
	gtk_signal_connect (GTK_OBJECT (DESKTOP_ICON (dii->dicon)->stipple), "event",
			    (GtkSignalFunc) icon_event,
			    dii);
	gtk_signal_connect (GTK_OBJECT (DESKTOP_ICON (dii->dicon)->text), "event",
			    (GtkSignalFunc) icon_event,
			    dii);

	/* Connect_after to button presses on the icon's window.  This is a
	 * fallback for when the icon is being edited and a button press is not
	 * handled by the icon's text item -- this means the user has clicked
	 * outside the text item and wishes to accept and terminate editing.
	 */
	gtk_signal_connect_after (GTK_OBJECT (dii->dicon), "button_press_event",
				  (GtkSignalFunc) icon_event_after,
				  dii);

	/* Connect to the text item's signals */

	if (dii->url)
		text_changed_func = (GtkSignalFunc) text_changed_url;
	else
		text_changed_func = (GtkSignalFunc) text_changed;

	gtk_signal_connect (GTK_OBJECT (DESKTOP_ICON (dii->dicon)->text), "text_changed",
			    text_changed_func, dii);

	gtk_signal_connect (GTK_OBJECT (DESKTOP_ICON (dii->dicon)->text), "editing_started",
			    (GtkSignalFunc) editing_started,
			    dii);
	gtk_signal_connect (GTK_OBJECT (DESKTOP_ICON (dii->dicon)->text), "editing_stopped",
			    (GtkSignalFunc) editing_stopped,
			    dii);
	gtk_signal_connect (GTK_OBJECT (DESKTOP_ICON (dii->dicon)->text), "selection_stopped",
			    (GtkSignalFunc) selection_stopped,
			    dii);

	/* Prepare the DnD functionality for this icon */

	setup_icon_dnd_source (dii);
	setup_icon_dnd_dest (dii);

	/* Place the icon and append it to the list */

	desktop_icon_info_place (dii, xpos, ypos);
	return dii;
}

/**
 * desktop_icon_update_url:
 * @dii: the desktop icon
 *
 * Loads from the metadata updated versions of the caption
 * and the url
 */
void
desktop_icon_update_url (DesktopIconInfo *dii)
{
	char *fullname = g_concat_dir_and_file (desktop_directory, dii->filename);
	char *caption = NULL;
	char *url = NULL;
	int size;
	
	gnome_metadata_get (fullname, "icon-caption", &size, &caption);
	if (caption){
		desktop_icon_set_text (DESKTOP_ICON (dii->dicon), caption);
		g_free (caption);
	}

	gnome_metadata_get (fullname, "desktop-url", &size, &url);
	if (url){
		if (dii->url)
			g_free (dii->url);
		dii->url = url;
	}
	
	g_free (fullname);
}

/**
 * desktop_icon_info_destroy:
 * @dii: The desktop icon to destroy
 *
 * Destroys the specified desktop icon.
 **/
void
desktop_icon_info_destroy (DesktopIconInfo *dii)
{
	gtk_widget_destroy (dii->dicon);
	remove_from_slot (dii);

	gnome_metadata_delete (dii->filename);
	g_free (dii->url);
	g_free (dii->filename);
	g_free (dii);
}

/* Creates the layout information array */
static void
create_layout_info (void)
{
	layout_screen_width = gdk_screen_width ();
	layout_screen_height = gdk_screen_height ();
	layout_cols = (layout_screen_width + DESKTOP_SNAP_X - 1) / DESKTOP_SNAP_X;
	layout_rows = (layout_screen_height + DESKTOP_SNAP_Y - 1) / DESKTOP_SNAP_Y;
	layout_slots = g_new0 (struct layout_slot, layout_cols * layout_rows);
}

static void
setup_trashcan (char *desktop_dir)
{
	char *trashcan_dir;
	char *trash_pix;
	
	trashcan_dir = g_concat_dir_and_file (desktop_directory, _("Trashcan"));
	trash_pix = g_concat_dir_and_file (ICONDIR, "trash.xpm");
		
	if (!g_file_exists (trashcan_dir)){
		mkdir (trashcan_dir, 0777);
		gnome_metadata_set (
			trashcan_dir, "icon-filename", strlen (trash_pix) + 1, trash_pix);
	}
	
	g_free (trashcan_dir);
	g_free (trash_pix);
}

/*
 * Check that the user's desktop directory exists, and if not, create
 * the default desktop setup.
 */
static void
create_desktop_dir (void)
{
	char *home_link_name;

	if (getenv ("GNOME_DESKTOP_DIR") != NULL)
		desktop_directory = g_strdup (getenv ("GNOME_DESKTOP_DIR"));
	else
		desktop_directory = g_concat_dir_and_file (gnome_user_home_dir, DESKTOP_DIR_NAME);

	if (!g_file_exists (desktop_directory)) {
		/* Create the directory */

		mkdir (desktop_directory, 0777);

		/* Create the link to the user's home directory so that he will have an icon */
		home_link_name = g_concat_dir_and_file (desktop_directory, _("Home directory"));

		if (mc_symlink (gnome_user_home_dir, home_link_name) != 0) {
			message (FALSE,
				 _("Warning"),
				 _("Could not symlink %s to %s; will not have initial desktop icons."),
				 gnome_user_home_dir, home_link_name);
		}
		g_free (home_link_name);
	}

/*	setup_trashcan (desktop_directory); */
}

/* Property placed on target windows */
typedef struct {
  guint8 byte_order;
  guint8 protocol_version;
  guint8 protocol_style;
  guint8 pad;
  guint32 proxy_window;
  guint16 num_drop_sites;
  guint16 padding;
  guint32 total_size;
} MotifDragReceiverInfo;

/* Sets up a proxy window for DnD on the specified X window.  Courtesy of Owen Taylor */
static gboolean 
setup_xdnd_proxy (guint32 xid, GdkWindow *proxy_window)
{
	GdkAtom xdnd_proxy_atom;
	guint32 proxy_xid;
	Atom type;
	int format;
	unsigned long nitems, after;
	Window *proxy_data;
	Window proxy;
	guint32 old_warnings;

	XGrabServer (GDK_DISPLAY ());

	xdnd_proxy_atom = gdk_atom_intern ("XdndProxy", FALSE);
	proxy_xid = GDK_WINDOW_XWINDOW (proxy_window);
	type = None;
	proxy = None;

	old_warnings = gdk_error_warnings;

	gdk_error_code = 0;
	gdk_error_warnings = 0;

	/* Check if somebody else already owns drops on the root window */

	XGetWindowProperty (GDK_DISPLAY (), xid,
			    xdnd_proxy_atom, 0,
			    1, False, AnyPropertyType,
			    &type, &format, &nitems, &after,
			    (guchar **) &proxy_data);

	if (type != None) {
		if (format == 32 && nitems == 1)
			proxy = *proxy_data;

		XFree (proxy_data);
	}

	/* The property was set, now check if the window it points to exists and
	 * has a XdndProxy property pointing to itself.
	 */
	if (proxy) {
		XGetWindowProperty (GDK_DISPLAY (), proxy, 
				    xdnd_proxy_atom, 0, 
				    1, False, AnyPropertyType,
				    &type, &format, &nitems, &after, 
				    (guchar **) &proxy_data);

		if (!gdk_error_code && type != None) {
			if (format == 32 && nitems == 1)
				if (*proxy_data != proxy)
					proxy = GDK_NONE;

			XFree (proxy_data);
		} else
			proxy = GDK_NONE;
	}

	if (!proxy) {
		/* OK, we can set the property to point to us */

		XChangeProperty (GDK_DISPLAY (), xid,
				 xdnd_proxy_atom, gdk_atom_intern ("WINDOW", FALSE),
				 32, PropModeReplace,
				 (guchar *) &proxy_xid, 1);
	}

	gdk_error_code = 0;
	gdk_error_warnings = old_warnings;
  
	XUngrabServer (GDK_DISPLAY ());
	gdk_flush ();

	if (!proxy) {
		/* Mark our window as a valid proxy window with a XdndProxy
		 * property pointing recursively;
		 */
		XChangeProperty (GDK_DISPLAY (), proxy_xid,
				 xdnd_proxy_atom, gdk_atom_intern ("WINDOW", FALSE),
				 32, PropModeReplace,
				 (guchar *) &proxy_xid, 1);

		return TRUE;
	} else
		return FALSE;
}

/* Sets up a window as a Motif DnD proxy */
static void
setup_motif_dnd_proxy (guint32 xid, GdkWindow *proxy_window)
{
	guint32 proxy_xid;
	MotifDragReceiverInfo info;
	Atom receiver_info_atom;
	guint32 myint;

	myint = 0x01020304;
	proxy_xid = GDK_WINDOW_XWINDOW (proxy_window);
	receiver_info_atom = gdk_atom_intern ("_MOTIF_DRAG_RECEIVER_INFO", FALSE);

	info.byte_order = (*((gchar *) &myint) == 1) ? 'B' : 'l';
	info.protocol_version = 0;
	info.protocol_style = 5; /* XmDRAG_DYNAMIC */
	info.proxy_window = proxy_xid;
	info.num_drop_sites = 0;
	info.total_size = sizeof(info);

	XChangeProperty (gdk_display, xid,
			 receiver_info_atom,
			 receiver_info_atom,
			 8, PropModeReplace,
			 (guchar *)&info,
			 sizeof (info));
}

/* Callback used when we get a drag_motion event from the desktop.  We must
 * decide what kind of operation can be performed with what the user is
 * dragging.
 */
static gboolean
desktop_drag_motion (GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time,
		     gpointer data)
{
	GdkDragAction action;
	GtkWidget *source_widget;
	int is_desktop_icon;

	gdnd_find_panel_by_drag_context (context, &source_widget);
	is_desktop_icon = gdnd_drag_context_has_target (context, TARGET_MC_DESKTOP_ICON);

	action = gdnd_validate_action (context,
				       TRUE,
				       source_widget != NULL,
				       source_widget && is_desktop_icon,
				       NULL,
				       FALSE);

	gdk_drag_status (context, action, time);
	return TRUE;
}

/* Callback used when the root window receives a drop */
static void
desktop_drag_data_received (GtkWidget *widget, GdkDragContext *context, gint x, gint y,
			    GtkSelectionData *data, guint info, guint time, gpointer user_data)
{
	gint dx, dy;

	/* Fix the proxy window offsets */

	gdk_window_get_position (widget->window, &dx, &dy);
	x += dx;
	y += dy;

	if (gdnd_drag_context_has_target (context, TARGET_MC_DESKTOP_ICON))
		drop_desktop_icons (context, data, x, y);
	else {
		file_entry *desktop_fe;

		desktop_fe = file_entry_from_file (desktop_directory);
		if (!desktop_fe)
			return; /* eeek */

		if (gdnd_perform_drop (context, data, desktop_fe, desktop_directory))
			reload_desktop_icons (TRUE, x, y);

		file_entry_free (desktop_fe);
	}
}

/* Sets up drag and drop to the desktop root window */
static void
setup_desktop_dnd (void)
{
	if (!setup_xdnd_proxy (GDK_ROOT_WINDOW (), proxy_invisible->window))
		g_warning ("There is already a process taking drops on the desktop!\n");

	setup_motif_dnd_proxy (GDK_ROOT_WINDOW (), proxy_invisible->window);

	gtk_drag_dest_set (proxy_invisible,
			   GTK_DEST_DEFAULT_DROP,
			   dnd_desktop_targets,
			   dnd_desktop_ntargets,
			   GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK | GDK_ACTION_ASK);

	gtk_signal_connect (GTK_OBJECT (proxy_invisible), "drag_motion",
			    (GtkSignalFunc) desktop_drag_motion,
			    NULL);
	gtk_signal_connect (GTK_OBJECT (proxy_invisible), "drag_data_received",
			    (GtkSignalFunc) desktop_drag_data_received,
			    NULL);
}

/* Looks for the proxy window to get root window clicks from the window manager */
static GdkWindow *
find_click_proxy_window (void)
{
	GdkAtom click_proxy_atom;
	Atom type;
	int format;
	unsigned long nitems, after;
	Window *proxy_data;
	Window proxy;
	guint32 old_warnings;
	GdkWindow *proxy_gdk_window;

	XGrabServer (GDK_DISPLAY ());

	click_proxy_atom = gdk_atom_intern ("_WIN_DESKTOP_BUTTON_PROXY", FALSE);
	type = None;
	proxy = None;

	old_warnings = gdk_error_warnings;

	gdk_error_code = 0;
	gdk_error_warnings = 0;

	/* Check if the proxy window exists */

	XGetWindowProperty (GDK_DISPLAY (), GDK_ROOT_WINDOW (),
			    click_proxy_atom, 0,
			    1, False, AnyPropertyType,
			    &type, &format, &nitems, &after,
			    (guchar **) &proxy_data);

	if (type != None) {
		if (format == 32 && nitems == 1)
			proxy = *proxy_data;

		XFree (proxy_data);
	}

	/* The property was set, now check if the window it points to exists and
	 * has a _WIN_DESKTOP_BUTTON_PROXY property pointing to itself.
	 */

	if (proxy) {
		XGetWindowProperty (GDK_DISPLAY (), proxy,
				    click_proxy_atom, 0,
				    1, False, AnyPropertyType,
				    &type, &format, &nitems, &after,
				    (guchar **) &proxy_data);

		if (!gdk_error_code && type != None) {
			if (format == 32 && nitems == 1)
				if (*proxy_data != proxy)
					proxy = GDK_NONE;

			XFree (proxy_data);
		} else
			proxy = GDK_NONE;
	}

	gdk_error_code = 0;
	gdk_error_warnings = old_warnings;

	XUngrabServer (GDK_DISPLAY ());
	gdk_flush ();

	if (proxy)
		proxy_gdk_window = gdk_window_foreign_new (proxy);
	else
		proxy_gdk_window = NULL;

	return proxy_gdk_window;
}

/* Callback for arranging the icons on the desktop */
static void
handle_arrange_icons (GtkWidget *widget, gpointer data)
{
	arrange_desktop_icons ();
}

/* Callback for rescanning the desktop directory */
static void
handle_rescan_desktop (GtkWidget *widget, gpointer data)
{
	reload_desktop_icons (FALSE, 0, 0);
}

/* The popup menu for the desktop */
static GnomeUIInfo desktop_popup_items[] = {
	GNOMEUIINFO_ITEM_NONE (N_("Arrange Icons"), NULL, handle_arrange_icons),
	GNOMEUIINFO_ITEM_NONE (N_("Rescan Desktop"), NULL, handle_rescan_desktop),
	GNOMEUIINFO_END
};

/* Executes the popup menu for the desktop */
static void
desktop_popup (GdkEventButton *event)
{
	GtkWidget *popup;

	popup = gnome_popup_menu_new (desktop_popup_items);
	gnome_popup_menu_do_popup_modal (popup, NULL, NULL, event, NULL);
	gtk_widget_destroy (popup);
}

/* Draws the rubberband rectangle for selecting icons on the desktop */
static void
draw_rubberband (int x, int y)
{
	int x1, y1, x2, y2;

	if (click_start_x < x) {
		x1 = click_start_x;
		x2 = x;
	} else {
		x1 = x;
		x2 = click_start_x;
	}

	if (click_start_y < y) {
		y1 = click_start_y;
		y2 = y;
	} else {
		y1 = y;
		y2 = click_start_y;
	}
	
	gdk_draw_rectangle (GDK_ROOT_PARENT (), click_gc, FALSE, x1, y1, x2 - x1, y2 - y1);
}

/*
 * Stores dii->selected into dii->tmp_selected to keep the original selection
 * around while the user is rubberbanding.
 */
static void
store_temp_selection (void)
{
	int i;
	GList *l;
	DesktopIconInfo *dii;

	for (i = 0; i < (layout_cols * layout_rows); i++)
		for (l = layout_slots[i].icons; l; l = l->next) {
			dii = l->data;

			dii->tmp_selected = dii->selected;
		}
}

/**
 * icon_is_in_area:
 * @dii: the desktop icon information
 * 
 * Returns TRUE if the specified icon is at least partially inside the specified
 * area, or FALSE otherwise.
 */
static int
icon_is_in_area (DesktopIconInfo *dii, int x1, int y1, int x2, int y2)
{
	DesktopIcon *dicon;

	dicon = DESKTOP_ICON (dii->dicon);

	x1 -= dii->x;
	y1 -= dii->y;
	x2 -= dii->x;
	y2 -= dii->y;

	if (x1 < dicon->icon_x + dicon->icon_w - 1
	    && x2 > dicon->icon_x
	    && y1 < dicon->icon_y + dicon->icon_h - 1
	    && y2 > dicon->icon_y)
		return TRUE;

	if (x1 < dicon->text_x + dicon->text_w - 1
	    && x2 > dicon->text_x
	    && y1 < dicon->text_y + dicon->text_h - 1
	    && y2 > dicon->text_y)
		return TRUE;

	return FALSE;
}

/* Update the selection being rubberbanded.  It selects or unselects the icons
 * as appropriate.
 */
static void
update_drag_selection (int x, int y)
{
	int x1, y1, x2, y2;
	int i;
	GList *l;
	DesktopIconInfo *dii;
	int additive, invert, in_area;

	if (click_start_x < x) {
		x1 = click_start_x;
		x2 = x;
	} else {
		x1 = x;
		x2 = click_start_x;
	}

	if (click_start_y < y) {
		y1 = click_start_y;
		y2 = y;
	} else {
		y1 = y;
		y2 = click_start_y;
	}

	/* Select or unselect icons as appropriate */

	additive = click_start_state & GDK_SHIFT_MASK;
	invert = click_start_state & GDK_CONTROL_MASK;

	for (i = 0; i < (layout_cols * layout_rows); i++)
		for (l = layout_slots[i].icons; l; l = l->next) {
			dii = l->data;

			in_area = icon_is_in_area (dii, x1, y1, x2, y2);

			if (in_area) {
				if (invert) {
					if (dii->selected == dii->tmp_selected) {
						desktop_icon_select (DESKTOP_ICON (dii->dicon), !dii->selected);
						dii->selected = !dii->selected;
					}
				} else if (additive) {
					if (!dii->selected) {
						desktop_icon_select (DESKTOP_ICON (dii->dicon), TRUE);
						dii->selected = TRUE;
					}
				} else {
					if (!dii->selected) {
						desktop_icon_select (DESKTOP_ICON (dii->dicon), TRUE);
						dii->selected = TRUE;
					}
				}
			} else if (dii->selected != dii->tmp_selected) {
				desktop_icon_select (DESKTOP_ICON (dii->dicon), dii->tmp_selected);
				dii->selected = dii->tmp_selected;
			}

		}
}

/* Handles button presses on the root window via the click_proxy_gdk_window */
static gint
click_proxy_button_press (GtkWidget *widget, GdkEventButton *event, gpointer data)
{
	GdkCursor *cursor;

	if (event->button == 1) {
		click_start_x = event->x;
		click_start_y = event->y;
		click_start_state = event->state;

		XGrabServer (GDK_DISPLAY ());

		cursor = gdk_cursor_new (GDK_TOP_LEFT_ARROW);
		gdk_pointer_grab (GDK_ROOT_PARENT (),
				  FALSE,
				  GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK,
				  NULL,
				  cursor,
				  event->time);
		gdk_cursor_destroy (cursor);

		/* If no modifiers are pressed, we unselect all the icons */

		if ((click_start_state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) == 0)
			unselect_all (NULL);

		store_temp_selection (); /* Save the original selection */

		draw_rubberband (event->x, event->y);
		click_current_x = event->x;
		click_current_y = event->y;
		click_dragging = TRUE;

		return TRUE;
	} else if (event->button == 3) {
		desktop_popup (event);
		return TRUE;
	}

	return FALSE;
}

/* Handles button releases on the root window via the click_proxy_gdk_window */
static gint
click_proxy_button_release (GtkWidget *widget, GdkEventButton *event, gpointer data)
{
	if (!click_dragging || event->button != 1)
		return FALSE;

	draw_rubberband (click_current_x, click_current_y);
	gdk_pointer_ungrab (event->time);
	click_dragging = FALSE;

	update_drag_selection (event->x, event->y);

	XUngrabServer (GDK_DISPLAY ());

	return TRUE;
}

/* Handles motion events when dragging the icon-selection rubberband on the desktop */
static gint
click_proxy_motion (GtkWidget *widget, GdkEventMotion *event, gpointer data)
{
	if (!click_dragging)
		return FALSE;

	draw_rubberband (click_current_x, click_current_y);
	draw_rubberband (event->x, event->y);
	update_drag_selection (event->x, event->y);
	click_current_x = event->x;
	click_current_y = event->y;

	return TRUE;
}

/*
 * Filter that translates proxied events from virtual root windows into normal
 * Gdk events for the proxy_invisible widget.
 */
static GdkFilterReturn
click_proxy_filter (GdkXEvent *xevent, GdkEvent *event, gpointer data)
{
	XEvent *xev;

	xev = xevent;

	switch (xev->type) {
	case ButtonPress:
	case ButtonRelease:
		/* Translate button events into events that come from the proxy
		 * window, so that we can catch them as a signal from the
		 * invisible widget.
		 */
		if (xev->type == ButtonPress)
			event->button.type = GDK_BUTTON_PRESS;
		else
			event->button.type = GDK_BUTTON_RELEASE;

		gdk_window_ref (click_proxy_gdk_window);

		event->button.window = click_proxy_gdk_window;
		event->button.send_event = xev->xbutton.send_event;
		event->button.time = xev->xbutton.time;
		event->button.x = xev->xbutton.x;
		event->button.y = xev->xbutton.y;
		event->button.state = xev->xbutton.state;
		event->button.button = xev->xbutton.button;

		return GDK_FILTER_TRANSLATE;

	case DestroyNotify:
		/* The proxy window was destroyed (i.e. the window manager
		 * died), so we have to cope with it
		 */
		if (((GdkEventAny *) event)->window == click_proxy_gdk_window) {
			gdk_window_destroy_notify (click_proxy_gdk_window);
			click_proxy_gdk_window = NULL;
		}

		return GDK_FILTER_REMOVE;

	default:
		break;
	}

	return GDK_FILTER_CONTINUE;
}

/*
 * Creates a proxy window to receive clicks from the root window and
 * sets up the necessary event filters.
 */
static void
setup_desktop_click_proxy_window (void)
{
	click_proxy_gdk_window = find_click_proxy_window ();
	if (!click_proxy_gdk_window) {
		g_warning ("Root window clicks will not work as no GNOME-compliant window manager could be found!");
		return;
	}

	/* Make the proxy window send events to the invisible proxy widget */
	gdk_window_set_user_data (click_proxy_gdk_window, proxy_invisible);

	/* Add our filter to get events */
	gdk_window_add_filter (click_proxy_gdk_window, click_proxy_filter, NULL);

	/*
	 * The proxy window for clicks sends us button press events with
	 * SubstructureNotifyMask.  We need StructureNotifyMask to receive
	 * DestroyNotify events, too.
	 */

	XSelectInput (GDK_DISPLAY (), GDK_WINDOW_XWINDOW (click_proxy_gdk_window),
		      SubstructureNotifyMask | StructureNotifyMask);
}

/*
 * Handler for PropertyNotify events from the root window; it must change the
 * proxy window to a new one.
 */
static gint
click_proxy_property_notify (GtkWidget *widget, GdkEventProperty *event, gpointer data)
{
	if (event->window != GDK_ROOT_PARENT ())
		return FALSE;

	if (event->atom != gdk_atom_intern ("_WIN_DESKTOP_BUTTON_PROXY", FALSE))
		return FALSE;

	/* If there already is a proxy window, destroy it */

	click_proxy_gdk_window = NULL;

	/* Get the new proxy window */

	setup_desktop_click_proxy_window ();

	return TRUE;
}

#define gray50_width 2
#define gray50_height 2
static char gray50_bits[] = {
  0x02, 0x01, };

/* Sets up the window manager proxy window to receive clicks on the desktop root window */
static void
setup_desktop_clicks (void)
{
	GdkColormap *cmap;
	GdkColor color;
	GdkBitmap *stipple;

	/* Make the root window send events to the invisible proxy widget */
	gdk_window_set_user_data (GDK_ROOT_PARENT (), proxy_invisible);

	/* Add our filter to get button press/release events (they are sent by
	 * the WM * with the window set to the root).  Our filter will translate
	 * them to a GdkEvent with the proxy window as its window field.
	 */
	gdk_window_add_filter (GDK_ROOT_PARENT (), click_proxy_filter, NULL);

	/* Select for PropertyNotify events from the root window */

	XSelectInput (GDK_DISPLAY (), GDK_ROOT_WINDOW (), PropertyChangeMask);

	/* Create the proxy window for clicks on the root window */
	setup_desktop_click_proxy_window ();

	/* Connect the signals */

	gtk_signal_connect (GTK_OBJECT (proxy_invisible), "button_press_event",
			    (GtkSignalFunc) click_proxy_button_press,
			    NULL);
	gtk_signal_connect (GTK_OBJECT (proxy_invisible), "button_release_event",
			    (GtkSignalFunc) click_proxy_button_release,
			    NULL);
	gtk_signal_connect (GTK_OBJECT (proxy_invisible), "motion_notify_event",
			    (GtkSignalFunc) click_proxy_motion,
			    NULL);

	gtk_signal_connect (GTK_OBJECT (proxy_invisible), "property_notify_event",
			    (GtkSignalFunc) click_proxy_property_notify,
			    NULL);

	/* Create the GC to paint the rubberband rectangle */

	click_gc = gdk_gc_new (GDK_ROOT_PARENT ());

	cmap = gdk_window_get_colormap (GDK_ROOT_PARENT ());

	gdk_color_white (cmap, &color);
	if (color.pixel == 0)
		gdk_color_black (cmap, &color);

	gdk_gc_set_foreground (click_gc, &color);
	gdk_gc_set_function (click_gc, GDK_XOR);

	gdk_gc_set_fill (click_gc, GDK_STIPPLED);

	stipple = gdk_bitmap_create_from_data (NULL, gray50_bits, gray50_width, gray50_height);
	gdk_gc_set_stipple (click_gc, stipple);
	gdk_bitmap_unref (stipple);
}

/**
 * desktop_init
 *
 * Initializes the desktop by setting up the default icons (if necessary), setting up drag and drop,
 * and other miscellaneous tasks.
 */
void
desktop_init (void)
{
	gdnd_init ();
	gicon_init ();
	create_layout_info ();
	create_desktop_dir ();
	reload_desktop_icons (FALSE, 0, 0);

	/* Create the proxy window and initialize all proxying stuff */

	proxy_invisible = gtk_invisible_new ();
	gtk_widget_show (proxy_invisible);

	setup_desktop_dnd ();
	setup_desktop_clicks ();
}

/**
 * desktop_destroy
 *
 * Shuts the desktop down by destroying the desktop icons.
 */
void
desktop_destroy (void)
{
	/* Destroy the desktop icons */

	destroy_desktop_icons ();

	/* Cleanup */

	g_free (layout_slots);
	layout_slots = NULL;
	layout_cols = 0;
	layout_rows = 0;

	g_free (desktop_directory);
	desktop_directory = NULL;

	/* Remove click-on-desktop crap */

	gdk_window_unref (click_proxy_gdk_window);

	/* Remove DnD crap */

	gtk_widget_destroy (proxy_invisible);
	XDeleteProperty (GDK_DISPLAY (), GDK_ROOT_WINDOW (), gdk_atom_intern ("XdndProxy", FALSE));
}

