#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>

#include "guiutils.h"
#include "cdialog.h"

#include "keymap_list.h"

#include "config.h"

#include "images/icon_customize_20x20.xpm"
#include "images/icon_clear_20x20.xpm"
#include "images/icon_revert_20x20.xpm"


typedef struct _query_window_struct	query_window_struct;
#define QUERY_WINDOW(p)			((query_window_struct *)(p))


/*
 *	Query Window:
 *
 *	For scanning keys and prompting user to press a key.
 */
struct _query_window_struct {

	GtkWidget	*toplevel,
			*shadow;
	GdkPixmap	*shadow_pm;

	keymap_list_struct	*kmlist;

	gboolean	got_key;

};


static gboolean KeymapListIsModifier(const guint c);
static gchar *KeymapListGetCellText(
	keymap_list_struct *kmlist,
	const gint row, const gint column
);

static void KeymapListCListDestroyCB(gpointer data);

static gint KeymapListCListEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
);
static void KeymapListSelectRowCB(
	GtkCList *clist, gint row, gint column, GdkEvent *event,
	gpointer data
);
static void KeymapListUnselectRowCB(
	GtkCList *clist, gint row, gint column, GdkEvent *event,
	gpointer data
);

static gint QueryWindowConfigureCB(
	GtkWidget *widget, GdkEventConfigure *configure, gpointer data
);
static gint QueryWindowShadowPaintCB(query_window_struct *qw);
static gint QueryWindowEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
);

static void KeymapListScanKeyCB(GtkWidget *widget, gpointer data);
static void KeymapListClearKeyCB(GtkWidget *widget, gpointer data);
static void KeymapListSetDefaultKeysCB(GtkWidget *widget, gpointer data);

static gint KeymapListScanIdleCB(gpointer data);

static void QueryWindowGrab(query_window_struct *qw);


/* Operations */
static void KeymapListDoScanKey(keymap_list_struct *kmlist);
void KeymapListScanKey(keymap_list_struct *kmlist, const gint row);
void KeymapListClearKey(keymap_list_struct *kmlist, const gint row);
void KeymapListSetDefaultKeys(keymap_list_struct *kmlist);
void KeymapListCheckConflicts(keymap_list_struct *kmlist);


/* Keymap Key */
keymap_key_struct *KeymapKeyNew(
	const guint keyval, const guint state, gpointer data
);
keymap_key_struct *KeymapKeyCopy(keymap_key_struct *km);
void KeymapKeyDelete(keymap_key_struct *km);


/* Keymap List */
keymap_list_struct *KeymapListNew(
	GtkWidget *parent,
	const GtkOrientation orientation
);
void KeymapListSetEditable(
	keymap_list_struct *kmlist, const gboolean editable
);
void KeymapListSetAutoCheckConflicts(
	keymap_list_struct *kmlist, const gboolean check
);
void KeymapListSetChangedCB(
	keymap_list_struct *kmlist,
	void (*cb)(keymap_list_struct *, gpointer),
	gpointer data
);
void KeymapListSetKeySetCB(
	keymap_list_struct *kmlist,
	void (*cb)(keymap_list_struct *, keymap_key_struct *, gpointer),
	gpointer data
);
void KeymapListSetKeyClearedCB(
	keymap_list_struct *kmlist,
	void (*cb)(keymap_list_struct *, keymap_key_struct *, gpointer),
	gpointer data
);
void KeymapListSetKeyDefaultRequestCB(
	keymap_list_struct *kmlist,
	void (*cb)(keymap_list_struct *, keymap_key_struct *, gpointer),
	gpointer data
);
gint KeymapListInsert( 
	keymap_list_struct *kmlist, gint row,
	guint8 **icon_data, const gchar *name,
	keymap_key_struct *km			/* Shared */
);
gint KeymapListAppend(
	keymap_list_struct *kmlist,
	guint8 **icon_data, const gchar *name,
	keymap_key_struct *km			/* Shared */
);
void KeymapListSet(
	keymap_list_struct *kmlist, gint row,
	guint8 **icon_data, const gchar *name,
	keymap_key_struct *km			/* Shared */
);
void KeymapListSetKey(
	keymap_list_struct *kmlist, gint row,
	keymap_key_struct *km			/* Shared */
);
void KeymapListClear(keymap_list_struct *kmlist);
keymap_key_struct *KeymapListGet(
	keymap_list_struct *kmlist, gint row
);
void KeymapListUpdateWidgets(keymap_list_struct *kmlist);
void KeymapListMap(keymap_list_struct *kmlist);
void KeymapListUnmap(keymap_list_struct *kmlist);
void KeymapListDelete(keymap_list_struct *kmlist);


#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)


#define KEYMAP_LIST_ROW_SPACING		20


/*
 *	Checks if the specified key is a modifier.
 */
static gboolean KeymapListIsModifier(const guint c)
{
	if((c == GDK_Shift_L) || (c == GDK_Shift_R) ||
	   (c == GDK_Control_L) || (c == GDK_Control_R) ||
	   (c == GDK_Caps_Lock) ||
	   (c == GDK_Shift_Lock) ||
	   (c == GDK_Meta_L) || (c == GDK_Meta_R) ||
	   (c == GDK_Alt_L) || (c == GDK_Alt_R) ||
	   (c == GDK_Super_L) || (c == GDK_Super_R) ||
	   (c == GDK_Hyper_L) || (c == GDK_Hyper_R) ||
	   (c == GDK_ISO_Lock) ||
	   (c == GDK_ISO_Level2_Latch) ||
	   (c == GDK_ISO_Level3_Shift) ||
	   (c == GDK_ISO_Level3_Latch) ||
	   (c == GDK_ISO_Level3_Lock) ||
	   (c == GDK_ISO_Group_Shift) ||
	   (c == GDK_ISO_Group_Latch) ||
	   (c == GDK_ISO_Group_Lock)
	)
	    return(TRUE);
	else
	    return(FALSE);
}

/*
 *	Returns a statically allocated pointer to the cell text on
 *	the Keymap List's GtkCList cell.
 */
static gchar *KeymapListGetCellText(
	keymap_list_struct *kmlist,
	const gint row, const gint column
)
{
	gchar *text = NULL;
	guint8 spacing;
	GdkPixmap *pixmap;
	GdkBitmap *mask;
	GtkCList *clist;

	if(kmlist == NULL)
	    return(NULL);

	clist = GTK_CLIST(kmlist->clist);

	if((row < 0) || (row >= clist->rows) ||
	   (column < 0) || (column >= clist->columns)
	)
	    return(NULL);

	switch(gtk_clist_get_cell_type(clist, row, column))
	{
	  case GTK_CELL_TEXT:
	    gtk_clist_get_text(clist, row, column, &text);
	    break;
	  case GTK_CELL_PIXTEXT:
	    gtk_clist_get_pixtext(
		clist, row, column, &text,
		&spacing, &pixmap, &mask
	    );
	    break;
	  case GTK_CELL_PIXMAP:
	  case GTK_CELL_WIDGET:
	  case GTK_CELL_EMPTY:
	    break;
	}

	return(text);
}


/*
 *	GtkCList item destroy callback.
 */
static void KeymapListCListDestroyCB(gpointer data)
{
	KeymapKeyDelete(KEYMAP_KEY(data));
}

/*
 *	GtkCList event signal callback.
 */
static gint KeymapListCListEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
)
{
	gint status = FALSE;
	gboolean key_press;
	GdkEventKey *key;
	GdkEventButton *button;
	GdkEventMotion *motion;
	keymap_list_struct *kmlist = KEYMAP_LIST(data);
	if((widget == NULL) || (event == NULL) || (kmlist == NULL))
	    return(status);

	switch((gint)event->type)
	{
	  case GDK_KEY_PRESS:
	  case GDK_KEY_RELEASE:
	    key = (GdkEventKey *)event;
	    key_press = (key->type == GDK_KEY_PRESS) ? TRUE : FALSE;
	    switch(key->keyval)
	    {
	      case GDK_Return:
		if(kmlist->flags & KEYMAP_LIST_EDITABLE)
		{
		    /* Start key scan only on key release, otherwise
		     * the a key press would be followed by a key
		     * release that would be immediately and
		     * undesireably scanned by the query window
		     */
		    if(!key_press)
			KeymapListScanKey(kmlist, -1);
		    status = TRUE;
		}
		break;

	      case GDK_Delete:
		if(kmlist->flags & KEYMAP_LIST_EDITABLE)
		{
		    if(key_press)
			KeymapListClearKey(kmlist, -1);
		    status = TRUE;
		}
		break;
	    }
	    break;

	  case GDK_BUTTON_PRESS:
	    button = (GdkEventButton *)event;
	    kmlist->button = button->button;
	    switch(button->button)
	    {
	      case GDK_BUTTON2:
		gdk_pointer_grab(
		    button->window,
		    FALSE,
		    GDK_BUTTON_PRESS_MASK |
		    GDK_BUTTON_RELEASE_MASK |
		    GDK_POINTER_MOTION_MASK |
		    GDK_POINTER_MOTION_HINT_MASK,
		    NULL,
		    kmlist->translate_cur,
		    button->time
		);
		kmlist->drag_last_x = (gint)button->x;
		kmlist->drag_last_y = (gint)button->y;
		status = TRUE;
		break;

	      case GDK_BUTTON3:
		break;
	    }
	    break;

	  case GDK_2BUTTON_PRESS:
	    button = (GdkEventButton *)event;
	    switch(button->button)
	    {
	      case GDK_BUTTON1:
		if(kmlist->flags & KEYMAP_LIST_EDITABLE)
		    KeymapListDoScanKey(kmlist);
		break;
	    }
	    /* Reset the pressed button on double clicks */
	    kmlist->button = 0;
	    kmlist->drag_last_x = 0;
	    kmlist->drag_last_y = 0;
	    break;

	  case GDK_BUTTON_RELEASE:
	    button = (GdkEventButton *)event;
	    switch(button->button)
	    {
	      case GDK_BUTTON2:
		if(kmlist->button > 0)
		    status = TRUE;
		if(gdk_pointer_is_grabbed())
		    gdk_pointer_ungrab(button->time);
		break;
	    }
	    kmlist->button = 0;
	    kmlist->drag_last_x = 0;
	    kmlist->drag_last_y = 0;
	    break;

	  case GDK_MOTION_NOTIFY:
	    motion = (GdkEventMotion *)event;
	    if(kmlist->button == GDK_BUTTON2)
	    {
		gint dx, dy;
		GtkCList *clist = GTK_CLIST(kmlist->clist);
		GtkAdjustment *hadj = clist->hadjustment,
			      *vadj = clist->vadjustment;
		if(motion->is_hint)
		{
		    gint x, y;
		    GdkModifierType mask;
		    gdk_window_get_pointer(
			motion->window, &x, &y, &mask
		    );
		    dx = x - kmlist->drag_last_x;
		    dy = y - kmlist->drag_last_y;
		    kmlist->drag_last_x = x;
		    kmlist->drag_last_y = y;
		}
		else
		{
		    dx = (gint)(motion->x - kmlist->drag_last_x);
		    kmlist->drag_last_x = (gint)motion->x;
		    dy = (gint)(motion->y - kmlist->drag_last_y);
		    kmlist->drag_last_y = (gint)motion->y;
		}
#define DO_SCROLL(adj,d)	{			\
 if((d) != 0) {						\
  if(((adj)->upper - (adj)->page_size) > (adj)->lower) {\
   gtk_adjustment_set_value(				\
    adj,						\
    CLIP(						\
     (adj)->value - (d),				\
     (adj)->lower, (adj)->upper - (adj)->page_size	\
    )							\
   );							\
  }							\
 }							\
}
		DO_SCROLL(hadj, dx);
		DO_SCROLL(vadj, dy);

#undef DO_SCROLL
		status = TRUE;
	    }
	    break;
	}

	return(status);
}

/*
 *	GtkCList "select_row" signal callback.
 */
static void KeymapListSelectRowCB(
	GtkCList *clist, gint row, gint column, GdkEvent *event,
	gpointer data
)
{
	keymap_list_struct *kmlist = KEYMAP_LIST(data);
	if((clist == NULL) || (event == NULL) || (kmlist == NULL))
	    return;

	if(gtk_clist_row_is_visible(clist, row) !=
	   GTK_VISIBILITY_FULL
	)
	    gtk_clist_moveto(
		clist,
		row, -1,	/* Row, column */
		0.5f, 0.0f	/* Row, column */
	    );

	KeymapListUpdateWidgets(kmlist);
}

/*
 *	GtkCList "unselect_row" signal callback.
 */
static void KeymapListUnselectRowCB(
	GtkCList *clist, gint row, gint column, GdkEvent *event,
	gpointer data
)
{
	keymap_list_struct *kmlist = KEYMAP_LIST(data);
	if((clist == NULL) || (event == NULL) || (kmlist == NULL))
	    return;





	KeymapListUpdateWidgets(kmlist);
}


/*
 *	Query Window toplevel "configure_event" event signal callback.
 *
 *	Used to update the Query Window's position relative to the
 *	Keymap List and its shadow.
 */
static gint QueryWindowConfigureCB(
	GtkWidget *widget, GdkEventConfigure *configure, gpointer data
)
{
	gint x, y, new_width, new_height;
	GtkWidget *w;
	keymap_list_struct *kmlist;
	query_window_struct *qw = QUERY_WINDOW(data);
	if((widget == NULL) || (configure == NULL) || (qw == NULL))
	    return(FALSE);

	new_width = configure->width;
	new_height = configure->height;
	kmlist = qw->kmlist;

	/* Calculate appropriate position of toplevel to be centered
	 * on the Keymap List's GtkCList
	 */
	x = y = 0;
	w = kmlist->clist;
	if(w != NULL)
	{
	    gint width, height;
	    GdkWindow *window = w->window;
	    gdk_window_get_root_position(window, &x, &y);
	    gdk_window_get_size(window, &width, &height);
	    x += (width - new_width) / 2;
	    y += (height - new_height) / 2;
	}

	if((x != configure->x) || (y != configure->y))
	{
	    w = qw->toplevel;
	    gdk_window_move(w->window, x, y);
	}

	/* Move/resize the shadow and recreate the shadow pixmap */
	w = qw->shadow;
	if(w != NULL)
	{
	    const gint	shadow_offset_x = 5,
			shadow_offset_y = 5;
	    GdkPixmap *pixmap;
	    GdkWindow *window = w->window;

	    gdk_window_move_resize(
		window,
		x + shadow_offset_x, y + shadow_offset_y,
		new_width, new_height
	    );

	    GDK_PIXMAP_UNREF(qw->shadow_pm);
	    qw->shadow_pm = pixmap = gdk_pixmap_new(
		window, new_width, new_height, -1
	    );
	    if(pixmap != NULL)
	    {
		GdkColor *c, shadow_color;
		GdkColormap *colormap = gdk_window_get_colormap(window);
		GdkWindow *root = gdk_window_get_parent(window);
		GdkGC *gc = GDK_GC_NEW();
		gdk_colormap_ref(colormap);
		c = &shadow_color;
		c->red = 0x5fff;
		c->green = 0x5fff;
		c->blue = 0x5fff;
		GDK_COLORMAP_ALLOC_COLOR(colormap, c);
		gdk_gc_set_subwindow(gc, GDK_INCLUDE_INFERIORS);
		gdk_window_copy_area(
		    pixmap, gc,
		    0, 0,
		    root,
		    x + shadow_offset_x, y + shadow_offset_y,
		    new_width, new_height
		);
		gdk_gc_set_subwindow(gc, GDK_CLIP_BY_CHILDREN);
		gdk_gc_set_function(gc, GDK_AND);
		gdk_gc_set_foreground(gc, c);
		gdk_draw_rectangle(
		    pixmap, gc, TRUE,
		    0, 0, new_width, new_height
		);
		GDK_GC_UNREF(gc);
		GDK_COLORMAP_FREE_COLOR(colormap, c);
		GDK_COLORMAP_UNREF(colormap);
	    }

	    gtk_widget_queue_draw(w);
	}

	return(FALSE);
}


/*
 *	Query Window shadow paint callback.
 */
static gint QueryWindowShadowPaintCB(query_window_struct *qw)
{
	gint width, height;
	GdkPixmap *pixmap = qw->shadow_pm;
	GtkWidget *w = qw->shadow;
	GdkWindow *window = (w != NULL) ? w->window : NULL;
	GtkStyle *style = gtk_widget_get_style(w);
	if((window == NULL) || (pixmap == NULL))
	    return(FALSE);

	gdk_window_get_size(pixmap, &width, &height);
	gdk_draw_pixmap(
	    window, style->white_gc, pixmap,
	    0, 0, 0, 0, width, height
	);

	return(FALSE);
}

/*
 *	Query Window toplevel event signal callback.
 */
static gint QueryWindowEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
)
{
	gint status = FALSE;
	GdkEventKey *key;
	GdkEventButton *button;
	GtkCList *clist;
	keymap_list_struct *kmlist;
	query_window_struct *qw = QUERY_WINDOW(data);
	if((widget == NULL) || (event == NULL) || (qw == NULL))
	    return(status);

	kmlist = qw->kmlist;
	if(kmlist == NULL)
	    return(status);

	switch((gint)event->type)
	{
	  case GDK_DELETE:
	    gtk_main_quit();
	    break;

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

	  /* Scan key on key releases only, otherwise a key press would
	   * be followed by a key release which would then be sent to
	   * the previously grabbed widget
	   */
	  case GDK_KEY_RELEASE:
	    key = (GdkEventKey *)event;
	    clist = (GtkCList *)kmlist->clist;
	    if(clist != NULL)
	    {
		/* Keyvals should always be scanned as their lowercase
		 * equivilent
		 */
		const guint keyval = gdk_keyval_to_lower(key->keyval);
		GList *glist = clist->selection_end;
		const gint row = (glist != NULL) ? (gint)glist->data : -1;
		keymap_key_struct *km = KeymapListGet(kmlist, row);

		/* Update the Keymap Key only if the keyval that was
		 * received is not a modifier key
		 */
		if((km != NULL) && !KeymapListIsModifier(keyval))
		{
		    keymap_key_struct *km_new = KeymapKeyNew(
			keyval, key->state, km->data
		    );
		    KeymapListSetKey(kmlist, row, km_new);
		    KeymapKeyDelete(km_new);

		    /* Mark that we got a response and a Keymap Key has
		     * been set
		     */
		    qw->got_key = TRUE;

		    gtk_main_quit();
		}
	    }
	    else
	    {
		gtk_main_quit();
	    }
	    status = TRUE;
	    break;

	  case GDK_BUTTON_PRESS:
	    button = (GdkEventButton *)event;
	    /* Any button press cancels the scan */
	    gtk_main_quit();
	    status = FALSE;
	    break;

	  case GDK_BUTTON_RELEASE:
	    button = (GdkEventButton *)event;
	    /* Grab the Query Window */
	    QueryWindowGrab(qw);
	    status = FALSE;
	    break;
	}

	return(status);
}


/*
 *	Scan key sequence callback.
 */
static void KeymapListScanKeyCB(GtkWidget *widget, gpointer data)
{
	KeymapListScanKey(KEYMAP_LIST(data), -1);
}

/*
 *	Clear key sequence callback.
 */
static void KeymapListClearKeyCB(GtkWidget *widget, gpointer data)
{
	KeymapListClearKey(KEYMAP_LIST(data), -1);
}

/*
 *	Set all Keymap Keys to default values callback.
 */
static void KeymapListSetDefaultKeysCB(GtkWidget *widget, gpointer data)
{
	KeymapListSetDefaultKeys(KEYMAP_LIST(data));
}


/*
 *	Keymap scan idle callback.
 *
 *	Set by KeymapListScanKey() to call KeymapListDoScanKey() at
 *	idle.
 */
static gint KeymapListScanIdleCB(gpointer data)
{
	KeymapListDoScanKey(KEYMAP_LIST(data));
	return(FALSE);
}


/*
 *	Grabs the Query Window.
 */
static void QueryWindowGrab(query_window_struct *qw)
{
	GtkWidget *w = (qw != NULL) ? qw->toplevel : NULL;
	if(w == NULL)
	    return;

	gtk_widget_grab_focus(w);
	gtk_grab_add(w);
	gdk_pointer_grab(
	    w->window,
	    TRUE,		/* Report event relative to event's window */
	    GDK_BUTTON_PRESS_MASK |
	    GDK_BUTTON_RELEASE_MASK,
	    NULL,		/* Do not confine pointer */
	    NULL,		/* Use default cursor */
	    GDK_CURRENT_TIME
	);
	gdk_keyboard_grab(
	    w->window,
	    TRUE,		/* Report event relative to event's window */
	    GDK_CURRENT_TIME
	);
}


/*
 *	Creates a Query Window, grabs the pointer, and blocks until the
 *	user responds with a key or button press.
 *
 *	If a key is pressed then the selected Keymap Key will be updated
 *	to the pressed key with any pressed modifiers.
 *
 *	If a button is pressed then the Keymap Key will not be updated.
 */
static void KeymapListDoScanKey(keymap_list_struct *kmlist)
{
	const gint	border_major = 5;
	gboolean got_key;
	gint x, y;
	GdkWindow *window;
	GtkWidget *w, *parent, *parent2, *parent3;
	query_window_struct *qw;

	if(kmlist == NULL)
	    return;

	/* Build query window */
	qw = QUERY_WINDOW(g_malloc0(sizeof(query_window_struct)));
	if(qw == NULL)
	    return;

	qw->kmlist = kmlist;
	qw->got_key = FALSE;

	/* Create toplevel */
	qw->toplevel = w = gtk_window_new(GTK_WINDOW_POPUP);
	gtk_widget_ref(w);
	gdk_window_get_root_position(
	    kmlist->clist->window, &x, &y
	);
	gtk_widget_set_uposition(w, x, y);
	gtk_window_set_policy(GTK_WINDOW(w), FALSE, FALSE, TRUE);
	gtk_widget_add_events(
	    w,
	    GDK_EXPOSURE_MASK | GDK_STRUCTURE_MASK |
	    GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK |
	    GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "delete_event",
	    GTK_SIGNAL_FUNC(QueryWindowEventCB), qw
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "configure_event",
	    GTK_SIGNAL_FUNC(QueryWindowConfigureCB), qw
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "key_press_event",
	    GTK_SIGNAL_FUNC(QueryWindowEventCB), qw
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "key_release_event",
	    GTK_SIGNAL_FUNC(QueryWindowEventCB), qw
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_press_event",
	    GTK_SIGNAL_FUNC(QueryWindowEventCB), qw
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_release_event",
	    GTK_SIGNAL_FUNC(QueryWindowEventCB), qw
	);
	gtk_widget_realize(w);
	window = w->window;
	parent = w;

	w = gtk_frame_new(NULL);
	gtk_frame_set_shadow_type(GTK_FRAME(w), GTK_SHADOW_OUT);
	gtk_container_add(GTK_CONTAINER(parent), w);
	gtk_widget_show(w);
	parent = w;

	w = gtk_vbox_new(FALSE, border_major);
	gtk_container_add(GTK_CONTAINER(parent), w);
	gtk_container_border_width(GTK_CONTAINER(w), border_major);
	gtk_widget_show(w);
	parent = w;

	w = gtk_hbox_new(FALSE, border_major);
	gtk_box_pack_start(GTK_BOX(parent), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent2 = w;

	w = gtk_vbox_new(TRUE, border_major);
	gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent3 = w;

	w = gtk_pixmap_new_from_xpm_d(
	    NULL, NULL,
	    (guint8 **)icon_customize_20x20_xpm
	);
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
	gtk_widget_show(w);

	w = gtk_vbox_new(TRUE, border_major);
	gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent3 = w;

	w = gtk_label_new(
#if defined(PROG_LANGUAGE_SPANISH)
"Apriete La Sucesion Clave..."
#elif defined(PROG_LANGUAGE_FRENCH)
"Raccourcis Clavier..."
#elif defined(PROG_LANGUAGE_GERMAN)
"Drucken Sie Reihenfolge Der Tasten..."
#elif defined(PROG_LANGUAGE_ITALIAN)
"Premere La Sequenza Di Chiavi..."
#elif defined(PROG_LANGUAGE_DUTCH)
"De Persopeenvolging Van Sleutels..."
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Pressione Sequencia Chave..."
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Trykke Rekkefolge Av Nekler"
#else
"Press key sequence"
#endif
	);
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
	gtk_widget_show(w);


	/* Create shadow */
	qw->shadow = w = gtk_window_new(GTK_WINDOW_POPUP);
	gtk_widget_ref(w);
	gdk_window_get_root_position(
	    kmlist->clist->window, &x, &y  
	);
	gtk_widget_set_uposition(w, x, y);
	gtk_widget_set_usize(w, 1, 1);
	gtk_widget_set_app_paintable(w, TRUE);
	gtk_signal_connect_object(
	    GTK_OBJECT(w), "expose_event",
	    GTK_SIGNAL_FUNC(QueryWindowShadowPaintCB), (GtkObject *)qw
	);
	gtk_signal_connect_object(
	    GTK_OBJECT(w), "draw",
	    GTK_SIGNAL_FUNC(QueryWindowShadowPaintCB), (GtkObject *)qw
	);
	gtk_window_set_policy(GTK_WINDOW(w), FALSE, FALSE, TRUE);
	gtk_widget_realize(w);


	/* Stop key repeating */
	gdk_key_repeat_disable();

	/* Show toplevel and shadow */
	gtk_widget_show_raise(qw->shadow);
	gtk_widget_show_raise(qw->toplevel);


	/* Grab the Query Window */
	QueryWindowGrab(qw);


	/* Push main loop level and block */
	gtk_main();


	/* Ungrab the Query Window */
	gtk_grab_remove(qw->toplevel);
	gdk_pointer_ungrab(GDK_CURRENT_TIME);
	gdk_keyboard_ungrab(GDK_CURRENT_TIME);

	/* Get response flag */
	got_key = qw->got_key;

	/* Destroy the Query Window */
	GTK_WIDGET_DESTROY(qw->toplevel);
	gtk_widget_unref(qw->toplevel);
	GTK_WIDGET_DESTROY(qw->shadow);
	gtk_widget_unref(qw->shadow);
	GDK_PIXMAP_UNREF(qw->shadow_pm);
	g_free(qw);

	/* Restore key repeating */
	gdk_key_repeat_restore();


	/* Was a new Keymap Key key sequence set? */
	if(got_key)
	{
	    GtkCList *clist = GTK_CLIST(kmlist->clist);
	    GList *glist = clist->selection_end;
	    const gint row = (glist != NULL) ? (gint)glist->data : -1;
	    keymap_key_struct *km;

	    /* Automatically check for conflicts? */
	    if(kmlist->flags & KEYMAP_LIST_AUTO_CHECK_CONFLICTS)
		KeymapListCheckConflicts(kmlist);

	    km = KeymapListGet(kmlist, row);

	    KeymapListUpdateWidgets(kmlist);

	    /* Report Keymap Key set and changed */
	    if(kmlist->key_set_cb != NULL)
		kmlist->key_set_cb(
		    kmlist,			/* Keymap List */
		    km,				/* Keymap Key */
		    kmlist->key_set_data	/* Data */
		);
	    if(kmlist->changed_cb != NULL)
		kmlist->changed_cb(
		    kmlist,			/* Keymap List */
		    kmlist->changed_data	/* Data */
	        );
	}
}


/*
 *	Prompts the user to enter the key sequence for the Keymap Key
 *	specified by row.
 *
 *	If row is -1 then the current selected Keymap Key will be used.
 *
 *	This call will actually queue a call to scan the key sequence
 *	at idle.
 */
void KeymapListScanKey(keymap_list_struct *kmlist, const gint row)
{
	if(kmlist == NULL)
	    return;

	/* Need to select Keymap Key? */
	if(row > -1)
	{
	    GtkCList *clist = GTK_CLIST(kmlist->clist);
	    gtk_clist_unselect_all(clist);
	    gtk_clist_select_row(clist, row, 0);
	}

	/* Schedual idle call to scan the key sequence */
	gtk_idle_add(KeymapListScanIdleCB, kmlist);
}

/*
 *	Clears the key sequence for the Keymap Key specified by row.
 *
 *	If row is -1 then the current selected Keymap Key will be used.
 */
void KeymapListClearKey(keymap_list_struct *kmlist, const gint row)
{
	gint _row = row;
	GList *glist;
	GtkCList *clist;
	keymap_key_struct *km, *km_new;

	if(kmlist == NULL)
	    return;

	clist = GTK_CLIST(kmlist->clist);

	/* Need to select Keymap Key? */
	if(_row > -1)
	{
	    gtk_clist_unselect_all(clist);
	    gtk_clist_select_row(clist, _row, 0);
	}

	/* Get selected Keymap Key */
	glist = clist->selection_end;
	_row = (glist != NULL) ? (gint)glist->data : -1;
	if(_row < 0)
	    return;

	km = KeymapListGet(kmlist, _row);
	if(km == NULL)
	    return;

	/* Copy/clear the Keymap Key's key sequence */
	km_new = KeymapKeyNew(0, 0, km->data);
	KeymapListSetKey(kmlist, _row, km_new);
	KeymapKeyDelete(km_new);

	/* Reget the Keymap Key */
	km = KeymapListGet(kmlist, _row);

	KeymapListUpdateWidgets(kmlist);

	/* Report Keymap Key cleared and changed */
	if(kmlist->key_cleared_cb != NULL)
	    kmlist->key_cleared_cb(
		kmlist,				/* Keymap List */
		km,				/* Keymap Key */
		kmlist->key_cleared_data	/* Data */
	    );
	if(kmlist->changed_cb != NULL)
	    kmlist->changed_cb(
		kmlist,				/* Keymap List */
		kmlist->changed_data		/* Data */
	    );
}

/*
 *	Requests default key sequences for all Keymap Keys.
 */
void KeymapListSetDefaultKeys(keymap_list_struct *kmlist)
{
	gint row, rows;
	void (*func_cb)(keymap_list_struct *, keymap_key_struct *, gpointer);
	gpointer func_data;
	GtkCList *clist;
	keymap_key_struct *km, *km_new;

	if(kmlist == NULL)
	    return;

	/* Confirm set default keys? */
	if(kmlist->flags & KEYMAP_LIST_CONFIRM_SET_DEFAULT_KEYS)
	{
	    gint response;
	    GtkWidget *toplevel = gtk_widget_get_toplevel(kmlist->toplevel);
	    CDialogSetTransientFor(toplevel);
	    response = CDialogGetResponse(
#if defined(PROG_LANGUAGE_SPANISH)
"Llaves Predefinidas Fijas",
"?Usted esta seguro que usted quiere poner las\n\
llaves predefinidas?",
#elif defined(PROG_LANGUAGE_FRENCH)
"Charger les raccourcis clavier prédéfinis",
"Etes vous sur de vouloir regler les raccourcis clavier\n\
prédéfinis?",
#elif defined(PROG_LANGUAGE_GERMAN)
"Fester Standardwert Gibt Ein",
"Sind Sie Sicher Sie Wollen Setzen Die\n\
Standardtasten?",
#elif defined(PROG_LANGUAGE_ITALIAN)
"Regolare Le Chiavi Predefinite",
"Lei e sicuro che lei vuole regolare le chiavi\n\
predefinite?",
#elif defined(PROG_LANGUAGE_DUTCH)
"Vaste Standaardsleutels",
"Bent u zeker u wil zetten de standaardsleutels?",
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Teclas Fixas De Omissao",
"Estao seguro quer por as teclas de omissao?",
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Fast Standardverdi Fester",
"Er De Sikker De setter standardnoklene?",
#else
"Set Default Keys",
"Are you sure you want to set default keys?",
#endif
		NULL,
		CDIALOG_ICON_QUESTION,
		CDIALOG_BTNFLAG_YES | CDIALOG_BTNFLAG_NO,
		CDIALOG_BTNFLAG_NO
	    );
	    CDialogSetTransientFor(NULL);
	    if(response != CDIALOG_RESPONSE_YES)
		return;
	}

	func_cb = kmlist->key_default_request_cb;
	func_data = kmlist->key_default_request_data;
	if(func_cb == NULL)
	    return;

	clist = GTK_CLIST(kmlist->clist);
	rows = clist->rows;
	for(row = 0; row < rows; row++)
	{
	    km = KeymapListGet(kmlist, row);
	    if(km == NULL)
		continue;

	    /* Create a copy of the Keymap Key and request default
	     * values for it, then update the Keymap Key and delete the
	     * copy
	     */
	    km_new = KeymapKeyCopy(km);
	    func_cb(kmlist, km_new, func_data);
	    KeymapListSetKey(kmlist, row, km_new);
	    KeymapKeyDelete(km_new);

	    /* Reget the Keymap Key */
	    km = KeymapListGet(kmlist, row);

	    /* Report Keymap Key set */
	    if(km->keyval != 0)
	    {
		if(kmlist->key_set_cb != NULL)
		    kmlist->key_set_cb(
			kmlist,			/* Keymap List */
			km,			/* Keymap Key */
			kmlist->key_set_data	/* Data */
		    );
	    }
	    else
	    {
		if(kmlist->key_cleared_cb != NULL)
		    kmlist->key_cleared_cb(
			kmlist,			/* Keymap List */
			km,			/* Keymap Key */
			kmlist->key_cleared_data	/* Data */
		    );
	    }
	}

	/* Check for conflicts */
	if(kmlist->flags & KEYMAP_LIST_AUTO_CHECK_CONFLICTS)
	    KeymapListCheckConflicts(kmlist);

	/* Report changed once at the end */
	if(kmlist->changed_cb != NULL)
	    kmlist->changed_cb(
		kmlist,				/* Keymap List */
		kmlist->changed_data		/* Data */
	    );

	KeymapListUpdateWidgets(kmlist);
}

/*
 *	Checks and reports key sequence conflicts amoung the Keymap
 *	Keys.
 */
void KeymapListCheckConflicts(keymap_list_struct *kmlist)
{
	gboolean has_conflict = FALSE;
	gint i, n, rows;
	GtkCList *clist;
	keymap_key_struct *km, *km2;

	if(kmlist == NULL)
	    return;

	clist = GTK_CLIST(kmlist->clist);
	rows = clist->rows;

	/* Iterate through all Keymap Keys */
	for(i = 0; i < rows; i++)
	{
	    km = KeymapListGet(kmlist, i);
	    if(km == NULL)
		continue;

	    /* Skip unset Keymap Keys */
	    if(km->keyval == 0)
		continue;

	    /* Iterate through all other Keymap Keys to see if one
	     * conflicts (has the same key sequence) as this Keymap Key
	     */
	    for(n = 0; n < rows; n++)
	    {
		/* Skip itself */
		if(n == i)
		    continue;

		km2 = KeymapListGet(kmlist, n);
		if(km2 == NULL)
		    continue;

		/* Has the same key sequence? */
		if((km2->keyval == km->keyval) &&
		   (km2->state == km->state)
		)
		{
		    /* Print warning */
		    gchar	*name1 = STRDUP(KeymapListGetCellText(
			kmlist, i, 0
		    )),
				*name2 = STRDUP(KeymapListGetCellText(
			kmlist, n, 0
		    ));
		    GtkWidget *toplevel = gtk_widget_get_toplevel(kmlist->toplevel);
		    gchar *s = g_strdup_printf(
#if defined(PROG_LANGUAGE_SPANISH)
"Llave:\n\
\n\
    %s\n\
\n\
Los conflictos con llave:\n\
\n\
    %s",
#elif defined(PROG_LANGUAGE_FRENCH)
"Séquence de touches:\n\
\n\
    %s\n\
\n\
en conflits avec le raccourci:\n\
\n\
    %s",
#elif defined(PROG_LANGUAGE_GERMAN)
"Tasten:\n\
\n\
    %s\n\
\n\
Konflikte mit taste:\n\
\n\
    %s",
#elif defined(PROG_LANGUAGE_ITALIAN)
"Chiave:\n\
\n\
    %s\n\
\n\
I conflitti con la chiave:\n\
\n\
    %s",
#elif defined(PROG_LANGUAGE_DUTCH)
"De sleutel:\n\
\n\
    %s\n\
\n\
Conflicten met sleutel:\n\
\n\
    %s",
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Tecla:\n\
\n\
    %s\n\
\n\
Os conflitos com tecla:\n\
\n\
    %s",
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Nokkel:\n\
\n\
    %s\n\
\n\
Konflikter med nokkel:\n\
\n\
    %s",
#else
"Key sequence for function:\n\
\n\
    %s\n\
\n\
Conflicts with key sequence for function:\n\
\n\
    %s",
#endif
			name1, name2
		    );
		    CDialogSetTransientFor(toplevel);
		    CDialogGetResponse(
#if defined(PROG_LANGUAGE_SPANISH)
"Llaves Opuestas",
#elif defined(PROG_LANGUAGE_FRENCH)
"Conflit de raccourcis",
#elif defined(PROG_LANGUAGE_GERMAN)
"Sich Widersprechende",
#elif defined(PROG_LANGUAGE_ITALIAN)
"Chiavi Opposte",
#elif defined(PROG_LANGUAGE_DUTCH)
"Tegenstijdig Sleutels",
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Teclas Contraditorias",
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Stridendee Nokler",
#else
"Key Sequence Conflict",
#endif
			s,
			NULL,
			CDIALOG_ICON_WARNING,
			CDIALOG_BTNFLAG_OK,
			CDIALOG_BTNFLAG_OK
		    );
		    CDialogSetTransientFor(NULL);
		    g_free(s);
		    g_free(name1);
		    g_free(name2);
		    has_conflict = TRUE;
		    break;
		}
	    }
	    /* Stop checking if there was a conflict */
	    if(has_conflict)
		break;
	}
}


/*
 *	Creates a new Keymap Key.
 */
keymap_key_struct *KeymapKeyNew(
	const guint keyval, const guint state, gpointer data
)
{
	keymap_key_struct *km = KEYMAP_KEY(g_malloc0(
	    sizeof(keymap_key_struct)
	));
	if(km == NULL)
	    return(NULL);

	km->keyval = keyval;
	km->state = state;
	km->data = data;

	return(km);
}

/*
 *	Coppies the Keymap Key.
 */
keymap_key_struct *KeymapKeyCopy(keymap_key_struct *km)
{
	return((km != NULL) ?
	    KeymapKeyNew(km->keyval, km->state, km->data) : NULL
	);
}

/*
 *	Deletes the Keymap Key.
 */
void KeymapKeyDelete(keymap_key_struct *km)
{
	if(km == NULL)
	    return;

	g_free(km);
}


/*
 *	Creates a new Keymap List.
 */
keymap_list_struct *KeymapListNew(
	GtkWidget *parent,
	const GtkOrientation orientation
)
{
	const gint	border_major = 5,
			border_minor = 2;
	gchar *heading[2];
	GtkWidget *w, *parent2, *parent3;
	GtkCList *clist;
	keymap_list_struct *kmlist = KEYMAP_LIST(g_malloc0(
	    sizeof(keymap_list_struct)
	));
	if(kmlist == NULL)
	    return(NULL);

	kmlist->translate_cur = gdk_cursor_new(GDK_FLEUR);
	kmlist->flags =	KEYMAP_LIST_EDITABLE |
			KEYMAP_LIST_CONFIRM_SET_DEFAULT_KEYS;
	kmlist->orientation = orientation;
	kmlist->button = 0;
	kmlist->drag_last_x = 0;
	kmlist->drag_last_y = 0;
	kmlist->changed_cb = NULL;
	kmlist->changed_data = NULL;
	kmlist->key_set_cb = NULL;
	kmlist->key_set_data = NULL;
	kmlist->key_cleared_cb = NULL;
	kmlist->key_cleared_data = NULL;
	kmlist->key_default_request_cb = NULL;
	kmlist->key_default_request_data = NULL;

	/* Create toplevel GtkBox */
	kmlist->toplevel = w = (orientation == GTK_ORIENTATION_VERTICAL) ?
	    gtk_vbox_new(FALSE, border_major) :
	    gtk_hbox_new(FALSE, border_major);
	if(parent != NULL)
	{
	    if(GTK_IS_BOX(parent))
		gtk_box_pack_start(GTK_BOX(parent), w, TRUE, TRUE, 0);
	    else if(GTK_IS_CONTAINER(parent))
		gtk_container_add(GTK_CONTAINER(parent), w);
	}
	parent = w;

	/* Create the GtkScrolledWindow for the GtkCList */
	w = gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_policy(
	    GTK_SCROLLED_WINDOW(w),
	    GTK_POLICY_AUTOMATIC,
	    GTK_POLICY_AUTOMATIC
	);
	gtk_box_pack_start(GTK_BOX(parent), w, TRUE, TRUE, 0);
	gtk_widget_show(w);
	parent2 = w;

	/* Create the GtkCList */
#if defined(PROG_LANGUAGE_SPANISH)
	heading[0] = "Funcion";
	heading[1] = "Sucesion De Llaves";
#elif defined(PROG_LANGUAGE_FRENCH)
	heading[0] = "Fonction";
	heading[1] = "Sequence de touches";
#elif defined(PROG_LANGUAGE_GERMAN)
	heading[0] = "Funktion";
	heading[1] = "Reihenfolge Der Tasten";
#elif defined(PROG_LANGUAGE_ITALIAN)
	heading[0] = "Funzione";
	heading[1] = "Sequenza Di Chiavi";
#elif defined(PROG_LANGUAGE_DUTCH)
	heading[0] = "Functie";
	heading[1] = "Opeenvolging Van Sleutels";
#elif defined(PROG_LANGUAGE_PORTUGUESE)
	heading[0] = "Funcao";
	heading[1] = "Sequencia De Teclas";
#elif defined(PROG_LANGUAGE_NORWEGIAN)
	heading[0] = "Funksjon";
	heading[1] = "Rekkefolge Av Nokler";
#else
	heading[0] = "Function";
	heading[1] = "Key Sequence";
#endif
	kmlist->clist = w = gtk_clist_new_with_titles(
	    sizeof(heading) / sizeof(gchar *), heading
	);
	clist = GTK_CLIST(w);
	gtk_widget_add_events(
	    w,
	    GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK |
	    GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "key_press_event", 
	    GTK_SIGNAL_FUNC(KeymapListCListEventCB), kmlist
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "key_release_event",
	    GTK_SIGNAL_FUNC(KeymapListCListEventCB), kmlist
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_press_event",
	    GTK_SIGNAL_FUNC(KeymapListCListEventCB), kmlist
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_release_event",
	    GTK_SIGNAL_FUNC(KeymapListCListEventCB), kmlist
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "motion_notify_event",
	    GTK_SIGNAL_FUNC(KeymapListCListEventCB), kmlist
	);
#if 0
	gtk_signal_connect_after(
	    GTK_OBJECT(w), "button_press_event",
	    GTK_SIGNAL_FUNC(KeymapListCListEventCB), kmlist
	);
#endif
	gtk_signal_connect(
	    GTK_OBJECT(w), "select_row",
	    GTK_SIGNAL_FUNC(KeymapListSelectRowCB), kmlist
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "unselect_row",
	    GTK_SIGNAL_FUNC(KeymapListUnselectRowCB), kmlist
	);
	gtk_container_add(GTK_CONTAINER(parent2), w);
	gtk_widget_realize(w);
	gtk_clist_set_row_height(clist, KEYMAP_LIST_ROW_SPACING);
	gtk_clist_set_column_auto_resize(clist, 0, TRUE);
	gtk_clist_set_column_auto_resize(clist, 1, TRUE);
	gtk_clist_set_selection_mode(clist, GTK_SELECTION_BROWSE);
	gtk_clist_column_titles_passive(clist);
	gtk_widget_show(w);


	if(orientation == GTK_ORIENTATION_VERTICAL)
	{
	    /* GtkBox for the buttons */
	    kmlist->buttons_box = w = gtk_hbox_new(TRUE, 0);
	    gtk_box_pack_start(GTK_BOX(parent), w, FALSE, FALSE, 0);
	    gtk_widget_show(w);
	    parent3 = w;

	}
	else
	{
	    /* GtkBox for the buttons */
	    kmlist->buttons_box = w = gtk_vbox_new(FALSE, 2 * border_major);
	    gtk_box_pack_start(GTK_BOX(parent), w, FALSE, FALSE, 0);
	    gtk_widget_show(w);
	    parent2 = w;

	    w = gtk_vbox_new(FALSE, border_minor);
	    gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0);
	    gtk_widget_show(w);
	    parent3 = w;
	}

	/* Scan button */
	kmlist->scan_btn = w = GUIButtonPixmapLabelH(
	    (guint8 **)icon_customize_20x20_xpm,
#if defined(PROG_LANGUAGE_SPANISH)
"Escudrine",
#elif defined(PROG_LANGUAGE_FRENCH)
"scrute",
#elif defined(PROG_LANGUAGE_GERMAN)
"Prufen",
#elif defined(PROG_LANGUAGE_ITALIAN)
"Scansione",
#elif defined(PROG_LANGUAGE_DUTCH)
"Onderzoe",
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Esquadrinhe",
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Avsok",
#else
"Scan",
#endif
	    NULL
	);
	gtk_widget_set_usize(
	    w, GUI_BUTTON_HLABEL_WIDTH, GUI_BUTTON_HLABEL_HEIGHT
	);
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(KeymapListScanKeyCB), kmlist
	);
	GUISetWidgetTip(w,
#if defined(PROG_LANGUAGE_SPANISH)
"Escudrina la sucesion clave para la llave escogida"
#elif defined(PROG_LANGUAGE_FRENCH)
"Scrute le raccourci pour la touche sélectée"
#elif defined(PROG_LANGUAGE_GERMAN)
"Pruft die schlusselreihenfolge fur die ausgewahlte taste"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Scandisce la sequenza principale per la chiave scelta"
#elif defined(PROG_LANGUAGE_DUTCH)
"Onderzoekt nauwkeurig de hoofdopeenvolging voor de geselecteerde sleutel"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Esquadrinha a sequencia chave para a tecla selecionada"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Avsoker nokkelrekkefolgen for den valgte ut nokkelen"
#else
"Scans the key sequence for the selected key"
#endif
	);
	gtk_widget_show(w);

	/* Clear button */
	kmlist->clear_btn = w = GUIButtonPixmapLabelH(
	    (guint8 **)icon_clear_20x20_xpm,
#if defined(PROG_LANGUAGE_SPANISH)
"Vacie",
#elif defined(PROG_LANGUAGE_FRENCH)
"Effacer",
#elif defined(PROG_LANGUAGE_GERMAN)
"Reinigen",
#elif defined(PROG_LANGUAGE_ITALIAN)
"Chiarire",
#elif defined(PROG_LANGUAGE_DUTCH)
"Klart",
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Aclare",
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Rydd",
#else
"Clear",
#endif
	    NULL
	);
	gtk_widget_set_usize(
	    w, GUI_BUTTON_HLABEL_WIDTH, GUI_BUTTON_HLABEL_HEIGHT
	);
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(KeymapListClearKeyCB), kmlist
	);
	GUISetWidgetTip(w,
#if defined(PROG_LANGUAGE_SPANISH)
"Vacia la sucesio'n clave para la llave escogida"
#elif defined(PROG_LANGUAGE_FRENCH)
"Effacer le raccourci pour la touche sélectée"
#elif defined(PROG_LANGUAGE_GERMAN)
"Reinigt die schlusselreihenfolge fur die ausgewahlte taste"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Chiarisce la sequenza principale per la chiave scelta"
#elif defined(PROG_LANGUAGE_DUTCH)
"Klaart de hoofdopeenvolging voor de geselecteerde sleutel op"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Aclara a sequencia chave para a tecla selecionada"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Rydder nokkelrekkefolgen for den valgte ut nokkelen"
#else
"Clears the key sequence for the selected key"
#endif
	);
	gtk_widget_show(w);

	if(orientation == GTK_ORIENTATION_HORIZONTAL)
	{
	    w = gtk_vbox_new(FALSE, border_minor);
	    gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0);
	    gtk_widget_show(w);
	    parent3 = w;
	}

	/* Defaults button */
	kmlist->defaults_btn = w = GUIButtonPixmapLabelH(
	    (guint8 **)icon_revert_20x20_xpm,
#if defined(PROG_LANGUAGE_SPANISH)
"Defectos",
#elif defined(PROG_LANGUAGE_FRENCH)
"prédéfinis",
#elif defined(PROG_LANGUAGE_GERMAN)
"Standardwerte",
#elif defined(PROG_LANGUAGE_ITALIAN)
"Predefinito",
#elif defined(PROG_LANGUAGE_DUTCH)
"Stdwrden",
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Omissoes",
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Stdvdr",
#else
"Defaults",
#endif
	    NULL
	);
	gtk_widget_set_usize(
	    w, GUI_BUTTON_HLABEL_WIDTH, GUI_BUTTON_HLABEL_HEIGHT
	);
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(KeymapListSetDefaultKeysCB), kmlist
	);
	GUISetWidgetTip(w,
#if defined(PROG_LANGUAGE_SPANISH)
"Pone la sucesion clave predefinida para el todo adapta"
#elif defined(PROG_LANGUAGE_FRENCH)
"Prédéfinir les raccourcis de toutes les touches"
#elif defined(PROG_LANGUAGE_GERMAN)
"Setzt die standardschlusselreihenfolge fur die alle tasten"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Regola la sequenza predefinita principale per le tutte le chiavi"
#elif defined(PROG_LANGUAGE_DUTCH)
"Zet de standaardhoofdopeenvolging voor de alle sleutels"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Ponha a omissao sequencia chave para as todas teclas"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Setter standardnokkelrekkefolgen for de alle nokler"
#else
"Sets the default key sequence for the all keys"
#endif
	);
	gtk_widget_show(w);


	KeymapListUpdateWidgets(kmlist);

	return(kmlist);
}

/*
 *	Sets the Keymap List editable.
 */
void KeymapListSetEditable(
	keymap_list_struct *kmlist, const gboolean editable  
)
{
	if(kmlist == NULL)
	    return;

	if(editable && !(kmlist->flags & KEYMAP_LIST_EDITABLE))
	{
	    kmlist->flags |= KEYMAP_LIST_EDITABLE;
	    gtk_widget_show(kmlist->buttons_box);
	    KeymapListUpdateWidgets(kmlist);
	}
	else if(!editable && (kmlist->flags & KEYMAP_LIST_EDITABLE))
	{
	    kmlist->flags &= ~KEYMAP_LIST_EDITABLE;
	    gtk_widget_hide(kmlist->buttons_box);
	    KeymapListUpdateWidgets(kmlist);
	}
}

/*
 *	Sets the Keymap List to automatically check for Keymap Key key
 *	sequence conflicts whenever a Keymap Key's key sequence is set.
 */
void KeymapListSetAutoCheckConflicts(
	keymap_list_struct *kmlist, const gboolean check
)
{
	if(kmlist == NULL)
	    return;

	if(check && !(kmlist->flags & KEYMAP_LIST_AUTO_CHECK_CONFLICTS))
	{
	    kmlist->flags |= KEYMAP_LIST_AUTO_CHECK_CONFLICTS;
	    KeymapListUpdateWidgets(kmlist);
	}
	else if(!check && (kmlist->flags & KEYMAP_LIST_AUTO_CHECK_CONFLICTS))
	{
	    kmlist->flags &= ~KEYMAP_LIST_AUTO_CHECK_CONFLICTS;
	    KeymapListUpdateWidgets(kmlist);
	}
}

/*
 *	Sets the Keymap List's changed callback.
 */
void KeymapListSetChangedCB(
	keymap_list_struct *kmlist,
	void (*cb)(keymap_list_struct *, gpointer),
	gpointer data
)
{
	if(kmlist == NULL)
	    return;

	kmlist->changed_cb = cb;
	kmlist->changed_data = data;

	KeymapListUpdateWidgets(kmlist);
}

/*
 *	Sets the Keymap List's key set callback.
 */
void KeymapListSetKeySetCB(
	keymap_list_struct *kmlist,
	void (*cb)(keymap_list_struct *, keymap_key_struct *, gpointer),
	gpointer data
)
{
	if(kmlist == NULL)
	    return;

	kmlist->key_set_cb = cb;
	kmlist->key_set_data = data;

	KeymapListUpdateWidgets(kmlist);
}

/*
 *	Sets the Keymap List's key cleared callback.
 */
void KeymapListSetKeyClearedCB(
	keymap_list_struct *kmlist,
	void (*cb)(keymap_list_struct *, keymap_key_struct *, gpointer),
	gpointer data
)
{
	if(kmlist == NULL)
	    return;

	kmlist->key_cleared_cb = cb;
	kmlist->key_cleared_data = data;

	KeymapListUpdateWidgets(kmlist);
}

/*
 *	Sets the Keymap List's key default request callback.
 */
void KeymapListSetKeyDefaultRequestCB(
	keymap_list_struct *kmlist,
	void (*cb)(keymap_list_struct *, keymap_key_struct *, gpointer),
	gpointer data
)
{ 
	if(kmlist == NULL)
	    return;

	kmlist->key_default_request_cb = cb;
	kmlist->key_default_request_data = data;

	KeymapListUpdateWidgets(kmlist);
}

/*
 *	Inserts an item to the Keymap List.
 */
gint KeymapListInsert(
	keymap_list_struct *kmlist, gint row,
	guint8 **icon_data, const gchar *name,
	keymap_key_struct *km		/* Shared */
)
{
	gint i, columns;
	gchar **strv;
	GtkCList *clist = (kmlist != NULL) ?
	    (GtkCList *)kmlist->clist : NULL;
	if(clist == NULL)
	    return(-1);

	columns = clist->columns;

	gtk_clist_freeze(clist);

	/* Allocate row cell values */
	strv = (gchar **)g_malloc0(columns * sizeof(gchar *));
	for(i = 0; i < columns; i++)
	    strv[i] = "";

	/* Insert or append row */
	if(row > -1)
	    row = gtk_clist_insert(clist, row, strv);
	else
	    row = gtk_clist_append(clist, strv);

	/* Delete row cell values */
	g_free(strv);

	/* Failed to create new row? */
	if(row < 0)
	{
	    gtk_clist_thaw(clist);
	    return(-1);
	}

	/* Set the row cells and copy/set the Keymap Key as the row
	 * data, this will delete the old Keymap Key (if any)
	 */
	KeymapListSet(kmlist, row, icon_data, name, km);

	gtk_clist_thaw(clist);

	KeymapListUpdateWidgets(kmlist);

	return(row);
}

/*
 *	Appends an item to the Keymap List.
 */
gint KeymapListAppend(
	keymap_list_struct *kmlist,
	guint8 **icon_data, const gchar *name,
	keymap_key_struct *km			/* Shared */
)
{
	return(KeymapListInsert(
	    kmlist, -1,
	    icon_data, name,
	    km
	));
}

/*
 *	Sets the Keymap Key for the specified Keymap List item and
 *	updates the Function and Key Sequence cells.
 */
void KeymapListSet(
	keymap_list_struct *kmlist, gint row,
	guint8 **icon_data, const gchar *name,
	keymap_key_struct *km			/* Shared */
)
{
	gint column;
	GtkCList *clist = (kmlist != NULL) ?
	    (GtkCList *)kmlist->clist : NULL;
	if(clist == NULL)
	    return;

	if((row < 0) || (row >= clist->rows))
	    return;

	gtk_clist_freeze(clist);

	/* Set the Function cell */
	column = 0;
	if(icon_data != NULL)
	{
	    GdkBitmap *mask;
	    GdkPixmap *pixmap = GDK_PIXMAP_NEW_FROM_XPM_DATA(
		&mask, icon_data
	    );
	    if(pixmap != NULL)
	    {
		gtk_clist_set_pixtext(
		    clist, row, column,
		    (name != NULL) ? name : "",
		    2,
		    pixmap,
		    mask
		);
		GDK_PIXMAP_UNREF(pixmap);
		GDK_BITMAP_UNREF(mask);
	    }
	    else
	    {
		gtk_clist_set_text(
		    clist, row, column,
		    (name != NULL) ? name : ""
		);
	    }
	}
	else
	{
	    gtk_clist_set_text(
		clist, row, column,
		(name != NULL) ? name : ""
	    );
	}

	/* Set the Key Sequence cell and copy/set the Keymap Key as
	 * the row data, this will delete the old Keymap Key (if any)
	 */
	KeymapListSetKey(kmlist, row, km);

	gtk_clist_thaw(clist);
}

/*
 *	Sets the Keymap Key for the specified Keymap List item and
 *	updates the Key Sequence cell.
 */
void KeymapListSetKey(
	keymap_list_struct *kmlist, gint row,
	keymap_key_struct *km			/* Shared */
)
{
	gint column;
	GtkCList *clist = (kmlist != NULL) ?
	    (GtkCList *)kmlist->clist : NULL;
	if(clist == NULL)
	    return;

	if((row < 0) || (row >= clist->rows))
	    return;

	gtk_clist_freeze(clist);

	/* Set the Key Sequence cell */
	column = 1;
	if((km != NULL) ? (km->keyval != 0) : FALSE)
	{
	    const guint	keyval = km->keyval,
			state = km->state;
	    gchar	*key_name = STRDUP(
			    gdk_keyval_name(gdk_keyval_to_upper(keyval))
			),
			*mods_name = STRDUP(""),
			*text;

#define CAT_MODS(_name_)	{			\
 gchar *s = g_strconcat(mods_name, (_name_), NULL);	\
 g_free(mods_name);					\
 mods_name = s;						\
}
	    if(state & GDK_LOCK_MASK)
	    {
		CAT_MODS("CapsLock+");
	    }
	    if(state & GDK_CONTROL_MASK)
	    {
		CAT_MODS("Ctrl+"); 
	    }
	    if(state & GDK_SHIFT_MASK)
	    {
		CAT_MODS("Shift+");
	    }
	    if(state & GDK_MOD1_MASK)
	    {
		CAT_MODS("Alt+");
	    }
	    if(state & GDK_MOD2_MASK)
	    {
		CAT_MODS("NumLock+");
	    }
	    if(state & GDK_MOD3_MASK)
	    {
		CAT_MODS("Mod3+");
	    }
	    if(state & GDK_MOD4_MASK)
	    {
		CAT_MODS("Mod4+");
	    }
	    if(state & GDK_MOD5_MASK)
	    {
		CAT_MODS("ScrollLock+");
	    }

	    text = g_strconcat(mods_name, key_name, NULL);

	    gtk_clist_set_text(
		clist, row, column,
		(text != NULL) ? text : ""
	    );

	    g_free(text);
	    g_free(mods_name);
	    g_free(key_name);
#undef CAT_MODS
	}
	else
	{
	    gtk_clist_set_text(
		clist, row, column, ""
	    );
	}

	/* Copy/set the Keymap Key as the row data, this will delete
	 * the old Keymap Key (if any)
	 */
	if(km != NULL)
	    gtk_clist_set_row_data_full(
		clist, row,
		KeymapKeyCopy(km),
		KeymapListCListDestroyCB
	    );

	gtk_clist_thaw(clist);
}

/*
 *	Clears the Keymap List.
 */
void KeymapListClear(keymap_list_struct *kmlist)
{
	GtkCList *clist = (kmlist != NULL) ?
	    (GtkCList *)kmlist->clist : NULL;
	if(clist == NULL)
	    return;

	gtk_clist_freeze(clist);
	gtk_clist_clear(clist);
	gtk_clist_thaw(clist);

	KeymapListUpdateWidgets(kmlist);
}

/*
 *	Returns the Keymap Key for the specified Keymap Key List item.
 */
keymap_key_struct *KeymapListGet(
	keymap_list_struct *kmlist, gint row
)
{
	GtkCList *clist = (kmlist != NULL) ?  
	    (GtkCList *)kmlist->clist : NULL;
	return((clist != NULL) ?
	    KEYMAP_KEY(gtk_clist_get_row_data(clist, row)) : NULL
	);
}

/*
 *	Updates the Keymap List's widgets.
 */
void KeymapListUpdateWidgets(keymap_list_struct *kmlist)
{
	gboolean b;
	gint row;
	GList *glist;
	GtkCList *clist;

	if(kmlist == NULL)
	    return;

	clist = GTK_CLIST(kmlist->clist);
	glist = clist->selection_end;
	row = (glist != NULL) ? (gint)glist->data : -1;

	b = (row > -1) ? TRUE : FALSE;
	GTK_WIDGET_SET_SENSITIVE(kmlist->scan_btn, b);
	b = (row > -1) ? TRUE : FALSE;
	GTK_WIDGET_SET_SENSITIVE(kmlist->clear_btn, b);
	b = (kmlist->key_default_request_cb != NULL) ? TRUE : FALSE;
	GTK_WIDGET_SET_SENSITIVE(kmlist->defaults_btn, b);



}

/*
 *	Maps the Keymap List.
 */
void KeymapListMap(keymap_list_struct *kmlist)
{
	GtkWidget *w;

	if(kmlist == NULL)
	    return;

	w = kmlist->toplevel;
	if(w != NULL)
	    gtk_widget_show(w);
}

/*
 *	Unmaps the Keymap List.
 */
void KeymapListUnmap(keymap_list_struct *kmlist)
{
	GtkWidget *w;

	if(kmlist == NULL)
	    return;

	w = kmlist->toplevel;
	if(w != NULL)
	    gtk_widget_hide(w);
}

/*
 *	Deletes the Keymap List.
 */
void KeymapListDelete(keymap_list_struct *kmlist)
{
	if(kmlist == NULL)
	    return;

	KeymapListClear(kmlist);

	GTK_WIDGET_DESTROY(kmlist->scan_btn);
	GTK_WIDGET_DESTROY(kmlist->clear_btn);
	GTK_WIDGET_DESTROY(kmlist->check_conflicts_btn);
	GTK_WIDGET_DESTROY(kmlist->defaults_btn);
	GTK_WIDGET_DESTROY(kmlist->buttons_box);
	GTK_WIDGET_DESTROY(kmlist->value_entry);
	GTK_WIDGET_DESTROY(kmlist->clist);
	GTK_WIDGET_DESTROY(kmlist->toplevel);

	GDK_CURSOR_DESTROY(kmlist->translate_cur);

	g_free(kmlist);
}
