/* File: main-gtk.c */

/*
 * Copyright (c) 2000-2001 Robert Ruehlmann,
 * Steven Fuerst, Uwe Siems, "pelpel", et al.
 *
 * This software may be copied and distributed for educational, research,
 * and not for profit purposes provided that this copyright and statement
 * are included in all such copies.
 */

/*
 * Robert Ruehlmann wrote the original Gtk port. Since an initial work is
 * much harder than enhancements, his effort worth more credits than
 * others.
 *
 * Steven Fuerst implemented colour-depth independent X server support,
 * graphics, resizing and big screen support for ZAngband as well as
 * fast image rescaling that is included here.
 *
 * Uwe Siems wrote smooth tiles rescaling code (on by default).
 * Try this with 8x8 tiles. They *will* look different.
 *
 * "pelpel" wrote another colour-depth independent X support
 * using GdkRGB, added several hooks and callbacks for various
 * reasons, wrote no-backing store mode (off by default),
 * added GtkItemFactory based menu system, introduced
 * USE_GRAPHICS code bloat (^ ^;), added comments (I have
 * a strange habit of writing comments while I code...)
 * and reorganised the file a bit.
 */

#include "angband.h"


/*
 * Activate variant-specific features
 *
 * Angband 2.9.3 and close variants don't require any.
 *
 * Angband 2.9.4 alpha and later removed the short-lived
 * can_save flag, so please #define can_save TRUE, or remove
 * all the references to it. They also changed long-lived
 * z-virt macro names. Find C_FREE/C_KILL and replace them
 * with FREE/KILL, which takes one pointer parameter.
 *
 * [Z]-based variants (Gum and Cth, for example) usually need
 * ANG293_COMPAT, ANG291_COMPAT and ANG281_RESET_VISUALS.
 *
 * [O] needs ANG293_COMPAT and ZANG_SAVE_GAME.
 *
 * ZAngband has its own enhanced main-gtk.c as mentioned above, and
 * you *should* use it :-)
 *
 * ANG291_COMPAT does not include Angband 2.9.x's gamma correction code.
 * If you like to use SUPPORT_GAMMA, copy the code bracketed
 * inside of #ifdef SUPPORT_GAMMA in util.c of Angband 2.9.1 or greater.
 */
#define TOME

#ifdef TOME
# define ANG293_COMPAT	/* Requires V2.9.3 compatibility code */
# define ANG291_COMPAT	/* Requires V2.9.1 compatibility code */
# define ANG281_RESET_VISUALS	/* The old style reset_visuals() */
# define INTERACTIVE_GAMMA	/* Supports interactive gamma correction */
# define SAVEFILE_SCREEN	/* New/Open integrated into the game */
# define USE_DOUBLE_TILES	/* Mogami's bigtile patch */
#endif /* TOME */

/*
 * Some examples
 */
#ifdef ANGBAND300
# define can_save TRUE	/* Mimick the short-lived flag */
# define C_FREE(P, N, T)	FREE(P)	/* Emulate the long-lived macro */
# define USE_TRANSPARENCY	/* Because it's default now */
#endif /* ANGBAND300 */

#ifdef GUMBAND
# define ANG293_COMPAT	/* Requires V2.9.3 compatibility code */
# define ANG291_COMPAT	/* Requires V2.9.1 compatibility code */
# define ANG281_RESET_VISUALS	/* The old style reset_visuals() */
# define OLD_SAVEFILE_CODE /* See also SAVEFILE_MUTABLE in files.c */
# define NO_REDRAW_SECTION	/* Doesn't have Term_redraw_section() */
#endif /* GUMBAND */

#ifdef OANGBAND
# define ANG293_COMPAT	/* Requires V2.9.3 compatibility code */
# define ZANG_SAVE_GAME	/* do_cmd_save_game with auto_save parameter */
#endif /* OANGBAND */


#ifdef USE_GTK

/* Force ANSI standard */
/* #define __STRICT_ANSI__ */

/* No GCC-specific includes */
/* #undef __GNUC__ */

#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>

/* /me pffts Solaris */
#ifndef NAME_MAX
#define	NAME_MAX	_POSIX_NAME_MAX
#endif


/*
 * Include some helpful X11 code.
 */
#ifndef ANG293_COMPAT
# include "maid-x11.h"
#endif /* !ANG293_COMPAT */


/*
 * Number of pixels inserted between the menu bar and the main screen
 */
#define NO_PADDING 0


/*
 * Largest possible number of terminal windows supported by the game
 */
#define MAX_TERM_DATA 8


/*
 * Extra data to associate with each "window"
 *
 * Each "window" is represented by a "term_data" structure, which
 * contains a "term" structure, which contains a pointer (t->data)
 * back to the term_data structure.
 */

#ifdef USE_GRAPHICS

/*
 * Since GdkRGB doesn't provide us some useful functions...
 */
typedef struct GdkRGBImage GdkRGBImage;

struct GdkRGBImage
{
	gint width;
	gint height;
	gint ref_count;
	guchar *image;
};

#endif /* USE_GRAPHICS */


/*
 * This structure holds everything you need to manipulate terminals
 */
typedef struct term_data term_data;

struct term_data
{
	term t;

	GtkWidget *window;
	GtkWidget *drawing_area;
	GdkPixmap *backing_store;
	GdkFont *font;
	GdkGC *gc;

	bool shown;
	byte last_attr;

	int font_wid;
	int font_hgt;

	int rows;
	int cols;

#ifdef USE_GRAPHICS

	int tile_wid;
	int tile_hgt;

	GdkRGBImage *tiles;
# ifdef USE_TRANSPARENCY
	guint32 bg_pixel;
	GdkRGBImage *trans_buf;
# endif  /* USE_TRANSPARENCY */

#endif /* USE_GRAPHICS */

	cptr name;
};


/*
 * Where to draw when we call Gdk drawing primitives
 */
# define TERM_DATA_DRAWABLE(td) \
((td)->backing_store ? (td)->backing_store : (td)->drawing_area->window)

# define TERM_DATA_REFRESH(td, x, y, wid, hgt) \
if ((td)->backing_store) gdk_draw_pixmap( \
(td)->drawing_area->window, \
(td)->gc, \
(td)->backing_store, \
(x) * (td)->font_wid, \
(y) * (td)->font_hgt, \
(x) * (td)->font_wid, \
(y) * (td)->font_hgt, \
(wid) * (td)->font_wid, \
(hgt) * (td)->font_hgt)


#if 0

/* Compile time option version */

# ifdef USE_BACKING_STORE

# define TERM_DATA_DRAWABLE(td) (td)->backing_store

# define TERM_DATA_REFRESH(td, x, y, wid, hgt) \
gdk_draw_pixmap( \
(td)->drawing_area->window, \
(td)->gc, \
(td)->backing_store, \
(x) * (td)->font_wid, \
(y) * (td)->font_hgt, \
(x) * (td)->font_wid, \
(y) * (td)->font_hgt, \
(wid) * (td)->font_wid, \
(hgt) * (td)->font_hgt)

# else /* USE_BACKING_STORE */

# define TERM_DATA_DRAWABLE(td) (td)->drawing_area->window
# define TERM_DATA_REFRESH(td, x, y, wid, hgt)

# endif  /* USE_BACKING_STORE */

#endif /* 0 */


/*
 * An array of "term_data" structures, one for each "sub-window"
 */
static term_data data[MAX_TERM_DATA];

/*
 * Number of active terms
 */
static int num_term = 1;


/*
 * RGB values of the sixteen Angband colours
 */
static guint32 angband_colours[16];


/*
 * Set to TRUE when a game is in progress
 */
static bool game_in_progress = FALSE;


/*
 * This is in some cases used for double buffering as well as
 * a backing store, speeding things up under client-server
 * configurations, while turning this off *might* work better
 * with the MIT Shm extention which is usually active if you run
 * Angband locally, because it reduces amount of memory-to-memory copy.
 */
static bool use_backing_store = TRUE;




/**** Vanilla compatibility functions ****/

#ifdef ANG293_COMPAT

/*
 * Look up some environment variables to find font name for each window.
 */
static cptr get_default_font(int term)
{
	char buf[64];
	cptr font_name;

	/* Window specific font name */
	strnfmt(buf, 64, "ANGBAND_X11_FONT_%s", angband_term_name[term]);

	/* Check environment for that font */
	font_name = getenv(buf);

	/* Window specific font name */
	strnfmt(buf, 64, "ANGBAND_X11_FONT_%d", term);

	/* Check environment for that font */
	if (!font_name) font_name = getenv(buf);

	/* Check environment for "base" font */
	if (!font_name) font_name = getenv("ANGBAND_X11_FONT");

	/* No environment variables, use default font */
	if (!font_name) font_name = DEFAULT_X11_FONT_SCREEN;

	return (font_name);
}


# ifndef SAVEFILE_SCREEN

/*
 * In [V]2.9.3, this frees all dynamically allocated memory
 */
static void cleanup_angband(void)
{
	/* XXX XXX XXX */
}

# endif  /* !SAVEFILE_SCREEN */

/*
 * New global flag to indicate if it's safe to save now
 */
#define can_save TRUE

#endif /* ANG293_COMPAT */


#ifdef ANG291_COMPAT

/*
 * The standard game uses this to implement lighting effects
 * for 16x16 tiles in cave.c...
 *
 * Because of the way it is implemented in X11 ports,
 * we can set this to TRUE even if we are using the 8x8 tileset.
 */
static bool use_transparency = TRUE;

#endif /* ANG291_COMPAT */




/**** Low level routines - memory allocation ****/

/*
 * Hook to "release" memory
 */
#ifdef ANGBAND300
static vptr hook_rnfree(vptr v)
#else
static vptr hook_rnfree(vptr v, huge size)
#endif /* ANGBAND300 */
{
	/* Dispose */
	g_free(v);

	/* Success */
	return (NULL);
}


/*
 * Hook to "allocate" memory
 */
static vptr hook_ralloc(huge size)
{
	/* Make a new pointer */
	return (g_malloc(size));
}



/**** Low level routines - colours and graphics ****/

#ifdef SUPPORT_GAMMA

/*
 * When set to TRUE, indicates that we can use gamma_table
 */
static bool gamma_table_ready = FALSE;


# ifdef INTERACTIVE_GAMMA

/*
 * Initialise the gamma-correction table for current gamma_val
 * - interactive version
 */
static void setup_gamma_table(void)
{
	static u16b old_gamma_val = 0;

	/* Don't have to rebuild the table */
	if (gamma_val == old_gamma_val) return;

	/* Temporarily inactivate the table */
	gamma_table_ready = FALSE;

	/* Validate gamma_val */
	if ((gamma_val <= 0) || (gamma_val >= 256))
	{
		/* Reset */
		old_gamma_val = gamma_val = 0;

		/* Leave it inactive */
		return;
	}

	/* (Re)build the gamma table */
	build_gamma_table(gamma_val);

	/* Remember the gamma value used */
	old_gamma_val = gamma_val;

	/* Activate the table */
	gamma_table_ready = TRUE;
}

# else /* INTERACTIVE_GAMMA */

/*
 * Initialise the gamma-correction table if environment variable
 * ANGBAND_X11_GAMMA is set and contains a meaningful value
 *
 * Restored for cross-variant compatibility
 */
static void setup_gamma_table(void)
{
	cptr tmp;
	int gamma_val;


	/* The table's already set up */
	if (gamma_table_ready) return;

	/*
	 * XXX XXX It's documented nowhere, but ANGBAND_X11_GAMMA is
	 * 256 * (1 / gamma), rounded to integer. A recommended value
	 * is 183, which is an approximation of the Macintosh hardware
	 * gamma of 1.4.
	 *
	 *   gamma	ANGBAND_X11_GAMMA
	 *   -----	-----------------
	 *   1.2	213
	 *   1.25	205
	 *   1.3	197
	 *   1.35	190
	 *   1.4	183
	 *   1.45	177
	 *   1.5	171
	 *   1.6	160
	 *   1.7	151
	 *   ...
	 *
	 * XXX XXX The environment variable, or better,
	 * the interact with colours command should allow users
	 * to specify gamma values (or gamma value * 100).
	 */
	tmp = getenv("ANGBAND_X11_GAMMA");

	/* Nothing to do */
	if (tmp == NULL) return;

	/* Extract the value */
	gamma_val = atoi(tmp);

	/*
	 * Only need to build the table if gamma exists and set to
	 * a meaningful value.
	 *
	 * XXX It may be a good idea to prevent use of very high gamma values,
	 * say, greater than 2.5, which is gamma of normal CRT display IIRC.
	 */
	if ((gamma_val <= 0) || (gamma_val >= 256)) return;

	/* Build the gamma correction table */
	build_gamma_table(gamma_val);

	/* The table is properly set up */
	gamma_table_ready = TRUE;
}

# endif  /* INTERACTIVE_GAMMA */

#endif /* SUPPORT_GAMMA */


/*
 * Remeber RGB values for sixteen Angband colours, in a format
 * that is convinient for GdkRGB GC functions.
 *
 * XXX XXX Duplication of maid-x11.c is far from the Angband
 * ideal of code cleanliness, but the whole point of using GdkRGB
 * is to let it handle colour allocation which it does in a very
 * clever fashion. Ditto for the tile scaling code and the BMP loader
 * below.
 */
static void init_colours(void)
{
	int i;


#ifdef SUPPORT_GAMMA

	/* (Re)build gamma table if necessary */
	setup_gamma_table();

#endif /* SUPPORT_GAMMA */

	/* Process each colour */
	for (i = 0; i < 16; i++)
	{
		u32b red, green, blue;

		/* Retrieve RGB values from the game */
		red = angband_color_table[i][1];
		green = angband_color_table[i][2];
		blue = angband_color_table[i][3];

#ifdef SUPPORT_GAMMA

		/* Hack -- Gamma Correction */
		if (gamma_table_ready)
		{
			red = gamma_table[red];
			green = gamma_table[green];
			blue = gamma_table[blue];
		}

#endif /* SUPPORT_GAMMA */

		/* Remember a GdkRGB value, that is 0xRRGGBB */
		angband_colours[i] = (red << 16) | (green << 8) | blue;
	}
}


/*
 * Set foreground colour of window td to attr, only when it is necessary
 */
static void term_data_set_fg(term_data *td, byte attr)
{
	/* We can use the current gc */
	if (td->last_attr == attr) return;

	/* Activate the colour */
	gdk_rgb_gc_set_foreground(td->gc, angband_colours[attr]);

	/* Remember it */
	td->last_attr = attr;
}


#ifdef USE_GRAPHICS

/*
 * Graphics mode selector - current setting and requested value
 */
#define GRAF_MODE_NONE	0
#define GRAF_MODE_OLD	1
#define GRAF_MODE_NEW	2

static int graf_mode = GRAF_MODE_NONE;
static int graf_mode_request = GRAF_MODE_NONE;

/*
 * Use smooth rescaling?
 */
static bool smooth_rescaling = TRUE;
static bool smooth_rescaling_request = TRUE;

/*
 * Dithering
 */
static GdkRgbDither dith_mode = GDK_RGB_DITHER_NORMAL;

/*
 * Need to reload and resize tiles when fonts are changed.
 */
static bool resize_request = FALSE;

/*
 * Numbers of columns and rows in current tileset
 * calculated and set by the tile loading code in graf_init()
 * and used by Term_pict_gtk()
 */
static int tile_rows;
static int tile_cols;


/*
 * Directory name(s)
 */
static cptr ANGBAND_DIR_XTRA_GRAF;


/*
 * Be nice to old graphics hardwares -- using GdkRGB.
 *
 * We don't have colour allocation failure any longer this way,
 * even with 8bpp X servers. Gimp *does* work with 8bpp, why not Angband?
 *
 * Initialisation (before any widgets are created)
 *	gdk_rgb_init();
 *	gtk_widget_set_default_colormap (gdk_rgb_get_cmap());
 *	gtk_widget_set_default_visual (gdk_rgb_get_visual());
 *
 * Setting fg/bg colours
 *	void gdk_rgb_gc_set_foreground(GdkGC *gc, guint32 rgb);
 *	void gdk_rgb_gc_set_background(GdkGC *gc, guint32 rgb);
 * where rgb is 0xRRGGBB.
 *
 * Drawing rgb images
 *	void gdk_draw_rgb_image(
 *		GdkDrawable *drawable,
 *		GdkGC *gc,
 *		gint x, gint y,
 *		gint width, gint height,
 *		GdkRgbDither dith,
 *		guchar *rgb_buf,
 *		gint rowstride);
 *
 * dith:
 *	GDK_RGB_DITHER_NORMAL : dither if 8bpp or below
 *	GDK_RGB_DITHER_MAX : dither if 16bpp or below.
 *
 * for 0 <= i < width and 0 <= j < height,
 * the pixel (x + i, y + j) is colored with
 *  red value rgb_buf[j * rowstride + i * 3],
 *  green value rgb_buf[j * rowstride + i * 3 + 1], and
 *  blue value rgb_buf[j * rowstride + i * 3 + 2].
 */

/*
 * gdk_image compatibility functions - should be part of gdk, IMHO.
 */

/*
 * Create GdkRGBImage of width * height and return pointer
 * to it. Returns NULL on failure
 */
static GdkRGBImage *gdk_rgb_image_new(
        gint width,
        gint height)
{
	GdkRGBImage *result;

	/* Allocate a struct */
	result = g_new(GdkRGBImage, 1);

	/* Oops */
	if (result == NULL) return (NULL);

	/* Allocate buffer */
	result->image = g_new0(guchar, width * height * 3);

	/* Oops */
	if (result->image == NULL)
	{
		g_free(result);
		return (NULL);
	}

	/* Initialise size fields */
	result->width = width;
	result->height = height;

	/* Initialise reference count */
	result->ref_count = 1;

	/* Success */
	return (result);
}

/*
 * Free a GdkRGBImage
 */
static void gdk_rgb_image_destroy(
        GdkRGBImage *im)
{
	/* Paranoia */
	if (im == NULL) return;

	/* Free the RGB buffer */
	g_free(im->image);

	/* Free the structure */
	g_free(im);
}


#if 0

/*
 * Unref a GdkRGBImage
 */
static void gdk_rgb_image_unref(
        GdkRGBImage *im)
{
	/* Paranoia */
	g_return_if_fail(im != NULL);

	/* Decrease reference count by 1 */
	im->ref_count--;

	/* Free if nobody's using it */
	if (im->ref_count <= 0) gdk_rgb_image_destroy(im);
}


/*
 * Reference a GdkRGBImage
 */
static void gdk_rgb_image_ref(
        GdkRGBImage *im)
{
	/* Paranoia */
	g_return_if_fail(im != NULL);

	/* Increase reference count by 1 */
	im->ref_count++;
}

#endif /* 0 */


/*
 * Write RGB pixel of the format 0xRRGGBB to (x, y) in GdkRGBImage
 */
static void gdk_rgb_image_put_pixel(
        GdkRGBImage *im,
        gint x,
        gint y,
        guint32 pixel)
{
	guchar *rgbp;

	/* Paranoia */
	g_return_if_fail(im != NULL);

	/* Paranoia */
	if ((x < 0) || (x >= im->width)) return;

	/* Paranoia */
	if ((y < 0) || (y >= im->height)) return;

	/* Access RGB data */
	rgbp = &im->image[(y * im->width * 3) + (x * 3)];

	/* Red */
	*rgbp++ = (pixel >> 16) & 0xFF;
	/* Green */
	*rgbp++ = (pixel >> 8) & 0xFF;
	/* Blue */
	*rgbp = pixel & 0xFF;
}


/*
 * Returns RGB pixel (0xRRGGBB) at (x, y) in GdkRGBImage
 */
static guint32 gdk_rgb_image_get_pixel(
        GdkRGBImage *im,
        gint x,
        gint y)
{
	guchar *rgbp;

	/* Paranoia */
	if (im == NULL) return (0);

	/* Paranoia - returns black */
	if ((x < 0) || (x >= im->width)) return (0);

	/* Paranoia */
	if ((y < 0) || (y >= im->height)) return (0);

	/* Access RGB data */
	rgbp = &im->image[(y * im->width * 3) + (x * 3)];

	/* Return result */
	return ((rgbp[0] << 16) | (rgbp[1] << 8) | (rgbp[2]));
}


/*
 * Since gdk_draw_rgb_image is a bit harder to use than it's
 * GdkImage counterpart, I wrote a grue function that takes
 * exactly the same parameters as gdk_draw_image, with
 * the GdkImage parameter replaced with GdkRGBImage.
 */
static void gdk_draw_rgb_image_2(
        GdkDrawable *drawable,
        GdkGC *gc,
        GdkRGBImage *image,
        gint xsrc,
        gint ysrc,
        gint xdest,
        gint ydest,
        gint width,
        gint height)
{
	/* Paranoia */
	g_return_if_fail(drawable != NULL);
	g_return_if_fail(image != NULL);

	/* Paranoia */
	if (xsrc < 0 || (xsrc + width - 1) >= image->width) return;
	if (ysrc < 0 || (ysrc + height - 1) >= image->height) return;

	/* Draw the image at (xdest, ydest), with dithering if bpp <= 8/16 */
	gdk_draw_rgb_image(
	        drawable,
	        gc,
	        xdest,
	        ydest,
	        width,
	        height,
	        dith_mode,
	        &image->image[(ysrc * image->width * 3) + (xsrc * 3)],
	        image->width * 3);
}


/*
 * Code for smooth icon rescaling from Uwe Siems, Jan 2000
 *
 * XXX XXX Duplication of maid-x11.c, again. It doesn't do any colour
 * allocation, either.
 */

/*
 * to save ourselves some labour, define a maximum expected icon width here:
 */
#define MAX_ICON_WIDTH 32


/*
 * Each pixel is kept in this structure during smooth rescaling
 * calculations, to make things a bit easier
 */
typedef struct rgb_type rgb_type;

struct rgb_type
{
	guint32 red;
	guint32 green;
	guint32 blue;
};

/*
 * Because there are many occurences of this, and because
 * it's logical to do so...
 */
#define pixel_to_rgb(pix, rgb_buf) \
(rgb_buf)->red   = ((pix) >> 16) & 0xFF; \
(rgb_buf)->green = ((pix) >> 8)  & 0xFF; \
(rgb_buf)->blue  = (pix) & 0xFF


/*
 * get_scaled_row reads a scan from the given GdkRGBImage, scales it smoothly
 * and returns the red, green and blue values in arrays.
 * The values in this arrays must be divided by a certain value that is
 * calculated in scale_icon.
 * x, y is the position, iw is the input width and ow the output width
 * scan must be sufficiently sized
 */
static void get_scaled_row(
        GdkRGBImage *im,
        int x,
        int y,
        int iw,
        int ow,
        rgb_type *scan)
{
	int xi, si, sifrac, ci, cifrac, add_whole, add_frac;
	guint32 pix;
	rgb_type prev;
	rgb_type next;
	bool get_next_pix;

	/* Unscaled */
	if (iw == ow)
	{
		for (xi = 0; xi < ow; xi++)
		{
			pix = gdk_rgb_image_get_pixel(im, x + xi, y);
			pixel_to_rgb(pix, &scan[xi]);
		}
	}

	/* Scaling by subsampling (grow) */
	else if (iw < ow)
	{
		iw--;
		ow--;

		/* read first pixel: */
		pix = gdk_rgb_image_get_pixel(im, x, y);
		pixel_to_rgb(pix, &next);
		prev = next;

		/* si and sifrac give the subsampling position: */
		si = x;
		sifrac = 0;

		/* get_next_pix tells us, that we need the next pixel */
		get_next_pix = TRUE;

		for (xi = 0; xi <= ow; xi++)
		{
			if (get_next_pix)
			{
				prev = next;
				if (xi < ow)
				{
					/* only get next pixel if in same icon */
					pix = gdk_rgb_image_get_pixel(im, si + 1, y);
					pixel_to_rgb(pix, &next);
				}
			}

			/* calculate subsampled color values: */
			/* division by ow occurs in scale_icon */
			scan[xi].red = prev.red * (ow - sifrac) + next.red * sifrac;
			scan[xi].green = prev.green * (ow - sifrac) + next.green * sifrac;
			scan[xi].blue = prev.blue * (ow - sifrac) + next.blue * sifrac;

			/* advance sampling position: */
			sifrac += iw;
			if (sifrac >= ow)
			{
				si++;
				sifrac -= ow;
				get_next_pix = TRUE;
			}
			else
			{
				get_next_pix = FALSE;
			}

		}
	}

	/* Scaling by averaging (shrink) */
	else
	{
		/* width of an output pixel in input pixels: */
		add_whole = iw / ow;
		add_frac = iw % ow;

		/* start position of the first output pixel: */
		si = x;
		sifrac = 0;

		/* get first input pixel: */
		pix = gdk_rgb_image_get_pixel(im, x, y);
		pixel_to_rgb(pix, &next);

		for (xi = 0; xi < ow; xi++)
		{
			/* find endpoint of the current output pixel: */
			ci = si + add_whole;
			cifrac = sifrac + add_frac;
			if (cifrac >= ow)
			{
				ci++;
				cifrac -= ow;
			}

			/* take fraction of current input pixel (starting segment): */
			scan[xi].red = next.red * (ow - sifrac);
			scan[xi].green = next.green * (ow - sifrac);
			scan[xi].blue = next.blue * (ow - sifrac);
			si++;

			/* add values for whole pixels: */
			while (si < ci)
			{
				rgb_type tmp_rgb;

				pix = gdk_rgb_image_get_pixel(im, si, y);
				pixel_to_rgb(pix, &tmp_rgb);
				scan[xi].red += tmp_rgb.red * ow;
				scan[xi].green += tmp_rgb.green * ow;
				scan[xi].blue += tmp_rgb.blue * ow;
				si++;
			}

			/* add fraction of current input pixel (ending segment): */
			if (xi < ow - 1)
			{
				/* only get next pixel if still in icon: */
				pix = gdk_rgb_image_get_pixel(im, si, y);
				pixel_to_rgb(pix, &next);
			}

			sifrac = cifrac;
			if (sifrac > 0)
			{
				scan[xi].red += next.red * sifrac;
				scan[xi].green += next.green * sifrac;
				scan[xi].blue += next.blue * sifrac;
			}
		}
	}
}


/*
 * put_rgb_scan takes arrays for red, green and blue and writes pixel values
 * according to this values in the GdkRGBImage-structure. w is the number of
 * pixels to write and div is the value by which all red/green/blue values
 * are divided first.
 */
static void put_rgb_scan(
        GdkRGBImage *im,
        int x,
        int y,
        int w,
        int div,
        rgb_type *scan)
{
	int xi;
	guint32 pix;
	guint32 adj = div / 2;

	for (xi = 0; xi < w; xi++)
	{
		byte r, g, b;

		/* un-factor the RGB values */
		r = (scan[xi].red + adj) / div;
		g = (scan[xi].green + adj) / div;
		b = (scan[xi].blue + adj) / div;

#ifdef SUPPORT_GAMMA

		/* Apply gamma correction if requested and available */
		if (gamma_table_ready)
		{
			r = gamma_table[r];
			g = gamma_table[g];
			b = gamma_table[b];
		}

#endif /* SUPPORT_GAMMA */

		/* Make a (virtual) 24-bit pixel */
		pix = (r << 16) | (g << 8) | (b);

		/* Draw it into image */
		gdk_rgb_image_put_pixel(im, x + xi, y, pix);
	}
}


/*
 * scale_icon transfers an area from GdkRGBImage im_in, locate (x1,y1) to
 * im_out, locate (x2, y2). Source size is (ix, iy) and destination size
 * is (ox, oy).
 *
 * It does this by getting icon scan line from get_scaled_scan and handling
 * them the same way as pixels are handled in get_scaled_scan.
 * This even allows icons to be scaled differently in horizontal and
 * vertical directions (eg. shrink horizontal, grow vertical).
 */
static void scale_icon(
        GdkRGBImage *im_in,
        GdkRGBImage *im_out,
        int x1,
        int y1,
        int x2,
        int y2,
        int ix,
        int iy,
        int ox,
        int oy)
{
	int div;
	int xi, yi, si, sifrac, ci, cifrac, add_whole, add_frac;

	/* buffers for pixel rows: */
	rgb_type prev[MAX_ICON_WIDTH];
	rgb_type next[MAX_ICON_WIDTH];
	rgb_type temp[MAX_ICON_WIDTH];

	bool get_next_row;

	/* get divider value for the horizontal scaling: */
	if (ix == ox)
		div = 1;
	else if (ix < ox)
		div = ox - 1;
	else
		div = ix;

	/* no scaling needed vertically: */
	if (iy == oy)
	{
		for (yi = 0; yi < oy; yi++)
		{
			get_scaled_row(im_in, x1, y1 + yi, ix, ox, temp);
			put_rgb_scan(im_out, x2, y2 + yi, ox, div, temp);
		}
	}

	/* scaling by subsampling (grow): */
	else if (iy < oy)
	{
		iy--;
		oy--;
		div *= oy;

		/* get first row: */
		get_scaled_row(im_in, x1, y1, ix, ox, next);

		/* si and sifrac give the subsampling position: */
		si = y1;
		sifrac = 0;

		/* get_next_row tells us, that we need the next row */
		get_next_row = TRUE;
		for (yi = 0; yi <= oy; yi++)
		{
			if (get_next_row)
			{
				for (xi = 0; xi < ox; xi++)
				{
					prev[xi] = next[xi];
				}
				if (yi < oy)
				{
					/* only get next row if in same icon */
					get_scaled_row(im_in, x1, si + 1, ix, ox, next);
				}
			}

			/* calculate subsampled color values: */
			/* division by oy occurs in put_rgb_scan */
			for (xi = 0; xi < ox; xi++)
			{
				temp[xi].red = (prev[xi].red * (oy - sifrac) +
				                next[xi].red * sifrac);
				temp[xi].green = (prev[xi].green * (oy - sifrac) +
				                  next[xi].green * sifrac);
				temp[xi].blue = (prev[xi].blue * (oy - sifrac) +
				                 next[xi].blue * sifrac);
			}

			/* write row to output image: */
			put_rgb_scan(im_out, x2, y2 + yi, ox, div, temp);

			/* advance sampling position: */
			sifrac += iy;
			if (sifrac >= oy)
			{
				si++;
				sifrac -= oy;
				get_next_row = TRUE;
			}
			else
			{
				get_next_row = FALSE;
			}

		}
	}

	/* scaling by averaging (shrink) */
	else
	{
		div *= iy;

		/* height of a output row in input rows: */
		add_whole = iy / oy;
		add_frac = iy % oy;

		/* start position of the first output row: */
		si = y1;
		sifrac = 0;

		/* get first input row: */
		get_scaled_row(im_in, x1, y1, ix, ox, next);
		for (yi = 0; yi < oy; yi++)
		{
			/* find endpoint of the current output row: */
			ci = si + add_whole;
			cifrac = sifrac + add_frac;
			if (cifrac >= oy)
			{
				ci++;
				cifrac -= oy;
			}

			/* take fraction of current input row (starting segment): */
			for (xi = 0; xi < ox; xi++)
			{
				temp[xi].red = next[xi].red * (oy - sifrac);
				temp[xi].green = next[xi].green * (oy - sifrac);
				temp[xi].blue = next[xi].blue * (oy - sifrac);
			}
			si++;

			/* add values for whole pixels: */
			while (si < ci)
			{
				get_scaled_row(im_in, x1, si, ix, ox, next);
				for (xi = 0; xi < ox; xi++)
				{
					temp[xi].red += next[xi].red * oy;
					temp[xi].green += next[xi].green * oy;
					temp[xi].blue += next[xi].blue * oy;
				}
				si++;
			}

			/* add fraction of current input row (ending segment): */
			if (yi < oy - 1)
			{
				/* only get next row if still in icon: */
				get_scaled_row(im_in, x1, si, ix, ox, next);
			}
			sifrac = cifrac;
			for (xi = 0; xi < ox; xi++)
			{
				temp[xi].red += next[xi].red * sifrac;
				temp[xi].green += next[xi].green * sifrac;
				temp[xi].blue += next[xi].blue * sifrac;
			}

			/* write row to output image: */
			put_rgb_scan(im_out, x2, y2 + yi, ox, div, temp);
		}
	}
}


/*
 * Rescale icons using sort of anti-aliasing technique.
 */
static GdkRGBImage *resize_tiles_smooth(
        GdkRGBImage *im,
        int ix,
        int iy,
        int ox,
        int oy)
{
	int width1, height1, width2, height2;
	int x1, x2, y1, y2;

	GdkRGBImage *tmp;

	/* Original size */
	width1 = im->width;
	height1 = im->height;

	/* Rescaled size */
	width2 = ox * width1 / ix;
	height2 = oy * height1 / iy;

	/* Allocate GdkRGBImage for resized tiles */
	tmp = gdk_rgb_image_new(width2, height2);

	/* Oops */
	if (tmp == NULL) return (NULL);

	/* Scale each icon */
	for (y1 = 0, y2 = 0; (y1 < height1) && (y2 < height2); y1 += iy, y2 += oy)
	{
		for (x1 = 0, x2 = 0; (x1 < width1) && (x2 < width2); x1 += ix, x2 += ox)
		{
			scale_icon(im, tmp, x1, y1, x2, y2,
			           ix, iy, ox, oy);
		}
	}

	return tmp;
}


/*
 * Steven Fuerst's tile resizing code
 * Taken from Z because I think the algorithm is cool.
 */

/* 24-bit version - GdkRGB uses 24 bit RGB data internally */
static void copy_pixels(
        int wid,
        int y,
        int offset,
        int *xoffsets,
        GdkRGBImage *old_image,
        GdkRGBImage *new_image)
{
	int i;

	/* Get source and destination */
	byte *src = &old_image->image[offset * old_image->width * 3];
	byte *dst = &new_image->image[y * new_image->width * 3];

	/* Copy to the image */
	for (i = 0; i < wid; i++)
	{
#ifdef SUPPORT_GAMMA

		if (gamma_table_ready)
		{
			*dst++ = gamma_table[src[3 * xoffsets[i]]];
			*dst++ = gamma_table[src[3 * xoffsets[i] + 1]];
			*dst++ = gamma_table[src[3 * xoffsets[i] + 2]];

			continue;
		}

#endif /* SUPPORT_GAMMA */

		*dst++ = src[3 * xoffsets[i]];
		*dst++ = src[3 * xoffsets[i] + 1];
		*dst++ = src[3 * xoffsets[i] + 2];
	}
}


#if 0

/* 32-bit version: it might be useful in the future */
static void copy_pixels(
        int wid,
        int y,
        int offset,
        int *xoffsets,
        GdkRGBImage *old_image,
        GdkRGBImage *new_image)
{
	int i;

	/* Get source and destination */
	byte *src = &old_image->image[offset * old_image->width * 4];
	byte *dst = &new_image->image[y * new_image->width * 4];

	/* Copy to the image */
	for (i = 0; i < wid; i++)
	{
		*dst++ = src[4 * xoffsets[i]];
		*dst++ = src[4 * xoffsets[i] + 1];
		*dst++ = src[4 * xoffsets[i] + 2];
		*dst++ = src[4 * xoffsets[i] + 3];
	}
}

#endif


/*
 * Resize ix * iy pixel tiles in old to ox * oy pixels
 * and return a new GdkRGBImage containing the resized tiles
 */
static GdkRGBImage *resize_tiles_fast(
        GdkRGBImage *old_image,
        int ix,
        int iy,
        int ox,
        int oy)
{
	GdkRGBImage *new_image;

	int old_wid, old_hgt;

	int new_wid, new_hgt;

	int add, remainder, rem_tot, offset;

	int *xoffsets;

	int i;


	/* Get the size of the old image */
	old_wid = old_image->width;
	old_hgt = old_image->height;

	/* Calculate the size of the new image */
	new_wid = (old_wid / ix) * ox;
	new_hgt = (old_hgt / iy) * oy;

	/* Allocate a GdkRGBImage to store resized tiles */
	new_image = gdk_rgb_image_new(new_wid, new_hgt);

	/* Paranoia */
	if (new_image == NULL) return (NULL);

	/* now begins the cool part of SF's code */

	/*
	 * Calculate an offsets table, so the transformation
	 * is faster.  This is much like the Bresenham algorithm
	 */

	/* Set up x offset table */
	C_MAKE(xoffsets, new_wid, int);

	/* Initialize line parameters */
	add = old_wid / new_wid;
	remainder = old_wid % new_wid;

	/* Start at left */
	offset = 0;

	/* Half-tile offset so 'line' is centered correctly */
	rem_tot = new_wid / 2;

	for (i = 0; i < new_wid; i++)
	{
		/* Store into the table */
		xoffsets[i] = offset;

		/* Move to next entry */
		offset += add;

		/* Take care of fractional part */
		rem_tot += remainder;
		if (rem_tot >= new_wid)
		{
			rem_tot -= new_wid;
			offset++;
		}
	}

	/* Scan each row */

	/* Initialize line parameters */
	add = old_hgt / new_hgt;
	remainder = old_hgt % new_hgt;

	/* Start at left */
	offset = 0;

	/* Half-tile offset so 'line' is centered correctly */
	rem_tot = new_hgt / 2;

	for (i = 0; i < new_hgt; i++)
	{
		/* Copy pixels to new image */
		copy_pixels(new_wid, i, offset, xoffsets, old_image, new_image);

		/* Move to next entry */
		offset += add;

		/* Take care of fractional part */
		rem_tot += remainder;
		if (rem_tot >= new_hgt)
		{
			rem_tot -= new_hgt;
			offset++;
		}
	}

	/* Free offset table */
	C_FREE(xoffsets, new_wid, int);

	return (new_image);
}


/*
 * Resize an image of ix * iy pixels and return a newly allocated
 * image of ox * oy pixels.
 */
static GdkRGBImage *resize_tiles(
        GdkRGBImage *im,
        int ix,
        int iy,
        int ox,
        int oy)
{
	GdkRGBImage *result;

	/*
	 * I hope we can always use this with GdkRGB, which uses a 5x5x5
	 * colour cube (125 colours) by default, and resort to dithering
	 * when it can't find good match there or expand the cube, so it
	 * works with 8bpp X servers.
	 */
	if (smooth_rescaling_request && (ix != ox || iy != oy))
	{
		result = resize_tiles_smooth(im, ix, iy, ox, oy);
	}

	/*
	 * Unless smoothing is requested by user, we use the fast
	 * resizing code.
	 */
	else
	{
		result = resize_tiles_fast(im, ix, iy, ox, oy);
	}

	/* Return rescaled tiles, or NULL */
	return (result);
}


/*
 * Tile loaders - XPM and BMP
 */

/*
 * A helper function for the XPM loader
 *
 * Read next string delimited by double quotes from
 * the input stream. Return TRUE on success, FALSE
 * if it finds EOF or buffer overflow.
 *
 * I never mean this to be generic, so its EOF and buffer
 * overflow behaviour is terribly stupid -- there are no
 * provisions for recovery.
 *
 * CAVEAT: treatment of backslash is not compatible with the standard
 * C usage XXX XXX XXX XXX
 */
static bool read_str(char *buf, u32b len, FILE *f)
{
	int c;

	/* Paranoia - Buffer too small */
	if (len <= 0) return (FALSE);

	/* Find " */
	while ((c = getc(f)) != '"')
	{
		/* Premature EOF */
		if (c == EOF) return (FALSE);
	}

	while (1)
	{
		/* Read next char */
		c = getc(f);

		/* Premature EOF */
		if (c == EOF) return (FALSE);

		/* Terminating " */
		if (c == '"') break;

		/* Escape */
		if (c == '\\')
		{
			/* Use next char */
			c = getc(f);

			/* Premature EOF */
			if (c == EOF) return (FALSE);
		}

		/* Store character in the buffer */
		*buf++ = c;

		/* Decrement count */
		len--;

		/* Buffer full - we have to place a NULL at the end */
		if (len <= 0) return (FALSE);
	}

	/* Make a C string if there's room left */
	if (len > 0) *buf = '\0';

	/* Success */
	return (TRUE);
}


/*
 * Remember pixel symbol to RGB colour mappings
 */

/*
 * I've forgot the formula, but I remember prime number yields
 * good results
 */
#define HASH_SIZE 19

typedef struct pal_type pal_type;

struct pal_type
{
	u32b str;
	u32b rgb;
	pal_type *next;
};


/*
 * A simple, slow and stupid XPM loader
 */
static GdkRGBImage *load_xpm(cptr filename)
{
	FILE *f;
	GdkRGBImage *img = NULL;
	int width, height, colours, chars;
	int i, j, k;
	bool ret;
	pal_type *pal = NULL;
	pal_type *head[HASH_SIZE];
	u32b buflen = 0;
	char *lin = NULL;
	char buf[1024];

	/* Build path to the XPM file */
	path_build(buf, 1024, ANGBAND_DIR_XTRA_GRAF, filename);

	/* Open it */
	f = my_fopen(buf, "r");

	/* Oops */
	if (f == NULL) return (NULL);

	/* Read header */
	ret = read_str(buf, 1024, f);

	/* Oops */
	if (!ret)
	{
		/* Notify error */
		plog("Cannot find XPM header");

		/* Failure */
		goto oops;
	}

	/* Parse header */
	if (4 != sscanf(buf, "%d %d %d %d", &width, &height, &colours, &chars))
	{
		/* Notify error */
		plog("Bad XPM header");

		/* Failure */
		goto oops;
	}

	/*
	 * Paranoia - the code can handle upto four letters per pixel,
	 * but such large number of colours certainly requires a smarter
	 * symbol-to-colour mapping algorithm...
	 */
	if ((width <= 0) || (height <= 0) || (colours <= 0) || (chars <= 0) ||
	                (chars > 2))
	{
		/* Notify error */
		plog("Invalid width/height/depth");

		/* Failure */
		goto oops;
	}

	/* Allocate palette */
	C_MAKE(pal, colours, pal_type);

	/* Initialise hash table */
	for (i = 0; i < HASH_SIZE; i++) head[i] = NULL;

	/* Parse palette */
	for (i = 0; i < colours; i++)
	{
		u32b tmp;
		int h_idx;

		/* Read next string */
		ret = read_str(buf, 1024, f);

		/* Check I/O result */
		if (!ret)
		{
			/* Notify error */
			plog("EOF in palette");

			/* Failure */
			goto oops;
		}

		/* Clear symbol code */
		tmp = 0;

		/* Encode pixel symbol */
		for (j = 0; j < chars; j++)
		{
			tmp = (tmp << 8) | (buf[j] & 0xFF);
		}

		/* Remember it */
		pal[i].str = tmp;

		/* Skip spaces */
		while ((buf[j] == ' ') || (buf[j] == '\t')) j++;

		/* Verify 'c' */
		if (buf[j] != 'c')
		{
			/* Notify error */
			plog("No 'c' in palette definition");

			/* Failure */
			goto oops;
		}

		/* Advance cursor */
		j++;

		/* Skip spaces */
		while ((buf[j] == ' ') || (buf[j] == '\t')) j++;

		/* Hack - Assume 'None' */
		if (buf[j] == 'N')
		{
			/* Angband always uses black background */
			pal[i].rgb = 0x000000;
		}

		/* Read colour */
		else if ((1 != sscanf(&buf[j], "#%06lX", &tmp)) &&
		                (1 != sscanf(&buf[j], "#%06lx", &tmp)))
		{
			/* Notify error */
			plog("Badly formatted colour");

			/* Failure */
			goto oops;
		}

		/* Remember it */
		pal[i].rgb = tmp;

		/* Store it in hash table as well */
		h_idx = pal[i].str % HASH_SIZE;

		/* Link the entry */
		pal[i].next = head[h_idx];
		head[h_idx] = &pal[i];
	}

	/* Allocate image */
	img = gdk_rgb_image_new(width, height);

	/* Oops */
	if (img == NULL)
	{
		/* Notify error */
		plog("Cannot allocate image");

		/* Failure */
		goto oops;
	}

	/* Calculate buffer length */
	buflen = width * chars + 1;

	/* Allocate line buffer */
	C_MAKE(lin, buflen, char);

	/* For each row */
	for (i = 0; i < height; i++)
	{
		/* Read a row of image data */
		ret = read_str(lin, buflen, f);

		/* Oops */
		if (!ret)
		{
			/* Notify error */
			plog("EOF in middle of image data");

			/* Failure */
			goto oops;
		}

		/* For each column */
		for (j = 0; j < width; j++)
		{
			u32b tmp;
			pal_type *h_ptr;

			/* Clear encoded pixel */
			tmp = 0;

			/* Encode pixel symbol */
			for (k = 0; k < chars; k++)
			{
				tmp = (tmp << 8) | (lin[j * chars + k] & 0xFF);
			}

			/* Find colour */
			for (h_ptr = head[tmp % HASH_SIZE];
			                h_ptr != NULL;
			                h_ptr = h_ptr->next)
			{
				/* Found a match */
				if (h_ptr->str == tmp) break;
			}

			/* No match found */
			if (h_ptr == NULL)
			{
				/* Notify error */
				plog("Invalid pixel symbol");

				/* Failure */
				goto oops;
			}

			/* Draw it */
			gdk_rgb_image_put_pixel(
			        img,
			        j,
			        i,
			        h_ptr->rgb);
		}
	}

	/* Close file */
	my_fclose(f);

	/* Free line buffer */
	C_FREE(lin, buflen, char);

	/* Free palette */
	C_FREE(pal, colours, pal_type);

	/* Return result */
	return (img);

oops:

	/* Close file */
	my_fclose(f);

	/* Free image */
	if (img) gdk_rgb_image_destroy(img);

	/* Free line buffer */
	if (lin) C_FREE(lin, buflen, char);

	/* Free palette */
	if (pal) C_FREE(pal, colours, pal_type);

	/* Failure */
	return (NULL);
}


/*
 * A BMP loader, yet another duplication of maid-x11.c functions.
 *
 * Another duplication, again because of different image format and
 * avoidance of colour allocation.
 *
 * XXX XXX XXX XXX Should avoid using a propriatary and closed format.
 * Since it's much bigger than gif that was used before, why don't
 * we switch to XPM?  NetHack does.  Well, NH has always been much
 * closer to the GNU/Un*x camp and it's GPL'ed quite early...
 *
 * The names and naming convention are worse than the worst I've ever
 * seen, so I deliberately changed them to fit well with the rest of
 * the code. Or are they what xx calls them? If it's the case, there's
 * no reason to follow *their* words.
 */

/*
 * BMP file header
 */
typedef struct bmp_file_type bmp_file_type;

struct bmp_file_type
{
	u16b type;
	u32b size;
	u16b reserved1;
	u16b reserved2;
	u32b offset;
};


/*
 * BMP file information fields
 */
typedef struct bmp_info_type bmp_info_type;

struct bmp_info_type
{
	u32b size;
	u32b width;
	u32b height;
	u16b planes;
	u16b bit_count;
	u32b compression;
	u32b size_image;
	u32b x_pels_per_meter;
	u32b y_pels_per_meter;
	u32b colors_used;
	u32b color_importand;
};

/*
 * "RGBQUAD" type.
 */
typedef struct rgb_quad_type rgb_quad_type;

struct rgb_quad_type
{
	unsigned char b, g, r;
	unsigned char filler;
};


/*** Helper functions for system independent file loading. ***/

static byte get_byte(FILE *fff)
{
	/* Get a character, and return it */
	return (getc(fff) & 0xFF);
}

static void rd_byte(FILE *fff, byte *ip)
{
	*ip = get_byte(fff);
}

static void rd_u16b(FILE *fff, u16b *ip)
{
	(*ip) = get_byte(fff);
	(*ip) |= ((u16b)(get_byte(fff)) << 8);
}

static void rd_u32b(FILE *fff, u32b *ip)
{
	(*ip) = get_byte(fff);
	(*ip) |= ((u32b)(get_byte(fff)) << 8);
	(*ip) |= ((u32b)(get_byte(fff)) << 16);
	(*ip) |= ((u32b)(get_byte(fff)) << 24);
}


/*
 * Read a BMP file (a certain trademark nuked)
 *
 * This function replaces the old ReadRaw and RemapColors functions.
 *
 * Assumes that the bitmap has a size such that no padding is needed in
 * various places.  Currently only handles bitmaps with 3 to 256 colors.
 */
GdkRGBImage *load_bmp(cptr filename)
{
	FILE *f;

	char path[1024];

	bmp_file_type file_hdr;
	bmp_info_type info_hdr;

	GdkRGBImage *result = NULL;

	int ncol;

	int i;

	u32b x, y;

	guint32 colour_pixels[256];


	/* Build the path to the bmp file */
	path_build(path, 1024, ANGBAND_DIR_XTRA_GRAF, filename);

	/* Open the BMP file */
	f = fopen(path, "r");

	/* No such file */
	if (f == NULL)
	{
		return (NULL);
	}

	/* Read the "bmp_file_type" */
	rd_u16b(f, &file_hdr.type);
	rd_u32b(f, &file_hdr.size);
	rd_u16b(f, &file_hdr.reserved1);
	rd_u16b(f, &file_hdr.reserved2);
	rd_u32b(f, &file_hdr.offset);

	/* Read the "bmp_info_type" */
	rd_u32b(f, &info_hdr.size);
	rd_u32b(f, &info_hdr.width);
	rd_u32b(f, &info_hdr.height);
	rd_u16b(f, &info_hdr.planes);
	rd_u16b(f, &info_hdr.bit_count);
	rd_u32b(f, &info_hdr.compression);
	rd_u32b(f, &info_hdr.size_image);
	rd_u32b(f, &info_hdr.x_pels_per_meter);
	rd_u32b(f, &info_hdr.y_pels_per_meter);
	rd_u32b(f, &info_hdr.colors_used);
	rd_u32b(f, &info_hdr.color_importand);

	/* Verify the header */
	if (feof(f) ||
	                (file_hdr.type != 19778) ||
	                (info_hdr.size != 40))
	{
		plog(format("Incorrect BMP file format %s", filename));
		fclose(f);
		return (NULL);
	}

	/*
	 * The two headers above occupy 54 bytes total
	 * The "offset" field says where the data starts
	 * The "colors_used" field does not seem to be reliable
	 */

	/* Compute number of colors recorded */
	ncol = (file_hdr.offset - 54) / 4;

	for (i = 0; i < ncol; i++)
	{
		rgb_quad_type clr;

		/* Read an "rgb_quad_type" */
		rd_byte(f, &clr.b);
		rd_byte(f, &clr.g);
		rd_byte(f, &clr.r);
		rd_byte(f, &clr.filler);

		/* Remember the pixel */
		colour_pixels[i] = (clr.r << 16) | (clr.g << 8) | (clr.b);
	}

	/* Allocate GdkRGBImage large enough to store the image */
	result = gdk_rgb_image_new(info_hdr.width, info_hdr.height);

	/* Failure */
	if (result == NULL)
	{
		fclose(f);
		return (NULL);
	}

	for (y = 0; y < info_hdr.height; y++)
	{
		u32b y2 = info_hdr.height - y - 1;

		for (x = 0; x < info_hdr.width; x++)
		{
			int ch = getc(f);

			/* Verify not at end of file XXX XXX */
			if (feof(f))
			{
				plog(format("Unexpected end of file in %s", filename));
				gdk_rgb_image_destroy(result);
				fclose(f);
				return (NULL);
			}

			if (info_hdr.bit_count == 24)
			{
				int c3, c2 = getc(f);

				/* Verify not at end of file XXX XXX */
				if (feof(f))
				{
					plog(format("Unexpected end of file in %s", filename));
					gdk_rgb_image_destroy(result);
					fclose(f);
					return (NULL);
				}

				c3 = getc(f);

				/* Verify not at end of file XXX XXX */
				if (feof(f))
				{
					plog(format("Unexpected end of file in %s", filename));
					gdk_rgb_image_destroy(result);
					fclose(f);
					return (NULL);
				}

				/* Draw the pixel */
				gdk_rgb_image_put_pixel(
				        result,
				        x,
				        y2,
				        (ch << 16) | (c2 << 8) | (c3));
			}
			else if (info_hdr.bit_count == 8)
			{
				gdk_rgb_image_put_pixel(result, x, y2, colour_pixels[ch]);
			}
			else if (info_hdr.bit_count == 4)
			{
				gdk_rgb_image_put_pixel(result, x, y2, colour_pixels[ch / 16]);
				x++;
				gdk_rgb_image_put_pixel(result, x, y2, colour_pixels[ch % 16]);
			}
			else
			{
				/* Technically 1 bit is legal too */
				plog(format("Illegal bit count %d in %s",
				            info_hdr.bit_count, filename));
				gdk_rgb_image_destroy(result);
				fclose(f);
				return (NULL);
			}
		}
	}

	fclose(f);

	return result;
}


/*
 * Try to load an XPM file, or a BMP file if it fails
 *
 * Choice of file format may better be made yet another option XXX
 */
static GdkRGBImage *load_tiles(cptr basename)
{
	char buf[32];
	GdkRGBImage *img;

	/* build xpm file name */
	strnfmt(buf, 32, "%s.xpm", basename);

	/* Try to load it */
	img = load_xpm(buf);

	/* OK */
	if (img) return (img);

	/* Try again for a bmp file */
	strnfmt(buf, 32, "%s.bmp", basename);

	/* Try loading it */
	img = load_bmp(buf);

	/* Return result, success or failure */
	return (img);
}


/*
 * Free all tiles and graphics buffers associated with windows
 *
 * This is conspirator of graf_init() below, sharing its inefficiency
 */
static void graf_nuke()
{
	int i;

	term_data *td;


	/* Nuke all terms */
	for (i = 0; i < MAX_TERM_DATA; i++)
	{
		/* Access term_data structure */
		td = &data[i];

		/* Disable graphics */
		td->t.higher_pict = FALSE;

		/* Free previously allocated tiles */
		if (td->tiles) gdk_rgb_image_destroy(td->tiles);

		/* Forget pointer */
		td->tiles = NULL;

# ifdef USE_TRANSPARENCY

		/* Free previously allocated transparency buffer */
		if (td->trans_buf) gdk_rgb_image_destroy(td->trans_buf);

		/* Forget stale pointer */
		td->trans_buf = NULL;

# endif  /* USE_TRANSPARENCY */

	}
}


/*
 * Load tiles, scale them to current font size, and store a pointer
 * to them in a term_data structure for each term.
 *
 * XXX XXX XXX This is a terribly stupid quick hack.
 *
 * XXX XXX XXX Windows using the same font should share resized tiles
 */
static bool graf_init(
        cptr filename,
        int tile_wid,
        int tile_hgt)
{
	term_data *td;

	bool result;

	GdkRGBImage *raw_tiles, *scaled_tiles;

# ifdef USE_TRANSPARENCY
	GdkRGBImage *buffer;
# endif  /* USE_TRANSPARENCY */

	int i;


	/* Paranoia */
	if (filename == NULL) return (FALSE);

	/* Load tiles */
	raw_tiles = load_tiles(filename);

	/* Oops */
	if (raw_tiles == NULL)
	{
		/* Clean up */
		graf_nuke();

		/* Failure */
		return (FALSE);
	}

	/* Calculate and remember numbers of rows and columns */
	tile_rows = raw_tiles->height / tile_hgt;
	tile_cols = raw_tiles->width / tile_wid;

	/* Be optimistic */
	result = TRUE;


	/*
	 * (Re-)init each term
	 * XXX It might help speeding this up to avoid doing so if a window
	 * doesn't need graphics (e.g. inventory/equipment and message recall).
	 */
	for (i = 0; i < MAX_TERM_DATA; i++)
	{
		/* Access term_data */
		td = &data[i];

		/* Shouldn't waste anything for unused terms */
		if (!td->shown) continue;

		/* Enable graphics */
		td->t.higher_pict = TRUE;

		/* See if we need rescaled tiles XXX */
		if ((td->tiles == NULL) ||
		                (td->tiles->width != td->tile_wid * tile_cols) ||
		                (td->tiles->height != td->tile_hgt * tile_rows))
		{
			/* Free old tiles if present */
			if (td->tiles) gdk_rgb_image_destroy(td->tiles);

			/* Forget pointer */
			td->tiles = NULL;

			/* Scale the tiles to current font bounding rect */
			scaled_tiles = resize_tiles(
			                       raw_tiles,
			                       tile_wid, tile_hgt,
			                       td->tile_wid, td->tile_hgt);

			/* Oops */
			if (scaled_tiles == NULL)
			{
				/* Failure */
				result = FALSE;

				break;
			}

			/* Store it */
			td->tiles = scaled_tiles;
		}

# ifdef USE_TRANSPARENCY

		/* See if we have to (re)allocate a new buffer XXX */
		if ((td->trans_buf == NULL) ||
		                (td->trans_buf->width != td->tile_wid) ||
		                (td->trans_buf->height != td->tile_hgt))
		{
			/* Free old buffer if present */
			if (td->trans_buf) gdk_rgb_image_destroy(td->trans_buf);

			/* Forget pointer */
			td->trans_buf = NULL;

			/* Allocate a new buffer */
			buffer = gdk_rgb_image_new(td->tile_wid, td->tile_hgt);

			/* Oops */
			if (buffer == NULL)
			{
				/* Failure */
				result = FALSE;

				break;
			}

			/* Store it */
			td->trans_buf = buffer;
		}

		/*
		 * Giga-Hack - assume top left corner of 0x86/0x80 should be
		 * in the background colour XXX XXX XXX XXX
		 */
		td->bg_pixel = gdk_rgb_image_get_pixel(
		                       raw_tiles,
		                       0,
		                       tile_hgt * 6);

# endif  /* USE_TRANSPARENCY */

	}


	/* Alas, we need to free wasted images */
	if (result == FALSE) graf_nuke();

	/* We don't need the raw image any longer */
	gdk_rgb_image_destroy(raw_tiles);

	/* Report success or failure */
	return (result);
}


/*
 * React to various changes in graphics mode settings
 *
 * It is *not* a requirement for tiles to have same pixel width and height.
 * The program can work with any conbinations of graf_wid and graf_hgt
 * (oops, they must be representable by u16b), as long as they are lesser
 * or equal to 32 if you use smooth rescaling.
 */
static void init_graphics(void)
{
	cptr tile_name;

	u16b graf_wid = 0, graf_hgt = 0;


	/* No graphics requests are made - Can't this be simpler? XXX XXX */
	if ((graf_mode_request == graf_mode) &&
	                (smooth_rescaling_request == smooth_rescaling) &&
	                !resize_request) return;

	/* Prevent further unsolicited reaction */
	resize_request = FALSE;


	/* Dispose unusable old tiles - awkward... XXX XXX */
	if ((graf_mode_request == GRAF_MODE_NONE) ||
	                (graf_mode_request != graf_mode) ||
	                (smooth_rescaling_request != smooth_rescaling)) graf_nuke();


	/* Setup parameters according to request */
	switch (graf_mode_request)
	{
		/* ASCII - no graphics whatsoever */
	default:
	case GRAF_MODE_NONE:
		{
			tile_name = NULL;
			use_graphics = arg_graphics = FALSE;

			break;
		}

		/*
		 * 8x8 tiles originally collected for the Amiga port
		 * from several contributers by Lars Haugseth, converted
		 * to 256 colours and expanded by the Z devteam
		 *
		 * Use the "old" tile assignments
		 *
		 * Dawnmist is working on it for ToME
		 */
	case GRAF_MODE_OLD:
		{
			tile_name = "8x8";
			graf_wid = graf_hgt = 8;
			ANGBAND_GRAF = "old";
			use_graphics = arg_graphics = TRUE;

			break;
		}

		/*
		 * Adam Bolt's 16x16 tiles
		 * "new" tile assignments
		 * It is updated for ToME by Andreas Koch
		 */
	case GRAF_MODE_NEW:
		{
			tile_name = "16x16";
			graf_wid = graf_hgt = 16;
			ANGBAND_GRAF = "new";
			use_graphics = arg_graphics = TRUE;

			break;
		}
	}


	/* load tiles and set them up if tiles are requested */
	if ((graf_mode_request != GRAF_MODE_NONE) &&
	                !graf_init(tile_name, graf_wid, graf_hgt))
	{
		/* Oops */
		plog("Cannot initialize graphics");

		/* reject requests */
		graf_mode_request = GRAF_MODE_NONE;
		smooth_rescaling_request = smooth_rescaling;

		/* reset graphics flags */
		use_graphics = arg_graphics = FALSE;
	}

	/* Update current graphics mode */
	graf_mode = graf_mode_request;
	smooth_rescaling = smooth_rescaling_request;

	/* Reset visuals */
#ifndef ANG281_RESET_VISUALS
	reset_visuals(TRUE);
#else
	reset_visuals();
#endif /* !ANG281_RESET_VISUALS */
}

#endif /* USE_GRAPHICS */




/**** Term package support routines ****/


/*
 * Free data used by a term
 */
static void Term_nuke_gtk(term *t)
{
	term_data *td = t->data;


	/* Free name */
	if (td->name) string_free(td->name);

	/* Forget it */
	td->name = NULL;

	/* Free font */
	if (td->font) gdk_font_unref(td->font);

	/* Forget it */
	td->font = NULL;

	/* Free backing store */
	if (td->backing_store) gdk_pixmap_unref(td->backing_store);

	/* Forget it too */
	td->backing_store = NULL;

#ifdef USE_GRAPHICS

	/* Free tiles */
	if (td->tiles) gdk_rgb_image_destroy(td->tiles);

	/* Forget pointer */
	td->tiles = NULL;

# ifdef USE_TRANSPARENCY

	/* Free transparency buffer */
	if (td->trans_buf) gdk_rgb_image_destroy(td->trans_buf);

	/* Amnesia */
	td->trans_buf = NULL;

# endif  /* USE_TRANSPARENCY */

#endif /* USE_GRAPHICS */
}


/*
 * Erase the whole term.
 */
static errr Term_clear_gtk(void)
{
	term_data *td = (term_data*)(Term->data);


	/* Don't draw to hidden windows */
	if (!td->shown) return (0);

	/* Paranoia */
	g_assert(td->drawing_area->window != 0);

	/* Clear the area */
	gdk_draw_rectangle(
	        TERM_DATA_DRAWABLE(td),
	        td->drawing_area->style->black_gc,
	        1,
	        0,
	        0,
	        td->cols * td->font_wid,
	        td->rows * td->font_hgt);

	/* Copy image from backing store if present */
	TERM_DATA_REFRESH(td, 0, 0, td->cols, td->rows);

	/* Success */
	return (0);
}


/*
 * Erase some characters.
 */
static errr Term_wipe_gtk(int x, int y, int n)
{
	term_data *td = (term_data*)(Term->data);


	/* Don't draw to hidden windows */
	if (!td->shown) return (0);

	/* Paranoia */
	g_assert(td->drawing_area->window != 0);

	/* Fill the area with the background colour */
	gdk_draw_rectangle(
	        TERM_DATA_DRAWABLE(td),
	        td->drawing_area->style->black_gc,
	        TRUE,
	        x * td->font_wid,
	        y * td->font_hgt,
	        n * td->font_wid,
	        td->font_hgt);

	/* Copy image from backing store if present */
	TERM_DATA_REFRESH(td, x, y, n, 1);

	/* Success */
	return (0);
}


/*
 * Draw some textual characters.
 */
static errr Term_text_gtk(int x, int y, int n, byte a, cptr s)
{
	term_data *td = (term_data*)(Term->data);


	/* Don't draw to hidden windows */
	if (!td->shown) return (0);

	/* Paranoia */
	g_assert(td->drawing_area->window != 0);

	/* Set foreground colour */
	term_data_set_fg(td, a);

	/* Clear the line */
	Term_wipe_gtk(x, y, n);

	/* Draw the text to the window */
	gdk_draw_text(
	        TERM_DATA_DRAWABLE(td),
	        td->font,
	        td->gc,
	        x * td->font_wid,
	        td->font->ascent + y * td->font_hgt,
	        s,
	        n);

	/* Copy image from backing store if present */
	TERM_DATA_REFRESH(td, x, y, n, 1);

	/* Success */
	return (0);
}


/*
 * Draw software cursor at (x, y)
 */
static errr Term_curs_gtk(int x, int y)
{
	term_data *td = (term_data*)(Term->data);
	int cells = 1;


	/* Don't draw to hidden windows */
	if (!td->shown) return (0);

	/* Paranoia */
	g_assert(td->drawing_area->window != 0);

	/* Set foreground colour */
	term_data_set_fg(td, TERM_YELLOW);

#ifdef USE_DOUBLE_TILES

	/* Mogami's bigtile patch */

	/* Adjust it if wide tiles are requested */
	if (use_bigtile &&
	                (x + 1 < Term->wid) &&
	                (Term->old->a[y][x + 1] == 255))
	{
		cells = 2;
	}

#endif /* USE_DOUBLE_TILES */

	/* Draw the software cursor */
	gdk_draw_rectangle(
	        TERM_DATA_DRAWABLE(td),
	        td->gc,
	        FALSE,
	        x * td->font_wid,
	        y * td->font_hgt,
	        td->font_wid * cells - 1,
	        td->font_hgt - 1);

	/* Copy image from backing store if present */
	TERM_DATA_REFRESH(td, x, y, cells, 1);

	/* Success */
	return (0);
}


#ifdef USE_GRAPHICS

# ifdef USE_TRANSPARENCY

/*
 * XXX XXX Low level graphics helper
 * Draw a tile at (s_x, s_y) over one at (t_x, t_y) and store the
 * result in td->trans_buf
 *
 * XXX XXX Even if CPU's are faster than necessary these days,
 * this should be made inline. Or better, there should be an API
 * to take advantage of graphics hardware. They almost always have
 * assortment of builtin bitblt's...
 */
static void overlay_tiles_2(
        term_data *td,
        int s_x, int s_y,
        int t_x, int t_y)
{
	guint32 pix;
	int x, y;


	/* Process each row */
	for (y = 0; y < td->tile_hgt; y++)
	{
		/* Process each column */
		for (x = 0; x < td->tile_wid; x++)
		{
			/* Get an overlay pixel */
			pix = gdk_rgb_image_get_pixel(td->tiles, s_x + x, s_y + y);

			/* If it's in background color, use terrain instead */
			if (pix == td->bg_pixel)
				pix = gdk_rgb_image_get_pixel(td->tiles, t_x + x, t_y + y);

			/* Store the result in trans_buf */
			gdk_rgb_image_put_pixel(td->trans_buf, x, y, pix);
		}
	}
}


# ifdef USE_EGO_GRAPHICS

/*
 * XXX XXX Low level graphics helper
 * Draw a tile at (e_x, e_y) over one at (s_x, s_y) over another one
 * at (t_x, t_y) and store the result in td->trans_buf
 *
 * XXX XXX The same comment applies as that for the above...
 */
static void overlay_tiles_3(
        term_data *td,
        int e_x, int e_y,
        int s_x, int s_y,
        int t_x, int t_y)
{
	guint32 pix;
	int x, y;


	/* Process each row */
	for (y = 0; y < td->tile_hgt; y++)
	{
		/* Process each column */
		for (x = 0; x < td->tile_wid; x++)
		{
			/* Get an overlay pixel */
			pix = gdk_rgb_image_get_pixel(td->tiles, e_x + x, e_y + y);

			/*
			 * If it's background colour, try to use one from
			 * the second layer
			 */
			if (pix == td->bg_pixel)
				pix = gdk_rgb_image_get_pixel(td->tiles, s_x + x, s_y + y);

			/*
			 * If it's background colour again, fall back to
			 * the terrain layer
			 */
			if (pix == td->bg_pixel)
				pix = gdk_rgb_image_get_pixel(td->tiles, t_x + x, t_y + y);

			/* Store the pixel in trans_buf */
			gdk_rgb_image_put_pixel(td->trans_buf, x, y, pix);
		}
	}
}

# endif  /* USE_EGO_GRAPHICS */

# endif  /* USE_TRANSPARENCY */


/*
 * Low level graphics (Assumes valid input)
 *
 * Draw "n" tiles/characters starting at (x,y)
 */
# ifdef USE_TRANSPARENCY
# ifdef USE_EGO_GRAPHICS
static errr Term_pict_gtk(
        int x, int y, int n,
        const byte *ap, const char *cp,
        const byte *tap, const char *tcp,
        const byte *eap, const char *ecp)
# else /* USE_EGO_GRAPHICS */
static errr Term_pict_gtk(
        int x, int y, int n,
        const byte *ap, const char *cp,
        const byte *tap, const char *tcp)
# endif  /* USE_EGO_GRAPHICS */
# else /* USE_TRANSPARENCY */
static errr Term_pict_gtk(
        int x, int y, int n,
        const byte *ap, const char *cp)
# endif  /* USE_TRANSPARENCY */
{
	term_data *td = (term_data*)(Term->data);

	int i;

	int d_x, d_y;

# ifdef USE_DOUBLE_TILES

	/* Hack - remember real number of columns affected XXX XXX XXX */
	int cols;

# endif  /* USE_DOUBLE_TILES */


	/* Don't draw to hidden windows */
	if (!td->shown) return (0);

	/* Paranoia */
	g_assert(td->drawing_area->window != 0);

	/* Top left corner of the destination rect */
	d_x = x * td->font_wid;
	d_y = y * td->font_hgt;


# ifdef USE_DOUBLE_TILES

	/* Reset column counter */
	cols = 0;

# endif  /* USE_DOUBLE_TILES */

	/* Scan the input */
	for (i = 0; i < n; i++)
	{
		byte a;
		char c;
		int s_x, s_y;

# ifdef USE_TRANSPARENCY

		byte ta;
		char tc;
		int t_x, t_y;

# ifdef USE_EGO_GRAPHICS

		byte ea;
		char ec;
		int e_x = 0, e_y = 0;
		bool has_overlay;

# endif  /* USE_EGO_GRAPHICS */

# endif  /* USE_TRANSPARENCY */


		/* Grid attr/char */
		a = *ap++;
		c = *cp++;

# ifdef USE_TRANSPARENCY

		/* Terrain attr/char */
		ta = *tap++;
		tc = *tcp++;

# ifdef USE_EGO_GRAPHICS

		/* Overlay attr/char */
		ea = *eap++;
		ec = *ecp++;
		has_overlay = (ea && ec);

# endif  /* USE_EGO_GRAPHICS */

# endif  /* USE_TRANSPARENCY */

		/* Row and Col */
		s_y = (((byte)a & 0x7F) % tile_rows) * td->tile_hgt;
		s_x = (((byte)c & 0x7F) % tile_cols) * td->tile_wid;

# ifdef USE_TRANSPARENCY

		/* Terrain Row and Col */
		t_y = (((byte)ta & 0x7F) % tile_rows) * td->tile_hgt;
		t_x = (((byte)tc & 0x7F) % tile_cols) * td->tile_wid;

# ifdef USE_EGO_GRAPHICS

		/* Overlay Row and Col */
		if (has_overlay)
		{
			e_y = (((byte)ea & 0x7F) % tile_rows) * td->tile_hgt;
			e_x = (((byte)ec & 0x7F) % tile_cols) * td->tile_wid;
		}

# endif  /* USE_EGO_GRAPHICS */


# ifdef USE_DOUBLE_TILES

		/* Mogami's bigtile patch */

		/* Hack -- a filler for wide tile */
		if (use_bigtile && (a == 255))
		{
			/* Advance */
			d_x += td->font_wid;

			/* Ignore */
			continue;
		}

# endif  /* USE_DOUBLE_TILES */

		/* Optimise the common case: terrain == obj/mons */
		if (!use_transparency ||
		                ((s_x == t_x) && (s_y == t_y)))
		{

# ifdef USE_EGO_GRAPHICS

			/* The simplest possible case - no overlay */
			if (!has_overlay)
			{
				/* Draw the tile */
				gdk_draw_rgb_image_2(
				        TERM_DATA_DRAWABLE(td), td->gc, td->tiles,
				        s_x, s_y,
				        d_x, d_y,
				        td->tile_wid, td->tile_hgt);
			}

			/* We have to draw overlay... */
			else
			{
				/* Overlay */
				overlay_tiles_2(td, e_x, e_y, s_x, s_y);

				/* And draw the result */
				gdk_draw_rgb_image_2(
				        TERM_DATA_DRAWABLE(td), td->gc, td->trans_buf,
				        0, 0,
				        d_x, d_y,
				        td->tile_wid, td->tile_hgt);

				/* Hack -- Prevent potential display problem */
				gdk_flush();
			}

# else /* USE_EGO_GRAPHICS */

			/* Draw the tile */
			gdk_draw_rgb_image_2(
			        TERM_DATA_DRAWABLE(td), td->gc, td->tiles,
			        s_x, s_y,
			        d_x, d_y,
			        td->tile_wid, td->tile_hgt);

# endif  /* USE_EGO_GRAPHICS */

		}

		/*
		 * Since there's no masking bitblt in X,
		 * we have to do that manually...
		 */
		else
		{

# ifndef USE_EGO_GRAPHICS

			/* Draw mon/PC/obj over terrain */
			overlay_tiles_2(td, s_x, s_y, t_x, t_y);

# else /* !USE_EGO_GRAPHICS */

			/* No overlay */
			if (!has_overlay)
			{
				/* Build terrain + masked overlay image */
				overlay_tiles_2(td, s_x, s_y, t_x, t_y);
			}

			/* With overlay */
			else
			{
				/* Ego over mon/PC over terrain */
				overlay_tiles_3(td, e_x, e_y, s_x, s_y,
				                t_x, t_y);
			}

# endif  /* !USE_EGO_GRAPHICS */

			/* Draw it */
			gdk_draw_rgb_image_2(
			        TERM_DATA_DRAWABLE(td), td->gc, td->trans_buf,
			        0, 0,
			        d_x, d_y,
			        td->tile_wid, td->tile_hgt);

			/* Hack -- Prevent potential display problem */
			gdk_flush();
		}

# else /* USE_TRANSPARENCY */

		/* Draw the tile */
		gdk_draw_rgb_image_2(
		        TERM_DATA_DRAWABLE(td), td->gc, td->tiles,
		        s_x, s_y,
		        d_x, d_y,
		        td->tile_wid, td->tile_hgt);

# endif  /* USE_TRANSPARENCY */

		/*
		 * Advance x-coordinate - wide font fillers are taken care of
		 * before entering the tile drawing code.
		 */
		d_x += td->font_wid;

# ifdef USE_DOUBLE_TILES

		/* Add up *real* number of columns updated XXX XXX XXX */
		cols += use_bigtile ? 2 : 1;

# endif  /* USE_DOUBLE_TILES */
	}

# ifndef USE_DOUBLE_TILES

	/* Copy image from backing store if present */
	TERM_DATA_REFRESH(td, x, y, n, 1);

# else

	/* Copy image from backing store if present */
	TERM_DATA_REFRESH(td, x, y, cols, 1);

# endif  /* USE_DOUBLE_TILES */

	/* Success */
	return (0);
}

#endif /* USE_GRAPHICS */


/*
 * Process an event, if there's none block when wait is set true,
 * return immediately otherwise.
 */
static void CheckEvent(bool wait)
{
	/* Process an event */
	(void)gtk_main_iteration_do(wait);
}


/*
 * Process all pending events (without blocking)
 */
static void DrainEvents(void)
{
	while (gtk_events_pending())
		gtk_main_iteration();
}


/*
 * Handle a "special request"
 */
static errr Term_xtra_gtk(int n, int v)
{
	/* Handle a subset of the legal requests */
	switch (n)
	{
		/* Make a noise */
	case TERM_XTRA_NOISE:
		{
			/* Beep */
			gdk_beep();

			/* Success */
			return (0);
		}

		/* Flush the output */
	case TERM_XTRA_FRESH:
		{
			/* Flush pending X requests - almost always no-op */
			gdk_flush();

			/* Success */
			return (0);
		}

		/* Process random events */
	case TERM_XTRA_BORED:
		{
			/* Process a pending event if there's one */
			CheckEvent(FALSE);

			/* Success */
			return (0);
		}

		/* Process Events */
	case TERM_XTRA_EVENT:
		{
			/* Process an event */
			CheckEvent(v);

			/* Success */
			return (0);
		}

		/* Flush the events */
	case TERM_XTRA_FLUSH:
		{
			/* Process all pending events */
			DrainEvents();

			/* Success */
			return (0);
		}

		/* Handle change in the "level" */
	case TERM_XTRA_LEVEL:
		return (0);

		/* Clear the screen */
	case TERM_XTRA_CLEAR:
		return (Term_clear_gtk());

		/* Delay for some milliseconds */
	case TERM_XTRA_DELAY:
		{
			/* sleep for v milliseconds */
			usleep(v * 1000);

			/* Done */
			return (0);
		}

		/* Get Delay of some milliseconds */
	case TERM_XTRA_GET_DELAY:
		{
			int ret;
			struct timeval tv;

			ret = gettimeofday(&tv, NULL);
			Term_xtra_long = (tv.tv_sec * 1000) + (tv.tv_usec / 1000);

			return ret;
		}

		/* Subdirectory scan */
	case TERM_XTRA_SCANSUBDIR:
		{
			DIR *directory;
			struct dirent *entry;

			scansubdir_max = 0;

			directory = opendir(scansubdir_dir);
			if (!directory) return (1);

			while ((entry = readdir(directory)) != NULL)
			{
				char file[PATH_MAX + NAME_MAX + 2];
				struct stat filedata;

				file[PATH_MAX + NAME_MAX] = 0;
				strncpy(file, scansubdir_dir, PATH_MAX);
				strncat(file, "/", 2);
				strncat(file, entry->d_name, NAME_MAX);
				if ((stat(file, &filedata) == 0) && S_ISDIR(filedata.st_mode))
				{
					string_free(scansubdir_result[scansubdir_max]);
					scansubdir_result[scansubdir_max] =
					        string_make(entry->d_name);
					++scansubdir_max;
				}
			}
		}

		/* Rename main window */
	case TERM_XTRA_RENAME_MAIN_WIN: gtk_window_set_title(GTK_WINDOW(data[0].window), angband_term_name[0]); return (0);

		/* React to changes */
	case TERM_XTRA_REACT:
		{
			/* (re-)init colours */
			init_colours();

#ifdef USE_GRAPHICS

			/* Initialise graphics */
			init_graphics();

#endif /* USE_GRAPHICS */

			/* Success */
			return (0);
		}
	}

	/* Unknown */
	return (1);
}




/**** Event handlers ****/


/*
 * Operation overkill
 * Verify term size info - just because the other windowing ports have this
 */
static void term_data_check_size(term_data *td)
{
	/* Enforce minimum window size */
	if (td == &data[0])
	{
		if (td->cols < 80) td->cols = 80;
		if (td->rows < 24) td->rows = 24;
	}
	else
	{
		if (td->cols < 1) td->cols = 1;
		if (td->rows < 1) td->rows = 1;
	}

	/* Paranoia - Enforce maximum size allowed by the term package */
	if (td->cols > 255) td->cols = 255;
	if (td->rows > 255) td->rows = 255;
}


/*
 * Enforce these size constraints within Gtk/Gdk
 * These increments are nice, because you can see numbers of rows/cols
 * while you resize a term.
 */
static void term_data_set_geometry_hints(term_data *td)
{
	GdkGeometry geometry;

	/* Resizing is character size oriented */
	geometry.width_inc = td->font_wid;
	geometry.height_inc = td->font_hgt;

	/* Enforce minimum size - the main window */
	if (td == &data[0])
	{
		geometry.min_width = 80 * td->font_wid;
		geometry.min_height = 24 * td->font_hgt;
	}

	/* Subwindows can be much smaller */
	else
	{
		geometry.min_width = 1 * td->font_wid;
		geometry.min_height = 1 * td->font_hgt;
	}

	/* Enforce term package's hard limit */
	geometry.max_width = 255 * td->font_wid;
	geometry.max_height = 255 * td->font_hgt;

	/* This affects geometry display while we resize a term */
	geometry.base_width = 0;
	geometry.base_height = 0;

	/* Give the window a new set of resizing hints */
	gtk_window_set_geometry_hints(GTK_WINDOW(td->window),
	                              td->drawing_area, &geometry,
	                              GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE
	                              | GDK_HINT_BASE_SIZE | GDK_HINT_RESIZE_INC);
}


/*
 * (Re)allocate a backing store for the window
 */
static void term_data_set_backing_store(term_data *td)
{
	/* Paranoia */
	if (!GTK_WIDGET_REALIZED(td->drawing_area)) return;

	/* Free old one if we cannot use it any longer */
	if (td->backing_store)
	{
		int wid, hgt;

		/* Retrive the size of the old backing store */
		gdk_window_get_size(td->backing_store, &wid, &hgt);

		/* Continue using it if it's the same with desired size */
		if (use_backing_store &&
		                (td->cols * td->font_wid == wid) &&
		                (td->rows * td->font_hgt == hgt)) return;

		/* Free it */
		gdk_pixmap_unref(td->backing_store);

		/* Forget the pointer */
		td->backing_store = NULL;
	}

	/* See user preference */
	if (use_backing_store)
	{
		/* Allocate new backing store */
		td->backing_store = gdk_pixmap_new(
		                            td->drawing_area->window,
		                            td->cols * td->font_wid,
		                            td->rows * td->font_hgt,
		                            -1);

		/* Oops - but we can do without it */
		g_return_if_fail(td->backing_store != NULL);

		/* Clear the backing store */
		gdk_draw_rectangle(
		        td->backing_store,
		        td->drawing_area->style->black_gc,
		        TRUE,
		        0,
		        0,
		        td->cols * td->font_wid,
		        td->rows * td->font_hgt);
	}
}


/*
 * Save game only when it's safe to do so
 */
static void save_game_gtk(void)
{
	/* We have nothing to save, yet */
	if (!game_in_progress || !character_generated) return;

	/* It isn't safe to save game now */
	if (!inkey_flag || !can_save)
	{
		plog("You may not save right now.");
		return;
	}

	/* Hack -- Forget messages */
	msg_flag = FALSE;

	/* Save the game */
#ifdef ZANG_SAVE_GAME
	/* Also for OAngband - the parameter tells if it's autosave */
	do_cmd_save_game(FALSE);
#else
/* Everything else */
	do_cmd_save_game();
#endif /* ZANG_SAVE_GAME */
}


/*
 * Display message in a modal dialog
 */
static void gtk_message(cptr msg)
{
	GtkWidget *dialog, *label, *ok_button;

	/* Create the widgets */
	dialog = gtk_dialog_new();
	g_assert(dialog != NULL);

	label = gtk_label_new(msg);
	g_assert(label != NULL);

	ok_button = gtk_button_new_with_label("OK");
	g_assert(ok_button != NULL);

	/* Ensure that the dialogue box is destroyed when OK is clicked */
	gtk_signal_connect_object(
	        GTK_OBJECT(ok_button),
	        "clicked",
	        GTK_SIGNAL_FUNC(gtk_widget_destroy),
	        (gpointer)dialog);
	gtk_container_add(
	        GTK_CONTAINER(GTK_DIALOG(dialog)->action_area),
	        ok_button);

	/* Add the label, and show the dialog */
	gtk_container_add(
	        GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),
	        label);

	/* And make it modal */
	gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);

	/* Show the dialog */
	gtk_widget_show_all(dialog);
}


/*
 * Hook to tell the user something important
 */
static void hook_plog(cptr str)
{
	/* Warning message */
	gtk_message(str);
}


/*
 * Process File-Quit menu command
 */
static void quit_event_handler(
        GtkButton *was_clicked,
        gpointer user_data)
{
	/* Save current game */
	save_game_gtk();

	/* It's done */
	quit(NULL);
}


/*
 * Process File-Save menu command
 */
static void save_event_handler(
        GtkButton *was_clicked,
        gpointer user_data)
{
	/* Save current game */
	save_game_gtk();
}


/*
 * Handle destruction of the Angband window
 */
static void destroy_main_event_handler(
        GtkButton *was_clicked,
        gpointer user_data)
{
	/* This allows for cheating, but... */
	quit(NULL);
}


/*
 * Handle destruction of Subwindows
 */
static void destroy_sub_event_handler(
        GtkWidget *window,
        gpointer user_data)
{
	/* Hide the window */
	gtk_widget_hide_all(window);
}


#ifndef SAVEFILE_SCREEN

/*
 * Process File-New menu command
 */
static void new_event_handler(
        GtkButton *was_clicked,
        gpointer user_data)
{
	if (game_in_progress)
	{
		plog("You can't start a new game while you're still playing!");
		return;
	}

	/* The game is in progress */
	game_in_progress = TRUE;

	/* Flush input */
	Term_flush();

	/* Play game */
	play_game(TRUE);

	/* Houseclearing */
	cleanup_angband();

	/* Done */
	quit(NULL);
}

#endif /* !SAVEFILE_SCREEN */


/*
 * Load fond specified by an XLFD fontname and
 * set up related term_data members
 */
static void load_font(term_data *td, cptr fontname)
{
	GdkFont *old = td->font;

	/* Load font */
	td->font = gdk_font_load(fontname);

	if (td->font)
	{
		/* Free the old font */
		if (old) gdk_font_unref(old);
	}
	else
	{
		/* Oops, but we can still use the old one */
		td->font = old;
	}

	/* Calculate the size of the font XXX */
	td->font_wid = gdk_char_width(td->font, '@');
	td->font_hgt = td->font->ascent + td->font->descent;

#ifndef USE_DOUBLE_TILES

	/* Use the current font size for tiles as well */
	td->tile_wid = td->font_wid;
	td->tile_hgt = td->font_hgt;

#else /* !USE_DOUBLE_TILES */

	/* Calculate the size of tiles */
	if (use_bigtile && (td == &data[0])) td->tile_wid = td->font_wid * 2;
	else td->tile_wid = td->font_wid;
	td->tile_hgt = td->font_hgt;

#endif /* !USE_DOUBLE_TILES */
}


/*
 * React to OK button press in font selection dialogue
 */
static void font_ok_callback(
        GtkWidget *widget,
        GtkWidget *font_selector)
{
	gchar *fontname;
	term_data *td;

	td = gtk_object_get_data(GTK_OBJECT(font_selector), "term_data");

	g_assert(td != NULL);

	/* Retrieve font name from player's selection */
	fontname = gtk_font_selection_dialog_get_font_name(
	                   GTK_FONT_SELECTION_DIALOG(font_selector));

	/* Leave unless selection was valid */
	if (fontname == NULL) return;

	/* Load font and update font size info */
	load_font(td, fontname);

	/* Hack - Hide the window - finally found the trick... */
	gtk_widget_hide_all(td->window);

	/* Resizes the drawing area */
	gtk_drawing_area_size(
	        GTK_DRAWING_AREA(td->drawing_area),
	        td->cols * td->font_wid,
	        td->rows * td->font_hgt);

	/* Update the geometry hints for the window */
	term_data_set_geometry_hints(td);

	/* Reallocate the backing store */
	term_data_set_backing_store(td);

	/* Hack - Show the window */
	gtk_widget_show_all(td->window);

#ifdef USE_GRAPHICS

	/* We have to resize tiles when we are in graphics mode */
	resize_request = TRUE;

#endif /* USE_GRAPHICS */

	/* Hack - force redraw */
	Term_key_push(KTRL('R'));
}


/*
 * Process Options-Font-* menu command
 */
static void change_font_event_handler(
        GtkWidget *widget,
        gpointer user_data)
{
	GtkWidget *font_selector;

	gchar *spacings[] = { "c", "m", NULL };

	font_selector = gtk_font_selection_dialog_new("Select font");

	gtk_object_set_data(
	        GTK_OBJECT(font_selector),
	        "term_data",
	        user_data);

	/* Filter to show only fixed-width fonts */
	gtk_font_selection_dialog_set_filter(
	        GTK_FONT_SELECTION_DIALOG(font_selector),
	        GTK_FONT_FILTER_BASE,
	        GTK_FONT_ALL,
	        NULL,
	        NULL,
	        NULL,
	        NULL,
	        spacings,
	        NULL);

	gtk_signal_connect(
	        GTK_OBJECT(GTK_FONT_SELECTION_DIALOG(font_selector)->ok_button),
	        "clicked",
	        font_ok_callback,
	        (gpointer)font_selector);

	/*
	 * Ensure that the dialog box is destroyed when the user clicks
	 * a button.
	 */
	gtk_signal_connect_object(
	        GTK_OBJECT(GTK_FONT_SELECTION_DIALOG(font_selector)->ok_button),
	        "clicked",
	        GTK_SIGNAL_FUNC(gtk_widget_destroy),
	        (gpointer)font_selector);

	gtk_signal_connect_object(
	        GTK_OBJECT(GTK_FONT_SELECTION_DIALOG(font_selector)->cancel_button),
	        "clicked",
	        GTK_SIGNAL_FUNC(gtk_widget_destroy),
	        (gpointer)font_selector);

	gtk_widget_show(GTK_WIDGET(font_selector));
}


/*
 * Process Terms-* menu command - hide/show terminal window
 */
static void term_event_handler(
        GtkWidget *widget,
        gpointer user_data)
{
	term_data *td = (term_data *)user_data;

	/* We don't mess with the Angband window */
	if (td == &data[0]) return;

	/* It's shown */
	if (td->shown)
	{
		/* Hide the window */
		gtk_widget_hide_all(td->window);
	}

	/* It's hidden */
	else
	{
		/* Show the window */
		gtk_widget_show_all(td->window);
	}
}


/*
 * Toggles the boolean value of use_backing_store and
 * setup / remove backing store for each term
 */
static void change_backing_store_event_handler(
        GtkButton *was_clicked,
        gpointer user_data)
{
	int i;

	/* Toggle the backing store mode */
	use_backing_store = !use_backing_store;

	/* Reset terms */
	for (i = 0; i < MAX_TERM_DATA; i++)
	{
		term_data_set_backing_store(&data[i]);
	}
}


#ifdef USE_GRAPHICS

/*
 * Set graf_mode_request according to user selection,
 * and let Term_xtra react to the change.
 */
static void change_graf_mode_event_handler(
        GtkButton *was_clicked,
        gpointer user_data)
{
	/* Set request according to user selection */
	graf_mode_request = (int)user_data;

	/*
	 * Hack - force redraw
	 * This induces a call to Term_xtra(TERM_XTRA_REACT, 0) as well
	 */
	Term_key_push(KTRL('R'));
}


/*
 * Set dither_mode according to user selection
 */
static void change_dith_mode_event_handler(
        GtkButton *was_clicked,
        gpointer user_data)
{
	/* Set request according to user selection */
	dith_mode = (int)user_data;

	/*
	 * Hack - force redraw
	 */
	Term_key_push(KTRL('R'));
}


/*
 * Toggles the graphics tile scaling mode (Fast/Smooth)
 */
static void change_smooth_mode_event_handler(
        GtkButton *was_clicked,
        gpointer user_data)
{
	/* (Try to) toggle the smooth rescaling mode */
	smooth_rescaling_request = !smooth_rescaling;

	/*
	 * Hack - force redraw
	 * This induces a call to Term_xtra(TERM_XTRA_REACT, 0) as well
	 */
	Term_key_push(KTRL('R'));
}


# ifdef USE_DOUBLE_TILES

static void change_wide_tile_mode_event_handler(
        GtkButton *was_clicked,
        gpointer user_data)
{
	term *old = Term;
	term_data *td = &data[0];

	/* Toggle "use_bigtile" */
	use_bigtile = !use_bigtile;

#ifdef TOME
	/* T.o.M.E. requires this as well */
	arg_bigtile = use_bigtile;
#endif /* TOME */

	/* Double the width of tiles (only for the main window) */
	if (use_bigtile)
	{
		td->tile_wid = td->font_wid * 2;
	}

	/* Use the width of current font */
	else
	{
		td->tile_wid = td->font_wid;
	}

	/* Need to resize the tiles */
	resize_request = TRUE;

	/* Activate the main window */
	Term_activate(&td->t);

	/* Resize the term */
	Term_resize(td->cols, td->rows);

	/* Activate the old term */
	Term_activate(old);

	/* Hack - force redraw XXX ??? XXX */
	Term_key_push(KTRL('R'));
}

# endif  /* USE_DOUBLE_TILES */


# ifdef USE_TRANSPARENCY

/*
 * Toggles the boolean value of use_transparency
 */
static void change_trans_mode_event_handler(
        GtkButton *was_clicked,
        gpointer user_data)
{
	/* Toggle the transparency mode */
	use_transparency = !use_transparency;

	/* Hack - force redraw */
	Term_key_push(KTRL('R'));
}

# endif  /* USE_TRANSPARENCY */

#endif /* USE_GRAPHICS */


#ifndef SAVEFILE_SCREEN

/*
 * Caution: Modal or not, callbacks are called by gtk_main(),
 * so this is the right place to start a game.
 */
static void file_ok_callback(
        GtkWidget *widget,
        GtkWidget *file_selector)
{
	strcpy(savefile,
	       gtk_file_selection_get_filename(GTK_FILE_SELECTION(file_selector)));

	gtk_widget_destroy(file_selector);

	/* game is in progress */
	game_in_progress = TRUE;

	/* Flush input */
	Term_flush();

	/* Play game */
	play_game(FALSE);

	/* Free memory allocated by game */
	cleanup_angband();

	/* Done */
	quit(NULL);
}


/*
 * Process File-Open menu command
 */
static void open_event_handler(
        GtkButton *was_clicked,
        gpointer user_data)
{
	GtkWidget *file_selector;
	char buf[1024];


	if (game_in_progress)
	{
		plog("You can't open a new game while you're still playing!");
		return;
	}

	/* Prepare the savefile path */
	path_build(buf, 1024, ANGBAND_DIR_SAVE, "*");

	file_selector = gtk_file_selection_new("Select a savefile");
	gtk_file_selection_set_filename(
	        GTK_FILE_SELECTION(file_selector),
	        buf);
	gtk_signal_connect(
	        GTK_OBJECT(GTK_FILE_SELECTION(file_selector)->ok_button),
	        "clicked",
	        file_ok_callback,
	        (gpointer)file_selector);

	/*
	 * Ensure that the dialog box is destroyed when the user
	 * clicks a button.
	 */
	gtk_signal_connect_object(
	        GTK_OBJECT(GTK_FILE_SELECTION(file_selector)->ok_button),
	        "clicked",
	        GTK_SIGNAL_FUNC(gtk_widget_destroy),
	        (gpointer)file_selector);

	gtk_signal_connect_object(
	        GTK_OBJECT(GTK_FILE_SELECTION(file_selector)->cancel_button),
	        "clicked",
	        GTK_SIGNAL_FUNC(gtk_widget_destroy),
	        (gpointer)file_selector);

	gtk_window_set_modal(GTK_WINDOW(file_selector), TRUE);
	gtk_widget_show(GTK_WIDGET(file_selector));
}

#endif /* !SAVEFILE_SCREEN */


/*
 * React to "delete" signal sent to Window widgets
 */
static gboolean delete_event_handler(
        GtkWidget *widget,
        GdkEvent *event,
        gpointer user_data)
{
	/* Save game if possible */
	save_game_gtk();

	/* Don't prevent closure */
	return (FALSE);
}


/*
 * Convert keypress events to ASCII codes and enqueue them
 * for game
 */
static gboolean keypress_event_handler(
        GtkWidget *widget,
        GdkEventKey *event,
        gpointer user_data)
{
	int i, mc, ms, mo, mx;

	char msg[128];


	/* Extract four "modifier flags" */
	mc = (event->state & GDK_CONTROL_MASK) ? TRUE : FALSE;
	ms = (event->state & GDK_SHIFT_MASK) ? TRUE : FALSE;
	mo = (event->state & GDK_MOD1_MASK) ? TRUE : FALSE;
	mx = (event->state & GDK_MOD3_MASK) ? TRUE : FALSE;

	/*
	 * Hack XXX
	 * Parse shifted numeric (keypad) keys specially.
	 */
	if ((event->state == GDK_SHIFT_MASK)
	                && (event->keyval >= GDK_KP_0) && (event->keyval <= GDK_KP_9))
	{
		/* Build the macro trigger string */
		strnfmt(msg, 128, "%cS_%X%c", 31, event->keyval, 13);

		/* Enqueue the "macro trigger" string */
		for (i = 0; msg[i]; i++) Term_keypress(msg[i]);

		/* Hack -- auto-define macros as needed */
		if (event->length && (macro_find_exact(msg) < 0))
		{
			/* Create a macro */
			macro_add(msg, event->string);
		}

		return (TRUE);
	}

	/* Normal keys with no modifiers */
	if (event->length && !mo && !mx)
	{
		/* Enqueue the normal key(s) */
		for (i = 0; i < event->length; i++) Term_keypress(event->string[i]);

		/* All done */
		return (TRUE);
	}

	/* Handle a few standard keys (bypass modifiers) XXX XXX XXX */
	switch ((uint) event->keyval)
	{
	case GDK_Escape:
		{
			Term_keypress(ESCAPE);
			return (TRUE);
		}

	case GDK_Return:
		{
			Term_keypress('\r');
			return (TRUE);
		}

	case GDK_Tab:
		{
			Term_keypress('\t');
			return (TRUE);
		}

	case GDK_Delete:
	case GDK_BackSpace:
		{
			Term_keypress('\010');
			return (TRUE);
		}

	case GDK_Shift_L:
	case GDK_Shift_R:
	case GDK_Control_L:
	case GDK_Control_R:
	case GDK_Caps_Lock:
	case GDK_Shift_Lock:
	case GDK_Meta_L:
	case GDK_Meta_R:
	case GDK_Alt_L:
	case GDK_Alt_R:
	case GDK_Super_L:
	case GDK_Super_R:
	case GDK_Hyper_L:
	case GDK_Hyper_R:
		{
			/* Hack - do nothing to control characters */
			return (TRUE);
		}
	}

	/* Build the macro trigger string */
	strnfmt(msg, 128, "%c%s%s%s%s_%X%c", 31,
	        mc ? "N" : "", ms ? "S" : "",
	        mo ? "O" : "", mx ? "M" : "",
	        event->keyval, 13);

	/* Enqueue the "macro trigger" string */
	for (i = 0; msg[i]; i++) Term_keypress(msg[i]);

	/* Hack -- auto-define macros as needed */
	if (event->length && (macro_find_exact(msg) < 0))
	{
		/* Create a macro */
		macro_add(msg, event->string);
	}

	return (TRUE);
}


/*
 * Widget customisation (for drawing area) - "realize" signal
 *
 * In this program, called when window containing the drawing
 * area is shown first time.
 */
static void realize_event_handler(
        GtkWidget *widget,
        gpointer user_data)
{
	term_data *td = (term_data *)user_data;

	/* Create graphic context */
	td->gc = gdk_gc_new(td->drawing_area->window);

	/* Set foreground and background colours - isn't bg used at all? */
	gdk_rgb_gc_set_background(td->gc, 0x000000);
	gdk_rgb_gc_set_foreground(td->gc, angband_colours[TERM_WHITE]);

	/* No last foreground colour, yet */
	td->last_attr = -1;

	/* Allocate the backing store */
	term_data_set_backing_store(td);

	/* Clear the window */
	gdk_draw_rectangle(
	        widget->window,
	        widget->style->black_gc,
	        TRUE,
	        0,
	        0,
	        td->cols * td->font_wid,
	        td->rows * td->font_hgt);
}


/*
 * Widget customisation (for drawing area) - "show" signal
 */
static void show_event_handler(
        GtkWidget *widget,
        gpointer user_data)
{
	term_data *td = (term_data *)user_data;

	/* Set the shown flag */
	td->shown = TRUE;
}


/*
 * Widget customisation (for drawing area) - "hide" signal
 */
static void hide_event_handler(
        GtkWidget *widget,
        gpointer user_data)
{
	term_data *td = (term_data *)user_data;

	/* Set the shown flag */
	td->shown = FALSE;
}


/*
 * Widget customisation (for drawing area)- handle size allocation requests
 */
static void size_allocate_event_handler(
        GtkWidget *widget,
        GtkAllocation *allocation,
        gpointer user_data)
{
	term_data *td = user_data;
	int old_rows, old_cols;
	term *old = Term;

	/* Paranoia */
	g_return_if_fail(widget != NULL);
	g_return_if_fail(allocation != NULL);
	g_return_if_fail(td != NULL);

	/* Remember old values */
	old_cols = td->cols;
	old_rows = td->rows;

	/* Update numbers of rows and columns */
	td->cols = (allocation->width + td->font_wid - 1) / td->font_wid;
	td->rows = (allocation->height + td->font_hgt - 1) / td->font_hgt;

	/* Overkill - Validate them */
	term_data_check_size(td);

	/* Adjust size request and set it */
	allocation->width = td->cols * td->font_wid;
	allocation->height = td->rows * td->font_hgt;
	widget->allocation = *allocation;

	/* Widget is realized, so we do some drawing works */
	if (GTK_WIDGET_REALIZED(widget))
	{
		/* Reallocate the backing store */
		term_data_set_backing_store(td);

		/* Actually handles resizing in Gtk */
		gdk_window_move_resize(
		        widget->window,
		        allocation->x,
		        allocation->y,
		        allocation->width,
		        allocation->height);

		/* And in the term package */
		Term_activate(&td->t);

		/* Resize if necessary */
		if ((td->cols != old_cols) || (td->rows != old_rows))
			(void)Term_resize(td->cols, td->rows);

		/* Redraw its content */
		Term_redraw();

		/* Refresh */
		Term_fresh();

		/* Restore */
		Term_activate(old);
	}
}


/*
 * Update exposed area in a window (for drawing area)
 */
static gboolean expose_event_handler(
        GtkWidget *widget,
        GdkEventExpose *event,
        gpointer user_data)
{
	term_data *td = user_data;

	term *old = Term;

#ifndef NO_REDRAW_SECTION

	int x1, x2, y1, y2;

#endif /* !NO_REDRAW_SECTION */


	/* Paranoia */
	if (td == NULL) return (TRUE);

	/* The window has a backing store */
	if (td->backing_store)
	{
		/* Simply restore the exposed area from the backing store */
		gdk_draw_pixmap(
		        td->drawing_area->window,
		        td->gc,
		        td->backing_store,
		        event->area.x,
		        event->area.y,
		        event->area.x,
		        event->area.y,
		        event->area.width,
		        event->area.height);
	}

	/* No backing store - use the game's code to redraw the area */
	else
	{

		/* Activate the relevant term */
		Term_activate(&td->t);

# ifdef NO_REDRAW_SECTION

		/* K.I.S.S. version */

		/* Redraw */
		Term_redraw();

# else /* NO_REDRAW_SECTION */

		/*
		 * Complex version - The above is enough, but since we have
		 * Term_redraw_section... This might help if we had a graphics
		 * mode.
		 */

		/* Convert coordinate in pixels to character cells */
		x1 = event->area.x / td->font_wid;
		x2 = (event->area.x + event->area.width) / td->font_wid;
		y1 = event->area.y / td->font_hgt;
		y2 = (event->area.y + event->area.height) / td->font_hgt;

		/*
		 * No paranoia - boundary checking is done in
		 * Term_redraw_section
		 */

		/* Redraw the area */
		Term_redraw_section(x1, y1, x2, y2);

# endif  /* NO_REDRAW_SECTION */

		/* Refresh */
		Term_fresh();

		/* Restore */
		Term_activate(old);
	}

	/* We've processed the event ourselves */
	return (TRUE);
}




/**** Initialisation ****/

/*
 * Initialise a term_data struct
 */
static errr term_data_init(term_data *td, int i)
{
	term *t = &td->t;
	char *p;

	td->cols = 80;
	td->rows = 24;

	/* Initialize the term */
	term_init(t, td->cols, td->rows, 1024);

	/* Store the name of the term */
	td->name = string_make(angband_term_name[i]);

	/* Instance names should start with a lowercase letter XXX */
	for (p = (char *)td->name; *p; p++) *p = tolower(*p);

	/* Use a "soft" cursor */
	t->soft_cursor = TRUE;

	/* Erase with "white space" */
	t->attr_blank = TERM_WHITE;
	t->char_blank = ' ';

	t->xtra_hook = Term_xtra_gtk;
	t->text_hook = Term_text_gtk;
	t->wipe_hook = Term_wipe_gtk;
	t->curs_hook = Term_curs_gtk;
#ifdef USE_GRAPHICS
	t->pict_hook = Term_pict_gtk;
#endif /* USE_GRAPHICS */
	t->nuke_hook = Term_nuke_gtk;

	/* Save the data */
	t->data = td;

	/* Activate (important) */
	Term_activate(t);

	/* Success */
	return (0);
}


/*
 * Neater menu code with GtkItemFactory.
 *
 * Menu bar of the Angband window
 *
 * Entry format: Path, Accelerator, Callback, Callback arg, type
 * where type is one of:
 * <Item> - simple item, alias NULL
 * <Branch> - has submenu
 * <Separator> - as you read it
 * <CheckItem> - has a check mark
 * <ToggleItem> - is a toggle
 */
static GtkItemFactoryEntry main_menu_items[] =
{
	/* "File" menu */
	{ "/File", NULL,
	  NULL, 0, "<Branch>"
	},
#ifndef SAVEFILE_SCREEN
	{ "/File/New", "<mod1>N",
	  new_event_handler, 0, NULL },
	{ "/File/Open", "<mod1>O",
	  open_event_handler, 0, NULL },
	{ "/File/sep1", NULL,
	  NULL, 0, "<Separator>" },
#endif /* !SAVEFILE_SCREEN */
	{ "/File/Save", "<mod1>S",
	  save_event_handler, 0, NULL },
	{ "/File/Quit", "<mod1>Q",
	  quit_event_handler, 0, NULL },

	/* "Terms" menu */
	{ "/Terms", NULL,
	  NULL, 0, "<Branch>" },
	/* XXX XXX XXX NULL's are replaced by the program */
	{ NULL, "<mod1>0",
	  term_event_handler, (guint)&data[0], "<CheckItem>" },
	{ NULL, "<mod1>1",
	  term_event_handler, (guint)&data[1], "<CheckItem>" },
	{ NULL, "<mod1>2",
	  term_event_handler, (guint)&data[2], "<CheckItem>" },
	{ NULL, "<mod1>3",
	  term_event_handler, (guint)&data[3], "<CheckItem>" },
	{ NULL, "<mod1>4",
	  term_event_handler, (guint)&data[4], "<CheckItem>" },
	{ NULL, "<mod1>5",
	  term_event_handler, (guint)&data[5], "<CheckItem>" },
	{ NULL, "<mod1>6",
	  term_event_handler, (guint)&data[6], "<CheckItem>" },
	{ NULL, "<mod1>7",
	  term_event_handler, (guint)&data[7], "<CheckItem>" },

	/* "Options" menu */
	{ "/Options", NULL,
	  NULL, 0, "<Branch>" },

	/* "Font" submenu */
	{ "/Options/Font", NULL,
	  NULL, 0, "<Branch>" },
	/* XXX XXX XXX Again, NULL's are filled by the program */
	{ NULL, NULL,
	  change_font_event_handler, (guint)&data[0], NULL },
	{ NULL, NULL,
	  change_font_event_handler, (guint)&data[1], NULL },
	{ NULL, NULL,
	  change_font_event_handler, (guint)&data[2], NULL },
	{ NULL, NULL,
	  change_font_event_handler, (guint)&data[3], NULL },
	{ NULL, NULL,
	  change_font_event_handler, (guint)&data[4], NULL },
	{ NULL, NULL,
	  change_font_event_handler, (guint)&data[5], NULL },
	{ NULL, NULL,
	  change_font_event_handler, (guint)&data[6], NULL },
	{ NULL, NULL,
	  change_font_event_handler, (guint)&data[7], NULL },

#ifdef USE_GRAPHICS

	/* "Graphics" submenu */
	{ "/Options/Graphics", NULL,
	  NULL, 0, "<Branch>" },
	{ "/Options/Graphics/None", NULL,
	  change_graf_mode_event_handler, GRAF_MODE_NONE, "<CheckItem>" },
	{ "/Options/Graphics/Old", NULL,
	  change_graf_mode_event_handler, GRAF_MODE_OLD, "<CheckItem>" },
	{ "/Options/Graphics/New", NULL,
	  change_graf_mode_event_handler, GRAF_MODE_NEW, "<CheckItem>" },
# ifdef USE_DOUBLE_TILES
	{ "/Options/Graphics/sep3", NULL,
	  NULL, 0, "<Separator>" },
	{ "/Options/Graphics/Wide tiles", NULL,
	  change_wide_tile_mode_event_handler, 0, "<CheckItem>" },
# endif  /* USE_DOUBLE_TILES */
	{ "/Options/Graphics/sep1", NULL,
	  NULL, 0, "<Separator>" },
	{ "/Options/Graphics/Dither if <= 8bpp", NULL,
	  change_dith_mode_event_handler, GDK_RGB_DITHER_NORMAL, "<CheckItem>" },
	{ "/Options/Graphics/Dither if <= 16bpp", NULL,
	  change_dith_mode_event_handler, GDK_RGB_DITHER_MAX, "<CheckItem>" },
	{ "/Options/Graphics/sep2", NULL,
	  NULL, 0, "<Separator>" },
	{ "/Options/Graphics/Smoothing", NULL,
	  change_smooth_mode_event_handler, 0, "<CheckItem>" },
# ifdef USE_TRANSPARENCY
	{ "/Options/Graphics/Transparency", NULL,
	  change_trans_mode_event_handler, 0, "<CheckItem>" },
# endif  /* USE_TRANSPARENCY */

#endif /* USE_GRAPHICS */

	/* "Misc" submenu */
	{ "/Options/Misc", NULL,
	  NULL, 0, "<Branch>" },
	{ "/Options/Misc/Backing store", NULL,
	  change_backing_store_event_handler, 0, "<CheckItem>" },
};


/*
 * XXX XXX Fill those NULL's in the menu definition with
 * angband_term_name[] strings
 */
static void setup_menu_paths(void)
{
	int i;
	int nmenu_items = sizeof(main_menu_items) / sizeof(main_menu_items[0]);
	GtkItemFactoryEntry *term_entry, *font_entry;
	char buf[64];

	/* Find the "Terms" menu */
	for (i = 0; i < nmenu_items; i++)
	{
		/* Skip NULLs */
		if (main_menu_items[i].path == NULL) continue;

		/* Find a match */
		if (streq(main_menu_items[i].path, "/Terms")) break;
	}
	g_assert(i < (nmenu_items - MAX_TERM_DATA));

	/* Remember the location */
	term_entry = &main_menu_items[i + 1];

	/* Find "Font" menu */
	for (i = 0; i < nmenu_items; i++)
	{
		/* Skip NULLs */
		if (main_menu_items[i].path == NULL) continue;

		/* Find a match */
		if (streq(main_menu_items[i].path, "/Options/Font")) break;
	}
	g_assert(i < (nmenu_items - MAX_TERM_DATA));

	/* Remember the location */
	font_entry = &main_menu_items[i + 1];

	/* For each terminal */
	for (i = 0; i < MAX_TERM_DATA; i++)
	{
		/* XXX XXX Build the real path name to the entry */
		strnfmt(buf, 64, "/Terms/%s", angband_term_name[i]);

		/* XXX XXX Store it in the menu definition */
		(cptr)term_entry[i].path = string_make(buf);

		/* XXX XXX Build the real path name to the entry */
		strnfmt(buf, 64, "/Options/Font/%s", angband_term_name[i]);

		/* XXX XXX Store it in the menu definition */
		(cptr)font_entry[i].path = string_make(buf);
	}
}


/*
 * XXX XXX Free strings allocated by setup_menu_paths()
 */
static void free_menu_paths(void)
{
	int i;
	int nmenu_items = sizeof(main_menu_items) / sizeof(main_menu_items[0]);
	GtkItemFactoryEntry *term_entry, *font_entry;

	/* Find the "Terms" menu */
	for (i = 0; i < nmenu_items; i++)
	{
		/* Skip NULLs */
		if (main_menu_items[i].path == NULL) continue;

		/* Find a match */
		if (streq(main_menu_items[i].path, "/Terms")) break;
	}
	g_assert(i < (nmenu_items - MAX_TERM_DATA));

	/* Remember the location */
	term_entry = &main_menu_items[i + 1];

	/* Find "Font" menu */
	for (i = 0; i < nmenu_items; i++)
	{
		/* Skip NULLs */
		if (main_menu_items[i].path == NULL) continue;

		/* Find a match */
		if (streq(main_menu_items[i].path, "/Options/Font")) break;
	}
	g_assert(i < (nmenu_items - MAX_TERM_DATA));

	/* Remember the location */
	font_entry = &main_menu_items[i + 1];

	/* For each terminal */
	for (i = 0; i < MAX_TERM_DATA; i++)
	{
		/* XXX XXX Free Term menu path */
		if (term_entry[i].path) string_free((cptr)term_entry[i].path);

		/* XXX XXX Free Font menu path */
		if (font_entry[i].path) string_free((cptr)font_entry[i].path);
	}
}


/*
 * Find widget corresponding to path name
 * return NULL on error
 */
static GtkWidget *get_widget_from_path(cptr path)
{
	GtkItemFactory *item_factory;
	GtkWidget *widget;

	/* Paranoia */
	if (path == NULL) return (NULL);

	/* Look up item factory */
	item_factory = gtk_item_factory_from_path(path);

	/* Oops */
	if (item_factory == NULL) return (NULL);

	/* Look up widget */
	widget = gtk_item_factory_get_widget(item_factory, path);

	/* Return result */
	return (widget);
}


/*
 * Enable/disable a menu item
 */
void enable_menu_item(cptr path, bool enabled)
{
	GtkWidget *widget;

	/* Access menu item widget */
	widget = get_widget_from_path(path);

	/* Paranoia */
	g_assert(widget != NULL);
	g_assert(GTK_IS_MENU_ITEM(widget));

	/*
	 * In Gtk's terminology, enabled is sensitive
	 * and disabled insensitive
	 */
	gtk_widget_set_sensitive(widget, enabled);
}


/*
 * Check/uncheck a menu item. The item should be of the GtkCheckMenuItem type
 */
void check_menu_item(cptr path, bool checked)
{
	GtkWidget *widget;

	/* Access menu item widget */
	widget = get_widget_from_path(path);

	/* Paranoia */
	g_assert(widget != NULL);
	g_assert(GTK_IS_CHECK_MENU_ITEM(widget));

	/*
	 * Put/remove check mark
	 *
	 * Mega-Hack -- The function supposed to be used here,
	 * gtk_check_menu_item_set_active(), emits an "activate" signal
	 * to the GtkMenuItem class of the widget, as if the menu item
	 * were selected by user, thereby causing bizarre behaviour.
	 * XXX XXX XXX
	 */
	GTK_CHECK_MENU_ITEM(widget)->active = checked;
}


/*
 * Update the "File" menu
 */
static void file_menu_update_handler(
        GtkWidget *widget,
        gpointer user_data)
{
#ifndef SAVEFILE_SCREEN
	bool game_start_ok;
#endif /* !SAVEFILE_SCREEN */
	bool save_ok, quit_ok;

#ifndef SAVEFILE_SCREEN

	/* Can we start a game now? */
	game_start_ok = !game_in_progress;

#endif /* !SAVEFILE_SCREEN */

	/* Cave we save/quit now? */
	if (!character_generated || !game_in_progress)
	{
		save_ok = FALSE;
		quit_ok = TRUE;
	}
	else
	{
		if (inkey_flag && can_save) save_ok = quit_ok = TRUE;
		else save_ok = quit_ok = FALSE;
	}

	/* Enable / disable menu items according to those conditions */
#ifndef SAVEFILE_SCREEN
	enable_menu_item("<Angband>/File/New", game_start_ok);
	enable_menu_item("<Angband>/File/Open", game_start_ok);
#endif /* !SAVEFILE_SCREEN */
	enable_menu_item("<Angband>/File/Save", save_ok);
	enable_menu_item("<Angband>/File/Quit", quit_ok);
}


/*
 * Update the "Terms" menu
 */
static void term_menu_update_handler(
        GtkWidget *widget,
        gpointer user_data)
{
	int i;
	char buf[64];

	/* For each term */
	for (i = 0; i < MAX_TERM_DATA; i++)
	{
		/* Build the path name */
		strnfmt(buf, 64, "<Angband>/Terms/%s", angband_term_name[i]);

		/* Update the check mark on the item */
		check_menu_item(buf, data[i].shown);
	}
}


/*
 * Update the "Font" submenu
 */
static void font_menu_update_handler(
        GtkWidget *widget,
        gpointer user_data)
{
	int i;
	char buf[64];

	/* For each term */
	for (i = 0; i < MAX_TERM_DATA; i++)
	{
		/* Build the path name */
		strnfmt(buf, 64, "<Angband>/Options/Font/%s", angband_term_name[i]);

		/* Enable selection if the term is shown */
		enable_menu_item(buf, data[i].shown);
	}
}


/*
 * Update the "Misc" submenu
 */
static void misc_menu_update_handler(
        GtkWidget *widget,
        gpointer user_data)
{
	/* Update an item */
	check_menu_item(
	        "<Angband>/Options/Misc/Backing store",
	        use_backing_store);
}


#ifdef USE_GRAPHICS

/*
 * Update the "Graphics" submenu
 */
static void graf_menu_update_handler(
        GtkWidget *widget,
        gpointer user_data)
{
	/* Update menu items */
	check_menu_item(
	        "<Angband>/Options/Graphics/None",
	        (graf_mode == GRAF_MODE_NONE));
	check_menu_item(
	        "<Angband>/Options/Graphics/Old",
	        (graf_mode == GRAF_MODE_OLD));
	check_menu_item(
	        "<Angband>/Options/Graphics/New",
	        (graf_mode == GRAF_MODE_NEW));

#ifdef USE_DOUBLE_TILES

	check_menu_item(
	        "<Angband>/Options/Graphics/Wide tiles",
	        use_bigtile);

#endif /* USE_DOUBLE_TILES */

	check_menu_item(
	        "<Angband>/Options/Graphics/Dither if <= 8bpp",
	        (dith_mode == GDK_RGB_DITHER_NORMAL));
	check_menu_item(
	        "<Angband>/Options/Graphics/Dither if <= 16bpp",
	        (dith_mode == GDK_RGB_DITHER_MAX));

	check_menu_item(
	        "<Angband>/Options/Graphics/Smoothing",
	        smooth_rescaling);

# ifdef USE_TRANSPARENCY

	check_menu_item(
	        "<Angband>/Options/Graphics/Transparency",
	        use_transparency);

# endif  /* USE_TRANSPARENCY */
}

#endif /* USE_GRAPHICS */


/*
 * Construct a menu hierarchy using GtkItemFactory, setting up
 * callbacks and accelerators along the way, and return
 * a GtkMenuBar widget.
 */
GtkWidget *get_main_menu(term_data *td)
{
	GtkItemFactory *item_factory;
	GtkAccelGroup *accel_group;
	gint nmenu_items = sizeof(main_menu_items) / sizeof(main_menu_items[0]);


	/* XXX XXX Setup path names in the "Terms" and "Font" menus */
	setup_menu_paths();

	/* Allocate an accelerator group */
	accel_group = gtk_accel_group_new();
	g_assert(accel_group != NULL);

	/* Initialise the item factory */
	item_factory = gtk_item_factory_new(
	                       GTK_TYPE_MENU_BAR,
	                       "<Angband>",
	                       accel_group);
	g_assert(item_factory != NULL);

	/* Generate the menu items */
	gtk_item_factory_create_items(
	        item_factory,
	        nmenu_items,
	        main_menu_items,
	        NULL);

	/* Attach the new accelerator group to the window */
	gtk_window_add_accel_group(
	        GTK_WINDOW(td->window),
	        accel_group);

	/* Return the actual menu bar created */
	return (gtk_item_factory_get_widget(item_factory, "<Angband>"));
}


/*
 * Install callbacks to update menus
 */
static void add_menu_update_callbacks()
{
	GtkWidget *widget;

	/* Access the "File" menu */
	widget = get_widget_from_path("<Angband>/File");

	/* Paranoia */
	g_assert(widget != NULL);
	g_assert(GTK_IS_MENU(widget));

	/* Assign callback */
	gtk_signal_connect(
	        GTK_OBJECT(widget),
	        "show",
	        GTK_SIGNAL_FUNC(file_menu_update_handler),
	        NULL);

	/* Access the "Terms" menu */
	widget = get_widget_from_path("<Angband>/Terms");

	/* Paranoia */
	g_assert(widget != NULL);
	g_assert(GTK_IS_MENU(widget));

	/* Assign callback */
	gtk_signal_connect(
	        GTK_OBJECT(widget),
	        "show",
	        GTK_SIGNAL_FUNC(term_menu_update_handler),
	        NULL);

	/* Access the "Font" menu */
	widget = get_widget_from_path("<Angband>/Options/Font");

	/* Paranoia */
	g_assert(widget != NULL);
	g_assert(GTK_IS_MENU(widget));

	/* Assign callback */
	gtk_signal_connect(
	        GTK_OBJECT(widget),
	        "show",
	        GTK_SIGNAL_FUNC(font_menu_update_handler),
	        NULL);

	/* Access the "Misc" menu */
	widget = get_widget_from_path("<Angband>/Options/Misc");

	/* Paranoia */
	g_assert(widget != NULL);
	g_assert(GTK_IS_MENU(widget));

	/* Assign callback */
	gtk_signal_connect(
	        GTK_OBJECT(widget),
	        "show",
	        GTK_SIGNAL_FUNC(misc_menu_update_handler),
	        NULL);

#ifdef USE_GRAPHICS

	/* Access Graphics menu */
	widget = get_widget_from_path("<Angband>/Options/Graphics");

	/* Paranoia */
	g_assert(widget != NULL);
	g_assert(GTK_IS_MENU(widget));

	/* Assign callback */
	gtk_signal_connect(
	        GTK_OBJECT(widget),
	        "show",
	        GTK_SIGNAL_FUNC(graf_menu_update_handler),
	        NULL);

#endif /* USE_GRAPHICS */
}


/*
 * Create Gtk widgets for a terminal window and set up callbacks
 */
static void init_gtk_window(term_data *td, int i)
{
	GtkWidget *menu_bar = NULL, *box;
	cptr font;

	bool main_window = (i == 0) ? TRUE : FALSE;


	/* Create window */
	td->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

	/* Set title */
	gtk_window_set_title(GTK_WINDOW(td->window), td->name);


	/* Get default font for this term */
	font = get_default_font(i);

	/* Load font and initialise related term_data fields */
	load_font(td, font);


	/* Create drawing area */
	td->drawing_area = gtk_drawing_area_new();

	/* Set the size of the drawing area */
	gtk_drawing_area_size(
	        GTK_DRAWING_AREA(td->drawing_area),
	        td->cols * td->font_wid,
	        td->rows * td->font_hgt);

	/* Set geometry hints */
	term_data_set_geometry_hints(td);


	/* Install window event handlers */
	gtk_signal_connect(
	        GTK_OBJECT(td->window),
	        "delete_event",
	        GTK_SIGNAL_FUNC(delete_event_handler),
	        NULL);
	gtk_signal_connect(
	        GTK_OBJECT(td->window),
	        "key_press_event",
	        GTK_SIGNAL_FUNC(keypress_event_handler),
	        NULL);

	/* Destroying the Angband window terminates the game */
	if (main_window)
	{
		gtk_signal_connect(
		        GTK_OBJECT(td->window),
		        "destroy_event",
		        GTK_SIGNAL_FUNC(destroy_main_event_handler),
		        NULL);
	}

	/* The other windows are just hidden */
	else
	{
		gtk_signal_connect(
		        GTK_OBJECT(td->window),
		        "destroy_event",
		        GTK_SIGNAL_FUNC(destroy_sub_event_handler),
		        td);
	}


	/* Install drawing area event handlers */
	gtk_signal_connect(
	        GTK_OBJECT(td->drawing_area),
	        "realize",
	        GTK_SIGNAL_FUNC(realize_event_handler),
	        (gpointer)td);
	gtk_signal_connect(
	        GTK_OBJECT(td->drawing_area),
	        "show",
	        GTK_SIGNAL_FUNC(show_event_handler),
	        (gpointer)td);
	gtk_signal_connect(
	        GTK_OBJECT(td->drawing_area),
	        "hide",
	        GTK_SIGNAL_FUNC(hide_event_handler),
	        (gpointer)td);
	gtk_signal_connect(
	        GTK_OBJECT(td->drawing_area),
	        "size_allocate",
	        GTK_SIGNAL_FUNC(size_allocate_event_handler),
	        (gpointer)td);
	gtk_signal_connect(
	        GTK_OBJECT(td->drawing_area),
	        "expose_event",
	        GTK_SIGNAL_FUNC(expose_event_handler),
	        (gpointer)td);


	/* Create menu */
	if (main_window)
	{
		/* Build the main menu bar */
		menu_bar = get_main_menu(td);
		g_assert(menu_bar != NULL);

		/* Since it's tedious to scatter the menu update code around */
		add_menu_update_callbacks();
	}


	/* Pack the menu bar together with the main window */
	/* For vertical placement of the menu bar and the drawing area */
	box = gtk_vbox_new(FALSE, 0);

	/* Let the window widget own it */
	gtk_container_add(GTK_CONTAINER(td->window), box);

	/* The main window has a menu bar */
	if (main_window)
		gtk_box_pack_start(
		        GTK_BOX(box),
		        menu_bar,
		        FALSE,
		        FALSE,
		        NO_PADDING);

	/* And place the drawing area just beneath it */
	gtk_box_pack_start_defaults(GTK_BOX(box), td->drawing_area);


	/* Show the widgets - use of td->shown is a dirty hack XXX XXX */
	if (td->shown) gtk_widget_show_all(td->window);
}


/*
 * To be hooked into quit(). See z-util.c
 */
static void hook_quit(cptr str)
{
	/* Free menu paths dynamically allocated */
	free_menu_paths();

# ifdef USE_GRAPHICS

	/* Free pathname string */
	if (ANGBAND_DIR_XTRA_GRAF) string_free(ANGBAND_DIR_XTRA_GRAF);

# endif  /* USE_GRAPHICS */

	/* Terminate the program */
	gtk_exit(0);
}


#ifdef ANGBAND300

/*
 * Help message for this port
 */
const char help_gtk[] =
	"GTK for X11, subopts -n<windows>\n"
	"           -b(acking store off)\n"
#ifdef USE_GRAPHICS
	"           -g(raphics) -o(ld graphics) -s(moothscaling off) \n"
	"           -t(ransparency on)\n"
# ifdef USE_DOUBLE_TILES
	"           -w(ide tiles)\n"
# endif  /* USE_DOUBLE_TILES */
#endif /* USE_GRAPHICS */
	"           and standard GTK options";

#endif /* ANGBAND300 */


/*
 * Initialization function
 */
errr init_gtk(int argc, char **argv)
{
	int i;


	/* Initialize the environment */
	gtk_init(&argc, &argv);

	/* Activate hooks - Use gtk/glib interface throughout */
	ralloc_aux = hook_ralloc;
	rnfree_aux = hook_rnfree;
	quit_aux = hook_quit;
	core_aux = hook_quit;

	/* Parse args */
	for (i = 1; i < argc; i++)
	{
		/* Number of terminals displayed at start up */
		if (prefix(argv[i], "-n"))
		{
			num_term = atoi(&argv[i][2]);
			if (num_term > MAX_TERM_DATA) num_term = MAX_TERM_DATA;
			else if (num_term < 1) num_term = 1;
			continue;
		}

		/* Disable use of pixmaps as backing store */
		if (streq(argv[i], "-b"))
		{
			use_backing_store = FALSE;
			continue;
		}

#ifdef USE_GRAPHICS

		/* Requests "old" graphics */
		if (streq(argv[i], "-o"))
		{
			graf_mode_request = GRAF_MODE_OLD;
			continue;
		}

		/* Requests "new" graphics */
		if (streq(argv[i], "-g"))
		{
			graf_mode_request = GRAF_MODE_NEW;
			continue;
		}

# ifdef USE_DOUBLE_TILES

		/* Requests wide tile mode */
		if (streq(argv[i], "-w"))
		{
			use_bigtile = TRUE;
# ifdef TOME
			/* T.o.M.E. uses older version of the patch */
			arg_bigtile = TRUE;
# endif  /* TOME */
			continue;
		}

# endif  /* USE_DOUBLE_TILES */


		/* Enable transparency effect */
		if (streq(argv[i], "-t"))
		{
			use_transparency = TRUE;
			continue;
		}

		/* Disable smooth rescaling of tiles */
		if (streq(argv[i], "-s"))
		{
			smooth_rescaling_request = FALSE;
			continue;
		}

#endif /* USE_GRAPHICS */

		/* None of the above */
		plog_fmt("Ignoring option: %s", argv[i]);
	}

#ifdef USE_GRAPHICS

	{
		char path[1024];

		/* Build the "graf" path */
		path_build(path, 1024, ANGBAND_DIR_XTRA, "graf");

		/* Allocate the path */
		ANGBAND_DIR_XTRA_GRAF = string_make(path);
	}

#endif /* USE_GRAPHICS */

	/* Initialise colours */
	gdk_rgb_init();
	gtk_widget_set_default_colormap(gdk_rgb_get_cmap());
	gtk_widget_set_default_visual(gdk_rgb_get_visual());
	init_colours();

	/*
	 * Initialise the windows backwards, so that
	 * the Angband window comes in front
	 */
	for (i = MAX_TERM_DATA - 1; i >= 0; i--)
	{
		term_data *td = &data[i];

		/* Initialize the term_data */
		term_data_init(td, i);

		/* Hack - Set the shown flag, meaning "to be shown" XXX XXX */
		if (i < num_term) td->shown = TRUE;
		else td->shown = FALSE;

		/* Save global entry */
		angband_term[i] = Term;

		/* Init the window */
		init_gtk_window(td, i);
	}

	/* Activate the "Angband" window screen */
	Term_activate(&data[0].t);

#ifndef SAVEFILE_SCREEN

	/* Set the system suffix */
	ANGBAND_SYS = "gtk";

	/* Catch nasty signals */
	signals_init();

	/* Initialize */
	init_angband();

#ifndef OLD_SAVEFILE_CODE

	/* Hack - because this port has New/Open menus XXX */
	savefile[0] = '\0';

#endif /* !OLD_SAVEFILE_CODE */

	/* Prompt the user */
	prt("[Choose 'New' or 'Open' from the 'File' menu]", 23, 17);
	Term_fresh();

	/* Activate more hook */
	plog_aux = hook_plog;


	/* Processing loop */
	gtk_main();


	/* Free allocated memory */
	cleanup_angband();

	/* Stop now */
	quit(NULL);

#else /* !SAVEFILE_SCREEN */

	/* Activate more hook */
	plog_aux = hook_plog;

	/* It's too early to set this, but cannot do so elsewhere XXX XXX */
	game_in_progress = TRUE;

#endif /* !SAVEFILE_SCREEN */

	/* Success */
	return (0);
}

#endif /* USE_GTK */
