From: jwalz Date: Sat, 5 Jan 2002 21:06:03 +0000 (+0000) Subject: *** empty log message *** X-Git-Tag: MOVE2GIT~3538 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=51c77c339e7a21823a5269f83250cb0e8546b280;p=nethack *** empty log message *** --- diff --git a/win/X11/winmap.c b/win/X11/winmap.c new file mode 100644 index 000000000..b028806a5 --- /dev/null +++ b/win/X11/winmap.c @@ -0,0 +1,1644 @@ +/* SCCS Id: @(#)winmap.c 3.3 96/04/05 */ +/* Copyright (c) Dean Luick, 1992 */ +/* NetHack may be freely redistributed. See license for details. */ + +/* + * This file contains: + * + global functions print_glyph() and cliparound() + * + the map window routines + * + the char and pointer input routines + * + * Notes: + * + We don't really have a good way to get the compiled ROWNO and + * COLNO as defaults. They are hardwired to the current "correct" + * values in the Window widget. I am _not_ in favor of including + * some nethack include file for Window.c. + */ + +#ifndef SYSV +#define PRESERVE_NO_SYSV /* X11 include files may define SYSV */ +#endif + +#include +#include +#include +#include +#include +#include +#include + +#ifdef PRESERVE_NO_SYSV +# ifdef SYSV +# undef SYSV +# endif +# undef PRESERVE_NO_SYSV +#endif + +#include "xwindow.h" /* map widget declarations */ + +#include "hack.h" +#include "dlb.h" +#include "winX.h" + +#ifdef USE_XPM +#include +#endif + + +/* from tile.c */ +extern short glyph2tile[]; +extern int total_tiles_used; + +/* Define these if you really want a lot of junk on your screen. */ +/* #define VERBOSE */ /* print various info & events as they happen */ +/* #define VERBOSE_UPDATE */ /* print screen update bounds */ +/* #define VERBOSE_INPUT */ /* print input events */ + + +#define USE_WHITE /* almost always use white as a tile cursor border */ + + +static boolean FDECL(init_tiles, (struct xwindow *)); +static void FDECL(set_button_values, (Widget,int,int,unsigned)); +static void FDECL(map_check_size_change, (struct xwindow *)); +static void FDECL(map_update, (struct xwindow *,int,int,int,int,BOOLEAN_P)); +static void FDECL(init_text, (struct xwindow *)); +static void FDECL(map_exposed, (Widget,XtPointer,XtPointer)); +static void FDECL(set_gc, (Widget,Font,char *,Pixel,GC *,GC *)); +static void FDECL(get_text_gc, (struct xwindow *,Font)); +static void FDECL(get_char_info, (struct xwindow *)); +static void FDECL(display_cursor, (struct xwindow *)); + +/* Global functions ======================================================== */ + +void +X11_print_glyph(window, x, y, glyph) + winid window; + xchar x, y; + int glyph; +{ + struct map_info_t *map_info; + boolean update_bbox; + + check_winid(window); + if (window_list[window].type != NHW_MAP) { + impossible("print_glyph: can (currently) only print to map windows"); + return; + } + map_info = window_list[window].map_information; + + if (map_info->is_tile) { + unsigned short *t_ptr; + + t_ptr = &map_info->mtype.tile_map->glyphs[y][x]; + + if (*t_ptr != glyph) { + *t_ptr = glyph; + update_bbox = TRUE; + } else + update_bbox = FALSE; + + } else { + uchar ch; + register unsigned char *ch_ptr; + int color,och; + unsigned special; +#ifdef TEXTCOLOR + register unsigned char *co_ptr; +#endif + /* map glyph to character and color */ + mapglyph(glyph, &och, &color, &special, x, y); + ch = (uchar)och; + + /* Only update if we need to. */ + ch_ptr = &map_info->mtype.text_map->text[y][x]; + +#ifdef TEXTCOLOR + co_ptr = &map_info->mtype.text_map->colors[y][x]; + if (*ch_ptr != ch || *co_ptr != color) +#else + if (*ch_ptr != ch) +#endif + { + *ch_ptr = ch; +#ifdef TEXTCOLOR + *co_ptr = color; +#endif + update_bbox = TRUE; + } else + update_bbox = FALSE; + +#undef zap_color +#undef cmap_color +#undef obj_color +#undef mon_color +#undef pet_color + } + + if (update_bbox) { /* update row bbox */ + if ((uchar) x < map_info->t_start[y]) map_info->t_start[y] = x; + if ((uchar) x > map_info->t_stop[y]) map_info->t_stop[y] = x; + } +} + +#ifdef CLIPPING +/* + * The is the tty clip call. Since X can resize at any time, we can't depend + * on this being defined. + */ +/*ARGSUSED*/ +void X11_cliparound(x, y) int x, y; { } +#endif /* CLIPPING */ + +/* End global functions ==================================================== */ + +#include "tile2x11.h" + +/* + * We're expecting to never read more than one tile file per session. + * If this is false, then we can make an array of this information, + * or just keep it on a per-window basis. + */ +Pixmap tile_pixmap = None; +static int tile_width; +static int tile_height; +static int tile_count; +static XImage *tile_image = 0; + +/* + * This structure is used for small bitmaps that are used for annotating + * tiles. For example, a "heart" annotates pets. + */ +struct tile_annotation { + Pixmap bitmap; + Pixel foreground; + unsigned int width, height; + int hotx, hoty; /* not currently used */ +}; + +static struct tile_annotation pet_annotation; + +static void +init_annotation(annotation, filename, colorpixel) +struct tile_annotation *annotation; +char *filename; +Pixel colorpixel; +{ + Display *dpy = XtDisplay(toplevel); + + if (0!=XReadBitmapFile(dpy, XtWindow(toplevel), filename, + &annotation->width, &annotation->height, &annotation->bitmap, + &annotation->hotx, &annotation->hoty)) { + char buf[BUFSZ]; + Sprintf(buf, "Failed to load %s", filename); + X11_raw_print(buf); + } + + annotation->foreground = colorpixel; +} + +/* + * Put the tile image on the server. + * + * We can't send the image to the server until the top level + * is realized. When the tile file is first processed, the top + * level is not realized. This routine is called after we + * realize the top level, but before we start resizing the + * map viewport. + */ +void +post_process_tiles() +{ + Display *dpy = XtDisplay(toplevel); + unsigned int width, height; + + height = tile_height * tile_count; + width = tile_width; + + if (tile_image == 0) return; /* no tiles */ + + tile_pixmap = XCreatePixmap(dpy, XtWindow(toplevel), + width, + height, + DefaultDepth(dpy, DefaultScreen(dpy))); + + XPutImage(dpy, tile_pixmap, + DefaultGC(dpy, DefaultScreen(dpy)), + tile_image, + 0,0, 0,0, /* src, dest top left */ + width, + height); + + XDestroyImage(tile_image); /* data bytes free'd also */ + tile_image = 0; + + init_annotation(&pet_annotation, + appResources.pet_mark_bitmap, appResources.pet_mark_color); +} + + +/* + * Open and read the tile file. Return TRUE if there were no problems. + * Return FALSE otherwise. + */ +static boolean +init_tiles(wp) + struct xwindow *wp; +{ +#ifndef USE_XPM + FILE *fp = (FILE *)0; + x11_header header; + unsigned char *cp, *colormap = (unsigned char *)0; + unsigned char *tb, *tile_bytes = (unsigned char *)0; + int size; + XColor *colors = (XColor *)0; + int i, x, y; + int bitmap_pad; + unsigned int image_height, image_width; + int ddepth; +#endif + Display *dpy = XtDisplay(toplevel); + Screen *screen = DefaultScreenOfDisplay(dpy); + struct map_info_t *map_info = (struct map_info_t *)0; + struct tile_map_info_t *tile_info = (struct tile_map_info_t *)0; + boolean result = TRUE; + XGCValues values; + XtGCMask mask; + + /* already have tile information */ + if (tile_pixmap != None) goto tiledone; + + map_info = wp->map_information; + tile_info = map_info->mtype.tile_map = + (struct tile_map_info_t *) alloc(sizeof(struct tile_map_info_t)); + (void) memset((genericptr_t) tile_info, 0, + sizeof(struct tile_map_info_t)); + +#ifdef USE_XPM + { + char buf[BUFSZ]; + XpmAttributes attributes; + int errorcode; + + attributes.valuemask = XpmCloseness; + attributes.closeness = 25000; + + errorcode=XpmReadFileToImage(dpy,appResources.tile_file,&tile_image,0,&attributes); + + if (errorcode == XpmColorFailed) { + Sprintf(buf, "Insufficient colors available to load %s.",appResources.tile_file); + X11_raw_print(buf); + X11_raw_print("Try closing other colorful applications and restart."); + X11_raw_print("Attempting to load with inferior colors."); + attributes.closeness = 50000; + errorcode=XpmReadFileToImage(dpy,appResources.tile_file,&tile_image,0,&attributes); + } + + if (errorcode!=XpmSuccess) { + if (errorcode == XpmColorFailed) { + Sprintf(buf, "Insufficient colors available to load %s.",appResources.tile_file); + X11_raw_print(buf); + } else { + Sprintf(buf, "Failed to load %s: %s",appResources.tile_file, + XpmGetErrorString(errorcode)); + X11_raw_print(buf); + } + result = FALSE; + X11_raw_print("Switching to text-based mode."); + goto tiledone; + } + + if (tile_image->height%total_tiles_used != 0) { + Sprintf(buf, + "%s is not a multiple of %d (the number of tiles) pixels high", + appResources.tile_file, total_tiles_used); + X11_raw_print(buf); + XDestroyImage(tile_image); + tile_image = 0; + result = FALSE; + goto tiledone; + } + + /* infer tile dimensions from image size */ + tile_count=total_tiles_used; + tile_width=tile_image->width; + tile_height=tile_image->height/tile_count; + } +#else + /* any less than 16 colours makes tiles useless */ + ddepth = DefaultDepthOfScreen(screen); + if (ddepth < 4) { + X11_raw_print("need a screen depth of at least 4"); + result = FALSE; + goto tiledone; + } + + fp = fopen_datafile(appResources.tile_file, RDBMODE, FALSE); + if (!fp) { + X11_raw_print("can't open tile file"); + result = FALSE; + goto tiledone; + } + + if (fread((char *) &header, sizeof(header), 1, fp) != 1) { + X11_raw_print("read of header failed"); + result = FALSE; + goto tiledone; + } + +# ifdef VERBOSE + fprintf(stderr, "X11 tile file:\n version %ld\n ncolors %ld\n tile width %ld\n tile height %ld\n ntiles %ld\n", + header.version, + header.ncolors, + header.tile_width, + header.tile_height, + header.ntiles); +# endif + + size = 3*header.ncolors; + colormap = (unsigned char *) alloc((unsigned)size); + if (fread((char *) colormap, 1, size, fp) != size) { + X11_raw_print("read of colormap failed"); + result = FALSE; + goto tiledone; + } + +/* defined in decl.h - these are _not_ good defines to have */ +#undef red +#undef green +#undef blue + + colors = (XColor *) alloc(sizeof(XColor) * (unsigned)header.ncolors); + for (i = 0; i < header.ncolors; i++) { + cp = colormap + (3 * i); + colors[i].red = cp[0] * 256; + colors[i].green = cp[1] * 256; + colors[i].blue = cp[2] * 256; + colors[i].flags = 0; + colors[i].pixel = 0; + + if (!XAllocColor(dpy, DefaultColormapOfScreen(screen), &colors[i]) && + !nhApproxColor(screen, DefaultColormapOfScreen(screen), + (char *)0, &colors[i])) { + char buf[BUFSZ]; + Sprintf(buf, "%dth out of %ld color allocation failed", + i, header.ncolors); + X11_raw_print(buf); + result = FALSE; + goto tiledone; + } + } + + size = header.tile_height * header.tile_width; + /* + * This alloc() and the one below require 32-bit ints, since tile_bytes + * is currently ~200k and alloc() takes an int + */ + tile_bytes = (unsigned char *) alloc((unsigned)header.ntiles*size); + tile_count = header.ntiles; + if (fread((char *) tile_bytes, size, tile_count, fp) != tile_count) { + X11_raw_print("read of tile bytes failed"); + result = FALSE; + goto tiledone; + } + + if (header.ntiles < total_tiles_used) { + char buf[BUFSZ]; + Sprintf(buf, "tile file incomplete, expecting %d tiles, found %lu", + total_tiles_used, header.ntiles); + X11_raw_print(buf); + result = FALSE; + goto tiledone; + } + + + if (appResources.double_tile_size) { + tile_width = 2*header.tile_width; + tile_height = 2*header.tile_height; + } else { + tile_width = header.tile_width; + tile_height = header.tile_height; + } + + image_height = tile_height * tile_count; + image_width = tile_width; + + /* calculate bitmap_pad */ + if (ddepth > 16) + bitmap_pad = 32; + else if (ddepth > 8) + bitmap_pad = 16; + else + bitmap_pad = 8; + + tile_image = XCreateImage(dpy, DefaultVisualOfScreen(screen), + ddepth, /* depth */ + ZPixmap, /* format */ + 0, /* offset */ + 0, /* data */ + image_width, /* width */ + image_height, /* height */ + bitmap_pad, /* bit pad */ + 0); /* bytes_per_line */ + + if (!tile_image) + impossible("init_tiles: insufficient memory to create image"); + + /* now we know the physical memory requirements, we can allocate space */ + tile_image->data = + (char *) alloc((unsigned)tile_image->bytes_per_line * image_height); + + if (appResources.double_tile_size) { + unsigned long *expanded_row = + (unsigned long *)alloc(sizeof(unsigned long)*(unsigned)tile_width); + + tb = tile_bytes; + for (y = 0; y < image_height; y++) { + for (x = 0; x < header.tile_width; x++) + expanded_row[2*x] = + expanded_row[(2*x)+1] = colors[*tb++].pixel; + + for (x = 0; x < tile_width; x++) + XPutPixel(tile_image, x, y, expanded_row[x]); + + y++; /* duplicate row */ + for (x = 0; x < tile_width; x++) + XPutPixel(tile_image, x, y, expanded_row[x]); + } + free((genericptr_t)expanded_row); + + } else { + + for (tb = tile_bytes, y = 0; y < image_height; y++) + for (x = 0; x < image_width; x++, tb++) + XPutPixel(tile_image, x, y, colors[*tb].pixel); + } +#endif /* USE_XPM */ + + /* fake an inverted tile by drawing a border around the edges */ +#ifdef USE_WHITE + /* use white or black as the border */ + mask = GCFunction | GCForeground | GCGraphicsExposures; + values.graphics_exposures = False; + values.foreground = WhitePixelOfScreen(screen); + values.function = GXcopy; + tile_info->white_gc = XtGetGC(wp->w, mask, &values); + values.graphics_exposures = False; + values.foreground = BlackPixelOfScreen(screen); + values.function = GXcopy; + tile_info->black_gc = XtGetGC(wp->w, mask, &values); +#else + /* + * Use xor so we don't have to check for special colors. Xor white + * against the upper left pixel of the corridor so that we have a + * white rectangle when in a corridor. + */ + mask = GCFunction | GCForeground | GCGraphicsExposures; + values.graphics_exposures = False; +#if 1 + values.foreground = WhitePixelOfScreen(screen) ^ + XGetPixel(tile_image, 0, tile_height*glyph2tile[cmap_to_glyph(S_corr)]); +#else + values.foreground = ~((unsigned long) 0); +#endif + values.function = GXxor; + tile_info->white_gc = XtGetGC(wp->w, mask, &values); + + mask = GCFunction | GCGraphicsExposures; + values.function = GXCopy; + values.graphics_exposures = False; + tile_info->black_gc = XtGetGC(wp->w, mask, &values); +#endif + +tiledone: +#ifndef USE_XPM + if (fp) (void) fclose(fp); + if (colormap) free((genericptr_t)colormap); + if (tile_bytes) free((genericptr_t)tile_bytes); + if (colors) free((genericptr_t)colors); +#endif + + if (result) { /* succeeded */ + map_info->square_height = tile_height; + map_info->square_width = tile_width; + map_info->square_ascent = 0; + map_info->square_lbearing = 0; + } else { + if (tile_info) free((genericptr_t)tile_info); + tile_info = 0; + } + + return result; +} + + +/* + * Make sure the map's cursor is always visible. + */ +void +check_cursor_visibility(wp) + struct xwindow *wp; +{ + Arg arg[2]; + Widget viewport, horiz_sb, vert_sb; + float top, shown, cursor_middle; + Boolean do_call, adjusted = False; +#ifdef VERBOSE + char *s; +#endif + + viewport = XtParent(wp->w); + horiz_sb = XtNameToWidget(viewport, "horizontal"); + vert_sb = XtNameToWidget(viewport, "vertical"); + +/* All values are relative to currently visible area */ + +#define V_BORDER 0.3 /* if this far from vert edge, shift */ +#define H_BORDER 0.3 /* if this from from horiz edge, shift */ + +#define H_DELTA 0.4 /* distance of horiz shift */ +#define V_DELTA 0.4 /* distance of vert shift */ + + if (horiz_sb) { + XtSetArg(arg[0], XtNshown, &shown); + XtSetArg(arg[1], XtNtopOfThumb, &top); + XtGetValues(horiz_sb, arg, TWO); + + /* [ALI] Don't assume map widget is the same size as actual map */ + cursor_middle = (wp->cursx + 0.5) * wp->map_information->square_width / + wp->pixel_width; + do_call = True; + +#ifdef VERBOSE + if (cursor_middle < top) { + s = " outside left"; + } else if (cursor_middle < top + shown*H_BORDER) { + s = " close to left"; + } else if (cursor_middle > (top + shown)) { + s = " outside right"; + } else if (cursor_middle > (top + shown - shown*H_BORDER)) { + s = " close to right"; + } else { + s = ""; + } + printf("Horiz: shown = %3.2f, top = %3.2f%s", shown, top, s); +#endif + + if (cursor_middle < top) { + top = cursor_middle - shown*H_DELTA; + if (top < 0.0) top = 0.0; + } else if (cursor_middle < top + shown*H_BORDER) { + top -= shown*H_DELTA; + if (top < 0.0) top = 0.0; + } else if (cursor_middle > (top + shown)) { + top = cursor_middle - shown*H_DELTA; + if (top < 0.0) top = 0.0; + if (top + shown > 1.0) top = 1.0 - shown; + } else if (cursor_middle > (top + shown - shown*H_BORDER)) { + top += shown*H_DELTA; + if (top + shown > 1.0) top = 1.0 - shown; + } else { + do_call = False; + } + + if (do_call) { + XtCallCallbacks(horiz_sb, XtNjumpProc, &top); + adjusted = True; + } + } + + if (vert_sb) { + XtSetArg(arg[0], XtNshown, &shown); + XtSetArg(arg[1], XtNtopOfThumb, &top); + XtGetValues(vert_sb, arg, TWO); + + cursor_middle = (wp->cursy + 0.5) * wp->map_information->square_height / + wp->pixel_height; + do_call = True; + +#ifdef VERBOSE + if (cursor_middle < top) { + s = " above top"; + } else if (cursor_middle < top + shown*V_BORDER) { + s = " close to top"; + } else if (cursor_middle > (top + shown)) { + s = " below bottom"; + } else if (cursor_middle > (top + shown - shown*V_BORDER)) { + s = " close to bottom"; + } else { + s = ""; + } + printf("%sVert: shown = %3.2f, top = %3.2f%s", + horiz_sb ? "; " : "", shown, top, s); +#endif + + if (cursor_middle < top) { + top = cursor_middle - shown*V_DELTA; + if (top < 0.0) top = 0.0; + } else if (cursor_middle < top + shown*V_BORDER) { + top -= shown*V_DELTA; + if (top < 0.0) top = 0.0; + } else if (cursor_middle > (top + shown)) { + top = cursor_middle - shown*V_DELTA; + if (top < 0.0) top = 0.0; + if (top + shown > 1.0) top = 1.0 - shown; + } else if (cursor_middle > (top + shown - shown*V_BORDER)) { + top += shown*V_DELTA; + if (top + shown > 1.0) top = 1.0 - shown; + } else { + do_call = False; + } + + if (do_call) { + XtCallCallbacks(vert_sb, XtNjumpProc, &top); + adjusted = True; + } + } + + /* make sure cursor is displayed during dowhatis.. */ + if (adjusted) display_cursor(wp); + +#ifdef VERBOSE + if (horiz_sb || vert_sb) printf("\n"); +#endif +} + + +/* + * Check to see if the viewport has grown smaller. If so, then we want to make + * sure that the cursor is still on the screen. We do this to keep the cursor + * on the screen when the user resizes the nethack window. + */ +static void +map_check_size_change(wp) + struct xwindow *wp; +{ + struct map_info_t *map_info = wp->map_information; + Arg arg[2]; + Dimension new_width, new_height; + Widget viewport; + + viewport = XtParent(wp->w); + + XtSetArg(arg[0], XtNwidth, &new_width); + XtSetArg(arg[1], XtNheight, &new_height); + XtGetValues(viewport, arg, TWO); + + /* Only do cursor check if new size is smaller. */ + if (new_width < map_info->viewport_width + || new_height < map_info->viewport_height) { + /* [ALI] If the viewport was larger than the map (and so the map + * widget was contrained to be larger than the actual map) then we + * may be able to shrink the map widget as the viewport shrinks. + */ + wp->pixel_width = map_info->square_width * COLNO; + if (wp->pixel_width < new_width) + wp->pixel_width = new_width; + wp->pixel_height = map_info->square_height * ROWNO; + if (wp->pixel_height < new_height) + wp->pixel_height = new_height; + XtSetArg(arg[0], XtNwidth, wp->pixel_width); + XtSetArg(arg[1], XtNheight, wp->pixel_height); + XtSetValues(wp->w, arg, TWO); + + check_cursor_visibility(wp); + } + + map_info->viewport_width = new_width; + map_info->viewport_height = new_height; + + /* [ALI] These may have changed if the user has re-sized the viewport */ + XtSetArg(arg[0], XtNwidth, &wp->pixel_width); + XtSetArg(arg[1], XtNheight, &wp->pixel_height); + XtGetValues(wp->w, arg, TWO); +} + +/* + * Fill in parameters "regular" and "inverse" with newly created GCs. + * Using the given background pixel and the foreground pixel optained + * by querying the widget with the resource name. + */ +static void +set_gc(w, font, resource_name, bgpixel, regular, inverse) + Widget w; + Font font; + char *resource_name; + Pixel bgpixel; + GC *regular, *inverse; +{ + XGCValues values; + XtGCMask mask = GCFunction | GCForeground | GCBackground | GCFont; + Pixel curpixel; + Arg arg[1]; + + XtSetArg(arg[0], resource_name, &curpixel); + XtGetValues(w, arg, ONE); + + values.foreground = curpixel; + values.background = bgpixel; + values.function = GXcopy; + values.font = font; + *regular = XtGetGC(w, mask, &values); + values.foreground = bgpixel; + values.background = curpixel; + values.function = GXcopy; + values.font = font; + *inverse = XtGetGC(w, mask, &values); +} + +/* + * Create the GC's for each color. + * + * I'm not sure if it is a good idea to have a GC for each color (and + * inverse). It might be faster to just modify the foreground and + * background colors on the current GC as needed. + */ +static void +get_text_gc(wp, font) + struct xwindow *wp; + Font font; +{ + struct map_info_t *map_info = wp->map_information; + Pixel bgpixel; + Arg arg[1]; + + /* Get background pixel. */ + XtSetArg(arg[0], XtNbackground, &bgpixel); + XtGetValues(wp->w, arg, ONE); + +#ifdef TEXTCOLOR +#define set_color_gc(nh_color, resource_name) \ + set_gc(wp->w, font, resource_name, bgpixel, \ + &map_info->mtype.text_map->color_gcs[nh_color], \ + &map_info->mtype.text_map->inv_color_gcs[nh_color]); + + set_color_gc(CLR_BLACK, XtNblack); + set_color_gc(CLR_RED, XtNred); + set_color_gc(CLR_GREEN, XtNgreen); + set_color_gc(CLR_BROWN, XtNbrown); + set_color_gc(CLR_BLUE, XtNblue); + set_color_gc(CLR_MAGENTA, XtNmagenta); + set_color_gc(CLR_CYAN, XtNcyan); + set_color_gc(CLR_GRAY, XtNgray); + set_color_gc(NO_COLOR, XtNforeground); + set_color_gc(CLR_ORANGE, XtNorange); + set_color_gc(CLR_BRIGHT_GREEN, XtNbright_green); + set_color_gc(CLR_YELLOW, XtNyellow); + set_color_gc(CLR_BRIGHT_BLUE, XtNbright_blue); + set_color_gc(CLR_BRIGHT_MAGENTA, XtNbright_magenta); + set_color_gc(CLR_BRIGHT_CYAN, XtNbright_cyan); + set_color_gc(CLR_WHITE, XtNwhite); +#else + set_gc(wp->w, font, XtNforeground, bgpixel, + &map_info->mtype.text_map->copy_gc, + &map_info->mtype.text_map->inv_copy_gc); +#endif +} + + +/* + * Display the cursor on the map window. + */ +static void +display_cursor(wp) + struct xwindow *wp; +{ + /* Redisplay the cursor location inverted. */ + map_update(wp, wp->cursy, wp->cursy, wp->cursx, wp->cursx, TRUE); +} + + +/* + * Check if there are any changed characters. If so, then plaster them on + * the screen. + */ +void +display_map_window(wp) + struct xwindow *wp; +{ + register int row; + struct map_info_t *map_info = wp->map_information; + + /* + * If the previous cursor position is not the same as the current + * cursor position, then update the old cursor position. + */ + if (wp->prevx != wp->cursx || wp->prevy != wp->cursy) { + register unsigned int x = wp->prevx, y = wp->prevy; + if (x < map_info->t_start[y]) map_info->t_start[y] = x; + if (x > map_info->t_stop[y]) map_info->t_stop[y] = x; + } + + for (row = 0; row < ROWNO; row++) { + if (map_info->t_start[row] <= map_info->t_stop[row]) { + map_update(wp, row, row, + (int) map_info->t_start[row], + (int) map_info->t_stop[row], FALSE); + map_info->t_start[row] = COLNO-1; + map_info->t_stop[row] = 0; + } + } + display_cursor(wp); + wp->prevx = wp->cursx; /* adjust old cursor position */ + wp->prevy = wp->cursy; +} + +/* + * Set all map tiles to S_stone + */ +static void +map_all_stone(map_info) +struct map_info_t *map_info; +{ + int i; + unsigned short *sp, stone; + stone = cmap_to_glyph(S_stone); + + for (sp = (unsigned short *) map_info->mtype.tile_map->glyphs, i = 0; + i < ROWNO*COLNO; sp++, i++) + + *sp = stone; +} + +/* + * Fill the saved screen characters with the "clear" tile or character. + * + * Flush out everything by resetting the "new" bounds and calling + * display_map_window(). + */ +void +clear_map_window(wp) + struct xwindow *wp; +{ + struct map_info_t *map_info = wp->map_information; + + if (map_info->is_tile) { + map_all_stone(map_info); + } else { + /* Fill text with spaces, and update */ + (void) memset((genericptr_t) map_info->mtype.text_map->text, ' ', + sizeof(map_info->mtype.text_map->text)); +#ifdef TEXTCOLOR + (void) memset((genericptr_t) map_info->mtype.text_map->colors, NO_COLOR, + sizeof(map_info->mtype.text_map->colors)); +#endif + } + + /* force a full update */ + (void) memset((genericptr_t) map_info->t_start, (char) 0, + sizeof(map_info->t_start)); + (void) memset((genericptr_t) map_info->t_stop, (char) COLNO-1, + sizeof(map_info->t_stop)); + display_map_window(wp); +} + +/* + * Retreive the font associated with the map window and save attributes + * that are used when updating it. + */ +static void +get_char_info(wp) + struct xwindow *wp; +{ + XFontStruct *fs; + struct map_info_t *map_info = wp->map_information; + + fs = WindowFontStruct(wp->w); + map_info->square_width = fs->max_bounds.width; + map_info->square_height = fs->max_bounds.ascent + fs->max_bounds.descent; + map_info->square_ascent = fs->max_bounds.ascent; + map_info->square_lbearing = -fs->min_bounds.lbearing; + +#ifdef VERBOSE + printf("Font information:\n"); + printf("fid = %d, direction = %d\n", fs->fid, fs->direction); + printf("first = %d, last = %d\n", + fs->min_char_or_byte2, fs->max_char_or_byte2); + printf("all chars exist? %s\n", fs->all_chars_exist?"yes":"no"); + printf("min_bounds:lb=%d rb=%d width=%d asc=%d des=%d attr=%d\n", + fs->min_bounds.lbearing, fs->min_bounds.rbearing, + fs->min_bounds.width, fs->min_bounds.ascent, + fs->min_bounds.descent, fs->min_bounds.attributes); + printf("max_bounds:lb=%d rb=%d width=%d asc=%d des=%d attr=%d\n", + fs->max_bounds.lbearing, fs->max_bounds.rbearing, + fs->max_bounds.width, fs->max_bounds.ascent, + fs->max_bounds.descent, fs->max_bounds.attributes); + printf("per_char = 0x%x\n", fs->per_char); + printf("Text: (max) width = %d, height = %d\n", + map_info->square_width, map_info->square_height); +#endif + + if (fs->min_bounds.width != fs->max_bounds.width) + X11_raw_print("Warning: map font is not monospaced!"); +} + +/* + * keyhit buffer + */ +#define INBUF_SIZE 64 +int inbuf[INBUF_SIZE]; +int incount = 0; +int inptr = 0; /* points to valid data */ + + +/* + * Keyboard and button event handler for map window. + */ +void +map_input(w, event, params, num_params) + Widget w; + XEvent *event; + String *params; + Cardinal *num_params; +{ + XKeyEvent *key; + XButtonEvent *button; + boolean meta = FALSE; + int i, nbytes; + Cardinal in_nparams = (num_params ? *num_params : 0); + char c; + char keystring[MAX_KEY_STRING]; + + switch (event->type) { + case ButtonPress: + button = (XButtonEvent *) event; +#ifdef VERBOSE_INPUT + printf("button press\n"); +#endif + if (in_nparams > 0 && + (nbytes = strlen(params[0])) < MAX_KEY_STRING) { + Strcpy(keystring, params[0]); + key = (XKeyEvent *) event; /* just in case */ + goto key_events; + } + if (w != window_list[WIN_MAP].w) { +#ifdef VERBOSE_INPUT + printf("map_input called from wrong window\n"); +#endif + X11_nhbell(); + return; + } + set_button_values(w, button->x, button->y, button->button); + break; + case KeyPress: +#ifdef VERBOSE_INPUT + printf("key: "); +#endif + if(appResources.slow && input_func) { + (*input_func)(w, event, params, num_params); + break; + } + + /* + * Don't use key_event_to_char() because we want to be able + * to allow keys mapped to multiple characters. + */ + key = (XKeyEvent *) event; + if (in_nparams > 0 && + (nbytes = strlen(params[0])) < MAX_KEY_STRING) { + Strcpy(keystring, params[0]); + } else { + /* + * Assume that mod1 is really the meta key. + */ + meta = !!(key->state & Mod1Mask); + nbytes = + XLookupString(key, keystring, MAX_KEY_STRING, + (KeySym *)0, (XComposeStatus *)0); + } + key_events: + /* Modifier keys return a zero length string when pressed. */ + if (nbytes) { +#ifdef VERBOSE_INPUT + printf("\""); +#endif + for (i = 0; i < nbytes; i++) { + c = keystring[i]; + + if (incount < INBUF_SIZE) { + inbuf[(inptr+incount)%INBUF_SIZE] = + ((int) c) + (meta ? 0x80 : 0); + incount++; + } else { + X11_nhbell(); + } +#ifdef VERBOSE_INPUT + if (meta) /* meta will print as M */ + (void) putchar('M'); + if (c < ' ') { /* ctrl will print as ^ */ + (void) putchar('^'); + c += '@'; + } + (void) putchar(c); +#endif + } +#ifdef VERBOSE_INPUT + printf("\" [%d bytes]\n", nbytes); +#endif + } + break; + + default: + impossible("unexpected X event, type = %d\n", (int) event->type); + break; + } +} + +static void +set_button_values(w, x, y, button) + Widget w; + int x; + int y; + unsigned int button; +{ + struct xwindow *wp; + struct map_info_t *map_info; + + wp = find_widget(w); + map_info = wp->map_information; + + click_x = x / map_info->square_width; + click_y = y / map_info->square_height; + + /* The values can be out of range if the map window has been resized */ + /* to be larger than the max size. */ + if (click_x >= COLNO) click_x = COLNO-1; + if (click_y >= ROWNO) click_x = ROWNO-1; + + /* Map all buttons but the first to the second click */ + click_button = (button == Button1) ? CLICK_1 : CLICK_2; +} + +/* + * Map window expose callback. + */ +/*ARGSUSED*/ +static void +map_exposed(w, client_data, widget_data) + Widget w; + XtPointer client_data; /* unused */ + XtPointer widget_data; /* expose event from Window widget */ +{ + int x, y; + struct xwindow *wp; + struct map_info_t *map_info; + unsigned width, height; + int start_row, stop_row, start_col, stop_col; + XExposeEvent *event = (XExposeEvent *) widget_data; + int t_height, t_width; /* tile/text height & width */ + + if (!XtIsRealized(w) || event->count > 0) return; + + wp = find_widget(w); + map_info = wp->map_information; + if (wp->keep_window && !map_info) return; + /* + * The map is sent an expose event when the viewport resizes. Make sure + * that the cursor is still in the viewport after the resize. + */ + map_check_size_change(wp); + + if (event) { /* called from button-event */ + x = event->x; + y = event->y; + width = event->width; + height = event->height; + } else { + x = 0; + y = 0; + width = wp->pixel_width; + height= wp->pixel_height; + } + /* + * Convert pixels into INCLUSIVE text rows and columns. + */ + t_height = map_info->square_height; + t_width = map_info->square_width; + start_row = y / t_height; + stop_row = ((y + height) / t_height) + + ((((y + height) % t_height) == 0) ? 0 : 1) - 1; + + start_col = x / t_width; + stop_col = ((x + width) / t_width) + + ((((x + width) % t_width) == 0) ? 0 : 1) - 1; + +#ifdef VERBOSE + printf("map_exposed: x = %d, y = %d, width = %d, height = %d\n", + x, y, width, height); + printf("chars %d x %d, rows %d to %d, columns %d to %d\n", + map_info->square_height, map_info->square_width, + start_row, stop_row, start_col, stop_col); +#endif + + /* Out of range values are possible if the map window is resized to be */ + /* bigger than the largest expected value. */ + if (stop_row >= ROWNO) stop_row = ROWNO-1; + if (stop_col >= COLNO) stop_col = COLNO-1; + + map_update(wp, start_row, stop_row, start_col, stop_col, FALSE); + display_cursor(wp); /* make sure cursor shows up */ +} + +/* + * Do the actual work of the putting characters onto our X window. This + * is called from the expose event routine, the display window (flush) + * routine, and the display cursor routine. The later is a kludge that + * involves the inverted parameter of this function. A better solution + * would be to double the color count, with any color above CLR_MAX + * being inverted. + * + * This works for rectangular regions (this includes one line rectangles). + * The start and stop columns are *inclusive*. + */ +static void +map_update(wp, start_row, stop_row, start_col, stop_col, inverted) + struct xwindow *wp; + int start_row, stop_row, start_col, stop_col; + boolean inverted; +{ + int win_start_row, win_start_col; + struct map_info_t *map_info = wp->map_information; + int row; + register int count; + + if (start_row < 0 || stop_row >= ROWNO) { + impossible("map_update: bad row range %d-%d\n", start_row, stop_row); + return; + } + if (start_col < 0 || stop_col >=COLNO) { + impossible("map_update: bad col range %d-%d\n", start_col, stop_col); + return; + } + +#ifdef VERBOSE_UPDATE + printf("update: [0x%x] %d %d %d %d\n", + (int) wp->w, start_row, stop_row, start_col, stop_col); +#endif + win_start_row = start_row; + win_start_col = start_col; + + if (map_info->is_tile) { + struct tile_map_info_t *tile_map = map_info->mtype.tile_map; + int cur_col; + Display* dpy=XtDisplay(wp->w); + Screen* screen=DefaultScreenOfDisplay(dpy); + + for (row = start_row; row <= stop_row; row++) { + for (cur_col = start_col; cur_col <= stop_col; cur_col++) { + int glyph = tile_map->glyphs[row][cur_col]; + int tile = glyph2tile[glyph]; + int dest_x = cur_col * map_info->square_width; + int dest_y = row * map_info->square_height; + + XCopyArea(dpy, tile_pixmap, XtWindow(wp->w), + tile_map->black_gc, /* no grapics_expose */ + 0, + tile * map_info->square_height, + tile_width, tile_height, + dest_x,dest_y); + + if (glyph_is_pet(glyph) +#ifdef TEXTCOLOR + && iflags.hilite_pet +#endif + ) { + /* draw pet annotation (a heart) */ + XSetForeground(dpy, tile_map->black_gc, pet_annotation.foreground); + XSetClipOrigin(dpy, tile_map->black_gc, dest_x, dest_y); + XSetClipMask(dpy, tile_map->black_gc, pet_annotation.bitmap); + XCopyPlane( + dpy, + pet_annotation.bitmap, + XtWindow(wp->w), + tile_map->black_gc, + 0,0, + pet_annotation.width,pet_annotation.height, + dest_x,dest_y, + 1 + ); + XSetClipOrigin(dpy, tile_map->black_gc, 0, 0); + XSetClipMask(dpy, tile_map->black_gc, None); + XSetForeground(dpy, tile_map->black_gc, BlackPixelOfScreen(screen)); + } + } + } + + if (inverted) { + XDrawRectangle(XtDisplay(wp->w), XtWindow(wp->w), +#ifdef USE_WHITE + /* kludge for white square... */ + tile_map->glyphs[start_row][start_col] == + cmap_to_glyph(S_ice) ? + tile_map->black_gc : tile_map->white_gc, +#else + tile_map->white_gc, +#endif + start_col * map_info->square_width, + start_row * map_info->square_height, + map_info->square_width-1, + map_info->square_height-1); + } + } else { + struct text_map_info_t *text_map = map_info->mtype.text_map; + +#ifdef TEXTCOLOR + if (iflags.use_color) { + register char *c_ptr; + char *t_ptr; + int cur_col, color, win_ystart; + + for (row = start_row; row <= stop_row; row++) { + win_ystart = map_info->square_ascent + + (row * map_info->square_height); + + t_ptr = (char *) &(text_map->text[row][start_col]); + c_ptr = (char *) &(text_map->colors[row][start_col]); + cur_col = start_col; + while (cur_col <= stop_col) { + color = *c_ptr++; + count = 1; + while ((cur_col + count) <= stop_col && *c_ptr == color) { + count++; + c_ptr++; + } + + XDrawImageString(XtDisplay(wp->w), XtWindow(wp->w), + inverted ? text_map->inv_color_gcs[color] : + text_map->color_gcs[color], + map_info->square_lbearing + (map_info->square_width * cur_col), + win_ystart, + t_ptr, count); + + /* move text pointer and column count */ + t_ptr += count; + cur_col += count; + } /* col loop */ + } /* row loop */ + } else +#endif /* TEXTCOLOR */ + { + int win_row, win_xstart; + + /* We always start at the same x window position and have */ + /* the same character count. */ + win_xstart = map_info->square_lbearing + + (win_start_col * map_info->square_width); + count = stop_col - start_col + 1; + + for (row = start_row, win_row = win_start_row; + row <= stop_row; row++, win_row++) { + + XDrawImageString(XtDisplay(wp->w), XtWindow(wp->w), + inverted ? text_map->inv_copy_gc : text_map->copy_gc, + win_xstart, + map_info->square_ascent + (win_row * map_info->square_height), + (char *) &(text_map->text[row][start_col]), count); + } + } + } +} + +/* Adjust the number of rows and columns on the given map window */ +void +set_map_size(wp, cols, rows) + struct xwindow *wp; + Dimension cols, rows; +{ + Arg args[4]; + Cardinal num_args; + + wp->pixel_width = wp->map_information->square_width * cols; + wp->pixel_height = wp->map_information->square_height * rows; + + num_args = 0; + XtSetArg(args[num_args], XtNwidth, wp->pixel_width); num_args++; + XtSetArg(args[num_args], XtNheight, wp->pixel_height); num_args++; + XtSetValues(wp->w, args, num_args); +} + + +static void +init_text(wp) + struct xwindow *wp; +{ + + struct map_info_t *map_info = wp->map_information; + struct text_map_info_t *text_map; + + map_info->is_tile = FALSE; + text_map = map_info->mtype.text_map = + (struct text_map_info_t *) alloc(sizeof(struct text_map_info_t)); + + (void) memset((genericptr_t) text_map->text, ' ', sizeof(text_map->text)); +#ifdef TEXTCOLOR + (void) memset((genericptr_t) text_map->colors, NO_COLOR, + sizeof(text_map->colors)); +#endif + + get_char_info(wp); + get_text_gc(wp, WindowFont(wp->w)); +} + +static char map_translations[] = +"#override\n\ + Left: scroll(4)\n\ + Right: scroll(6)\n\ + Up: scroll(8)\n\ + Down: scroll(2)\n\ + : input() \ +"; + +/* + * The map window creation routine. + */ +void +create_map_window(wp, create_popup, parent) + struct xwindow *wp; + boolean create_popup; /* parent is a popup shell that we create */ + Widget parent; +{ + struct map_info_t *map_info; /* map info pointer */ + Widget map, viewport; + Arg args[16]; + Cardinal num_args; + Dimension rows, columns; +#if 0 + int screen_width, screen_height; +#endif + + wp->type = NHW_MAP; + + if (create_popup) { + /* + * Create a popup that accepts key and button events. + */ + num_args = 0; + XtSetArg(args[num_args], XtNinput, False); num_args++; + + wp->popup = parent = XtCreatePopupShell("nethack", + topLevelShellWidgetClass, + toplevel, args, num_args); + /* + * If we're here, then this is an auxiliary map window. If we're + * cancelled via a delete window message, we should just pop down. + */ + } + + num_args = 0; + XtSetArg(args[num_args], XtNallowHoriz, True); num_args++; + XtSetArg(args[num_args], XtNallowVert, True); num_args++; + /* XtSetArg(args[num_args], XtNforceBars, True); num_args++; */ + XtSetArg(args[num_args], XtNuseBottom, True); num_args++; + XtSetArg(args[num_args], XtNtranslations, + XtParseTranslationTable(map_translations)); num_args++; + viewport = XtCreateManagedWidget( + "map_viewport", /* name */ + viewportWidgetClass, /* widget class from Window.h */ + parent, /* parent widget */ + args, /* set some values */ + num_args); /* number of values to set */ + + /* + * Create a map window. We need to set the width and height to some + * value when we create it. We will change it to the value we want + * later + */ + num_args = 0; + XtSetArg(args[num_args], XtNwidth, 100); num_args++; + XtSetArg(args[num_args], XtNheight, 100); num_args++; + XtSetArg(args[num_args], XtNtranslations, + XtParseTranslationTable(map_translations)); num_args++; + + wp->w = map = XtCreateManagedWidget( + "map", /* name */ + windowWidgetClass, /* widget class from Window.h */ + viewport, /* parent widget */ + args, /* set some values */ + num_args); /* number of values to set */ + + XtAddCallback(map, XtNexposeCallback, map_exposed, (XtPointer) 0); + + map_info = wp->map_information = + (struct map_info_t *) alloc(sizeof(struct map_info_t)); + + map_info->viewport_width = map_info->viewport_height = 0; + + /* reset the "new entry" indicators */ + (void) memset((genericptr_t) map_info->t_start, (char) COLNO, + sizeof(map_info->t_start)); + (void) memset((genericptr_t) map_info->t_stop, (char) 0, + sizeof(map_info->t_stop)); + + /* we probably want to restrict this to the 1st map window only */ + if (appResources.tile_file[0] && init_tiles(wp)) { + map_info->is_tile = TRUE; + } else { + init_text(wp); + map_info->is_tile = FALSE; + } + + + /* + * Initially, set the map widget to be the size specified by the + * widget rows and columns resources. We need to do this to + * correctly set the viewport window size. After the viewport is + * realized, then the map can resize to its normal size. + */ + num_args = 0; + XtSetArg(args[num_args], XtNrows, &rows); num_args++; + XtSetArg(args[num_args], XtNcolumns, &columns); num_args++; + XtGetValues(wp->w, args, num_args); + + /* Don't bother with windows larger than ROWNOxCOLNO. */ + if (columns > COLNO) columns = COLNO; + if (rows > ROWNO) rows = ROWNO; + +#if 0 /* This is insufficient. We now resize final window in winX.c */ + /* + * Check for overrunning the size of the screen. This does an ad hoc + * job. + * + * Width: We expect that there is nothing but borders on either side + * of the map window. Use some arbitrary width to decide + * when to shrink. + * + * Height: if the map takes up more than 1/2 of the screen height, start + * reducing its size. + */ + screen_height = HeightOfScreen(XtScreen(wp->w)); + screen_width = WidthOfScreen(XtScreen(wp->w)); + +#define WOFF 50 + if ((int)(columns*map_info->square_width) > screen_width-WOFF) { + columns = (screen_width-WOFF) / map_info->square_width; + if (columns == 0) columns = 1; + } + + if ((int)(rows*map_info->square_height) > screen_height/2) { + rows = screen_height / (2*map_info->square_height); + if (rows == 0) rows = 1; + } +#endif + + set_map_size(wp, columns, rows); + + + /* + * If we have created our own popup, then realize it so that the + * viewport is also realized. Then resize the map window. + */ + if (create_popup) { + XtRealizeWidget(wp->popup); + XSetWMProtocols(XtDisplay(wp->popup), XtWindow(wp->popup), + &wm_delete_window, 1); + set_map_size(wp, COLNO, ROWNO); + } + + if (map_info->is_tile) { + map_all_stone(map_info); + } +} + +/* + * Destroy this map window. + */ +void +destroy_map_window(wp) + struct xwindow *wp; +{ + struct map_info_t *map_info = wp->map_information; + + if (wp->popup) + nh_XtPopdown(wp->popup); + + if (map_info) { + struct text_map_info_t *text_map = map_info->mtype.text_map; + + /* Free allocated GCs. */ + if (!map_info->is_tile) { +#ifdef TEXTCOLOR + int i; + + for (i = 0; i < CLR_MAX; i++) { + XtReleaseGC(wp->w, text_map->color_gcs[i]); + XtReleaseGC(wp->w, text_map->inv_color_gcs[i]); + } +#else + XtReleaseGC(wp->w, text_map->copy_gc); + XtReleaseGC(wp->w, text_map->inv_copy_gc); +#endif + } + /* free alloc'ed text information */ + free((genericptr_t)text_map), map_info->mtype.text_map = 0; + + /* Free malloc'ed space. */ + free((genericptr_t)map_info), wp->map_information = 0; + } + + /* Destroy map widget. */ + if (wp->popup && !wp->keep_window) + XtDestroyWidget(wp->popup), wp->popup = (Widget)0; + + if (wp->keep_window) + XtRemoveCallback(wp->w, XtNexposeCallback, map_exposed, (XtPointer)0); + else + wp->type = NHW_NONE; /* allow re-use */ +} + + + +boolean exit_x_event; /* exit condition for the event loop */ +/******* +pkey(k) + int k; +{ + printf("key = '%s%c'\n", (k<32) ? "^":"", (k<32) ? '@'+k : k); +} +******/ + +/* + * Main X event loop. Here we accept and dispatch X events. We only exit + * under certain circumstances. + */ +int +x_event(exit_condition) + int exit_condition; +{ + XEvent event; + int retval = 0; + boolean keep_going = TRUE; + + /* Hold globals so function is re-entrant */ + boolean hold_exit_x_event = exit_x_event; + + click_button = NO_CLICK; /* reset click exit condition */ + exit_x_event = FALSE; /* reset callback exit condition */ + + /* + * Loop until we get a sent event, callback exit, or are accepting key + * press and button press events and we receive one. + */ + if((exit_condition == EXIT_ON_KEY_PRESS || + exit_condition == EXIT_ON_KEY_OR_BUTTON_PRESS) && incount) + goto try_test; + + do { + XtAppNextEvent(app_context, &event); + XtDispatchEvent(&event); + + /* See if we can exit. */ + try_test: + switch (exit_condition) { + case EXIT_ON_SENT_EVENT: { + XAnyEvent *any = (XAnyEvent *) &event; + if (any->send_event) { + retval = 0; + keep_going = FALSE; + } + break; + } + case EXIT_ON_EXIT: + if (exit_x_event) { + incount = 0; + retval = 0; + keep_going = FALSE; + } + break; + case EXIT_ON_KEY_PRESS: + if (incount != 0) { + /* get first pressed key */ + --incount; + retval = inbuf[inptr]; + inptr = (inptr+1) % INBUF_SIZE; + /* pkey(retval); */ + keep_going = FALSE; + } + break; + case EXIT_ON_KEY_OR_BUTTON_PRESS: + if (incount != 0 || click_button != NO_CLICK) { + if (click_button != NO_CLICK) { /* button press */ + /* click values are already set */ + retval = 0; + } else { /* key press */ + /* get first pressed key */ + --incount; + retval = inbuf[inptr]; + inptr = (inptr+1) % INBUF_SIZE; + /* pkey(retval); */ + } + keep_going = FALSE; + } + break; + default: + panic("x_event: unknown exit condition %d\n", exit_condition); + break; + } + } while (keep_going); + + /* Restore globals */ + exit_x_event = hold_exit_x_event; + + return retval; +} + +/*winmap.c*/