diff options
author | Bardur Arantsson <bardur@scientician.net> | 2010-01-08 20:28:34 +0100 |
---|---|---|
committer | Bardur Arantsson <bardur@scientician.net> | 2010-01-08 23:46:06 +0100 |
commit | 6aa48afdd57d03314fdf4be6c9da911c32277c84 (patch) | |
tree | 2dc401f9aae2dc6736d2fc3811c8f8099d3eabe6 /src/main-gtk2.c |
Import tome-2.3.5.
Diffstat (limited to 'src/main-gtk2.c')
-rw-r--r-- | src/main-gtk2.c | 5412 |
1 files changed, 5412 insertions, 0 deletions
diff --git a/src/main-gtk2.c b/src/main-gtk2.c new file mode 100644 index 00000000..ada45aa9 --- /dev/null +++ b/src/main-gtk2.c @@ -0,0 +1,5412 @@ +/* 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_GTK2 + +/* 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( + gpointer user_data, + guint user_action, + GtkWidget *was_clicked) +{ + /* Save current game */ + save_game_gtk(); + + /* It's done */ + quit(NULL); +} + + +/* + * Process File-Save menu command + */ +static void save_event_handler( + gpointer user_data, + guint user_action, + GtkWidget *was_clicked) +{ + /* 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( + gpointer user_data, + guint user_action, + GtkWidget *was_clicked) +{ + 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( + gpointer user_data, + guint user_action, + GtkWidget *widget) +{ + GtkWidget *font_selector; + + font_selector = gtk_font_selection_dialog_new("Select font"); +#if 0 // DGDGDGDG + 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)); +#endif +} + + +/* + * Process Terms-* menu command - hide/show terminal window + */ +static void term_event_handler( + gpointer user_data, + guint user_action, + GtkWidget *widget) +{ + term_data *td = &data[user_action]; + + /* 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( + gpointer user_data, + guint user_action, + GtkWidget *was_clicked) +{ + 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( + gpointer user_data, + guint user_action, + GtkWidget *was_clicked) +{ + /* Set request according to user selection */ + graf_mode_request = (int)user_action; + + /* + * 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( + gpointer user_data, + guint user_action, + GtkWidget *was_clicked) +{ + /* Set request according to user selection */ + dith_mode = (int)user_action; + + /* + * Hack - force redraw + */ + Term_key_push(KTRL('R')); +} + + +/* + * Toggles the graphics tile scaling mode (Fast/Smooth) + */ +static void change_smooth_mode_event_handler( + gpointer user_data, + guint user_action, + GtkWidget *was_clicked) +{ + /* (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( + gpointer user_data, + guint user_action, + GtkWidget *was_clicked) +{ + 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( + gpointer user_data, + guint user_aciton, + GtkWidget *was_clicked) +{ + /* 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( + gpointer user_data, + guint user_action, + GtkWidget *was_clicked) +{ + 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) +{ +#if 1 + int i, mc, ms, mo, mx; + + char msg[128]; + + /* Hack - do not do anything until the player picks from the menu */ + if (!game_in_progress) return (TRUE); + + /* Hack - Ignore parameters */ + (void) widget; + (void) user_data; + + /* 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); + } + + /* Hack - the cursor keys */ + case GDK_Up: + { + Term_keypress('8'); + return (TRUE); + } + + case GDK_Down: + { + Term_keypress('2'); + return (TRUE); + } + + case GDK_Left: + { + Term_keypress('4'); + return (TRUE); + } + + case GDK_Right: + { + Term_keypress('6'); + 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); + +#else + 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; + printf("0=%d 9=%d;; keyval=%d; mc=%d, ms=%d ::=:: ", GDK_KP_0, GDK_KP_9, event->keyval, mc, ms); + /* Enqueue the normal key(s) */ + for (i = 0; i < event->length; i++) printf("%d;", event->string[i]); + printf("\n"); + + /* + * Hack XXX + * Parse shifted numeric (keypad) keys specially. + */ + if ((event->state & GDK_SHIFT_MASK) + && (event->keyval >= GDK_KP_Left) && (event->keyval <= GDK_KP_Delete)) + { + /* Build the macro trigger string */ + strnfmt(msg, 128, "%cS_%X%c", 31, event->keyval, 13); + printf("%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); +#endif +} + + +/* + * 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>", NULL + }, +#ifndef SAVEFILE_SCREEN + { "/File/New", "<mod1>N", + new_event_handler, 0, NULL, NULL }, + { "/File/Open", "<mod1>O", + open_event_handler, 0, NULL, NULL }, + { "/File/sep1", NULL, + NULL, 0, "<Separator>", NULL }, +#endif /* !SAVEFILE_SCREEN */ + { "/File/Save", "<mod1>S", + save_event_handler, 0, NULL, NULL }, + { "/File/Quit", "<mod1>Q", + quit_event_handler, 0, NULL, NULL }, + + /* "Terms" menu */ + { "/Terms", NULL, + NULL, 0, "<Branch>", NULL }, + /* XXX XXX XXX NULL's are replaced by the program */ + { NULL, "<mod1>0", + term_event_handler, 0, "<CheckItem>", NULL }, + { NULL, "<mod1>1", + term_event_handler, 1, "<CheckItem>", NULL }, + { NULL, "<mod1>2", + term_event_handler, 2, "<CheckItem>", NULL }, + { NULL, "<mod1>3", + term_event_handler, 3, "<CheckItem>", NULL }, + { NULL, "<mod1>4", + term_event_handler, 4, "<CheckItem>", NULL }, + { NULL, "<mod1>5", + term_event_handler, 5, "<CheckItem>", NULL }, + { NULL, "<mod1>6", + term_event_handler, 6, "<CheckItem>", NULL }, + { NULL, "<mod1>7", + term_event_handler, 7, "<CheckItem>", NULL }, + + /* "Options" menu */ + { "/Options", NULL, + NULL, 0, "<Branch>", NULL }, + + /* "Font" submenu */ + { "/Options/Font", NULL, + NULL, 0, "<Branch>", NULL }, + /* XXX XXX XXX Again, NULL's are filled by the program */ + { NULL, NULL, + change_font_event_handler, 0, NULL, NULL }, + { NULL, NULL, + change_font_event_handler, 1, NULL, NULL }, + { NULL, NULL, + change_font_event_handler, 2, NULL, NULL }, + { NULL, NULL, + change_font_event_handler, 3, NULL, NULL }, + { NULL, NULL, + change_font_event_handler, 4, NULL, NULL }, + { NULL, NULL, + change_font_event_handler, 5, NULL, NULL }, + { NULL, NULL, + change_font_event_handler, 6, NULL, NULL }, + { NULL, NULL, + change_font_event_handler, 7, NULL, NULL }, + +#ifdef USE_GRAPHICS + + /* "Graphics" submenu */ + { "/Options/Graphics", NULL, + NULL, 0, "<Branch>", NULL }, + { "/Options/Graphics/None", NULL, + change_graf_mode_event_handler, GRAF_MODE_NONE, "<CheckItem>", NULL }, + { "/Options/Graphics/Old", NULL, + change_graf_mode_event_handler, GRAF_MODE_OLD, "<CheckItem>", NULL }, + { "/Options/Graphics/New", NULL, + change_graf_mode_event_handler, GRAF_MODE_NEW, "<CheckItem>", NULL }, +# ifdef USE_DOUBLE_TILES + { "/Options/Graphics/sep3", NULL, + NULL, 0, "<Separator>", NULL }, + { "/Options/Graphics/Wide tiles", NULL, + change_wide_tile_mode_event_handler, 0, "<CheckItem>", NULL }, +# endif /* USE_DOUBLE_TILES */ + { "/Options/Graphics/sep1", NULL, + NULL, 0, "<Separator>", NULL }, + { "/Options/Graphics/Dither if <= 8bpp", NULL, + change_dith_mode_event_handler, GDK_RGB_DITHER_NORMAL, "<CheckItem>", NULL }, + { "/Options/Graphics/Dither if <= 16bpp", NULL, + change_dith_mode_event_handler, GDK_RGB_DITHER_MAX, "<CheckItem>", NULL }, + { "/Options/Graphics/sep2", NULL, + NULL, 0, "<Separator>", NULL }, + { "/Options/Graphics/Smoothing", NULL, + change_smooth_mode_event_handler, 0, "<CheckItem>", NULL }, +# ifdef USE_TRANSPARENCY + { "/Options/Graphics/Transparency", NULL, + change_trans_mode_event_handler, 0, "<CheckItem>", NULL }, +# endif /* USE_TRANSPARENCY */ + +#endif /* USE_GRAPHICS */ + + /* "Misc" submenu */ + { "/Options/Misc", NULL, + NULL, 0, "<Branch>", NULL }, + { "/Options/Misc/Backing store", NULL, + change_backing_store_event_handler, 0, "<CheckItem>", NULL }, +}; + + +/* + * 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 */ + term_entry[i].path = (gchar*)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 */ + font_entry[i].path = (gchar*)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_gtk2(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_GTK2 */ |