#include <stdlib.h>
#include <string.h>
#include <gtk/gtk.h>

#include "guiutils.h"
#include "fprompt.h"
#include "deskicon.h"


static void DeskIconFPromptApplyCB(
	gpointer data, const gchar *s
);
static void DeskIconDoRename(deskicon_struct *di);

static void DeskIconDraw(deskicon_struct *di);
static void DeskIconQueueDraw(deskicon_struct *di);

static gint DeskIconEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
);

static void DeskIconSyncMoveLabelToIcon(
	deskicon_struct *di, gint x, gint y, gboolean need_resize
);

void DeskIconSetFlags(
	deskicon_struct *di, guint flags
);
void DeskIconUnsetFlags(
	deskicon_struct *di, guint flags
);

void DeskIconGetPosition(
	deskicon_struct *di, gint *x, gint *y
);
void DeskIconSetPosition(
	deskicon_struct *di, gint x, gint y
);

static gboolean DeskIconLoadIconPixmap(
	deskicon_struct *di, guint8 **icon_data
);
static gboolean DeskIconLoadLabel(
	deskicon_struct *di, const gchar *label
);

deskicon_struct *DeskIconNew(
	gint x, gint y,
	guint8 **icon_data, const gchar *label
);

void DeskIconSet(
	deskicon_struct *di,
	guint8 **icon_data, const gchar *label
);

gboolean DeskIconIsSensitive(deskicon_struct *di);
void DeskIconSetSensitive(deskicon_struct *di, gboolean sensitive);

gboolean DeskIconIsSelected(deskicon_struct *di);
void DeskIconSelect(deskicon_struct *di);
void DeskIconUnselect(deskicon_struct *di);

void DeskIconSetMapRefCB(
	deskicon_struct *di,
	void (*map_ref_cb)(gpointer, gpointer),
	gpointer client_data
);
void DeskIconSetSelectCB(
	deskicon_struct *di,
	void (*select_cb)(gpointer, gpointer),
	gpointer client_data
);
void DeskIconSetUnselectCB(
	deskicon_struct *di,
	void (*unselect_cb)(gpointer, gpointer),
	gpointer client_data
);
void DeskIconSetRenameCB(
	deskicon_struct *di,
	void (*rename_cb)(gpointer, const gchar *, const gchar *, gpointer),
	gpointer client_data
);
void DeskIconSetMoveBeginCB(
	deskicon_struct *di,
	void (*move_begin_cb)(gpointer, gint, gint, gpointer),
	gpointer client_data
);
void DeskIconSetMovingCB(
	deskicon_struct *di,
	void (*moving_cb)(gpointer, gint, gint, gpointer),
	gpointer client_data
);
void DeskIconSetMovedCB(
	deskicon_struct *di,
	void (*moved_cb)(gpointer, gint, gint, gpointer),
	gpointer client_data
);

void DeskIconSetMenu(deskicon_struct *di, GtkWidget *w);
GtkWidget *DeskIconGetMenu(deskicon_struct *di);

void DeskIconMap(deskicon_struct *di);
void DeskIconUnmap(deskicon_struct *di);

void DeskIconDelete(deskicon_struct *di);


#define ATOI(s)         (((s) != NULL) ? atoi(s) : 0)
#define ATOL(s)         (((s) != NULL) ? atol(s) : 0)
#define ATOF(s)         (((s) != NULL) ? atof(s) : 0.0f)
#define STRDUP(s)       (((s) != NULL) ? g_strdup(s) : NULL)

#define MAX(a,b)	(((a) > (b)) ? (a) : (b))
#define MIN(a,b)	(((a) < (b)) ? (a) : (b))
#define CLIP(a,l,h)	(MIN(MAX((a),(l)),(h)))
#define STRLEN(s)       (((s) != NULL) ? strlen(s) : 0)
#define STRISEMPTY(s)   (((s) != NULL) ? (*(s) == '\0') : TRUE)


/*
 *	Float prompt rename apply callback.
 */
static void DeskIconFPromptApplyCB(
	gpointer data, const gchar *s
)
{
	gchar *new_label;
	deskicon_struct *di = DESKICON(data);
	if((di == NULL) || (s == NULL))
	    return;

	/* Get new label */
	new_label = STRDUP(s);

	/* Set new label */
	DeskIconSet(di, NULL, new_label);

	g_free(new_label);
}

/*
 *	Renames the Desktop Icon.
 */
static void DeskIconDoRename(deskicon_struct *di)
{
	gint x, y, width;
	GdkWindow *window;
	GtkWidget *w;

	if((di == NULL) || FPromptIsQuery())
	    return;

	/* Renaming not allowed? */
	if(!(di->flags & DESKICON_CAN_RENAME))
	    return;

	w = di->label_toplevel;
	window = (w != NULL) ? w->window : NULL;
	if(window == NULL)
	    return;

	/* Get position of label window relative to the root window */
	gdk_window_get_root_origin(window, &x, &y);

	w = di->label_da;
	if(w == NULL)
	    return;

	/* Calculate position of prompt to fit over the current position 
	 * and size of the deskicon's label
	 */
	width = MAX(di->label_width, 100);
	x = x - 2 - ((width - di->label_width) / 2);
	y = y - 4;

	/* Set position and map prompt */
	FPromptSetPosition(x, y);
	FPromptMapQuery(
	    NULL, di->label_str, NULL,
	    FPROMPT_MAP_NO_MOVE,
	    width, -1,
	    0,		/* Flags */
	    di,
	    NULL,
	    DeskIconFPromptApplyCB,
	    NULL
	);
}

/*
 *	Draws the Desktop Icon.
 */
static void DeskIconDraw(deskicon_struct *di)
{
	const gchar *s;
	GdkGC *gc;
	GdkPixmap *pixmap;
	GdkBitmap *mask;
	GtkWidget *w;

	if(di == NULL)
	    return;

	if(!di->map_state)
	    return;

	gc = di->gc;
	if(gc == NULL)
	    return;

	/* Begin drawing icon */
	w = di->icon_da;
	pixmap = di->icon_pixmap;
	mask = di->icon_mask;
	if((w != NULL) && (pixmap != NULL))
	{
	    GdkWindow *window = w->window;
	    GtkStyle *style = gtk_widget_get_style(w);
	    gint	state = GTK_WIDGET_STATE(w),
			x = 0,
			y = 0,
			width = di->icon_width,
			height = di->icon_height;

	    if(GTK_WIDGET_VISIBLE(w) &&
	       (window != NULL) && (style != NULL)
	    )
	    {
		/* Draw icon pixmap */
		gdk_gc_set_function(gc, GDK_COPY);
		gdk_gc_set_fill(gc, GDK_SOLID);
		gdk_gc_set_clip_mask(gc, mask);
		gdk_gc_set_clip_origin(gc, x, y);
		gdk_gc_set_foreground(gc, &style->bg[state]);
		gdk_draw_pixmap(
		    window, gc,
		    pixmap,
		    0, 0,
		    x, y,
		    width, height
		);
		gdk_gc_set_clip_mask(gc, NULL);

		/* Draw selection? */
		if(state == GTK_STATE_SELECTED)
		{
		    gdk_gc_set_function(gc, GDK_INVERT);
		    gdk_gc_set_foreground(gc, &style->white);
		    gdk_draw_rectangle(
			window, gc, TRUE,
			x, y, width, height
		    );
		    gdk_gc_set_function(gc, GDK_COPY);
		}
	    }
	}

	/* Begin drawing label */
	w = di->label_da;
	s = di->label_str;
	if(w != NULL)
	{
	    GdkWindow *window = w->window;
	    GtkStyle *style = gtk_widget_get_style(w);
	    gint        state = GTK_WIDGET_STATE(w),
			x = 0,
			y = 0,
			width = di->label_width,
			height = di->label_height;

	    if(GTK_WIDGET_VISIBLE(w) &&
	       (window != NULL) && (style != NULL)
	    )
	    {
		gdk_gc_set_function(gc, GDK_COPY);
		gdk_gc_set_fill(gc, GDK_SOLID);
		gdk_gc_set_foreground(
		    gc,
		    (state == GTK_STATE_SELECTED) ?
			&style->bg[state] : &style->base[state]
		);
		gdk_draw_rectangle(
		    window, gc, TRUE,
		    x, y, width, height
		);

		/* Begin drawing label */
		if(!STRISEMPTY(s) && (style->font != NULL))
		{
		    const gchar *line = s, *line_end;
		    gint x, y, line_len;
		    GdkTextBounds b;
		    GdkFont *font = style->font;

		    gdk_gc_set_foreground(gc, &style->text[state]);

		    /* Calculate starting position */
		    x = 0;
		    y = 2;

		    /* Iterate through each line */
		    while(*line != '\0')
		    {
			line_end = strchr(line, '\n');
			if(line_end != NULL)
			    line_len = MAX(line_end - line, 0);
			else
			    line_len = STRLEN(line);

			if(line_len > 0)
			{
			    /* Get geometry of this line */
			    gdk_text_bounds(
				font, line, line_len, &b
			    );

			    /* Calculate x position */
			    x = (width - b.width) / 2;

			    /* Draw this line */
			    gdk_draw_text(
				window, font, gc,
				x + b.lbearing,
				y + font->ascent,
				line, line_len
			    );
			}

			/* Increment y position */
			y += font->ascent + font->descent +
			    ((line_end != NULL) ? 2 : 0);

			/* Seek to next line */
			line += line_len;
			/* Increment once more if line ended with a line
			 * deliminator character
			 */
			if(*line != '\0')
			    line++;
		    }
		}

		/* Draw focus rectangle for the label if the icon has
		 * focus
		 */
		if(GTK_WIDGET_HAS_FOCUS(di->icon_toplevel))
		{
		    gdk_gc_set_function(gc, GDK_INVERT);
		    gdk_gc_set_foreground(gc, &style->white);
		    gdk_draw_rectangle(
			window, gc, FALSE,
			0, 0, width - 1, height - 1
		    );
		    gdk_gc_set_function(gc, GDK_COPY);
		}

	    }
	}
}

/*
 *	Queue draw for the Desktop Icon.
 */
static void DeskIconQueueDraw(deskicon_struct *di)
{
	GtkWidget *w = (di != NULL) ? di->icon_toplevel : NULL;
	if(w == NULL)
	    return;

	gtk_widget_queue_draw(w);
}


/*
 *	Desktop Icon event signal callback.
 */
static gint DeskIconEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
)
{
	gint status = FALSE;
	GdkEventFocus *focus;
	GdkEventKey *key;
	GdkEventButton *button;
	GdkEventMotion *motion;
	deskicon_struct *di = DESKICON(data);
	if((widget == NULL) || (event == NULL) || (di == NULL))
	    return(status);

	/* Handle by event type */
	switch((gint)event->type)
	{
	  case GDK_EXPOSE:
	    DeskIconDraw(di);
	    status = TRUE;
	    break;

	  case GDK_FOCUS_CHANGE:
	    focus = (GdkEventFocus *)event;
	    if(focus->in)
	    {
		GtkWidget *w = di->icon_toplevel;
		if(!GTK_WIDGET_HAS_FOCUS(w))
		{
		    GTK_WIDGET_SET_FLAGS(w, GTK_HAS_FOCUS);
		    DeskIconQueueDraw(di);
		}
	    }
	    else
	    {
		GtkWidget *w = di->icon_toplevel;
		if(GTK_WIDGET_HAS_FOCUS(w))
		{
		    GTK_WIDGET_UNSET_FLAGS(w, GTK_HAS_FOCUS);
		    DeskIconQueueDraw(di);
		}
	    }
	    status = TRUE;
	    break;

	  case GDK_KEY_PRESS:
	    key = (GdkEventKey *)event;
	    status = TRUE;
	    break;

	  case GDK_KEY_RELEASE:
	    key = (GdkEventKey *)event;
	    status = TRUE;
	    break;

	  case GDK_BUTTON_PRESS:
	    button = (GdkEventButton *)event;
	    if(!GTK_WIDGET_HAS_FOCUS(di->icon_toplevel))
	    {
		/* Need to grab focus manually */
		GtkWidget *w = di->icon_toplevel;
		gtk_widget_grab_focus(w);
		GTK_WIDGET_SET_FLAGS(w, GTK_HAS_FOCUS);
		DeskIconQueueDraw(di);
	    }
	    switch(button->button)
	    {
	      case 1:
		/* Record button and pointer coordinates */
		di->button_down = button->button;
		di->last_x = (gint)button->x_root;
		di->last_y = (gint)button->y_root;
		/* Select/unselect? */
		if(DeskIconIsSelected(di))
		    DeskIconUnselect(di);
		else
		    DeskIconSelect(di);
		break;
	      case 2:
		/* Rename */
		DeskIconDoRename(di);
		break;
	      case 3:
		/* Map menu */
		if(di->menu != NULL)
		    gtk_menu_popup(
			GTK_MENU(di->menu), NULL, NULL,
			NULL, NULL,
			button->button, button->time
		    );
		break;
	    }
	    status = TRUE;
	    break;

	  case GDK_2BUTTON_PRESS:
	    button = (GdkEventButton *)event;
	    switch(button->button)
	    {
	      case 1:
		/* Must reset button and pointer coordinates on double
		 * clicks
		 */
		di->button_down = 0;
		di->last_x = 0;
		di->last_y = 0;
		/* Call map reference callback? */
		if(di->map_ref_cb != NULL)
		    di->map_ref_cb(
			di,			/* Deskicon */
			di->map_ref_client_data	/* Data */
		    );
		break;
	    }
	    status = TRUE;
	    break;

	  case GDK_BUTTON_RELEASE:
	    button = (GdkEventButton *)event;
	    /* Reset button and pointer coordinates */
	    di->button_down = 0;
	    di->last_x = 0;
	    di->last_y = 0;
	    /* Ungrab pointer as needed */
	    if(gdk_pointer_is_grabbed())
		gdk_pointer_ungrab(button->time);
	    /* Was the icon moved? */
	    if(di->flags & DESKICON_IS_MOVING)
	    {
		di->flags &= ~DESKICON_IS_MOVING;

		/* Call moved callback */
		if(di->moved_cb != NULL)
		{
		    GtkWidget *w = di->icon_toplevel;
		    GdkWindow *window = (w != NULL) ? w->window : NULL;
		    if(window != NULL)
		    {
			gint x, y;
			gdk_window_get_position(window, &x, &y);
			di->moved_cb(
			    di,				/* Deskicon */
			    x, y,			/* Position */
			    di->moved_client_data	/* Data */
			);
		    }
		}
	    }
	    status = TRUE;
	    break;

	  case GDK_MOTION_NOTIFY:
	    motion = (GdkEventMotion *)event;
	    /* Button 1 pressed? */
	    if((di->button_down == 1) && (di->flags & DESKICON_CAN_MOVE))
	    {
		gint x, y, dx, dy, drag_tolor = 5;
		GdkModifierType mask;
		GdkWindow *window = motion->window;
		GtkWidget *w;

		/* If this event is a motion hint then request additional
		 * motion events to be sent
		 */
		if(motion->is_hint)
		    gdk_window_get_pointer(
			window, &x, &y, &mask
		    );

		/* Get current pointer coordinates */
		x = (gint)motion->x_root;
		y = (gint)motion->y_root;

		/* Calculate motion movement */
		dx = x - di->last_x;
		dy = y - di->last_y;

		/* Moving yet? */
		if(di->flags & DESKICON_IS_MOVING)
		{
		    /* Is moving, so move icon and sync label to the
		     * icon's new position
		     */
		    gint icon_x, icon_y;

		    w = di->icon_toplevel;
		    window = (w != NULL) ? w->window : NULL;
		    if(window != NULL)
		    {
			gdk_window_get_position(window, &icon_x, &icon_y);
			icon_x += dx;
			icon_y += dy;
			gdk_window_move(window, icon_x, icon_y);
			DeskIconSyncMoveLabelToIcon(di, icon_x, icon_y, FALSE);
		    }
		    else
		    {
			icon_x = dx;
			icon_y = dy;
		    }

		    /* Record new pointer position */
		    di->last_x = x;
		    di->last_y = y;

		    /* Call moving callback */
		    if(di->moving_cb != NULL)
			di->moving_cb(
			    di,				/* Deskicon */
			    icon_x, icon_y,		/* Position */
			    di->moving_client_data	/* Data */
			);
		}
		else
		{
		    /* Not yet moving, check if pointed moved beyond
		     * the drag tolorance?
		     */
		    if((dx >= drag_tolor) || (dx <= -drag_tolor) ||
		       (dy >= drag_tolor) || (dy <= -drag_tolor)
		    )
		    {
			/* Begin moving */
			gint icon_x = 0, icon_y = 0;

			/* Select as needed */
			if(!DeskIconIsSelected(di))
			    DeskIconSelect(di);

			/* Get initial position of icon */
			w = di->icon_toplevel;
			window = (w != NULL) ? w->window : NULL;
			if(window != NULL)
			{
			    gdk_window_get_position(window, &icon_x, &icon_y);

			    /* Call move begin callback */
			    if(di->move_begin_cb != NULL)
				di->move_begin_cb(
				    di,				/* Deskicon */
				    icon_x, icon_y,		/* Position */
				    di->move_begin_client_data	/* Data */
				);
			}

			/* Mark that we are now moving */
			di->flags |= DESKICON_IS_MOVING;

			/* Move icon toplevel and sync label to its new
			 * position
			 */
			icon_x += dx;
			icon_y += dy;
			gdk_window_move(window, icon_x, icon_y);
			DeskIconSyncMoveLabelToIcon(di, icon_x, icon_y, FALSE);

			/* Grab pointer */
			gdk_pointer_grab(
			    motion->window, TRUE,
			    GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
			    GDK_POINTER_MOTION_MASK,
			    NULL, NULL, motion->time
			);

			/* Record new pointer position */
			di->last_x = x;
			di->last_y = y;

			/* Call moving callback */
			if(di->moving_cb != NULL)
			    di->moving_cb(
				di,			/* Deskicon */
				icon_x, icon_y,		/* Position */
				di->moving_client_data	/* Data */
			    );
		    }
		}
	    }
	    status = TRUE;
	    break;

	}

	return(status);
}

/*
 *	Moves the Desktop Icon's label to the location aligned with the
 *	current position of its icon.
 *
 *	If need_resize is TRUE then a resize will be queued for the
 *	icon and label (this is needed if the icon or label size has
 *	changed).
 */
static void DeskIconSyncMoveLabelToIcon(
	deskicon_struct *di, gint x, gint y, gboolean need_resize
)
{
	gint width, height, nx, ny;
	GdkWindow *window;
	GtkWidget *w;

	if(di == NULL)
	    return;

	/* Get icon toplevel window */
	w = di->icon_toplevel;
	window = (w != NULL) ? w->window : NULL;
	if(window == NULL)
	    return;

	/* Get size of icon toplevel */
	width = di->icon_width;
	height = di->icon_height;

	/* Get label toplevel */
	w = di->label_toplevel;
	window = (w != NULL) ? w->window : NULL;
	if(window == NULL)
	    return;

	/* Label not shown? */
	if(!GTK_WIDGET_MAPPED(w))
	    return;

	/* Calculate new label position */
	nx = x + (width / 2) - (di->label_width / 2);
	ny = y + height;
	gtk_widget_set_uposition(w, nx, ny);
	gdk_window_move(window, nx, ny);

	/* Need to queue resizing of label and icon in case there
	 * was a resize prior to this call
	 */
	if(need_resize)
	{
	    gtk_widget_queue_resize(di->label_da);
	    gtk_widget_queue_resize(w);
	}
}


/*
 *	Sets the Desktop Icon's flags.
 */
void DeskIconSetFlags(
	deskicon_struct *di, guint flags
)
{
	guint last_flags;

	if(di == NULL)
	    return;

	/* Record previous flags and set new flags */
	last_flags = di->flags;
	di->flags |= flags;

	/* Flags changed? */
	if(di->flags != last_flags)
	{
	    /* Need to remap if currently mapped */
	    if(di->map_state)
	    {
		di->map_state = FALSE;
		DeskIconMap(di);
	    }

	    DeskIconQueueDraw(di);
	}
}

/*
 *	Unset the Desktop Icon's flags.
 */
void DeskIconUnsetFlags(
	deskicon_struct *di, guint flags
)
{
	guint last_flags;

	if(di == NULL)
	    return;

	/* Record previous flags and unset new flags */
	last_flags = di->flags;
	di->flags &= ~flags;

	/* Flags changed? */
	if(di->flags != last_flags)
	{
	    /* Need to remap if currently mapped */
	    if(di->map_state)
	    {
		di->map_state = FALSE;
		DeskIconMap(di);
	    }

	    DeskIconQueueDraw(di);
	}
}


/*
 *	Returns the position of the Desktop Icon.
 */
void DeskIconGetPosition(
	deskicon_struct *di, gint *x, gint *y
)
{
	gint cx, cy;
	GdkWindow *window;
	GtkWidget *w;

	if(x != NULL)
	    *x = 0;
	if(y != NULL)
	    *y = 0;

	if(di == NULL)
	    return;

	w = di->icon_toplevel;
	window = (w != NULL) ? w->window : NULL;
	if(window == NULL)
	    return;

	/* Get position of icon window relative to the root window */
	gdk_window_get_root_origin(window, &cx, &cy);
	if(x != NULL)
	    *x = cx;
	if(y != NULL)
	    *y = cy;
}

/*
 *	Moves the Desktop Icon.
 */
void DeskIconSetPosition(
	deskicon_struct *di, gint x, gint y
)
{
	GdkWindow *window;
	GtkWidget *w;

	if(di == NULL)
	    return;

	w = di->icon_toplevel;
	window = (w != NULL) ? w->window : NULL;
	if(window == NULL)
	    return;

	/* Set new icon position relative to the root window */
	gtk_widget_set_uposition(w, x, y);
	gdk_window_move(window, x, y);

	/* Move label to match icon position */
	DeskIconSyncMoveLabelToIcon(di, x, y, FALSE);

	/* Call move callback */
	if(di->moved_cb != NULL)
	    di->moved_cb(
		di,			/* Desktop icon */
		x, y,			/* Position */
		di->moved_client_data	/* Data */
	    );
}

/*
 *	Sets the Desktop Icon's icon from the specified icon data.
 *
 *	Returns TRUE if there is a change.
 */
static gboolean DeskIconLoadIconPixmap(
	deskicon_struct *di, guint8 **icon_data
)
{
	gint width, height;
	GdkPixmap *pixmap, *old_pixmap;
	GdkBitmap *mask, *old_mask;
	GtkWidget *w;

	if((di == NULL) || (icon_data == NULL))
	    return(FALSE);

	/* Check if there is no change in the icon data */
	if(di->last_icon_data == icon_data)
	    return(FALSE);

	w = di->icon_da;
	if(w == NULL)
	    return(FALSE);

	/* Record old pixmap and mask pair that will be unref'ed later
	 * on a successful load
	 */
	old_pixmap = di->icon_pixmap;
	old_mask = di->icon_mask;

	/* Load new icon pixmap and mask pair from the given data */
	pixmap = GDK_PIXMAP_NEW_FROM_XPM_DATA(&mask, icon_data);
	if(pixmap == NULL)
	    return(FALSE);

	/* Loaded icon pixmap and mask pair successfully, now begin
	 * updating values
	 */

	/* Record icon data so we know when the icon has changed
	 * in the future
	 */
	di->last_icon_data = icon_data;

	/* Get size of pixmap */
	gdk_window_get_size(pixmap, &width, &height);
	di->icon_width = width;
	di->icon_height = height;

	/* Set pixmap and mask */
	di->icon_pixmap = pixmap;
	di->icon_mask = mask;


	/* Update size and shape of toplevel widget and drawing area */
	w = di->icon_toplevel;
	if(w != NULL)
	{
	    gdk_window_resize(w->window, width, height);
	    gtk_widget_set_usize(w, width, height);
	    gtk_widget_shape_combine_mask(w, mask, 0, 0);
	}
	w = di->icon_da;
	if(w != NULL)
	{
	    gdk_window_resize(w->window, width, height);
	    gtk_widget_set_usize(w, width, height);
	    gtk_widget_shape_combine_mask(w, mask, 0, 0);
	}

	/* Unref old icon pixmap and mask */
	GDK_PIXMAP_UNREF(old_pixmap)
	GDK_BITMAP_UNREF(old_mask)

	return(TRUE);
}

/*
 *	Sets the Desktop Icon's label.
 *
 *      Returns TRUE if there is a change.
 */
static gboolean DeskIconLoadLabel(
	deskicon_struct *di, const gchar *label
)
{
	gint width, height;
	GtkWidget *toplevel, *w;
	GtkStyle *style;

	if(di == NULL)
	    return(FALSE);

	/* Check if there is no change in label value */
	if(STRISEMPTY(di->label_str) && STRISEMPTY(label))
	    return(FALSE);
	if((di->label_str != NULL) && (label != NULL))
	{
	    if(!strcmp(di->label_str, label))
		return(FALSE);
	}

	toplevel = di->label_toplevel;
	w = di->label_da;
	if((toplevel == NULL) || (w == NULL))
	    return(FALSE);

	style = gtk_widget_get_style(w);
	if(style == NULL)
	    return(FALSE);

	/* Update label */
	if(!STRISEMPTY(label))
	{
	    gint	longest_line_width = 0,
			height_lines = 0;

	    /* Set new label */
	    g_free(di->label_str);
	    di->label_str = STRDUP(label);

	    /* Calculate new label geometry */
	    if(style->font != NULL)
	    {
		const gchar *s = label, *s2;
		gint len, lbearing, rbearing, swidth, ascent, descent;
		GdkFont *font = style->font;

		/* Calculate the longest_line_width and height_lines
		 * (height of all lines), in pixels
		 */
		while(*s != '\0')
		{
		    s2 = strchr(s, '\n');
		    if(s2 != NULL)
			len = MAX(s2 - s, 0);
		    else
			len = STRLEN(s);

		    if(len > 0)
		    {
			/* Get geometry of this line */
			gdk_text_extents(
			    font, s, len,
			    &lbearing, &rbearing, &swidth,
			    &ascent, &descent
			);

			/* Record this line's width if it is longer than
			 * any of the previous lines
			 */
			if(swidth > longest_line_width)
			    longest_line_width = swidth;
		    }

		    /* Increment height for this line */
		    height_lines += font->ascent + font->descent +
			((s2 != NULL) ? 2 : 0);

		    /* Seek to next line */
		    s += len;
		    /* Increment once more if line ended with a line
		     * deliminator character
		     */
		    if(*s != '\0')
			s++;
		}

		/* Calculate overall size of label string */
		gdk_string_extents(
		    style->font,
		    label,
		    &di->label_str_lbearing,
		    &di->label_str_rbearing,
		    &di->label_str_width,
		    &di->label_str_ascent,
		    &di->label_str_descent
		);
	    }

	    /* Calculate resulting label size (with border) */
	    di->label_width = width = longest_line_width + (2 * 2);
	    di->label_height = height = height_lines + (2 * 2);

	    /* Set new label size */
	    if((width > 0) && (height > 0))
	    {
		gdk_window_resize(toplevel->window, width, height);
		gtk_widget_set_usize(toplevel, width, height);
		gdk_window_resize(w->window, width, height);
		gtk_widget_set_usize(w, width, height);
		gtk_widget_show(toplevel);
	    }
	}
	else
	{
	    g_free(di->label_str);
	    di->label_str = NULL;
	    di->label_width = 0;
	    di->label_height = 0;
	    di->label_str_lbearing = 0;
	    di->label_str_rbearing = 0;
	    di->label_str_width = 0;
	    di->label_str_ascent = 0;
	    di->label_str_descent = 0;

	    gtk_widget_hide(toplevel);
	}

	return(TRUE);
}


/*
 *	Creates a new Desktop Icon.
 */
deskicon_struct *DeskIconNew(
	gint x, gint y,
	guint8 **icon_data, const gchar *label
)
{
	GdkWindow *window;
	GtkWidget *w, *parent;
	deskicon_struct *di = DESKICON(g_malloc0(
	    sizeof(deskicon_struct)
	));
	if(di == NULL)
	    return(di);

	/* Reset values */
	di->initialized = TRUE;
	di->map_state = FALSE;
	di->flags = 0;
	di->last_icon_data = NULL;
	di->icon_pixmap = NULL;
	di->icon_mask = NULL;
	di->label_str = NULL;
	di->menu = NULL;
	di->button_down = 0;
	di->last_x = 0;
	di->last_y = 0;
	di->map_ref_cb = NULL;
	di->map_ref_client_data = NULL;
	di->select_cb = NULL;
	di->select_client_data = NULL;
	di->unselect_cb = NULL;
	di->unselect_client_data = NULL;
	di->rename_cb = NULL;
	di->rename_client_data = NULL;
	di->move_begin_cb = NULL;
	di->move_begin_client_data = NULL;
	di->moving_cb = NULL;
	di->moving_client_data = NULL;
	di->moved_cb = NULL;
	di->moved_client_data = NULL;

	/* Icon toplevel */
	di->icon_toplevel = w = gtk_window_new(GTK_WINDOW_POPUP);
	GTK_WIDGET_SET_FLAGS(w, GTK_CAN_FOCUS | GTK_CAN_DEFAULT);
	gtk_widget_add_events(
	    w,
	    GDK_STRUCTURE_MASK | GDK_FOCUS_CHANGE_MASK |
	    GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK |
	    GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
	    GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK
	);
        gtk_signal_connect(
            GTK_OBJECT(w), "configure_event",
            GTK_SIGNAL_FUNC(DeskIconEventCB), di
        );
	gtk_signal_connect(
	    GTK_OBJECT(w), "focus_in_event",
	    GTK_SIGNAL_FUNC(DeskIconEventCB), di
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "focus_out_event",
	    GTK_SIGNAL_FUNC(DeskIconEventCB), di
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "key_press_event",
	    GTK_SIGNAL_FUNC(DeskIconEventCB), di
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "key_release_event",
	    GTK_SIGNAL_FUNC(DeskIconEventCB), di
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_press_event",
	    GTK_SIGNAL_FUNC(DeskIconEventCB), di
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_release_event",
	    GTK_SIGNAL_FUNC(DeskIconEventCB), di
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "motion_notify_event",
	    GTK_SIGNAL_FUNC(DeskIconEventCB), di
	);
	gtk_widget_realize(w);
	window = w->window;
	if(window != NULL)
	{
	    gdk_window_move(window, x, y);

	    /* No decorations */
	    gdk_window_set_decorations(window, 0);

	    /* No functions, we'll do all the WM managing ourselves */
	    gdk_window_set_functions(window, 0);
	}
	gtk_container_border_width(GTK_CONTAINER(w), 0);
	parent = w;

	/* Create graphics context */
	di->gc = GDK_GC_NEW();

	/* Icon drawing area */
	di->icon_da = w = gtk_drawing_area_new();
	gtk_drawing_area_size(GTK_DRAWING_AREA(w), 48, 48);
	gtk_widget_add_events(
	    w,
	    GDK_VISIBILITY_NOTIFY_MASK | GDK_EXPOSURE_MASK
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "expose_event",
	    GTK_SIGNAL_FUNC(DeskIconEventCB), di
	);
	gtk_container_add(GTK_CONTAINER(parent), w);
	gtk_widget_show(w);


	/* Label toplevel */
	di->label_toplevel = w = gtk_window_new(GTK_WINDOW_POPUP);
	gtk_widget_add_events(
	    w,
	    GDK_STRUCTURE_MASK |
	    GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
	    GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "configure_event",
	    GTK_SIGNAL_FUNC(DeskIconEventCB), di
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_press_event",
	    GTK_SIGNAL_FUNC(DeskIconEventCB), di
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_release_event",
	    GTK_SIGNAL_FUNC(DeskIconEventCB), di
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "motion_notify_event",
	    GTK_SIGNAL_FUNC(DeskIconEventCB), di
	);
	gtk_widget_realize(w);
	window = w->window;
	if(window != NULL)
	{
	    /* Do not set position for label toplevel, it follows the
	     * pixmap toplevel
	     */

	    /* No decorations */
	    gdk_window_set_decorations(window, 0);

	    /* No functions, we'll do all the WM managing ourselves */
	    gdk_window_set_functions(window, 0);
	}
	gtk_container_border_width(GTK_CONTAINER(w), 0);
	parent = w;

	/* Label drawing area */
	di->label_da = w = gtk_drawing_area_new();
	gtk_drawing_area_size(GTK_DRAWING_AREA(w), 48, 48);
	gtk_widget_add_events(
	    w,
	    GDK_VISIBILITY_NOTIFY_MASK | GDK_EXPOSURE_MASK
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "expose_event",
	    GTK_SIGNAL_FUNC(DeskIconEventCB), di
	);
	gtk_container_add(GTK_CONTAINER(parent), w);
	gtk_widget_show(w);


	/* Set icon and label */
	DeskIconLoadIconPixmap(di, icon_data);
	DeskIconLoadLabel(di, label);

	/* Set position */
	DeskIconSetPosition(di, x, y);

	return(di);
}

/*
 *	Updates the Desktop Icon's icon and/or label.
 */
void DeskIconSet(
	deskicon_struct *di,
	guint8 **icon_data, const gchar *label
)
{
	gboolean icon_changed, label_changed;
	gchar *old_label_str;

	if(di == NULL)
	    return;

	if((di->icon_toplevel == NULL) || (di->label_toplevel == NULL))
	    return;

	/* Record previous label string */
	old_label_str = STRDUP(di->label_str);

	/* Load icon and label if they are not NULL and check if
	 * either has changed
	 */
	icon_changed = (icon_data != NULL) ?
	    DeskIconLoadIconPixmap(di, icon_data) : FALSE;
	label_changed = (label != NULL) ?
	    DeskIconLoadLabel(di, label) : FALSE;

	/* Change in icon or label? */
	if(icon_changed || label_changed)
	{
	    gint x, y;

	    /* Need to move label to new position */
	    DeskIconGetPosition(di, &x, &y);
	    DeskIconSyncMoveLabelToIcon(di, x, y, TRUE);

	    DeskIconQueueDraw(di);
	}

	/* Call rename callback */
	if((di->rename_cb != NULL) && label_changed)
	    di->rename_cb(
		di,			/* Deskicon */
		old_label_str,		/* Old name */
		di->label_str,		/* New name */
		di->rename_client_data	/* Data */
	    );

	g_free(old_label_str);
}


/*
 *      Returns the Desktop Icon's sensitivity.
 */
gboolean DeskIconIsSensitive(deskicon_struct *di)
{
	GtkWidget *w = (di != NULL) ? di->icon_toplevel : NULL;
	return((w != NULL) ? GTK_WIDGET_SENSITIVE(w) : FALSE);
}

/*
 *	Sets the Desktop Icon's sensitivity.
 */
void DeskIconSetSensitive(deskicon_struct *di, gboolean sensitive)
{
	if(di == NULL)
	    return;

	if(sensitive)
	{
	    if(DeskIconIsSensitive(di))
		return;

	    GTK_WIDGET_SET_SENSITIVE(di->icon_toplevel, TRUE)
	    GTK_WIDGET_SET_SENSITIVE(di->label_toplevel, TRUE)
	    DeskIconQueueDraw(di);
	}
	else
	{
	    if(!DeskIconIsSensitive(di))
		return;

	    GTK_WIDGET_SET_SENSITIVE(di->icon_toplevel, FALSE)
	    GTK_WIDGET_SET_SENSITIVE(di->label_toplevel, FALSE)
	    DeskIconQueueDraw(di);
	}
}


/*
 *	Returns the Desktop Icon's select state.
 */
gboolean DeskIconIsSelected(deskicon_struct *di)
{
	GtkWidget *w = (di != NULL) ? di->icon_toplevel : NULL;
	return((w != NULL) ?
	    (GTK_WIDGET_STATE(w) == GTK_STATE_SELECTED) : FALSE
	);
}

/*
 *	Selects the Desktop Icon.
 */
void DeskIconSelect(deskicon_struct *di)
{
	GtkWidget *w;

	if(di == NULL)
	    return;

	if(!(di->flags & DESKICON_CAN_SELECT))
	    return;

	if(DeskIconIsSelected(di) && DeskIconIsSensitive(di))
	    return;

	w = di->icon_toplevel;
	if(w != NULL)
	    gtk_widget_set_state(w, GTK_STATE_SELECTED);
	w = di->label_toplevel;
	if(w != NULL)
	    gtk_widget_set_state(w, GTK_STATE_SELECTED);

	DeskIconQueueDraw(di);

	/* Call select callback */
	if(di->select_cb != NULL)
	    di->select_cb(
		di,			/* Deskicon */
		di->select_client_data	/* Data */
	    );
}

/*
 *	Unselects the Desktop Icon.
 */
void DeskIconUnselect(deskicon_struct *di)
{
	GtkWidget *w;

	if(di == NULL)
	    return;

	if(!(di->flags & DESKICON_CAN_SELECT))
	    return;

	if(!DeskIconIsSelected(di) && DeskIconIsSensitive(di))
	    return;

	w = di->icon_toplevel;
	if(w != NULL)
	    gtk_widget_set_state(w, GTK_STATE_NORMAL);
	w = di->label_toplevel;
	if(w != NULL)
	    gtk_widget_set_state(w, GTK_STATE_NORMAL);

	DeskIconQueueDraw(di);

	/* Call unselect callback */
	if(di->unselect_cb != NULL)
	    di->unselect_cb(
		di,				/* Deskicon */
		di->unselect_client_data	/* Data */
	    );
}


/*
 *	Sets the Desktop Icon's map reference callback function.
 */
void DeskIconSetMapRefCB(
	deskicon_struct *di,
	void (*map_ref_cb)(gpointer, gpointer),
	gpointer client_data
)
{
	if(di == NULL)
	    return;

	di->map_ref_cb = map_ref_cb;
	di->map_ref_client_data = client_data;
}

/*
 *      Sets the Desktop Icon's select callback function.
 */
void DeskIconSetSelectCB(
	deskicon_struct *di,
	void (*select_cb)(gpointer, gpointer),
	gpointer client_data
)
{
	if(di == NULL)
	    return;

	di->select_cb = select_cb;
	di->select_client_data = client_data;
}

/*
 *	Sets the Desktop Icon's unselect callback function.
 */
void DeskIconSetUnselectCB(
	deskicon_struct *di,
	void (*unselect_cb)(gpointer, gpointer),
	gpointer client_data
)
{
	if(di == NULL)
	    return;

	di->unselect_cb = unselect_cb;
	di->unselect_client_data = client_data;
}

/*
 *	Sets the Desktop Icon's rename callback function.
 */
void DeskIconSetRenameCB(
	deskicon_struct *di,
	void (*rename_cb)(gpointer, const gchar *, const gchar *, gpointer),
	gpointer client_data
)
{
	if(di == NULL)
	    return;

	di->rename_cb = rename_cb;
	di->rename_client_data = client_data;
}

/*
 *	Sets the Desktop Icon's move begin callback function.
 */
void DeskIconSetMoveBeginCB(
	deskicon_struct *di,
	void (*move_begin_cb)(gpointer, gint, gint, gpointer),
	gpointer client_data
)
{
	if(di == NULL)
	    return;

	di->move_begin_cb = move_begin_cb;
	di->move_begin_client_data = client_data;
}

/*
 *	Sets the Desktop Icon's moving callback function.
 */
void DeskIconSetMovingCB(
	deskicon_struct *di,
	void (*moving_cb)(gpointer, gint, gint, gpointer),
	gpointer client_data
)
{
	if(di == NULL)
	    return;

	di->moving_cb = moving_cb;
	di->moving_client_data = client_data;
}

/*
 *	Sets the Desktop Icon's moved callback function.
 */
void DeskIconSetMovedCB(
	deskicon_struct *di,
	void (*moved_cb)(gpointer, gint, gint, gpointer),
	gpointer client_data
)
{
	if(di == NULL)
	    return;

	di->moved_cb = moved_cb;
	di->moved_client_data = client_data;
}

/*
 *	Sets the Desktop Icon's menu.
 *
 *	If there was a previous menu and it is not the same as the
 *	given menu then it will be destroyed.
 *
 *	If the given menu is NULL then the previous menu (if any)
 *	will be destroyed.
 */
void DeskIconSetMenu(deskicon_struct *di, GtkWidget *w)
{
	if(di == NULL)
	    return;

	/* No change in menu? */
	if(di->menu == w)
	    return;

	/* Destroy previous menu */
	if(di->menu != NULL)
	    gtk_widget_destroy(di->menu);

	/* Set new menu (which may be NULL) */
	di->menu = w;
}

/*
 *	Returns the Desktop Icon's menu.
 */
GtkWidget *DeskIconGetMenu(deskicon_struct *di)
{
	return((di != NULL) ? di->menu : NULL);
}


/*
 *	Maps the Desktop Icon.
 */
void DeskIconMap(deskicon_struct *di)
{
	GtkWidget *w;

	if(di == NULL)
	    return;

	if(!di->map_state)
	{
	    gint x, y;

	    w = di->icon_toplevel;
	    if(w != NULL)
	    {
		if(di->flags & DESKICON_ALWAYS_ON_TOP)
		{
		    gtk_widget_show_raise(w);
		}
		else
		{
		    gtk_widget_show(w);
		    if(w->window != NULL)
			gdk_window_lower(w->window);
		}
	    }

	    w = di->label_toplevel;
	    if((w != NULL) && !STRISEMPTY(di->label_str))
	    {
		if(di->flags & DESKICON_ALWAYS_ON_TOP)
		{
		    gtk_widget_show_raise(w);
		}
		else
		{
		    gtk_widget_show(w);
		    if(w->window != NULL)
			gdk_window_lower(w->window);
		}
	    }

	    di->map_state = TRUE;

	    DeskIconGetPosition(di, &x, &y);
	    DeskIconSetPosition(di, x, y);
	}
}

/*
 *	Unmaps the Desktop Icon.
 */
void DeskIconUnmap(deskicon_struct *di)
{
	GtkWidget *w;


	if(di == NULL)
	    return;

	if(di->map_state)
	{
	    w = di->icon_toplevel;
	    if(w != NULL)
		gtk_widget_hide(w);

	    w = di->label_toplevel;
	    if(w != NULL)
		gtk_widget_hide(w);

	    di->map_state = FALSE;
	}
}

/*
 *	Deletes the Desktop Icon.
 */
void DeskIconDelete(deskicon_struct *di)
{
	if(di == NULL)
	    return;

	GTK_WIDGET_DESTROY(di->menu)
	GTK_WIDGET_DESTROY(di->label_da)
	GTK_WIDGET_DESTROY(di->label_toplevel)
	GTK_WIDGET_DESTROY(di->icon_da)
	GTK_WIDGET_DESTROY(di->icon_toplevel)

	GDK_PIXMAP_UNREF(di->icon_pixmap)
	GDK_BITMAP_UNREF(di->icon_mask)

	GDK_GC_UNREF(di->gc)

	g_free(di->label_str);

	g_free(di);
}
