/*
 * Copyright (C) 2006 Sergey V. Udaltsov <svu@gnome.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include <config.h>
#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include <gdk/gdkkeysyms.h>
#include <X11/XKBlib.h>
#include <X11/extensions/XKBgeom.h>
#include <stdlib.h>
#include <memory.h>
#include <math.h>
#include <glib/gi18n-lib.h>

#include <gkbd-keyboard-config.h>
#include <gkbd-keyboard-drawing.h>
#include <gkbd-keyboard-drawing-marshal.h>
#include <gkbd-util.h>

#define noKBDRAW_DEBUG

#define INVALID_KEYCODE ((guint)(-1))

#define GTK_RESPONSE_PRINT 2

#define CAIRO_LINE_WIDTH 1.0

#define KEY_FONT_SIZE 12

enum {
	BAD_KEYCODE = 0,
	NUM_SIGNALS
};

static guint gkbd_keyboard_drawing_signals[NUM_SIGNALS] = { 0 };

static GkbdKeyboardDrawingGroupLevel defaultGroupsLevels[] = {
	{0, 1},
	{0, 3},
	{0, 0},
	{0, 2}
};

static GkbdKeyboardDrawingGroupLevel *pGroupsLevels[] = {
	defaultGroupsLevels,
	defaultGroupsLevels + 1,
	defaultGroupsLevels + 2,
	defaultGroupsLevels + 3
};

static void gkbd_keyboard_drawing_set_mods (GkbdKeyboardDrawing * drawing,
					    guint mods);

extern gboolean xkl_xkb_config_native_prepare (XklEngine * engine,
					       const XklConfigRec * data,
					       gpointer component_names);

extern void xkl_xkb_config_native_cleanup (XklEngine * engine,
					   gpointer component_names);

static gint
xkb_to_pixmap_coord (GkbdKeyboardDrawingRenderContext * context, gint n)
{
	return n * context->scale_numerator / context->scale_denominator;
}

static gdouble
xkb_to_pixmap_double (GkbdKeyboardDrawingRenderContext * context,
		      gdouble d)
{
	return d * context->scale_numerator / context->scale_denominator;
}


/* angle is in tenths of a degree; coordinates can be anything as (xkb,
 * pixels, pango) as long as they are all the same */
static void
rotate_coordinate (gint origin_x,
		   gint origin_y,
		   gint x,
		   gint y, gint angle, gint * rotated_x, gint * rotated_y)
{
	*rotated_x =
	    origin_x + (x - origin_x) * cos (M_PI * angle / 1800.0) - (y -
								       origin_y)
	    * sin (M_PI * angle / 1800.0);
	*rotated_y =
	    origin_y + (x - origin_x) * sin (M_PI * angle / 1800.0) + (y -
								       origin_y)
	    * cos (M_PI * angle / 1800.0);
}

static gdouble
length (gdouble x, gdouble y)
{
	return sqrt (x * x + y * y);
}

static gdouble
point_line_distance (gdouble ax, gdouble ay, gdouble nx, gdouble ny)
{
	return ax * nx + ay * ny;
}

static void
normal_form (gdouble ax, gdouble ay,
	     gdouble bx, gdouble by,
	     gdouble * nx, gdouble * ny, gdouble * d)
{
	gdouble l;

	*nx = by - ay;
	*ny = ax - bx;

	l = length (*nx, *ny);

	*nx /= l;
	*ny /= l;

	*d = point_line_distance (ax, ay, *nx, *ny);
}

static void
inverse (gdouble a, gdouble b, gdouble c, gdouble d,
	 gdouble * e, gdouble * f, gdouble * g, gdouble * h)
{
	gdouble det;

	det = a * d - b * c;

	*e = d / det;
	*f = -b / det;
	*g = -c / det;
	*h = a / det;
}

static void
multiply (gdouble a, gdouble b, gdouble c, gdouble d,
	  gdouble e, gdouble f, gdouble * x, gdouble * y)
{
	*x = a * e + b * f;
	*y = c * e + d * f;
}

static void
intersect (gdouble n1x, gdouble n1y, gdouble d1,
	   gdouble n2x, gdouble n2y, gdouble d2, gdouble * x, gdouble * y)
{
	gdouble e, f, g, h;

	inverse (n1x, n1y, n2x, n2y, &e, &f, &g, &h);
	multiply (e, f, g, h, d1, d2, x, y);
}


/* draw an angle from the current point to b and then to c,
 * with a rounded corner of the given radius.
 */
static void
rounded_corner (cairo_t * cr,
		gdouble bx, gdouble by,
		gdouble cx, gdouble cy, gdouble radius)
{
	gdouble ax, ay;
	gdouble n1x, n1y, d1;
	gdouble n2x, n2y, d2;
	gdouble pd1, pd2;
	gdouble ix, iy;
	gdouble dist1, dist2;
	gdouble nx, ny, d;
	gdouble a1x, a1y, c1x, c1y;
	gdouble phi1, phi2;

	cairo_get_current_point (cr, &ax, &ay);
#ifdef KBDRAW_DEBUG
	printf ("        current point: (%f, %f), radius %f:\n", ax, ay,
		radius);
#endif

	/* make sure radius is not too large */
	dist1 = length (bx - ax, by - ay);
	dist2 = length (cx - bx, cy - by);

	radius = MIN (radius, MIN (dist1, dist2));

	/* construct normal forms of the lines */
	normal_form (ax, ay, bx, by, &n1x, &n1y, &d1);
	normal_form (bx, by, cx, cy, &n2x, &n2y, &d2);

	/* find which side of the line a,b the point c is on */
	if (point_line_distance (cx, cy, n1x, n1y) < d1)
		pd1 = d1 - radius;
	else
		pd1 = d1 + radius;

	/* find which side of the line b,c the point a is on */
	if (point_line_distance (ax, ay, n2x, n2y) < d2)
		pd2 = d2 - radius;
	else
		pd2 = d2 + radius;

	/* intersect the parallels to find the center of the arc */
	intersect (n1x, n1y, pd1, n2x, n2y, pd2, &ix, &iy);

	nx = (bx - ax) / dist1;
	ny = (by - ay) / dist1;
	d = point_line_distance (ix, iy, nx, ny);

	/* a1 is the point on the line a-b where the arc starts */
	intersect (n1x, n1y, d1, nx, ny, d, &a1x, &a1y);

	nx = (cx - bx) / dist2;
	ny = (cy - by) / dist2;
	d = point_line_distance (ix, iy, nx, ny);

	/* c1 is the point on the line b-c where the arc ends */
	intersect (n2x, n2y, d2, nx, ny, d, &c1x, &c1y);

	/* determine the first angle */
	if (a1x - ix == 0)
		phi1 = (a1y - iy > 0) ? M_PI_2 : 3 * M_PI_2;
	else if (a1x - ix > 0)
		phi1 = atan ((a1y - iy) / (a1x - ix));
	else
		phi1 = M_PI + atan ((a1y - iy) / (a1x - ix));

	/* determine the second angle */
	if (c1x - ix == 0)
		phi2 = (c1y - iy > 0) ? M_PI_2 : 3 * M_PI_2;
	else if (c1x - ix > 0)
		phi2 = atan ((c1y - iy) / (c1x - ix));
	else
		phi2 = M_PI + atan ((c1y - iy) / (c1x - ix));

	/* compute the difference between phi2 and phi1 mod 2pi */
	d = phi2 - phi1;
	while (d < 0)
		d += 2 * M_PI;
	while (d > 2 * M_PI)
		d -= 2 * M_PI;

#ifdef KBDRAW_DEBUG
	printf ("        line 1 to: (%f, %f):\n", a1x, a1y);
#endif
	if (!(isnan (a1x) || isnan (a1y)))
		cairo_line_to (cr, a1x, a1y);

	/* pick the short arc from phi1 to phi2 */
	if (d < M_PI)
		cairo_arc (cr, ix, iy, radius, phi1, phi2);
	else
		cairo_arc_negative (cr, ix, iy, radius, phi1, phi2);

#ifdef KBDRAW_DEBUG
	printf ("        line 2 to: (%f, %f):\n", cx, cy);
#endif
	cairo_line_to (cr, cx, cy);
}

static void
rounded_polygon (cairo_t * cr,
		 gboolean filled,
		 gdouble radius, GdkPoint * points, gint num_points)
{
	gint i, j;

	cairo_move_to (cr,
		       (gdouble) (points[num_points - 1].x +
				  points[0].x) / 2,
		       (gdouble) (points[num_points - 1].y +
				  points[0].y) / 2);


#ifdef KBDRAW_DEBUG
	printf ("    rounded polygon of radius %f:\n", radius);
#endif
	for (i = 0; i < num_points; i++) {
		j = (i + 1) % num_points;
		rounded_corner (cr, (gdouble) points[i].x,
				(gdouble) points[i].y,
				(gdouble) (points[i].x + points[j].x) / 2,
				(gdouble) (points[i].y + points[j].y) / 2,
				radius);
#ifdef KBDRAW_DEBUG
		printf ("      corner (%d, %d) -> (%d, %d):\n",
			points[i].x, points[i].y, points[j].x,
			points[j].y);
#endif
	};
	cairo_close_path (cr);

	if (filled)
		cairo_fill (cr);
	else {
		cairo_set_line_width (cr, CAIRO_LINE_WIDTH);
		cairo_stroke (cr);
	}
}

static void
draw_polygon (GkbdKeyboardDrawingRenderContext * context,
	      GdkRGBA * fill_color,
	      gint xkb_x,
	      gint xkb_y, XkbPointRec * xkb_points, guint num_points,
	      gdouble radius)
{
	GdkPoint *points;
	gboolean filled;
	gint i;

	if (fill_color) {
		filled = TRUE;
	} else {
		fill_color = &context->dark_color;
		filled = FALSE;
	}

	gdk_cairo_set_source_rgba (context->cr, fill_color);

	points = g_new (GdkPoint, num_points);

#ifdef KBDRAW_DEBUG
	printf ("    Polygon points:\n");
#endif
	for (i = 0; i < num_points; i++) {
		points[i].x =
		    xkb_to_pixmap_coord (context, xkb_x + xkb_points[i].x);
		points[i].y =
		    xkb_to_pixmap_coord (context, xkb_y + xkb_points[i].y);
#ifdef KBDRAW_DEBUG
		printf ("      %d, %d\n", points[i].x, points[i].y);
#endif
	}

	rounded_polygon (context->cr, filled,
			 xkb_to_pixmap_double (context, radius),
			 points, num_points);

	g_free (points);
}

static void
curve_rectangle (cairo_t * cr,
		 gdouble x0,
		 gdouble y0, gdouble width, gdouble height, gdouble radius)
{
	gdouble x1, y1;

	if (!width || !height)
		return;

	x1 = x0 + width;
	y1 = y0 + height;

	radius = MIN (radius, MIN (width / 2, height / 2));

	cairo_move_to (cr, x0, y0 + radius);
	cairo_arc (cr, x0 + radius, y0 + radius, radius, M_PI,
		   3 * M_PI / 2);
	cairo_line_to (cr, x1 - radius, y0);
	cairo_arc (cr, x1 - radius, y0 + radius, radius, 3 * M_PI / 2,
		   2 * M_PI);
	cairo_line_to (cr, x1, y1 - radius);
	cairo_arc (cr, x1 - radius, y1 - radius, radius, 0, M_PI / 2);
	cairo_line_to (cr, x0 + radius, y1);
	cairo_arc (cr, x0 + radius, y1 - radius, radius, M_PI / 2, M_PI);

	cairo_close_path (cr);
}

static void
draw_curve_rectangle (cairo_t * cr,
		      gboolean filled,
		      GdkRGBA * color,
		      gint x, gint y, gint width, gint height, gint radius)
{
	curve_rectangle (cr, x, y, width, height, radius);

	gdk_cairo_set_source_rgba (cr, color);

	if (filled)
		cairo_fill (cr);
	else {
		cairo_set_line_width (cr, CAIRO_LINE_WIDTH);
		cairo_stroke (cr);
	}
}

/* x, y, width, height are in the xkb coordinate system */
static void
draw_rectangle (GkbdKeyboardDrawingRenderContext * context,
		GdkRGBA * color,
		gint angle,
		gint xkb_x, gint xkb_y, gint xkb_width, gint xkb_height,
		gint radius)
{
	if (angle == 0) {
		gint x, y, width, height;
		gboolean filled;

		if (color) {
			filled = TRUE;
		} else {
			color = &context->dark_color;
			filled = FALSE;
		}

		x = xkb_to_pixmap_coord (context, xkb_x);
		y = xkb_to_pixmap_coord (context, xkb_y);
		width =
		    xkb_to_pixmap_coord (context, xkb_x + xkb_width) - x;
		height =
		    xkb_to_pixmap_coord (context, xkb_y + xkb_height) - y;

		draw_curve_rectangle (context->cr, filled, color,
				      x, y, width, height,
				      xkb_to_pixmap_double (context,
							    radius));
	} else {
		XkbPointRec points[4];
		gint x, y;

		points[0].x = xkb_x;
		points[0].y = xkb_y;
		rotate_coordinate (xkb_x, xkb_y, xkb_x + xkb_width, xkb_y,
				   angle, &x, &y);
		points[1].x = x;
		points[1].y = y;
		rotate_coordinate (xkb_x, xkb_y, xkb_x + xkb_width,
				   xkb_y + xkb_height, angle, &x, &y);
		points[2].x = x;
		points[2].y = y;
		rotate_coordinate (xkb_x, xkb_y, xkb_x, xkb_y + xkb_height,
				   angle, &x, &y);
		points[3].x = x;
		points[3].y = y;

		/* the points we've calculated are relative to 0,0 */
		draw_polygon (context, color, 0, 0, points, 4, radius);
	}
}

static void
draw_outline (GkbdKeyboardDrawingRenderContext * context,
	      XkbOutlineRec * outline,
	      GdkRGBA * color, gint angle, gint origin_x, gint origin_y)
{
#ifdef KBDRAW_DEBUG
	printf ("origin: %d, %d, num_points in %p: %d\n", origin_x,
		origin_y, outline, outline->num_points);
#endif

	if (outline->num_points == 1) {
#ifdef KBDRAW_DEBUG
		printf
		    ("1 point (rectangle): width, height: %d, %d, radius %d\n",
		     outline->points[0].x, outline->points[0].y,
		     outline->corner_radius);
#endif
		if (color)
			draw_rectangle (context, color, angle, origin_x,
					origin_y, outline->points[0].x,
					outline->points[0].y,
					outline->corner_radius);

		draw_rectangle (context, NULL, angle, origin_x,
				origin_y, outline->points[0].x,
				outline->points[0].y,
				outline->corner_radius);
	} else if (outline->num_points == 2) {
		gint rotated_x0, rotated_y0;

		rotate_coordinate (origin_x, origin_y,
				   origin_x + outline->points[0].x,
				   origin_y + outline->points[0].y,
				   angle, &rotated_x0, &rotated_y0);
#ifdef KBDRAW_DEBUG
		printf
		    ("2 points (rectangle): from %d, %d, width, height: %d, %d, radius %d\n",
		     rotated_x0, rotated_y0, outline->points[1].x,
		     outline->points[1].y, outline->corner_radius);
#endif
		if (color)
			draw_rectangle (context, color, angle, rotated_x0,
					rotated_y0, outline->points[1].x,
					outline->points[1].y,
					outline->corner_radius);
		draw_rectangle (context, NULL, angle, rotated_x0,
				rotated_y0, outline->points[1].x,
				outline->points[1].y,
				outline->corner_radius);
	} else {
#ifdef KBDRAW_DEBUG
		printf ("multiple points (%d) from %d %d, radius %d\n",
			outline->num_points, origin_x, origin_y,
			outline->corner_radius);
#endif
		if (color)
			draw_polygon (context, color, origin_x, origin_y,
				      outline->points, outline->num_points,
				      outline->corner_radius);
		draw_polygon (context, NULL, origin_x, origin_y,
			      outline->points, outline->num_points,
			      outline->corner_radius);
	}
}

/* see PSColorDef in xkbprint */
static gboolean
parse_xkb_color_spec (gchar * colorspec, GdkRGBA * color)
{
	glong level;

	color->alpha = 1;
	if (g_ascii_strcasecmp (colorspec, "black") == 0) {
		color->red = 0;
		color->green = 0;
		color->blue = 0;
	} else if (g_ascii_strcasecmp (colorspec, "white") == 0) {
		color->red = 1.0;
		color->green = 1.0;
		color->blue = 1.0;
	} else if (g_ascii_strncasecmp (colorspec, "grey", 4) == 0 ||
		   g_ascii_strncasecmp (colorspec, "gray", 4) == 0) {
		level = strtol (colorspec + 4, NULL, 10);

		color->red = 1.0 - level / 100.0;
		color->green = 1.0 - level / 100.0;
		color->blue = 1.0 - level / 100.0;
	} else if (g_ascii_strcasecmp (colorspec, "red") == 0) {
		color->red = 1.0;
		color->green = 0;
		color->blue = 0;
	} else if (g_ascii_strcasecmp (colorspec, "green") == 0) {
		color->red = 0;
		color->green = 1.0;
		color->blue = 0;
	} else if (g_ascii_strcasecmp (colorspec, "blue") == 0) {
		color->red = 0;
		color->green = 0;
		color->blue = 1.0;
	} else if (g_ascii_strncasecmp (colorspec, "red", 3) == 0) {
		level = strtol (colorspec + 3, NULL, 10);

		color->red = level / 100.0;
		color->green = 0;
		color->blue = 0;
	} else if (g_ascii_strncasecmp (colorspec, "green", 5) == 0) {
		level = strtol (colorspec + 5, NULL, 10);

		color->red = 0;
		color->green = level / 100.0;
		color->blue = 0;
	} else if (g_ascii_strncasecmp (colorspec, "blue", 4) == 0) {
		level = strtol (colorspec + 4, NULL, 10);

		color->red = 0;
		color->green = 0;
		color->blue = level / 100.0;
	} else
		return FALSE;

	return TRUE;
}


static guint
find_keycode (GkbdKeyboardDrawing * drawing, gchar * key_name)
{
#define KEYSYM_NAME_MAX_LENGTH 4
	guint keycode;
	gint i, j;
	XkbKeyNamePtr pkey;
	XkbKeyAliasPtr palias;
	guint is_name_matched;
	gchar *src, *dst;

	if (!drawing->xkb)
		return INVALID_KEYCODE;

#ifdef KBDRAW_DEBUG
	printf ("    looking for keycode for (%c%c%c%c)\n",
		key_name[0], key_name[1], key_name[2], key_name[3]);
#endif

	pkey = drawing->xkb->names->keys + drawing->xkb->min_key_code;
	for (keycode = drawing->xkb->min_key_code;
	     keycode <= drawing->xkb->max_key_code; keycode++) {
		is_name_matched = 1;
		src = key_name;
		dst = pkey->name;
		for (i = KEYSYM_NAME_MAX_LENGTH; --i >= 0;) {
			if ('\0' == *src)
				break;
			if (*src++ != *dst++) {
				is_name_matched = 0;
				break;
			}
		}
		if (is_name_matched) {
#ifdef KBDRAW_DEBUG
			printf ("      found keycode %u\n", keycode);
#endif
			return keycode;
		}
		pkey++;
	}

	palias = drawing->xkb->names->key_aliases;
	for (j = drawing->xkb->names->num_key_aliases; --j >= 0;) {
		is_name_matched = 1;
		src = key_name;
		dst = palias->alias;
		for (i = KEYSYM_NAME_MAX_LENGTH; --i >= 0;) {
			if ('\0' == *src)
				break;
			if (*src++ != *dst++) {
				is_name_matched = 0;
				break;
			}
		}

		if (is_name_matched) {
			keycode = find_keycode (drawing, palias->real);
#ifdef KBDRAW_DEBUG
			printf ("found alias keycode %u\n", keycode);
#endif
			return keycode;
		}
		palias++;
	}

	return INVALID_KEYCODE;
}

static void
set_markup (GkbdKeyboardDrawingRenderContext * context, gchar * txt)
{
	PangoLayout *layout = context->layout;
	txt = strcmp ("<", txt) ? txt : "&lt;";
	txt = strcmp ("&", txt) ? txt : "&amp;";
	if (g_utf8_strlen (txt, -1) > 1) {
		gchar *buf =
		    g_strdup_printf ("<span size=\"x-small\">%s</span>",
				     txt);
		pango_layout_set_markup (layout, buf, -1);
		g_free (buf);
	} else {
		pango_layout_set_markup (layout, txt, -1);
	}
}

static void
set_key_label_in_layout (GkbdKeyboardDrawingRenderContext * context,
			 guint keyval)
{
	gchar buf[5];
	gunichar uc;

	switch (keyval) {
	case GDK_KEY_Scroll_Lock:
		set_markup (context, "Scroll\nLock");
		break;

	case GDK_KEY_space:
		set_markup (context, "");
		break;

	case GDK_KEY_Sys_Req:
		set_markup (context, "Sys Rq");
		break;

	case GDK_KEY_Page_Up:
		set_markup (context, "Page\nUp");
		break;

	case GDK_KEY_Page_Down:
		set_markup (context, "Page\nDown");
		break;

	case GDK_KEY_Num_Lock:
		set_markup (context, "Num\nLock");
		break;

	case GDK_KEY_KP_Page_Up:
		set_markup (context, "Pg Up");
		break;

	case GDK_KEY_KP_Page_Down:
		set_markup (context, "Pg Dn");
		break;

	case GDK_KEY_KP_Home:
		set_markup (context, "Home");
		break;

	case GDK_KEY_KP_Left:
		set_markup (context, "Left");
		break;

	case GDK_KEY_KP_End:
		set_markup (context, "End");
		break;

	case GDK_KEY_KP_Up:
		set_markup (context, "Up");
		break;

	case GDK_KEY_KP_Begin:
		set_markup (context, "Begin");
		break;

	case GDK_KEY_KP_Right:
		set_markup (context, "Right");
		break;

	case GDK_KEY_KP_Enter:
		set_markup (context, "Enter");
		break;

	case GDK_KEY_KP_Down:
		set_markup (context, "Down");
		break;

	case GDK_KEY_KP_Insert:
		set_markup (context, "Ins");
		break;

	case GDK_KEY_KP_Delete:
		set_markup (context, "Del");
		break;

	case GDK_KEY_dead_grave:
		set_markup (context, "ˋ");
		break;

	case GDK_KEY_dead_acute:
		set_markup (context, "ˊ");
		break;

	case GDK_KEY_dead_circumflex:
		set_markup (context, "ˆ");
		break;

	case GDK_KEY_dead_tilde:
		set_markup (context, "~");
		break;

	case GDK_KEY_dead_macron:
		set_markup (context, "ˉ");
		break;

	case GDK_KEY_dead_breve:
		set_markup (context, "˘");
		break;

	case GDK_KEY_dead_abovedot:
		set_markup (context, "˙");
		break;

	case GDK_KEY_dead_diaeresis:
		set_markup (context, "¨");
		break;

	case GDK_KEY_dead_abovering:
		set_markup (context, "˚");
		break;

	case GDK_KEY_dead_doubleacute:
		set_markup (context, "˝");
		break;

	case GDK_KEY_dead_caron:
		set_markup (context, "ˇ");
		break;

	case GDK_KEY_dead_cedilla:
		set_markup (context, "¸");
		break;

	case GDK_KEY_dead_ogonek:
		set_markup (context, "˛");
		break;

		/* case GDK_KEY_dead_iota:
		 * case GDK_KEY_dead_voiced_sound:
		 * case GDK_KEY_dead_semivoiced_sound: */

	case GDK_KEY_dead_belowdot:
		set_markup (context, " ̣");
		break;

	case GDK_KEY_horizconnector:
		set_markup (context, "horiz\nconn");
		break;

	case GDK_KEY_Mode_switch:
		set_markup (context, "AltGr");
		break;

	case GDK_KEY_Multi_key:
		set_markup (context, "Compose");
		break;

	case GDK_KEY_VoidSymbol:
		set_markup (context, "");
		break;

	default:
		uc = gdk_keyval_to_unicode (keyval);
		if (uc != 0 && g_unichar_isgraph (uc)) {
			buf[g_unichar_to_utf8 (uc, buf)] = '\0';
			set_markup (context, buf);
		} else {
			gchar *name = gdk_keyval_name (keyval);
			if (name) {
				gchar *fixed_name = g_strdup (name), *p;
				/* Replace underscores with spaces */
				for (p = fixed_name; *p; p++)
					if (*p == '_')
						*p = ' ';
				/* Get rid of scary ISO_ prefix */
				if (g_strstr_len (fixed_name, -1, "ISO "))
					set_markup (context,
						    fixed_name + 4);
				else
					set_markup (context, fixed_name);
				g_free (fixed_name);
			} else
				set_markup (context, "");
		}
	}
}


static void
draw_pango_layout (GkbdKeyboardDrawingRenderContext * context,
		   GkbdKeyboardDrawing * drawing,
		   gint angle, gint x, gint y, gboolean is_pressed)
{
	PangoLayout *layout = context->layout;
	GdkRGBA *pcolor, color;
	PangoLayoutLine *line;
	gint x_off, y_off;
	gint i;

	if (is_pressed) {
		GtkStyleContext *style_context =
		    gtk_widget_get_style_context (GTK_WIDGET (drawing));
		pcolor = &color;
		gtk_style_context_get_color (style_context,
					     GTK_STATE_FLAG_SELECTED,
					     pcolor);
	} else {
		pcolor =
		    drawing->colors + (drawing->xkb->geom->label_color -
				       drawing->xkb->geom->colors);
	}

	if (angle != context->angle) {
		PangoMatrix matrix = PANGO_MATRIX_INIT;
		pango_matrix_rotate (&matrix, -angle / 10.0);
		pango_context_set_matrix (pango_layout_get_context
					  (layout), &matrix);
		pango_layout_context_changed (layout);
		context->angle = angle;
	}

	i = 0;
	y_off = 0;
	for (line = pango_layout_get_line (layout, i);
	     line != NULL; line = pango_layout_get_line (layout, ++i)) {
		GSList *runp;
		PangoRectangle line_extents;

		x_off = 0;

		for (runp = line->runs; runp != NULL; runp = runp->next) {
			PangoGlyphItem *run = runp->data;
			gint j;

			for (j = 0; j < run->glyphs->num_glyphs; j++) {
				PangoGlyphGeometry *geometry;

				geometry =
				    &run->glyphs->glyphs[j].geometry;

				x_off += geometry->width;
			}
		}

		pango_layout_line_get_extents (line, NULL, &line_extents);
		y_off +=
		    line_extents.height +
		    pango_layout_get_spacing (layout);
	}

	cairo_move_to (context->cr, x, y);
	gdk_cairo_set_source_rgba (context->cr, pcolor);
	pango_cairo_show_layout (context->cr, layout);
}

static void
draw_key_label_helper (GkbdKeyboardDrawingRenderContext * context,
		       GkbdKeyboardDrawing * drawing,
		       KeySym keysym,
		       gint angle,
		       GkbdKeyboardDrawingGroupLevelPosition glp,
		       gint x,
		       gint y, gint width, gint height, gint padding,
		       gboolean is_pressed)
{
	gint label_x, label_y, label_max_width, ycell;

	if (keysym == 0)
		return;
#ifdef KBDRAW_DEBUG
	printf ("keysym: %04X(%c) at glp: %d\n",
		(unsigned) keysym, (char) keysym, (int) glp);
#endif

	switch (glp) {
	case GKBD_KEYBOARD_DRAWING_POS_TOPLEFT:
	case GKBD_KEYBOARD_DRAWING_POS_BOTTOMLEFT:
		{
			ycell =
			    glp == GKBD_KEYBOARD_DRAWING_POS_BOTTOMLEFT;

			rotate_coordinate (x, y, x + padding,
					   y + padding + (height -
							  2 * padding) *
					   ycell * 4 / 7, angle, &label_x,
					   &label_y);
			label_max_width =
			    PANGO_SCALE * (width - 2 * padding);
			break;
		}
	case GKBD_KEYBOARD_DRAWING_POS_TOPRIGHT:
	case GKBD_KEYBOARD_DRAWING_POS_BOTTOMRIGHT:
		{
			ycell =
			    glp == GKBD_KEYBOARD_DRAWING_POS_BOTTOMRIGHT;

			rotate_coordinate (x, y,
					   x + padding + (width -
							  2 * padding) *
					   4 / 7,
					   y + padding + (height -
							  2 * padding) *
					   ycell * 4 / 7, angle, &label_x,
					   &label_y);
			label_max_width =
			    PANGO_SCALE * ((width - 2 * padding) -
					   (width - 2 * padding) * 4 / 7);
			break;
		}
	default:
		return;
	}
	set_key_label_in_layout (context, keysym);
	pango_layout_set_width (context->layout, label_max_width);
	label_y -= (pango_layout_get_line_count (context->layout) - 1) *
	    (pango_font_description_get_size (context->font_desc) /
	     PANGO_SCALE);
	cairo_save (context->cr);
	cairo_rectangle (context->cr, x + padding / 2, y + padding / 2,
			 width - padding, height - padding);
	cairo_clip (context->cr);
	draw_pango_layout (context, drawing, angle, label_x, label_y,
			   is_pressed);
	cairo_restore (context->cr);
}

static void
draw_key_label (GkbdKeyboardDrawingRenderContext * context,
		GkbdKeyboardDrawing * drawing,
		guint keycode,
		gint angle,
		gint xkb_origin_x,
		gint xkb_origin_y, gint xkb_width, gint xkb_height,
		gboolean is_pressed)
{
	gint x, y, width, height;
	gint padding;
	gint g, l, glp;

	if (!drawing->xkb || !drawing->groupLevels || keycode == INVALID_KEYCODE)
		return;

	padding = 23 * context->scale_numerator / context->scale_denominator;	/* 2.3mm */

	x = xkb_to_pixmap_coord (context, xkb_origin_x);
	y = xkb_to_pixmap_coord (context, xkb_origin_y);
	width =
	    xkb_to_pixmap_coord (context, xkb_origin_x + xkb_width) - x;
	height =
	    xkb_to_pixmap_coord (context, xkb_origin_y + xkb_height) - y;

	for (glp = GKBD_KEYBOARD_DRAWING_POS_TOPLEFT;
	     glp < GKBD_KEYBOARD_DRAWING_POS_TOTAL; glp++) {
		if (drawing->groupLevels[glp] == NULL)
			continue;
		g = drawing->groupLevels[glp]->group;
		l = drawing->groupLevels[glp]->level;

		if (g < 0 || g >= XkbKeyNumGroups (drawing->xkb, keycode))
			continue;
		if (l < 0
		    || l >= XkbKeyGroupWidth (drawing->xkb, keycode, g))
			continue;

		/* Skip "exotic" levels like the "Ctrl" level in PC_SYSREQ */
		if (l > 0) {
			guint mods = XkbKeyKeyType (drawing->xkb, keycode,
						    g)->mods.mask;
			if ((mods & (ShiftMask | drawing->l3mod)) == 0)
				continue;
		}

		if (drawing->track_modifiers) {
			guint mods_rtrn;
			KeySym keysym;

			if (XkbTranslateKeyCode (drawing->xkb, keycode,
						 XkbBuildCoreState
						 (drawing->mods, g),
						 &mods_rtrn, &keysym)) {
				draw_key_label_helper (context, drawing,
						       keysym, angle, glp,
						       x, y, width, height,
						       padding,
						       is_pressed);
				/* reverse y order */
			}
		} else {
			KeySym keysym;

			keysym =
			    XkbKeySymEntry (drawing->xkb, keycode, l, g);

			draw_key_label_helper (context, drawing, keysym,
					       angle, glp, x, y, width,
					       height, padding,
					       is_pressed);
			/* reverse y order */
		}
	}
}

/*
 * The x offset is calculated for complex shapes. It is the rightmost of the vertical lines in the outline
 */
static gint
calc_shape_origin_offset_x (XkbOutlineRec * outline)
{
	gint rv = 0;
	gint i;
	XkbPointPtr point = outline->points;
	if (outline->num_points < 3)
		return 0;
	for (i = outline->num_points; --i > 0;) {
		gint x1 = point->x;
		gint y1 = point++->y;
		gint x2 = point->x;
		gint y2 = point->y;

		/*vertical, bottom to top (clock-wise), on the left */
		if ((x1 == x2) && (y1 > y2) && (x1 > rv)) {
			rv = x1;
		}
	}
	return rv;
}

/* groups are from 0-3 */
static void
draw_key (GkbdKeyboardDrawingRenderContext * context,
	  GkbdKeyboardDrawing * drawing, GkbdKeyboardDrawingKey * key)
{
	XkbShapeRec *shape;
	GdkRGBA *pcolor, color;
	XkbOutlineRec *outline;
	int origin_offset_x;
	/* gint i; */

	if (!drawing->xkb)
		return;

#ifdef KBDRAW_DEBUG
	printf ("shape: %p (base %p, index %d)\n",
		drawing->xkb->geom->shapes + key->xkbkey->shape_ndx,
		drawing->xkb->geom->shapes, key->xkbkey->shape_ndx);
#endif

	shape = drawing->xkb->geom->shapes + key->xkbkey->shape_ndx;

	if (key->pressed) {
		GtkStyleContext *style_context =
		    gtk_widget_get_style_context (GTK_WIDGET (drawing));
		pcolor = &color;
		gtk_style_context_get_background_color (style_context,
							GTK_STATE_FLAG_SELECTED,
							pcolor);
	} else
		pcolor = drawing->colors + key->xkbkey->color_ndx;

#ifdef KBDRAW_DEBUG
	printf
	    (" outlines base in the shape: %p (total: %d), origin: (%d, %d), angle %d, colored: %s\n",
	     shape->outlines, shape->num_outlines, key->origin_x,
	     key->origin_y, key->angle, pcolor ? "yes" : "no");
#endif

	/* draw the primary outline */
	outline = shape->primary ? shape->primary : shape->outlines;
	draw_outline (context, outline, pcolor, key->angle, key->origin_x,
		      key->origin_y);
#if 0
	/* don't draw other outlines for now, since
	 * the text placement does not take them into account 
	 */
	for (i = 0; i < shape->num_outlines; i++) {
		if (shape->outlines + i == shape->approx ||
		    shape->outlines + i == shape->primary)
			continue;
		draw_outline (context, shape->outlines + i, NULL,
			      key->angle, key->origin_x, key->origin_y);
	}
#endif
	origin_offset_x = calc_shape_origin_offset_x (outline);
	draw_key_label (context, drawing, key->keycode, key->angle,
			key->origin_x + origin_offset_x, key->origin_y,
			shape->bounds.x2, shape->bounds.y2, key->pressed);
}

static void
invalidate_region (GkbdKeyboardDrawing * drawing,
		   gdouble angle,
		   gint origin_x, gint origin_y, XkbShapeRec * shape)
{
	GdkPoint points[4];
	GtkAllocation alloc;
	gint x_min, x_max, y_min, y_max;
	gint x, y, width, height;
	gint xx, yy;

	rotate_coordinate (0, 0, 0, 0, angle, &xx, &yy);
	points[0].x = xx;
	points[0].y = yy;
	rotate_coordinate (0, 0, shape->bounds.x2, 0, angle, &xx, &yy);
	points[1].x = xx;
	points[1].y = yy;
	rotate_coordinate (0, 0, shape->bounds.x2, shape->bounds.y2, angle,
			   &xx, &yy);
	points[2].x = xx;
	points[2].y = yy;
	rotate_coordinate (0, 0, 0, shape->bounds.y2, angle, &xx, &yy);
	points[3].x = xx;
	points[3].y = yy;

	x_min =
	    MIN (MIN (points[0].x, points[1].x),
		 MIN (points[2].x, points[3].x));
	x_max =
	    MAX (MAX (points[0].x, points[1].x),
		 MAX (points[2].x, points[3].x));
	y_min =
	    MIN (MIN (points[0].y, points[1].y),
		 MIN (points[2].y, points[3].y));
	y_max =
	    MAX (MAX (points[0].y, points[1].y),
		 MAX (points[2].y, points[3].y));

	x = xkb_to_pixmap_coord (drawing->renderContext,
				 origin_x + x_min) - 6;
	y = xkb_to_pixmap_coord (drawing->renderContext,
				 origin_y + y_min) - 6;
	width =
	    xkb_to_pixmap_coord (drawing->renderContext,
				 x_max - x_min) + 12;
	height =
	    xkb_to_pixmap_coord (drawing->renderContext,
				 y_max - y_min) + 12;

	gtk_widget_get_allocation (GTK_WIDGET (drawing), &alloc);
	gtk_widget_queue_draw_area (GTK_WIDGET (drawing),
				    x + alloc.x, y + alloc.y,
				    width, height);
}

static void
invalidate_indicator_doodad_region (GkbdKeyboardDrawing * drawing,
				    GkbdKeyboardDrawingDoodad * doodad)
{
	if (!drawing->xkb)
		return;

	invalidate_region (drawing,
			   doodad->angle,
			   doodad->origin_x +
			   doodad->doodad->indicator.left,
			   doodad->origin_y +
			   doodad->doodad->indicator.top,
			   &drawing->xkb->geom->shapes[doodad->doodad->
						       indicator.
						       shape_ndx]);
}

static void
invalidate_key_region (GkbdKeyboardDrawing * drawing,
		       GkbdKeyboardDrawingKey * key)
{
	if (!drawing->xkb)
		return;

	invalidate_region (drawing,
			   key->angle,
			   key->origin_x,
			   key->origin_y,
			   &drawing->xkb->geom->shapes[key->xkbkey->
						       shape_ndx]);
}

static void
draw_text_doodad (GkbdKeyboardDrawingRenderContext * context,
		  GkbdKeyboardDrawing * drawing,
		  GkbdKeyboardDrawingDoodad * doodad,
		  XkbTextDoodadRec * text_doodad)
{
	gint x, y;
	if (!drawing->xkb)
		return;

	x = xkb_to_pixmap_coord (context,
				 doodad->origin_x + text_doodad->left);
	y = xkb_to_pixmap_coord (context,
				 doodad->origin_y + text_doodad->top);

	set_markup (context, text_doodad->text);
	draw_pango_layout (context, drawing, doodad->angle, x, y, FALSE);
}

static void
draw_indicator_doodad (GkbdKeyboardDrawingRenderContext * context,
		       GkbdKeyboardDrawing * drawing,
		       GkbdKeyboardDrawingDoodad * doodad,
		       XkbIndicatorDoodadRec * indicator_doodad)
{
	GdkRGBA *color;
	XkbShapeRec *shape;
	gint i;

	if (!drawing->xkb)
		return;

	shape = drawing->xkb->geom->shapes + indicator_doodad->shape_ndx;

	color = drawing->colors + (doodad->on ?
				   indicator_doodad->on_color_ndx :
				   indicator_doodad->off_color_ndx);

	for (i = 0; i < 1; i++)
		draw_outline (context, shape->outlines + i, color,
			      doodad->angle,
			      doodad->origin_x + indicator_doodad->left,
			      doodad->origin_y + indicator_doodad->top);
}

static void
draw_shape_doodad (GkbdKeyboardDrawingRenderContext * context,
		   GkbdKeyboardDrawing * drawing,
		   GkbdKeyboardDrawingDoodad * doodad,
		   XkbShapeDoodadRec * shape_doodad)
{
	XkbShapeRec *shape;
	GdkRGBA *color;
	gint i;

	if (!drawing->xkb)
		return;

	shape = drawing->xkb->geom->shapes + shape_doodad->shape_ndx;
	color = drawing->colors + shape_doodad->color_ndx;

	/* draw the primary outline filled */
	draw_outline (context,
		      shape->primary ? shape->primary : shape->outlines,
		      color, doodad->angle,
		      doodad->origin_x + shape_doodad->left,
		      doodad->origin_y + shape_doodad->top);

	/* stroke the other outlines */
	for (i = 0; i < shape->num_outlines; i++) {
		if (shape->outlines + i == shape->approx ||
		    shape->outlines + i == shape->primary)
			continue;
		draw_outline (context, shape->outlines + i, NULL,
			      doodad->angle,
			      doodad->origin_x + shape_doodad->left,
			      doodad->origin_y + shape_doodad->top);
	}
}

static void
draw_doodad (GkbdKeyboardDrawingRenderContext * context,
	     GkbdKeyboardDrawing * drawing,
	     GkbdKeyboardDrawingDoodad * doodad)
{
	switch (doodad->doodad->any.type) {
	case XkbOutlineDoodad:
	case XkbSolidDoodad:
		draw_shape_doodad (context, drawing, doodad,
				   &doodad->doodad->shape);
		break;

	case XkbTextDoodad:
		draw_text_doodad (context, drawing, doodad,
				  &doodad->doodad->text);
		break;

	case XkbIndicatorDoodad:
		draw_indicator_doodad (context, drawing, doodad,
				       &doodad->doodad->indicator);
		break;

	case XkbLogoDoodad:
		/* g_print ("draw_doodad: logo: %s\n", doodad->doodad->logo.logo_name); */
		/* XkbLogoDoodadRec is essentially a subclass of XkbShapeDoodadRec */
		draw_shape_doodad (context, drawing, doodad,
				   &doodad->doodad->shape);
		break;
	}
}

typedef struct {
	GkbdKeyboardDrawing *drawing;
	GkbdKeyboardDrawingRenderContext *context;
} DrawKeyboardItemData;

static void
draw_keyboard_item (GkbdKeyboardDrawingItem * item,
		    DrawKeyboardItemData * data)
{
	GkbdKeyboardDrawing *drawing = data->drawing;
	GkbdKeyboardDrawingRenderContext *context = data->context;

	if (!drawing->xkb)
		return;

	switch (item->type) {
	case GKBD_KEYBOARD_DRAWING_ITEM_TYPE_INVALID:
		break;

	case GKBD_KEYBOARD_DRAWING_ITEM_TYPE_KEY:
	case GKBD_KEYBOARD_DRAWING_ITEM_TYPE_KEY_EXTRA:
		draw_key (context, drawing,
			  (GkbdKeyboardDrawingKey *) item);
		break;

	case GKBD_KEYBOARD_DRAWING_ITEM_TYPE_DOODAD:
		draw_doodad (context, drawing,
			     (GkbdKeyboardDrawingDoodad *) item);
		break;
	}
}

static void
draw_keyboard_to_context (GkbdKeyboardDrawingRenderContext * context,
			  GkbdKeyboardDrawing * drawing)
{
	DrawKeyboardItemData data = { drawing, context };
#ifdef KBDRAW_DEBUG
	printf ("mods: %d\n", drawing->mods);
#endif
	g_list_foreach (drawing->keyboard_items,
			(GFunc) draw_keyboard_item, &data);
}

static gboolean
prepare_cairo (GkbdKeyboardDrawing * drawing, cairo_t * cr)
{
	GtkStateFlags state;
	GtkStyleContext *style_context;
	if (drawing == NULL)
		return FALSE;

	style_context =
	    gtk_widget_get_style_context (GTK_WIDGET (drawing));
	drawing->renderContext->cr = cr;
	state = gtk_widget_get_state_flags (GTK_WIDGET (drawing));
	gtk_style_context_get_background_color (style_context, state,
						&drawing->
						renderContext->dark_color);

	/* same approach as gtk - dark color = background color * 0.7 */
	drawing->renderContext->dark_color.red *= 0.7;
	drawing->renderContext->dark_color.green *= 0.7;
	drawing->renderContext->dark_color.blue *= 0.7;
	return TRUE;
}

static void
draw_keyboard (GkbdKeyboardDrawing * drawing, cairo_t * cr)
{
	GtkStateFlags state =
	    gtk_widget_get_state_flags (GTK_WIDGET (drawing));
	GtkStyleContext *style_context =
	    gtk_widget_get_style_context (GTK_WIDGET (drawing));
	GtkAllocation allocation;

	if (!drawing->xkb)
		return;

	gtk_widget_get_allocation (GTK_WIDGET (drawing), &allocation);

	if (prepare_cairo (drawing, cr)) {
		/* blank background */
		GdkRGBA color;
		gtk_style_context_get_background_color (style_context,
							state, &color);
		gdk_cairo_set_source_rgba (cr, &color);
		cairo_paint (cr);
#ifdef KBDRAW_DEBUG
		GdkRGBA yellow = { 1.0, 1.0, 0, 1.0 };
		gdk_cairo_set_source_rgba (cr, &yellow);

		cairo_move_to (cr, 0, 0);
		cairo_line_to (cr, allocation.width, 0);
		cairo_line_to (cr, allocation.width, allocation.height);
		cairo_line_to (cr, 0, allocation.height);
		cairo_close_path (cr);

		cairo_stroke (cr);
#endif

		draw_keyboard_to_context (drawing->renderContext, drawing);
	}
}

static void
alloc_render_context (GkbdKeyboardDrawing * drawing)
{
	GkbdKeyboardDrawingRenderContext *context =
	    drawing->renderContext =
	    g_new0 (GkbdKeyboardDrawingRenderContext, 1);

	PangoContext *pangoContext =
	    gtk_widget_get_pango_context (GTK_WIDGET (drawing));
	context->layout = pango_layout_new (pangoContext);
	pango_layout_set_ellipsize (context->layout, PANGO_ELLIPSIZE_END);

	context->font_desc =
		pango_font_description_copy (pango_context_get_font_description (pangoContext));
	context->angle = 0;
	context->scale_numerator = 1;
	context->scale_denominator = 1;
}

static void
free_render_context (GkbdKeyboardDrawing * drawing)
{
	GkbdKeyboardDrawingRenderContext *context = drawing->renderContext;
	g_object_unref (G_OBJECT (context->layout));
	pango_font_description_free (context->font_desc);

	g_free (drawing->renderContext);
	drawing->renderContext = NULL;
}

static gboolean
draw (GtkWidget * widget, cairo_t * cr, GkbdKeyboardDrawing * drawing)
{
	if (!drawing->xkb)
		return FALSE;

	draw_keyboard (drawing, cr);
	return FALSE;
}

static gboolean
context_setup_scaling (GkbdKeyboardDrawingRenderContext * context,
		       GkbdKeyboardDrawing * drawing,
		       gdouble width, gdouble height,
		       gdouble dpi_x, gdouble dpi_y)
{
	if (!drawing->xkb)
		return FALSE;

	if (drawing->xkb->geom->width_mm <= 0
	    || drawing->xkb->geom->height_mm <= 0) {
		g_critical
		    ("keyboard geometry reports width or height as zero!");
		return FALSE;
	}

	if (width * drawing->xkb->geom->height_mm <
	    height * drawing->xkb->geom->width_mm) {
		context->scale_numerator = width;
		context->scale_denominator = drawing->xkb->geom->width_mm;
	} else {
		context->scale_numerator = height;
		context->scale_denominator = drawing->xkb->geom->height_mm;
	}

	pango_font_description_set_size (context->font_desc,
					 72 * KEY_FONT_SIZE * dpi_x *
					 context->scale_numerator /
					 context->scale_denominator);
	pango_layout_set_spacing (context->layout,
				  -160 * dpi_y * context->scale_numerator /
				  context->scale_denominator);
	pango_layout_set_font_description (context->layout,
					   context->font_desc);

	return TRUE;
}

static void
size_allocate (GtkWidget * widget,
	       GtkAllocation * allocation, GkbdKeyboardDrawing * drawing)
{
	GkbdKeyboardDrawingRenderContext *context = drawing->renderContext;

	context_setup_scaling (context, drawing,
			       allocation->width, allocation->height,
			       50, 50);
}

static gint
key_event (GtkWidget * widget,
	   GdkEventKey * event, GkbdKeyboardDrawing * drawing)
{
	GkbdKeyboardDrawingKey *key;
	if (!drawing->xkb)
		return FALSE;

	key = drawing->keys + event->hardware_keycode;

	if (event->hardware_keycode > drawing->xkb->max_key_code ||
	    event->hardware_keycode < drawing->xkb->min_key_code ||
	    key->xkbkey == NULL) {
		g_signal_emit (drawing,
			       gkbd_keyboard_drawing_signals[BAD_KEYCODE],
			       0, event->hardware_keycode);
		return TRUE;
	}

	if ((event->type == GDK_KEY_PRESS && key->pressed) ||
	    (event->type == GDK_KEY_RELEASE && !key->pressed))
		return TRUE;
	/* otherwise this event changes the state we believed we had before */

	key->pressed = (event->type == GDK_KEY_PRESS);

	invalidate_key_region (drawing, key);
	return TRUE;
}

static gint
button_press_event (GtkWidget * widget,
		    GdkEventButton * event, GkbdKeyboardDrawing * drawing)
{
	if (!drawing->xkb)
		return FALSE;

	gtk_widget_grab_focus (widget);
	return FALSE;
}

static gboolean
unpress_keys (GkbdKeyboardDrawing * drawing)
{
	gint i;

	drawing->timeout = 0;

	if (!drawing->xkb)
		return FALSE;

	for (i = drawing->xkb->min_key_code;
	     i <= drawing->xkb->max_key_code; i++)
		if (drawing->keys[i].pressed) {
			drawing->keys[i].pressed = FALSE;
			draw_key (drawing->renderContext, drawing,
				  drawing->keys + i);
			invalidate_key_region (drawing, drawing->keys + i);
		}

	return FALSE;
}

static gint
focus_event (GtkWidget * widget,
	     GdkEventFocus * event, GkbdKeyboardDrawing * drawing)
{
	if (event->in && drawing->timeout > 0) {
		g_source_remove (drawing->timeout);
		drawing->timeout = 0;
	} else if (!drawing->timeout)
		drawing->timeout =
		    g_timeout_add (120, (GSourceFunc) unpress_keys,
				   drawing);

	return FALSE;
}

static gint
compare_keyboard_item_priorities (GkbdKeyboardDrawingItem * a,
				  GkbdKeyboardDrawingItem * b)
{
	if (a->priority > b->priority)
		return 1;
	else if (a->priority < b->priority)
		return -1;
	else
		return 0;
}

static void
init_indicator_doodad (GkbdKeyboardDrawing * drawing,
		       XkbDoodadRec * xkbdoodad,
		       GkbdKeyboardDrawingDoodad * doodad)
{
	if (!drawing->xkb)
		return;

	if (xkbdoodad->any.type == XkbIndicatorDoodad) {
		gint index;
		Atom iname = 0;
		Atom sname = xkbdoodad->indicator.name;
		unsigned long phys_indicators =
		    drawing->xkb->indicators->phys_indicators;
		Atom *pind = drawing->xkb->names->indicators;

#ifdef KBDRAW_DEBUG
		printf ("Looking for %d[%s]\n",
			(int) sname, XGetAtomName (drawing->display,
						   sname));
#endif

		for (index = 0; index < XkbNumIndicators; index++) {
			iname = *pind++;
			/* name matches and it is real */
			if (iname == sname
			    && (phys_indicators & (1 << index)))
				break;
			if (iname == 0)
				break;
		}
		if (iname == 0)
			g_warning ("Could not find indicator %d [%s]\n",
				   (int) sname,
				   XGetAtomName (drawing->display, sname));
		else {
#ifdef KBDRAW_DEBUG
			printf ("Found in xkbdesc as %d\n", index);
#endif
			drawing->physical_indicators[index] = doodad;
			/* Trying to obtain the real state, but if fail - just assume OFF */
			if (!XkbGetNamedIndicator
			    (drawing->display, sname, NULL, &doodad->on,
			     NULL, NULL))
				doodad->on = 0;
		}
	}
}

static void
init_keys_and_doodads (GkbdKeyboardDrawing * drawing)
{
	gint i, j, k;
	gint x, y;

	if (!drawing->xkb)
		return;

	for (i = 0; i < drawing->xkb->geom->num_doodads; i++) {
		XkbDoodadRec *xkbdoodad = drawing->xkb->geom->doodads + i;
		GkbdKeyboardDrawingDoodad *doodad =
		    g_new (GkbdKeyboardDrawingDoodad, 1);

		doodad->type = GKBD_KEYBOARD_DRAWING_ITEM_TYPE_DOODAD;
		doodad->origin_x = 0;
		doodad->origin_y = 0;
		doodad->angle = 0;
		doodad->priority = xkbdoodad->any.priority * 256 * 256;
		doodad->doodad = xkbdoodad;

		init_indicator_doodad (drawing, xkbdoodad, doodad);

		drawing->keyboard_items =
		    g_list_append (drawing->keyboard_items, doodad);
	}

	for (i = 0; i < drawing->xkb->geom->num_sections; i++) {
		XkbSectionRec *section = drawing->xkb->geom->sections + i;
		guint priority;

#ifdef KBDRAW_DEBUG
		printf ("initing section %d containing %d rows\n", i,
			section->num_rows);
#endif
		x = section->left;
		y = section->top;
		priority = section->priority * 256 * 256;

		for (j = 0; j < section->num_rows; j++) {
			XkbRowRec *row = section->rows + j;

#ifdef KBDRAW_DEBUG
			printf ("  initing row %d\n", j);
#endif
			x = section->left + row->left;
			y = section->top + row->top;

			for (k = 0; k < row->num_keys; k++) {
				XkbKeyRec *xkbkey = row->keys + k;
				GkbdKeyboardDrawingKey *key;
				XkbShapeRec *shape =
				    drawing->xkb->geom->shapes +
				    xkbkey->shape_ndx;
				guint keycode = find_keycode (drawing,
							      xkbkey->name.
							      name);

#ifdef KBDRAW_DEBUG
				printf
				    ("    initing key %d, shape: %p(%p + %d), code: %u\n",
				     k, shape, drawing->xkb->geom->shapes,
				     xkbkey->shape_ndx, keycode);
#endif
				if (row->vertical)
					y += xkbkey->gap;
				else
					x += xkbkey->gap;

				if (keycode >= drawing->xkb->min_key_code
				    && keycode <=
				    drawing->xkb->max_key_code) {
					key = drawing->keys + keycode;
					if (key->type ==
					    GKBD_KEYBOARD_DRAWING_ITEM_TYPE_INVALID)
					{
						key->type =
						    GKBD_KEYBOARD_DRAWING_ITEM_TYPE_KEY;
					} else {
						/* duplicate key for the same keycode, 
						   already defined as GKBD_KEYBOARD_DRAWING_ITEM_TYPE_KEY */
						key =
						    g_new0
						    (GkbdKeyboardDrawingKey,
						     1);
						key->type =
						    GKBD_KEYBOARD_DRAWING_ITEM_TYPE_KEY_EXTRA;
					}
				} else {
					g_warning
					    ("key %4.4s: keycode = %u; not in range %d..%d\n",
					     xkbkey->name.name, keycode,
					     drawing->xkb->min_key_code,
					     drawing->xkb->max_key_code);

					key =
					    g_new0 (GkbdKeyboardDrawingKey,
						    1);
					key->type =
					    GKBD_KEYBOARD_DRAWING_ITEM_TYPE_KEY_EXTRA;
				}

				key->xkbkey = xkbkey;
				key->angle = section->angle;
				rotate_coordinate (section->left,
						   section->top, x, y,
						   section->angle,
						   &key->origin_x,
						   &key->origin_y);
				key->priority = priority;
				key->keycode = keycode;

				drawing->keyboard_items =
				    g_list_append (drawing->keyboard_items,
						   key);

				if (row->vertical)
					y += shape->bounds.y2;
				else
					x += shape->bounds.x2;

				priority++;
			}
		}

		for (j = 0; j < section->num_doodads; j++) {
			XkbDoodadRec *xkbdoodad = section->doodads + j;
			GkbdKeyboardDrawingDoodad *doodad =
			    g_new (GkbdKeyboardDrawingDoodad, 1);

			doodad->type =
			    GKBD_KEYBOARD_DRAWING_ITEM_TYPE_DOODAD;
			doodad->origin_x = x;
			doodad->origin_y = y;
			doodad->angle = section->angle;
			doodad->priority =
			    priority + xkbdoodad->any.priority;
			doodad->doodad = xkbdoodad;

			init_indicator_doodad (drawing, xkbdoodad, doodad);

			drawing->keyboard_items =
			    g_list_append (drawing->keyboard_items,
					   doodad);
		}
	}

	drawing->keyboard_items = g_list_sort (drawing->keyboard_items,
					       (GCompareFunc)
					       compare_keyboard_item_priorities);
}

static void
init_colors (GkbdKeyboardDrawing * drawing)
{
	gboolean result;
	gint i;

	if (!drawing->xkb)
		return;

	drawing->colors = g_new (GdkRGBA, drawing->xkb->geom->num_colors);

	for (i = 0; i < drawing->xkb->geom->num_colors; i++) {
		result =
		    parse_xkb_color_spec (drawing->xkb->geom->colors[i].
					  spec, drawing->colors + i);

		if (!result)
			g_warning
			    ("init_colors: unable to parse color %s\n",
			     drawing->xkb->geom->colors[i].spec);
	}
}

static void
free_cdik (			/*colors doodads indicators keys */
		  GkbdKeyboardDrawing * drawing)
{
	GList *itemp;

	if (!drawing->xkb)
		return;

	for (itemp = drawing->keyboard_items; itemp; itemp = itemp->next) {
		GkbdKeyboardDrawingItem *item = itemp->data;

		switch (item->type) {
		case GKBD_KEYBOARD_DRAWING_ITEM_TYPE_INVALID:
		case GKBD_KEYBOARD_DRAWING_ITEM_TYPE_KEY:
			break;

		case GKBD_KEYBOARD_DRAWING_ITEM_TYPE_KEY_EXTRA:
		case GKBD_KEYBOARD_DRAWING_ITEM_TYPE_DOODAD:
			g_free (item);
			break;
		}
	}

	g_list_free (drawing->keyboard_items);
	drawing->keyboard_items = NULL;

	g_free (drawing->keys);
	g_free (drawing->colors);
}

static void
alloc_cdik (GkbdKeyboardDrawing * drawing)
{
	if (!drawing->xkb)
		return;

	drawing->physical_indicators_size =
	    drawing->xkb->indicators->phys_indicators + 1;
	drawing->physical_indicators =
	    g_new0 (GkbdKeyboardDrawingDoodad *,
		    drawing->physical_indicators_size);
	drawing->keys =
	    g_new0 (GkbdKeyboardDrawingKey,
		    drawing->xkb->max_key_code + 1);
}

static void
process_indicators_state_notify (XkbIndicatorNotifyEvent * iev,
				 GkbdKeyboardDrawing * drawing)
{
	/* Good question: should we track indicators when the keyboard is
	   NOT really taken from the screen */
	gint i;

	for (i = 0; i <= drawing->xkb->indicators->phys_indicators; i++)
		if (drawing->physical_indicators[i] != NULL
		    && (iev->changed & 1 << i)) {
			gint state = (iev->state & 1 << i) != FALSE;

			if ((state && !drawing->physical_indicators[i]->on)
			    || (!state
				&& drawing->physical_indicators[i]->on)) {
				drawing->physical_indicators[i]->on =
				    state;
				invalidate_indicator_doodad_region
				    (drawing,
				     drawing->physical_indicators[i]);
			}
		}
}

static GdkFilterReturn
xkb_state_notify_event_filter (GdkXEvent * gdkxev,
			       GdkEvent * event,
			       GkbdKeyboardDrawing * drawing)
{
#define group_change_mask (XkbGroupStateMask | XkbGroupBaseMask | XkbGroupLatchMask | XkbGroupLockMask)
#define modifier_change_mask (XkbModifierStateMask | XkbModifierBaseMask | XkbModifierLatchMask | XkbModifierLockMask)

	if (!drawing->xkb)
		return GDK_FILTER_CONTINUE;

	if (((XEvent *) gdkxev)->type == drawing->xkb_event_type) {
		XkbEvent *kev = (XkbEvent *) gdkxev;
		GtkAllocation allocation;
		switch (kev->any.xkb_type) {
		case XkbStateNotify:
			if (((kev->state.changed & modifier_change_mask) &&
			     drawing->track_modifiers))
				gkbd_keyboard_drawing_set_mods
				    (drawing,
				     kev->state.compat_state);
			break;

		case XkbIndicatorStateNotify:
			{
				process_indicators_state_notify (&
								 ((XkbEvent
								   *)
								  gdkxev)->
indicators, drawing);
			}
			break;

		case XkbIndicatorMapNotify:
		case XkbControlsNotify:
		case XkbNamesNotify:
		case XkbNewKeyboardNotify:
			{
				XkbStateRec state;
				memset (&state, 0, sizeof (state));
				XkbGetState (drawing->display,
					     XkbUseCoreKbd, &state);
				if (drawing->track_modifiers)
					gkbd_keyboard_drawing_set_mods
					    (drawing, state.compat_state);
				if (drawing->track_config)
					gkbd_keyboard_drawing_set_keyboard
					    (drawing, NULL);
			}
			break;
		}
	}

	return GDK_FILTER_CONTINUE;
}

static void
destroy (GkbdKeyboardDrawing * drawing)
{
	free_render_context (drawing);
	gdk_window_remove_filter (NULL, (GdkFilterFunc)
				  xkb_state_notify_event_filter, drawing);
	if (drawing->timeout > 0) {
		g_source_remove (drawing->timeout);
		drawing->timeout = 0;
	}
}

static void
style_changed (GkbdKeyboardDrawing * drawing)
{
	pango_layout_context_changed (drawing->renderContext->layout);
}

static void
gkbd_keyboard_drawing_init (GkbdKeyboardDrawing * drawing)
{
	gint opcode = 0, error = 0, major = 1, minor = 0;
	gint mask;

	drawing->display =
	    GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());

	if (!XkbQueryExtension
	    (drawing->display, &opcode, &drawing->xkb_event_type, &error,
	     &major, &minor))
		g_critical
		    ("XkbQueryExtension failed! Stuff probably won't work.");

	/* XXX: this stuff probably doesn't matter.. also, gdk_screen_get_default can fail */
	if (gtk_widget_has_screen (GTK_WIDGET (drawing)))
		drawing->screen_num =
		    gdk_screen_get_number (gtk_widget_get_screen
					   (GTK_WIDGET (drawing)));
	else
		drawing->screen_num =
		    gdk_screen_get_number (gdk_screen_get_default ());

	alloc_render_context (drawing);

	drawing->keyboard_items = NULL;
	drawing->colors = NULL;

	drawing->track_modifiers = 0;
	drawing->track_config = 0;

	gtk_widget_set_has_window (GTK_WIDGET (drawing), FALSE);

	/* XXX: XkbClientMapMask | XkbIndicatorMapMask | XkbNamesMask | XkbGeometryMask */
	drawing->xkb = XkbGetKeyboard (drawing->display,
				       XkbGBN_GeometryMask |
				       XkbGBN_KeyNamesMask |
				       XkbGBN_OtherNamesMask |
				       XkbGBN_SymbolsMask |
				       XkbGBN_IndicatorMapMask,
				       XkbUseCoreKbd);
	if (drawing->xkb) {
		XkbGetNames (drawing->display, XkbAllNamesMask, drawing->xkb);
		XkbSelectEventDetails (drawing->display, XkbUseCoreKbd,
				       XkbIndicatorStateNotify,
				       drawing->xkb->indicators->phys_indicators,
				       drawing->xkb->indicators->phys_indicators);
	}

	drawing->l3mod = XkbKeysymToModifiers (drawing->display,
					       GDK_KEY_ISO_Level3_Shift);

	drawing->xkbOnDisplay = TRUE;

	alloc_cdik (drawing);

	mask =
	    (XkbStateNotifyMask | XkbNamesNotifyMask |
	     XkbControlsNotifyMask | XkbIndicatorMapNotifyMask |
	     XkbNewKeyboardNotifyMask);
	XkbSelectEvents (drawing->display, XkbUseCoreKbd, mask, mask);

	mask = XkbGroupStateMask | XkbModifierStateMask;
	XkbSelectEventDetails (drawing->display, XkbUseCoreKbd,
			       XkbStateNotify, mask, mask);

	mask = (XkbGroupNamesMask | XkbIndicatorNamesMask);
	XkbSelectEventDetails (drawing->display, XkbUseCoreKbd,
			       XkbNamesNotify, mask, mask);
	init_keys_and_doodads (drawing);
	init_colors (drawing);

	/* required to get key events */
	gtk_widget_set_can_focus (GTK_WIDGET (drawing), TRUE);

	gtk_widget_set_events (GTK_WIDGET (drawing),
			       GDK_EXPOSURE_MASK | GDK_KEY_PRESS_MASK |
			       GDK_KEY_RELEASE_MASK | GDK_BUTTON_PRESS_MASK
			       | GDK_FOCUS_CHANGE_MASK);
	g_signal_connect (G_OBJECT (drawing), "draw",
			  G_CALLBACK (draw), drawing);
	g_signal_connect_after (G_OBJECT (drawing), "key-press-event",
				G_CALLBACK (key_event), drawing);
	g_signal_connect_after (G_OBJECT (drawing), "key-release-event",
				G_CALLBACK (key_event), drawing);
	g_signal_connect (G_OBJECT (drawing), "button-press-event",
			  G_CALLBACK (button_press_event), drawing);
	g_signal_connect (G_OBJECT (drawing), "focus-out-event",
			  G_CALLBACK (focus_event), drawing);
	g_signal_connect (G_OBJECT (drawing), "focus-in-event",
			  G_CALLBACK (focus_event), drawing);
	g_signal_connect (G_OBJECT (drawing), "size-allocate",
			  G_CALLBACK (size_allocate), drawing);
	g_signal_connect (G_OBJECT (drawing), "destroy",
			  G_CALLBACK (destroy), drawing);
	g_signal_connect (G_OBJECT (drawing), "style-set",
			  G_CALLBACK (style_changed), drawing);

	gdk_window_add_filter (NULL, (GdkFilterFunc)
			       xkb_state_notify_event_filter, drawing);
}

GtkWidget *
gkbd_keyboard_drawing_new (void)
{
	return
	    GTK_WIDGET (g_object_new
			(gkbd_keyboard_drawing_get_type (), NULL));
}

static GtkSizeRequestMode
get_request_mode (GtkWidget * widget)
{
	return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
}

static void
get_preferred_width (GtkWidget * widget,
		     gint * minimum_width, gint * natural_width)
{
	GdkRectangle rect;
	gint w, monitor;
	GdkScreen *scr = NULL;

	scr = gdk_screen_get_default ();
	monitor = gdk_screen_get_primary_monitor (scr);

	gdk_screen_get_monitor_geometry (scr, monitor, &rect);
	w = rect.width;
	*minimum_width = *natural_width = w - (w >> 2);
}

static void
get_preferred_height_for_width (GtkWidget * widget,
				gint width,
				gint * minimum_height,
				gint * natural_height)
{
	GkbdKeyboardDrawing *drawing = GKBD_KEYBOARD_DRAWING (widget);
	*minimum_height = *natural_height =
	    width * drawing->xkb->geom->height_mm /
	    drawing->xkb->geom->width_mm;
}

static void
gkbd_keyboard_drawing_class_init (GkbdKeyboardDrawingClass * klass)
{
	klass->bad_keycode = NULL;
	GTK_WIDGET_CLASS (klass)->get_preferred_height_for_width =
	    get_preferred_height_for_width;
	GTK_WIDGET_CLASS (klass)->get_preferred_width =
	    get_preferred_width;
	GTK_WIDGET_CLASS (klass)->get_request_mode = get_request_mode;

	gkbd_keyboard_drawing_signals[BAD_KEYCODE] =
	    g_signal_new ("bad-keycode", gkbd_keyboard_drawing_get_type (),
			  G_SIGNAL_RUN_FIRST,
			  G_STRUCT_OFFSET (GkbdKeyboardDrawingClass,
					   bad_keycode), NULL, NULL,
			  gkbd_keyboard_drawing_VOID__UINT, G_TYPE_NONE, 1,
			  G_TYPE_UINT);
}

GType
gkbd_keyboard_drawing_get_type (void)
{
	static GType gkbd_keyboard_drawing_type = 0;

	if (!gkbd_keyboard_drawing_type) {
		static const GTypeInfo gkbd_keyboard_drawing_info = {
			sizeof (GkbdKeyboardDrawingClass),
			NULL,	/* base_init */
			NULL,	/* base_finalize */
			(GClassInitFunc) gkbd_keyboard_drawing_class_init,
			NULL,	/* class_finalize */
			NULL,	/* class_data */
			sizeof (GkbdKeyboardDrawing),
			0,	/* n_preallocs */
			(GInstanceInitFunc) gkbd_keyboard_drawing_init,
		};

		gkbd_keyboard_drawing_type =
		    g_type_register_static (GTK_TYPE_DRAWING_AREA,
					    "GkbdKeyboardDrawing",
					    &gkbd_keyboard_drawing_info,
					    0);
	}

	return gkbd_keyboard_drawing_type;
}

void
gkbd_keyboard_drawing_set_mods (GkbdKeyboardDrawing * drawing, guint mods)
{
#ifdef KBDRAW_DEBUG
	printf ("set_mods: %d\n", mods);
#endif
	if (mods != drawing->mods) {
		drawing->mods = mods;
		gtk_widget_queue_draw (GTK_WIDGET (drawing));
	}
}

/**
 * gkbd_keyboard_drawing_render:
 * @kbdrawing: keyboard layout to render
 * @cr:        Cairo context to render to
 * @layout:    Pango layout to use to render text
 * @x:         left coordinate (pixels) of region to render in
 * @y:         top coordinate (pixels) of region to render in
 * @width:     width (pixels) of region to render in
 * @height:    height (pixels) of region to render in
 *
 * Renders a keyboard layout to a cairo_t context.  @cr and @layout can be got
 * from e.g. a GtkWidget or a GtkPrintContext.  @cr and @layout may be modified
 * by the function but will not be unreffed.
 *
 * Returns: %TRUE on success, %FALSE on failure
 */
gboolean
gkbd_keyboard_drawing_render (GkbdKeyboardDrawing * kbdrawing,
			      cairo_t * cr,
			      PangoLayout * layout,
			      double x, double y,
			      double width, double height,
			      double dpi_x, double dpi_y)
{
	GtkStateFlags state =
	    gtk_widget_get_state_flags (GTK_WIDGET (kbdrawing));
	GtkStyleContext *style_context =
	    gtk_widget_get_style_context (GTK_WIDGET (kbdrawing));
	PangoContext *pangoContext =
		gtk_widget_get_pango_context (GTK_WIDGET (kbdrawing));
	GkbdKeyboardDrawingRenderContext context = {
		cr,
		kbdrawing->renderContext->angle,
		layout,
		pango_font_description_copy (pango_context_get_font_description (pangoContext)),
		1, 1
	};

	gtk_style_context_get_background_color (style_context, state,
						&context.dark_color);

	if (!context_setup_scaling (&context, kbdrawing, width, height,
				    dpi_x, dpi_y))
		return FALSE;
	cairo_translate (cr, x, y);

	draw_keyboard_to_context (&context, kbdrawing);

	pango_font_description_free (context.font_desc);

	return TRUE;
}

/**
 * gkbd_keyboard_drawing_set_keyboard: (skip)
 */
gboolean
gkbd_keyboard_drawing_set_keyboard (GkbdKeyboardDrawing * drawing,
				    XkbComponentNamesRec * names)
{
	GtkAllocation allocation;

	free_cdik (drawing);
	if (drawing->xkb)
		XkbFreeKeyboard (drawing->xkb, 0, TRUE);	/* free_all = TRUE */
	drawing->xkb = NULL;

	if (names) {
		drawing->xkb =
		    XkbGetKeyboardByName (drawing->display, XkbUseCoreKbd,
					  names, 0,
					  XkbGBN_GeometryMask |
					  XkbGBN_KeyNamesMask |
					  XkbGBN_OtherNamesMask |
					  XkbGBN_ClientSymbolsMask |
					  XkbGBN_IndicatorMapMask, FALSE);
		drawing->xkbOnDisplay = FALSE;
	} else {
		drawing->xkb = XkbGetKeyboard (drawing->display,
					       XkbGBN_GeometryMask |
					       XkbGBN_KeyNamesMask |
					       XkbGBN_OtherNamesMask |
					       XkbGBN_SymbolsMask |
					       XkbGBN_IndicatorMapMask,
					       XkbUseCoreKbd);
		XkbGetNames (drawing->display, XkbAllNamesMask,
			     drawing->xkb);
		drawing->xkbOnDisplay = TRUE;
	}

	if (drawing->xkb) {
		XkbSelectEventDetails (drawing->display, XkbUseCoreKbd,
				       XkbIndicatorStateNotify,
				       drawing->xkb->indicators->phys_indicators,
				       drawing->xkb->indicators->phys_indicators);
	}

	alloc_cdik (drawing);

	init_keys_and_doodads (drawing);
	init_colors (drawing);

	gtk_widget_get_allocation (GTK_WIDGET (drawing), &allocation);
	size_allocate (GTK_WIDGET (drawing), &allocation, drawing);
	gtk_widget_queue_draw (GTK_WIDGET (drawing));

	return TRUE;
}

G_CONST_RETURN gchar *
gkbd_keyboard_drawing_get_keycodes (GkbdKeyboardDrawing * drawing)
{
	if (!drawing->xkb || drawing->xkb->names->keycodes <= 0)
		return NULL;
	else
		return XGetAtomName (drawing->display,
				     drawing->xkb->names->keycodes);
}

G_CONST_RETURN gchar *
gkbd_keyboard_drawing_get_geometry (GkbdKeyboardDrawing * drawing)
{
	if (!drawing->xkb || drawing->xkb->names->geometry <= 0)
		return NULL;
	else
		return XGetAtomName (drawing->display,
				     drawing->xkb->names->geometry);
}

G_CONST_RETURN gchar *
gkbd_keyboard_drawing_get_symbols (GkbdKeyboardDrawing * drawing)
{
	if (!drawing->xkb || drawing->xkb->names->symbols <= 0)
		return NULL;
	else
		return XGetAtomName (drawing->display,
				     drawing->xkb->names->symbols);
}

G_CONST_RETURN gchar *
gkbd_keyboard_drawing_get_types (GkbdKeyboardDrawing * drawing)
{
	if (!drawing->xkb || drawing->xkb->names->types <= 0)
		return NULL;
	else
		return XGetAtomName (drawing->display,
				     drawing->xkb->names->types);
}

G_CONST_RETURN gchar *
gkbd_keyboard_drawing_get_compat (GkbdKeyboardDrawing * drawing)
{
	if (!drawing->xkb || drawing->xkb->names->compat <= 0)
		return NULL;
	else
		return XGetAtomName (drawing->display,
				     drawing->xkb->names->compat);
}

void
gkbd_keyboard_drawing_set_track_modifiers (GkbdKeyboardDrawing * drawing,
					   gboolean enable)
{
	if (enable) {
		XkbStateRec state;
		drawing->track_modifiers = 1;
		memset (&state, 0, sizeof (state));
		XkbGetState (drawing->display, XkbUseCoreKbd, &state);
		gkbd_keyboard_drawing_set_mods (drawing,
						state.compat_state);
	} else
		drawing->track_modifiers = 0;
}

void
gkbd_keyboard_drawing_set_track_config (GkbdKeyboardDrawing * drawing,
					gboolean enable)
{
	if (enable)
		drawing->track_config = 1;
	else
		drawing->track_config = 0;
}

void
gkbd_keyboard_drawing_set_groups_levels (GkbdKeyboardDrawing * drawing,
					 GkbdKeyboardDrawingGroupLevel *
					 groupLevels[])
{
#ifdef KBDRAW_DEBUG
	printf ("set_group_levels [topLeft]: %d %d \n",
		groupLevels[GKBD_KEYBOARD_DRAWING_POS_TOPLEFT]->group,
		groupLevels[GKBD_KEYBOARD_DRAWING_POS_TOPLEFT]->level);
	printf ("set_group_levels [topRight]: %d %d \n",
		groupLevels[GKBD_KEYBOARD_DRAWING_POS_TOPRIGHT]->group,
		groupLevels[GKBD_KEYBOARD_DRAWING_POS_TOPRIGHT]->level);
	printf ("set_group_levels [bottomLeft]: %d %d \n",
		groupLevels[GKBD_KEYBOARD_DRAWING_POS_BOTTOMLEFT]->group,
		groupLevels[GKBD_KEYBOARD_DRAWING_POS_BOTTOMLEFT]->level);
	printf ("set_group_levels [bottomRight]: %d %d \n",
		groupLevels[GKBD_KEYBOARD_DRAWING_POS_BOTTOMRIGHT]->group,
		groupLevels[GKBD_KEYBOARD_DRAWING_POS_BOTTOMRIGHT]->level);
#endif
	drawing->groupLevels = groupLevels;

	gtk_widget_queue_draw (GTK_WIDGET (drawing));
}

typedef struct {
	GkbdKeyboardDrawing *drawing;
	const gchar *description;
} XkbLayoutPreviewPrintData;

static void
gkbd_keyboard_drawing_begin_print (GtkPrintOperation * operation,
				   GtkPrintContext * context,
				   XkbLayoutPreviewPrintData * data)
{
	/* We always print single-page documents */
	GtkPrintSettings *settings =
	    gtk_print_operation_get_print_settings (operation);
	gtk_print_operation_set_n_pages (operation, 1);
	if (!gtk_print_settings_has_key
	    (settings, GTK_PRINT_SETTINGS_ORIENTATION))
		gtk_print_settings_set_orientation (settings,
						    GTK_PAGE_ORIENTATION_LANDSCAPE);
}

static void
gkbd_keyboard_drawing_draw_page (GtkPrintOperation * operation,
				 GtkPrintContext * context,
				 gint page_nr,
				 XkbLayoutPreviewPrintData * data)
{
	cairo_t *cr = gtk_print_context_get_cairo_context (context);
	PangoLayout *layout =
	    gtk_print_context_create_pango_layout (context);
	PangoFontDescription *desc =
	    pango_font_description_from_string ("sans 8");
	gdouble width = gtk_print_context_get_width (context);
	gdouble height = gtk_print_context_get_height (context);
	gdouble dpi_x = gtk_print_context_get_dpi_x (context);
	gdouble dpi_y = gtk_print_context_get_dpi_y (context);
	gchar *header;

	gtk_print_operation_set_unit (operation, GTK_UNIT_PIXEL);

	header = g_strdup_printf
	    (_("Keyboard layout \"%s\"\n"
	       "Copyright &#169; X.Org Foundation and "
	       "XKeyboardConfig contributors\n"
	       "For licensing see package metadata"), data->description);
	pango_layout_set_markup (layout, header, -1);
	pango_layout_set_font_description (layout, desc);
	pango_font_description_free (desc);
	pango_layout_set_width (layout, pango_units_from_double (width));
	pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER);
	cairo_set_source_rgb (cr, 0, 0, 0);
	cairo_move_to (cr, 0, 0);
	pango_cairo_show_layout (cr, layout);

	gkbd_keyboard_drawing_render (GKBD_KEYBOARD_DRAWING
				      (data->drawing), cr, layout, 0.0,
				      0.0, width, height, dpi_x, dpi_y);

	g_object_unref (layout);
}

void
gkbd_keyboard_drawing_print (GkbdKeyboardDrawing * drawing,
			     GtkWindow * parent_window,
			     const gchar * description)
{
	GtkPrintOperation *print;
	GtkPrintOperationResult res;
	static GtkPrintSettings *settings = NULL;
	XkbLayoutPreviewPrintData data = { drawing, description };

	print = gtk_print_operation_new ();

	if (settings != NULL)
		gtk_print_operation_set_print_settings (print, settings);

	g_signal_connect (print, "begin_print",
			  G_CALLBACK (gkbd_keyboard_drawing_begin_print),
			  &data);
	g_signal_connect (print, "draw_page",
			  G_CALLBACK (gkbd_keyboard_drawing_draw_page),
			  &data);

	res = gtk_print_operation_run (print,
				       GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG,
				       parent_window, NULL);

	if (res == GTK_PRINT_OPERATION_RESULT_APPLY) {
		if (settings != NULL)
			g_object_unref (settings);
		settings = gtk_print_operation_get_print_settings (print);
		g_object_ref (settings);
	}

	g_object_unref (print);
}

void
gkbd_keyboard_drawing_set_layout (GkbdKeyboardDrawing * drawing,
				  const gchar * id)
{
	XklConfigRec *data;
	char **p, *layout, *variant;
	XkbComponentNamesRec component_names;
	XklEngine *engine;

	if (drawing == NULL)
		return;

	if (id == NULL) {
		gkbd_keyboard_drawing_set_keyboard (drawing, NULL);
		return;
	}

	engine = xkl_engine_get_instance (GDK_DISPLAY_XDISPLAY
					  (gdk_display_get_default ()));

	data = xkl_config_rec_new ();
	if (xkl_config_rec_get_from_server (data, engine)) {
		if ((p = data->layouts) != NULL)
			g_strfreev (data->layouts);

		if ((p = data->variants) != NULL)
			g_strfreev (data->variants);

		data->layouts = g_new0 (char *, 2);
		data->variants = g_new0 (char *, 2);
		if (gkbd_keyboard_config_split_items
		    (id, &layout, &variant)
		    && variant != NULL) {
			data->layouts[0] =
			    (layout == NULL) ? NULL : g_strdup (layout);
			data->variants[0] =
			    (variant == NULL) ? NULL : g_strdup (variant);
		} else {
			data->layouts[0] =
			    (id == NULL) ? NULL : g_strdup (id);
			data->variants[0] = NULL;
		}

		if (xkl_xkb_config_native_prepare
		    (engine, data, &component_names)) {
			if (!gkbd_keyboard_drawing_set_keyboard
			    (drawing, &component_names))
				gkbd_keyboard_drawing_set_keyboard
				    (drawing, NULL);

			xkl_xkb_config_native_cleanup
			    (engine, &component_names);
		} else {
			xkl_debug (0, "Could not find the keyboard\n");
		}
	}
	g_object_unref (G_OBJECT (data));
}

static void
gkbd_keyboard_drawing_dialog_set_layout_name (GtkWidget * dialog,
					      const gchar * layout_name)
{
	gtk_window_set_title (GTK_WINDOW (dialog), layout_name);
	g_object_set_data_full (G_OBJECT (dialog), "layout_name",
				g_strdup (layout_name), g_free);
}

static void
gkbd_keyboard_drawing_dialog_response (GtkWidget * dialog, gint resp)
{
	GdkRectangle rect;
	GtkWidget *kbdraw;
	const gchar *groupName;

	switch (resp) {
	case GTK_RESPONSE_CLOSE:
		gtk_window_get_position (GTK_WINDOW (dialog), &rect.x,
					 &rect.y);
		gtk_window_get_size (GTK_WINDOW (dialog), &rect.width,
				     &rect.height);
		gkbd_preview_save_position (&rect);
		gtk_widget_destroy (dialog);
		break;
	case GTK_RESPONSE_PRINT:
		kbdraw =
		    GTK_WIDGET (g_object_get_data
				(G_OBJECT (dialog), "kbdraw"));
		groupName =
		    (const gchar *) g_object_get_data (G_OBJECT (dialog),
						       "groupName");
		gkbd_keyboard_drawing_print (GKBD_KEYBOARD_DRAWING
					     (kbdraw), GTK_WINDOW (dialog),
					     groupName ? groupName :
					     _("Unknown"));
	}
}

void
gkbd_keyboard_drawing_dialog_set_group (GtkWidget * dialog,
					XklConfigRegistry * registry,
					gint group)
{
	XkbComponentNamesRec component_names;
	XklConfigRec *xkl_data;
	XklEngine *engine =
	    xkl_engine_get_instance (GDK_DISPLAY_XDISPLAY
				     (gdk_display_get_default ()));

	xkl_data = xkl_config_rec_new ();
	if (xkl_config_rec_get_from_server (xkl_data, engine)) {
		int num_layouts = g_strv_length (xkl_data->layouts);
		int num_variants = g_strv_length (xkl_data->variants);
		if (group >= 0 && group < num_layouts
		    && group < num_variants) {
			XklConfigItem *xki = xkl_config_item_new ();
			gchar *l = g_strdup (xkl_data->layouts[group]);
			gchar *v = g_strdup (xkl_data->variants[group]);
			const gchar *layout_name = NULL;
			gchar **p;
			int i;

			if ((p = xkl_data->layouts) != NULL)
				for (i = num_layouts; --i >= 0;)
					g_free (*p++);

			if ((p = xkl_data->variants) != NULL)
				for (i = num_variants; --i >= 0;)
					g_free (*p++);

			xkl_data->layouts =
			    g_realloc (xkl_data->layouts,
				       sizeof (char *) * 2);
			xkl_data->variants =
			    g_realloc (xkl_data->variants,
				       sizeof (char *) * 2);
			xkl_data->layouts[0] = l;
			xkl_data->variants[0] = v;
			xkl_data->layouts[1] = xkl_data->variants[1] =
			    NULL;

			if (v[0] != 0) {
				strncpy (xki->name, v,
					 XKL_MAX_CI_NAME_LENGTH);
				xki->name[XKL_MAX_CI_NAME_LENGTH - 1] = 0;
				if (xkl_config_registry_find_variant
				    (registry, l, xki))
					layout_name = xki->description;
			} else {
				strncpy (xki->name, l,
					 XKL_MAX_CI_NAME_LENGTH);
				xki->name[XKL_MAX_CI_NAME_LENGTH - 1] = 0;
				if (xkl_config_registry_find_layout
				    (registry, xki))
					layout_name = xki->description;
			}
			gkbd_keyboard_drawing_dialog_set_layout_name
			    (dialog, layout_name);
			g_object_unref (xki);
		}

		if (xkl_xkb_config_native_prepare
		    (engine, xkl_data, &component_names)) {
			GtkWidget *kbdraw =
			    g_object_get_data (G_OBJECT (dialog),
					       "kbdraw");
			if (!gkbd_keyboard_drawing_set_keyboard
			    (GKBD_KEYBOARD_DRAWING (kbdraw),
			     &component_names))
				gkbd_keyboard_drawing_set_keyboard
				    (GKBD_KEYBOARD_DRAWING (kbdraw), NULL);
			xkl_xkb_config_native_cleanup (engine,
						       &component_names);
		}
	}

	g_object_unref (G_OBJECT (xkl_data));
}

GtkWidget *
gkbd_keyboard_drawing_dialog_new ()
{
	GtkBuilder *builder;
	GtkWidget *dialog, *kbdraw;
	GdkRectangle *rect;
	GError *error = NULL;

	builder = gtk_builder_new ();
	gtk_builder_add_from_file (builder, UIDIR "/show-layout.ui",
				   &error);

	if (error) {
		g_error ("building ui from %s failed: %s",
			 UIDIR "/show-layout.ui", error->message);
		g_clear_error (&error);
	}

	dialog =
	    GTK_WIDGET (gtk_builder_get_object
			(builder, "gswitchit_layout_view"));
	kbdraw = gkbd_keyboard_drawing_new ();

	gkbd_keyboard_drawing_set_groups_levels (GKBD_KEYBOARD_DRAWING
						 (kbdraw), pGroupsLevels);

	g_object_set_data (G_OBJECT (dialog), "builderData", builder);
	g_signal_connect (G_OBJECT (dialog), "response",
			  G_CALLBACK
			  (gkbd_keyboard_drawing_dialog_response), NULL);

	gtk_window_set_resizable (GTK_WINDOW (dialog), TRUE);

	gtk_box_pack_start (GTK_BOX
			    (gtk_builder_get_object
			     (builder, "preview_vbox")), kbdraw, TRUE,
			    TRUE, 0);

	g_object_set_data (G_OBJECT (dialog), "kbdraw", kbdraw);

	g_signal_connect_swapped (dialog, "destroy",
				  G_CALLBACK (g_object_unref),
				  g_object_get_data (G_OBJECT (dialog),
						     "builderData"));

	rect = gkbd_preview_load_position ();
	if (rect != NULL) {
		gtk_window_move (GTK_WINDOW (dialog), rect->x, rect->y);
		g_free (rect);
	}

	return dialog;
}

void
gkbd_keyboard_drawing_dialog_set_layout (GtkWidget * dialog,
					 XklConfigRegistry * registry,
					 const gchar * full_layout)
{
	const gchar *layout_name = "?";
	XklConfigItem *xki = xkl_config_item_new ();
	gchar *layout = NULL, *variant = NULL;

	GkbdKeyboardDrawing *kbdraw =
	    GKBD_KEYBOARD_DRAWING (g_object_get_data
				   (G_OBJECT (dialog), "kbdraw"));

	if (full_layout == NULL || full_layout[0] == 0)
		return;

	gkbd_keyboard_drawing_set_layout (kbdraw, full_layout);

	if (gkbd_keyboard_config_split_items
	    (full_layout, &layout, &variant)) {
		if (variant != NULL) {
			strncpy (xki->name, variant,
				 XKL_MAX_CI_NAME_LENGTH);
			xki->name[XKL_MAX_CI_NAME_LENGTH - 1] = 0;
			if (xkl_config_registry_find_variant
			    (registry, layout, xki))
				layout_name = xki->description;
		} else {
			strncpy (xki->name, layout,
				 XKL_MAX_CI_NAME_LENGTH);
			xki->name[XKL_MAX_CI_NAME_LENGTH - 1] = 0;
			if (xkl_config_registry_find_layout
			    (registry, xki))
				layout_name = xki->description;
		}
	}

	gkbd_keyboard_drawing_dialog_set_layout_name (dialog, layout_name);
	g_object_unref (xki);
}
