/* Map widget for the tcl/tk interface to Xconq.
   Copyright (C) 1998, 1999 Stanley T. Shebs.

Xconq is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.  See the file COPYING.  */

#include "conq.h"
#include "kpublic.h"
#include "ai.h"
#include "tkconq.h"

void update_area(Map *map, int x, int y, int w, int h);
void handle_designer_mouse_down(Side *side, Map *map, int sx, int sy);
void move_look(Map *map, int sx, int sy);
void move_the_selected_unit(Map *map, Unit *unit, int sx, int sy);

extern VP default_vp;
extern int default_draw_lines;
extern int default_draw_polygons;
extern int default_draw_terrain_images;
extern int default_draw_terrain_patterns;
extern int default_draw_transitions;

extern Pixmap fuzzpics[NUMPOWERS];
extern Pixmap windpics[5][NUMDIRS];
extern Pixmap antpic;

extern ImageFamily *generic_transition;

typedef struct {
    Tk_Window tkwin;		/* Window that embodies the MapW.  NULL
				 * means window has been deleted but
				 * widget record hasn't been cleaned up yet. */
    Display *display;		/* X's token for the window's display. */
    Tcl_Interp *interp;		/* Interpreter associated with widget. */
    Tcl_Command widgetCmd;	/* Token for MapW's widget command. */
    Tk_3DBorder bg_border;	/* Used for drawing background. */

    GC gc;
    GC copygc;			/* Graphics context for copying from
				 * off-screen pixmap onto screen. */
    int double_buffer;		/* Non-zero means double-buffer redisplay
				 * with pixmap;  zero means draw straight
				 * onto the display. */
    Drawable d;
    int update_pending;		/* Non-zero means a call to mapw_display
				 * has already been scheduled. */
    int rsx, rsy, rsw, rsh;     /* rect to update */

    int width, height;
    VP *vp;
    int power;
    int world;
    Map *map;

    Tk_Cursor cursor;

    /* Copies of the generic viewport options. */
    int see_all;
    int aux_terrain_types[MAXTTYPES];
    int draw_aux_terrain[MAXTTYPES];
    int draw_grid;		/* Draw outlines around cells? */
    int draw_names;		/* Draw unit names/numbers on the map? */
    int draw_people;		/* Draw people sides on the map? */
    int draw_control;		/* Draw controlling sides on the map? */
    int draw_elevations;	/* Draw elevations on the map? */
    int draw_materials[MAXMTYPES];
    int num_materials_to_draw;
    int draw_lighting;		/* Draw day/night on the map? */
    int draw_temperature;	/* Draw temperatures on the map? */
    int draw_winds;		/* Draw wind vectors on the map? */
    int draw_clouds;		/* Draw clouds on the map? */
    int draw_cover;
    int draw_feature_boundaries;
    int draw_feature_names;
    int draw_latlong;		/* Draw latitude/longitude in view */
    int latlong_interval;	/* Spacing of meridians in arc minutes */
    int draw_ai;		/* Draw AI info in view */

    /* View options specific to this interface. */
    int draw_lines;
    int draw_polygons;
    int draw_terrain_images;
    int draw_terrain_patterns;
    int draw_transitions;

    Tk_Font main_font;

    int last_wsx, last_wsy, last_wsw, last_wsh;

    int blastsx, blastsy, blastsw, blastsh, blasttype;
} MapW;

static Tk_ConfigSpec config_specs[] = {
    {TK_CONFIG_INT, "-ai", "ai", "AI",
	"0", Tk_Offset(MapW, draw_ai), 0},
    {TK_CONFIG_BORDER, "-background", "background", "Background",
	"gray", Tk_Offset(MapW, bg_border), TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_BORDER, "-background", "background", "Background",
	"gray50", Tk_Offset(MapW, bg_border), TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *) NULL,
	(char *) NULL, 0, 0},
    {TK_CONFIG_SYNONYM, "-bg", "background", (char *) NULL,
	(char *) NULL, 0, 0},
#if 0 /* Although borderwidth is "standard", it's hard to make it work
	 right with the cell drawing, and the effect can be achieved
	 just by embedding the map in a frame, so omit it. */
    {TK_CONFIG_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
	"0", Tk_Offset(MapW, border_width), 0},
    {TK_CONFIG_RELIEF, "-relief", "relief", "Relief",
	"sunken", Tk_Offset(MapW, relief), 0},
#endif
    {TK_CONFIG_INT, "-clouds", "clouds", "Clouds",
	"0", Tk_Offset(MapW, draw_clouds), 0},
    {TK_CONFIG_INT, "-control", "control", "Control",
	"0", Tk_Offset(MapW, draw_control), 0},
    {TK_CONFIG_INT, "-coverage", "coverage", "Coverage",
     "0", Tk_Offset(MapW, draw_cover), 0},
    {TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor",
	"", Tk_Offset(MapW, cursor), TK_CONFIG_NULL_OK},
    {TK_CONFIG_INT, "-dbl", "doubleBuffer", "DoubleBuffer",
	"1", Tk_Offset(MapW, double_buffer), 0},
    {TK_CONFIG_INT, "-elevations", "elevations", "Elevations",
	"0", Tk_Offset(MapW, draw_elevations), 0},
    {TK_CONFIG_INT, "-featureboundaries", "featureboundaries", "FeatureBoundaries",
	"0", Tk_Offset(MapW, draw_feature_boundaries), 0},
    {TK_CONFIG_INT, "-featurenames", "featurenames", "FeatureNames",
	"0", Tk_Offset(MapW, draw_feature_names), 0},
    {TK_CONFIG_FONT, "-font", "font", "Font",
	"Courier -12", Tk_Offset(MapW, main_font), 0},
    {TK_CONFIG_INT, "-grid", "grid", "Grid",
	"0", Tk_Offset(MapW, draw_grid), 0},
    {TK_CONFIG_PIXELS, "-height", "height", "Height",
	"0", Tk_Offset(MapW, height), 0},
    {TK_CONFIG_INT, "-lighting", "lighting", "Lighting",
	"0", Tk_Offset(MapW, draw_lighting), 0},
    {TK_CONFIG_INT, "-lines", "lines", "Lines",
	"0", Tk_Offset(MapW, draw_lines), 0},
    {TK_CONFIG_INT, "-meridians", "meridians", "Meridians",
	"0", Tk_Offset(MapW, draw_latlong), 0},
    {TK_CONFIG_INT, "-meridianinterval", "meridianinterval", "MeridianInterval",
	"0", Tk_Offset(MapW, latlong_interval), 0},
    {TK_CONFIG_INT, "-people", "people", "People",
	"0", Tk_Offset(MapW, draw_people), 0},
    {TK_CONFIG_INT, "-polygons", "polygons", "Polygons",
	"0", Tk_Offset(MapW, draw_polygons), 0},
    {TK_CONFIG_INT, "-power", "power", "Power",
	"5", Tk_Offset(MapW, power), 0},
    {TK_CONFIG_INT, "-temperature", "temperature", "Temperature",
	"0", Tk_Offset(MapW, draw_temperature), 0},
    {TK_CONFIG_INT, "-terrainimages", "terrain_images", "TerrainImages",
	"0", Tk_Offset(MapW, draw_terrain_images), 0},
    {TK_CONFIG_INT, "-terrainpatterns", "terrain_patterns", "TerrainPatterns",
	"0", Tk_Offset(MapW, draw_terrain_patterns), 0},
    {TK_CONFIG_INT, "-transitions", "transitions", "Transitions",
	"0", Tk_Offset(MapW, draw_transitions), 0},
    {TK_CONFIG_INT, "-unitnames", "unitnames", "UnitNames",
	"0", Tk_Offset(MapW, draw_names), 0},
    {TK_CONFIG_PIXELS, "-width", "width", "Width",
	"0", Tk_Offset(MapW, width), 0},
    {TK_CONFIG_INT, "-winds", "winds", "Winds",
	"0", Tk_Offset(MapW, draw_winds), 0},
    {TK_CONFIG_INT, "-world", "world", "World",
	"0", Tk_Offset(MapW, world), 0},
    {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
	(char *) NULL, 0, 0}
};

static void mapw_cmd_deleted_proc(ClientData cldata);
static int mapw_configure(Tcl_Interp *interp, MapW *MapW,
			  int argc, char **argv, int flags);
static void set_scrollbars(MapW *mapw);
static void mapw_destroy(char *memPtr);
static void mapw_event_proc(ClientData cldata, XEvent *eventPtr);
static int mapw_widget_cmd(ClientData cldata, Tcl_Interp *interp,
			   int argc, char **argv);
static void mapw_display(ClientData cldata);
static void draw_map_widget(MapW *mapw);
static void draw_map_overhead(MapW *mapw);
static void draw_map_isometric(MapW *mapw);

static void draw_unseen_fuzz(MapW *mapw, int vx, int vyhi, int vylo);
static void draw_row(MapW *mapw, int x0, int y0, int len);
static void draw_current(MapW *mapw);
static void draw_blast_image(MapW *mapw, int sx, int sy, int sw, int sh,
			     int blasttype);

static void draw_unit_image(MapW *mapw, int sx, int sy, int sw, int sh,
			    int u, int s2, int mod);
static void draw_side_emblem(MapW *mapw, int ex, int ey, int ew, int eh,
			     int s2);

static void xform(MapW *mapw, int x, int y, int *sxp, int *syp);
static void xform_fractional(MapW *mapw, int x, int y, int xf, int yf,
			     int *sxp, int *syp);
static void x_xform_unit(MapW *mapw, Unit *unit, int *sxp, int *syp,
			 int *swp, int *shp);
static void x_xform_unit_self(MapW *mapw, Unit *unit, int *sxp, int *syp,
			      int *swp, int *shp);
static void x_xform_occupant(MapW *mapw, Unit *transport, Unit *unit,
			     int sx, int sy, int sw, int sh,
			     int *sxp, int *syp, int *swp, int *shp);
static int x_nearest_cell(MapW *mapw, int sx, int sy, int *xp, int *yp);
static Unit *x_find_unit_or_occ(MapW *mapw, Unit *unit, int usx, int usy,
				int usw, int ush, int sx, int sy);
static Unit *x_find_unit_at(MapW *mapw, int x, int y, int sx, int sy);
static int x_nearest_unit(MapW *mapw, int sx, int sy, Unit **unitp);

static void draw_feature_name(MapW *mapw, int f);
static void draw_ai_region(MapW *mapw, int x, int y);
static void draw_hex_polygon(MapW *mapw, GC gc, int sx, int sy,
			     int power, int over, int dogrid);
static void draw_area_background(MapW *mapw);
static void meridian_line_callback(int x1, int y1, int x1f, int y1f,
				   int x2, int y2, int x2f, int y2f);
static void meridian_text_callback(int x1, int y1, int x1f, int y1f,
				   char *str);
static int cell_drawing_info(MapW *mapw, int x, int y, Pixmap *patp,
			     XColor **colorp, int *overp);
static void set_terrain_gc_for_image(MapW *mapw, GC gc, Image *timg);
static void draw_terrain_row(MapW *mapw, int x0, int y0, int len, int force);
static int compute_x1_len(MapW *mapw, int vx, int vy, int y, int *x1p,
			  int *lenp);
static void draw_terrain_transitions(MapW *mapw, int x0, int y0, int len,
				     int force);
static void draw_contours(MapW *mapw, int x0, int y0, int len);
static void draw_clouds_row(MapW *mapw, int x0, int y0, int len);
static void draw_temperature_row(MapW *mapw, int x0, int y0, int len);
static void draw_winds_row(MapW *mapw, int x0, int y0, int len);
static void draw_units(MapW *mapw, int x, int y);
static void draw_unit_and_occs(MapW *mapw, Unit *unit, int sx, int sy,
			       int sw, int sh, int drawoccs);
static void draw_unit_name(MapW *mapw, Unit *unit, int sx, int sy,
			   int sw, int sh);
static void draw_people(MapW *mapw, int x, int y);
static void draw_borders(MapW *mapw, int vx, int vyhi, int vylo, int b);
static void draw_connections(MapW *mapw, int vx, int vyhi, int vylo, int c);
static void draw_country_border_line(MapW *mapw, int sx, int sy, int dir,
				     int con, int heavy);
static void draw_feature_boundary(MapW *mapw, int x, int y);
static void draw_meridians(MapW *mapw);
static void draw_map_outline(MapW *worldw, MapW *mapw, int update);

static void eventually_redraw(MapW *mapw, int sx, int sy, int sw, int sh);

#define GRAY(over) ((over) == -1 ? lightgray : gray)

int mapw_cmd(ClientData cldata, Tcl_Interp *interp, int argc, char **argv);

int
mapw_cmd(ClientData cldata, Tcl_Interp *interp, int argc, char **argv)
{
    int x, y;
    Tk_Window mainw = (Tk_Window) cldata;
    MapW *mapw;
    Tk_Window tkwin;

    if (argc < 2) {
	Tcl_AppendResult(interp, "wrong # args: should be \"",
		argv[0], " pathName ?options?\"", (char *) NULL);
	return TCL_ERROR;
    }

    tkwin = Tk_CreateWindowFromPath(interp, mainw, argv[1], (char *) NULL);
    if (tkwin == NULL)
      return TCL_ERROR;

    Tk_SetClass(tkwin, "MapW");

    /* Allocate and initialize the widget record.  */

    mapw = (MapW *) ckalloc(sizeof(MapW));
    mapw->tkwin = tkwin;
    mapw->display = Tk_Display(tkwin);
    mapw->interp = interp;
    mapw->widgetCmd =
      Tcl_CreateCommand(interp,
			Tk_PathName(mapw->tkwin), mapw_widget_cmd,
			(ClientData) mapw, mapw_cmd_deleted_proc);
    mapw->bg_border = NULL;
    mapw->gc = None;
    mapw->copygc = None;
    mapw->main_font = NULL;
    mapw->double_buffer = 1;
    mapw->update_pending = FALSE;

    mapw->cursor = None;

    mapw->draw_lines = FALSE;
    mapw->draw_polygons = FALSE;
    mapw->draw_terrain_images = default_draw_terrain_images;
    mapw->draw_terrain_patterns = default_draw_terrain_patterns;
    mapw->draw_transitions = default_draw_transitions;

    mapw->map = dside->ui->maps;

    mapw->rsx = mapw->rsy = mapw->rsw = mapw->rsh = -1;

    mapw->width = 0;
    mapw->height = 0;

    /* Set up the map widget's generic viewport. */
    mapw->vp = new_vp();
    mapw->vp->draw_terrain = TRUE;
    mapw->vp->draw_units = TRUE;
    /* Set a default power that will get modified by widget configure. */
    set_view_power(mapw->vp, 2);

    Tk_CreateEventHandler(mapw->tkwin, ExposureMask|StructureNotifyMask,
			  mapw_event_proc, (ClientData) mapw);
    if (mapw_configure(interp, mapw, argc-2, argv+2, 0) != TCL_OK) {
	Tk_DestroyWindow(mapw->tkwin);
	return TCL_ERROR;
    }

    if (mapw->world)
      mapw->map->worldw = (char *) mapw;
    else
      mapw->map->widget = (char *) mapw;

    pick_a_focus(dside, &x, &y);
    set_view_focus(mapw->vp, x, y);

    mapw->blasttype = -1;

    interp->result = Tk_PathName(mapw->tkwin);
    return TCL_OK;
}

static int
mapw_widget_cmd(ClientData cldata, Tcl_Interp *interp, int argc, char **argv)
{
    MapW *mapw = (MapW *) cldata;
    int result = TCL_OK;
    int update, sx, sy, nsx, nsy;
    int nrsx, nrsy, nrsw, nrsh;
    size_t length;
    char c;

    if (argc < 2) {
	Tcl_AppendResult(interp, "wrong # args: should be \"",
		argv[0], " option ?arg arg ...?\"", (char *) NULL);
	return TCL_ERROR;
    }
    Tcl_Preserve((ClientData) mapw);
    update = FALSE;
    sx = nsx = mapw->vp->sx;  sy = nsy = mapw->vp->sy;
    c = argv[1][0];
    length = strlen(argv[1]);
    if ((c == 'c') && (strncmp(argv[1], "cget", length) == 0)
	&& (length >= 2)) {
	if (argc != 3) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
		    argv[0], " cget option\"",
		    (char *) NULL);
	    goto error;
	}
	result = Tk_ConfigureValue(interp, mapw->tkwin, config_specs,
				   (char *) mapw, argv[2], 0);
	update = TRUE;
    } else if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0)
	       && (length >= 2)) {
	if (argc == 2) {
	    result = Tk_ConfigureInfo(interp, mapw->tkwin, config_specs,
		    (char *) mapw, (char *) NULL, 0);
	} else if (argc == 3) {
	    result = Tk_ConfigureInfo(interp, mapw->tkwin, config_specs,
		    (char *) mapw, argv[2], 0);
	} else {
	    result = mapw_configure(interp, mapw, argc-2, argv+2,
		    TK_CONFIG_ARGV_ONLY);
	}
	update = TRUE;
    } else if ((c == 'x') && (strncmp(argv[1], "xview", length) == 0)) {
	int count, type;
	double fraction, fraction2;

	if (argc == 2) {
	    fraction = 0;
	    fraction2 = 1;
	    printf("map xview %g %g\n", fraction, fraction2);
	    sprintf(interp->result, "%g %g", fraction, fraction2);
	} else {
	    type = Tk_GetScrollInfo(interp, argc, argv, &fraction, &count);
	    switch (type) {
	      case TK_SCROLL_ERROR:
		goto error;
	      case TK_SCROLL_MOVETO:
		nsx = ((mapw->vp->sxmax - mapw->vp->sxmin) * fraction);
		break;
	      case TK_SCROLL_PAGES:
		nsx += (count * mapw->vp->pxw * 4) / 5;
		break;
	      case TK_SCROLL_UNITS:
		nsx += (count * 12);
		break;
	    }
	    /* Correct any out-of-bounds values. */
	    if (nsx < mapw->vp->sxmin)
	      nsx = mapw->vp->sxmin;
	    if (nsx > mapw->vp->sxmax)
	      nsx = mapw->vp->sxmax;
	}
    } else if ((c == 'y') && (strncmp(argv[1], "yview", length) == 0)) {
	int count, type;
	double fraction, fraction2;

	if (argc == 2) {
	    fraction = 0;
	    fraction2 = 1;
	    printf("map yview %g %g\n", fraction, fraction2);
	    sprintf(interp->result, "%g %g", fraction, fraction2);
	} else {
	    type = Tk_GetScrollInfo(interp, argc, argv, &fraction, &count);
	    switch (type) {
	      case TK_SCROLL_ERROR:
		goto error;
	      case TK_SCROLL_MOVETO:
		nsy = ((mapw->vp->symax - mapw->vp->symin) * fraction);
		break;
	      case TK_SCROLL_PAGES:
		nsy += (count * mapw->vp->pxh * 4) / 5;
		break;
	      case TK_SCROLL_UNITS:
		nsy += (count * 12);
		break;
	    }
	    /* Correct any out-of-bounds values. */
	    if (nsy < mapw->vp->symin)
	      nsy = mapw->vp->symin;
	    if (nsy > mapw->vp->symax)
	      nsy = mapw->vp->symax;
	}
    } else {
	Tcl_AppendResult(interp, "bad option \"", argv[1],
		"\": must be cget, configure, xview, or yview",
		(char *) NULL);
	goto error;
    }
    nrsx = nrsy = nrsw = nrsh = -1;
    /* See if we're scrolling. */
    if (nsx != sx || nsy != sy) {
	int wid = Tk_Width(mapw->tkwin), hgt = Tk_Height(mapw->tkwin);
	Window win = Tk_WindowId(mapw->tkwin);
	GC gc = mapw->gc;
	Display *dpy = mapw->display;

	XSetClipMask(mapw->display, gc, None);
	if (nsx != sx) {
	    if (nsx > sx) {
		XCopyArea(dpy, win, win, gc, (nsx - sx), 0, wid, hgt, 0, 0);
		nrsx = mapw->vp->pxw - (nsx - sx);  nrsy = 0;
		nrsw = nsx - sx;  nrsh = mapw->vp->pxh;
	    } else {
		XCopyArea(dpy, win, win, gc, 0, 0, wid, hgt, (sx - nsx), 0);
		nrsx = 0;  nrsy = 0;
		nrsw = sx - nsx;  nrsh = mapw->vp->pxh;
	    }
	} else { /* nsy != sy */
	    if (nsy > sy) {
		XCopyArea(dpy, win, win, gc, 0, (nsy - sy), wid, hgt, 0, 0);
		nrsx = 0;  nrsy = mapw->vp->pxh - (nsy - sy);
		nrsw = mapw->vp->pxw;  nrsh = nsy - sy;
	    } else {
		XCopyArea(dpy, win, win, gc, 0, 0, wid, hgt, 0, (sy - nsy));
		nrsx = 0;  nrsy = 0;
		nrsw = mapw->vp->pxw;  nrsh = sy - nsy;
	    }
	}
	set_view_position(mapw->vp, nsx, nsy);
	/* After scrolling around a bit, you want to zoom in on the
	   current center of the display, rather than bouncing back to
	   the last focus point (a very annoying behavior!). */
	focus_on_center(mapw->vp);
	update = TRUE;
    }
    if (update) {
	eventually_redraw(mapw, nrsx, nrsy, nrsw, nrsh);
	if (!mapw->world)
	  draw_map_outline((MapW *) mapw->map->worldw, mapw, TRUE);
    }
    Tcl_Release((ClientData) mapw);
    return result;

error:
    Tcl_Release((ClientData) mapw);
    return TCL_ERROR;
}

static int
mapw_configure(interp, mapw, argc, argv, flags)
Tcl_Interp *interp;
MapW *mapw;
int argc;
char **argv;
int flags;
{
    int wid, hgt, pow;
    XGCValues gc_values;

    if (Tk_ConfigureWidget(interp, mapw->tkwin, config_specs,
			   argc, argv, (char *) mapw, flags) != TCL_OK)
      return TCL_ERROR;

    /* Set the background for the window and create graphics contexts
       for use during redisplay.  */
    Tk_SetWindowBackground(mapw->tkwin,
			   Tk_3DBorderColor(mapw->bg_border)->pixel);
    if (mapw->copygc == None) {
	gc_values.function = GXcopy;
	gc_values.graphics_exposures = False;
	mapw->copygc = XCreateGC(mapw->display,
				 DefaultRootWindow(mapw->display),
				 GCFunction|GCGraphicsExposures, &gc_values);
    }
    if (mapw->gc == None) {
	mapw->gc = XCreateGC(mapw->display, DefaultRootWindow(mapw->display),
			     None, NULL);
	XSetTSOrigin(mapw->display, mapw->gc, 0, 0);
	XSetLineAttributes(mapw->display, mapw->gc, 0, LineSolid, CapButt, JoinMiter);
    }
#if 0
    if (mapw->main_font == NULL)
      mapw->main_font = Tk_GetFont(interp, mapw->tkwin, "-weight bold");
    if (mapw->main_font == NULL)
      mapw->main_font = Tk_GetFont(interp, mapw->tkwin, "fixed");
#endif
    XSetFont(mapw->display, mapw->gc, Tk_FontId(mapw->main_font));

    /* Register the desired geometry for the window. */
    wid = mapw->width;  hgt = mapw->height;
    /* First "pass" at the size. */
    set_view_power(mapw->vp, mapw->power);
    center_on_focus(mapw->vp);
    pow = mapw->power;
    if (wid <= 0) {
	wid = mapw->vp->totsw;
    }
    if (hgt <= 0) {
	hgt = mapw->vp->totsh;
    }
    Tk_GeometryRequest(mapw->tkwin, wid, hgt);

    /* Copy view settings into the viewport. */
    mapw->vp->draw_ai = mapw->draw_ai;
    mapw->vp->draw_clouds = mapw->draw_clouds;
    mapw->vp->draw_control = mapw->draw_control;
    mapw->vp->draw_cover = mapw->draw_cover;
    mapw->vp->draw_elevations = mapw->draw_elevations;
    mapw->vp->draw_feature_boundaries = mapw->draw_feature_boundaries;
    mapw->vp->draw_feature_names = mapw->draw_feature_names;
    mapw->vp->draw_grid = mapw->draw_grid;
    mapw->vp->draw_lighting = mapw->draw_lighting;
    mapw->vp->draw_latlong = mapw->draw_latlong;
    mapw->vp->draw_people = mapw->draw_people;
    mapw->vp->draw_temperature = mapw->draw_temperature;
    mapw->vp->draw_names = mapw->draw_names;
    mapw->vp->draw_winds = mapw->draw_winds;
    mapw->vp->latlong_interval = mapw->latlong_interval;

    Tk_SetInternalBorder(mapw->tkwin, 0);

    /* Set the map widget to be redisplayed when convenient. */
    eventually_redraw(mapw, -1, -1, -1, -1);

    return TCL_OK;
}

static void
mapw_event_proc(ClientData cldata, XEvent *event_ptr)
{
    MapW *mapw = (MapW *) cldata;

    if (event_ptr->type == Expose) {
	eventually_redraw(mapw, -1, -1, -1, -1);
    } else if (event_ptr->type == ConfigureNotify) {
	eventually_redraw(mapw, -1, -1, -1, -1);
    } else if (event_ptr->type == DestroyNotify) {
	if (mapw->tkwin != NULL) {
	    mapw->tkwin = NULL;
	    Tcl_DeleteCommand(mapw->interp,
			      Tcl_GetCommandName(mapw->interp,
						 mapw->widgetCmd));
	}
	if (mapw->update_pending) {
	    Tcl_CancelIdleCall(mapw_display, cldata);
	}
	Tcl_EventuallyFree(cldata, mapw_destroy);
    }
}

static void
mapw_cmd_deleted_proc(ClientData cldata)
{
    MapW *mapw = (MapW *) cldata;
    Tk_Window tkwin = mapw->tkwin;

    if (tkwin != NULL) {
	mapw->tkwin = NULL;
	Tk_DestroyWindow(tkwin);
    }
}

static void
mapw_display(ClientData cldata)
{
    Map *map;
    MapW *mapw = (MapW *) cldata;
    Tk_Window tkwin = mapw->tkwin;
    int winwidth = Tk_Width(tkwin), winheight = Tk_Height(tkwin);

    mapw->update_pending = FALSE;
    if (!Tk_IsMapped(tkwin))
      return;

    map = mapw->map;

    set_view_size(mapw->vp, winwidth, winheight);

    if (mapw == (MapW *) map->widget)
      set_scrollbars(mapw);

    if (mapw->rsw >= 0 && mapw->rsh >= 0) {
	VP *saved_vp = mapw->vp, tmpvp;

	/* Use this to see each rect update. */
	if (DebugG) {
	    XSetClipMask(mapw->display, mapw->gc, None);
	    XSetForeground(mapw->display, mapw->gc,
			   dside->ui->badcolor->pixel);
	    XFillRectangle(mapw->display, Tk_WindowId(tkwin), mapw->gc,
			   mapw->rsx - 1, mapw->rsy - 1,
			   mapw->rsw + 2, mapw->rsh + 2);
	}
	/* Redraw a restricted rect of the widget. */
	tmpvp = *(mapw->vp);
	if (mapw->rsw == 0)
	  mapw->rsw = 1;
	if (mapw->rsh == 0)
	  mapw->rsh = 1;
	set_view_size(&tmpvp, mapw->rsw, mapw->rsh);
	set_view_position(&tmpvp, tmpvp.sx + mapw->rsx, tmpvp.sy + mapw->rsy);
	mapw->vp = &tmpvp;
	mapw->d = Tk_GetPixmap(mapw->display, Tk_WindowId(tkwin),
			       mapw->rsw, mapw->rsh,
			       DefaultDepthOfScreen(Tk_Screen(tkwin)));
	Tk_Fill3DRectangle(tkwin, mapw->d, mapw->bg_border, 0, 0, mapw->rsw,
			   mapw->rsh, 0, TK_RELIEF_FLAT);
	XSetTSOrigin(mapw->display, mapw->gc, - mapw->rsx, - mapw->rsy);
	draw_map_widget(mapw);
	XSetTSOrigin(mapw->display, mapw->gc, 0, 0);
	/* Copy to the screen and release the pixmap. */
	XCopyArea(mapw->display, mapw->d, Tk_WindowId(tkwin), mapw->copygc,
		  0, 0,
		  (unsigned) mapw->rsw, (unsigned) mapw->rsh,
		  mapw->rsx, mapw->rsy);
	Tk_FreePixmap(mapw->display, mapw->d);
	mapw->vp = saved_vp;
    } else {
	/* Do a full redraw of the widget. */
	mapw->d = Tk_WindowId(tkwin);
	/* Create a pixmap for double-buffering if preferred. */
	if (mapw->double_buffer) {
	    mapw->d = Tk_GetPixmap(mapw->display, Tk_WindowId(tkwin),
				   winwidth, winheight,
				   DefaultDepthOfScreen(Tk_Screen(tkwin)));
	}
	/* Redraw the widget's background and border. */
	Tk_Fill3DRectangle(tkwin, mapw->d, mapw->bg_border, 0, 0, winwidth,
			   winheight, 0, TK_RELIEF_FLAT);
	draw_map_widget(mapw);
	/* If double-buffered, copy to the screen and release the pixmap. */
	if (mapw->double_buffer) {
	    XCopyArea(mapw->display, mapw->d, Tk_WindowId(tkwin), mapw->copygc,
		      0, 0,
		      (unsigned) winwidth, (unsigned) winheight,
		      0, 0);
	    Tk_FreePixmap(mapw->display, mapw->d);
	}
    }
    mapw->rsx = mapw->rsy = mapw->rsw = mapw->rsh = -2;
}

static void
set_scrollbars(MapW *mapw)
{
    float fsx, fsy, first, last, tot;

    fsx = (float) mapw->vp->sx;
    tot = (mapw->vp->sxmax - mapw->vp->sxmin + mapw->vp->pxw);
    first = (fsx - mapw->vp->sxmin) / tot;
    last = (fsx - mapw->vp->sxmin + mapw->vp->pxw) / tot;
    eval_tcl_cmd("map_xscroll_set %f %f", first, last);
    fsy = (float) mapw->vp->sy;
    tot = (mapw->vp->symax - mapw->vp->symin + mapw->vp->pxh);
    first = (fsy - mapw->vp->symin) / tot;
    last = (fsy - mapw->vp->symin + mapw->vp->pxh) / tot;
    eval_tcl_cmd("map_yscroll_set %f %f", first, last);
}

static void
mapw_destroy(char *ptr)
{
    MapW *mapw = (MapW *) ptr;

    Tk_FreeOptions(config_specs, (char *) mapw, mapw->display, 0);
    if (mapw->gc != None)
      XFreeGC(mapw->display, mapw->gc);
    if (mapw->copygc != None)
      XFreeGC(mapw->display, mapw->copygc);
    if (mapw->main_font != NULL)
      Tk_FreeFont(mapw->main_font);
    ckfree((char *) mapw);
}


/* Transform map coordinates into screen coordinates. */

static void
xform(MapW *mapw, int x, int y, int *sxp, int *syp)
{
    xform_cell(mapw->vp, x, y, sxp, syp);
}

/* Transform, but also use position within cell. */

static void
xform_fractional(MapW *mapw, int x, int y, int xf, int yf, int *sxp, int *syp)
{
    xform_cell_fractional(mapw->vp, x, y, xf, yf, sxp, syp);
}

static void
x_xform_unit(MapW *mapw, Unit *unit, int *sxp, int *syp, int *swp, int *shp)
{
    xform_unit(mapw->vp, unit, sxp, syp, swp, shp);
}

static void
x_xform_unit_self(MapW *mapw, Unit *unit, int *sxp, int *syp,
		  int *swp, int *shp)
{
    xform_unit_self(mapw->vp, unit, sxp, syp, swp, shp);
}

static void
x_xform_occupant(MapW *mapw, Unit *transport, Unit *unit, int sx, int sy,
		 int sw, int sh, int *sxp, int *syp, int *swp, int *shp)
{
    xform_occupant(mapw->vp, transport, unit, sx, sy, sw, sh, sxp, syp,
		   swp, shp);
}

static int
x_nearest_cell(MapW *mapw, int sx, int sy, int *xp, int *yp)
{
    return nearest_cell(mapw->vp, sx, sy, xp, yp, NULL, NULL);
}

static Unit *
x_find_unit_or_occ(MapW *mapw, Unit *unit, int usx, int usy, int usw, int ush,
		   int sx, int sy)
{
    int usx1, usy1, usw1, ush1;
    Unit *occ, *rslt;

    /* See if the point might be over an occupant. */
    if (unit->occupant != NULL
	&& (side_controls_unit(dside, unit)
	    || mapw->map->see_all
	    || u_see_occupants(unit->type)
	    || side_owns_occupant(dside, unit))) {
	for_all_occupants(unit, occ) {
	    x_xform_unit(mapw, occ, &usx1, &usy1, &usw1, &ush1);
	    rslt =
	      x_find_unit_or_occ(mapw, occ, usx1, usy1, usw1, ush1, sx, sy);
	    if (rslt) {
		return rslt;
	    }
	}
    }
    /* Otherwise see if it could be the unit itself.  This has the effect of
       "giving" the transport everything in its box that is not in an occ. */
    x_xform_unit(mapw, unit, &usx1, &usy1, &usw1, &ush1);
    if (between(usx1, sx, usx1 + usw1) && between(usy1, sy, usy1 + ush1)) {
	return unit;
    }
    return NULL;
}

static Unit *
x_find_unit_at(MapW *mapw, int x, int y, int sx, int sy)
{
    int usx, usy, usw, ush;
    Unit *unit, *rslt;
	
    for_all_stack(x, y, unit) {
	x_xform_unit(mapw, unit, &usx, &usy, &usw, &ush);
	rslt = x_find_unit_or_occ(mapw, unit, usx, usy, usw, ush, sx, sy);
	if (rslt)
	  return rslt;
    }
    return NULL;
}

static int
x_nearest_unit(MapW *mapw, int sx, int sy, Unit **unitp)
{
    int x, y;

    if (!x_nearest_cell(mapw, sx, sy, &x, &y)) {
	*unitp = NULL;
    } else if (mapw->vp->uw >= 32) {
	*unitp = x_find_unit_at(mapw, x, y, sx, sy);
    } else {
	*unitp = unit_at(x, y);
    }
    DGprintf("Pixel %d,%d -> unit %s\n", sx, sy, unit_desig(*unitp));
    return TRUE;
}

/* Draw the background area for the map. */

static void
draw_area_background(MapW *mapw)
{
    int sx, sy, sx2, sy2, sw, sh, aw, ah, i;
    int llx, lly, lrx, lry, rx, ry, urx, ury, ulx, uly, lx, ly;
    XPoint points[6];
    Display *dpy = mapw->display;
    GC gc = mapw->gc;
    XColor *color;

    /* If the world is unexplored, then it should have the unseen color. */
    color = (1 /* grid color matches unseen color */ ? dside->ui->grid_color : dside->ui->unseen_color);
    XSetClipMask(dpy, gc, None);
    XSetForeground(dpy, gc, color->pixel);

    if (area.xwrap) {
	/* Area is cylinder; draw a rectangle. */
	xform(mapw, 0, area.height - 1, &sx, &sy);
	xform(mapw, area.width - 1, 0, &sx2, &sy2);
	sw = sx2 - sx;
	sh = sy2 - sy;
	XFillRectangle(dpy, mapw->d, gc, sx, sy + mapw->vp->hh / 2, sw, sh);
	if (0 /* map bg matches widget bg */) {
	    XSetForeground(dpy, gc, dside->ui->whitecolor->pixel);
	    XDrawRectangle(dpy, mapw->d, gc, sx, sy + mapw->vp->hh / 2, sw, sh);
	}
    } else {
	/* Area is hexagon; draw a hexagon. */
	aw = area.width;  ah = area.height;
	xform(mapw, 0 + ah / 2, 0, &llx, &lly);
	points[0].x = llx;  points[0].y = lly;
	xform(mapw, aw - 1, 0, &lrx, &lry);
	points[1].x = lrx;  points[1].y = lry;
	xform(mapw, aw - 1, ah / 2, &rx, &ry);
	points[2].x = rx;   points[2].y = ry;
	xform(mapw, aw - 1 - (ah - 1) / 2, ah - 1, &urx, &ury);
	points[3].x = urx;  points[3].y = ury;
	xform(mapw, 0, ah - 1, &ulx, &uly);
	points[4].x = ulx;  points[4].y = uly;
	xform(mapw, 0, ah / 2, &lx, &ly);
	points[5].x = lx;   points[5].y = ly;
	/* Offset so polygon edges run through middles of cells. */
	for (i = 0; i < 6; ++i) {
	    points[i].x += mapw->vp->hw / 2;  points[i].y += mapw->vp->hh / 2;
	}
	XFillPolygon(dpy, mapw->d, gc, points, 6, Convex, CoordModeOrigin);
	if (0 /* map bg matches widget bg */) {
	    XSetForeground(dpy, gc, dside->ui->whitecolor->pixel);
	    XDrawLines(dpy, mapw->d, gc, points, 7, CoordModeOrigin);
	}
    }
}

/* Draw the view proper. */

static void
draw_map_widget(MapW *mapw)
{
    draw_area_background(mapw);

    if (mapw->vp->isometric)
      draw_map_isometric(mapw);
    else
      draw_map_overhead(mapw);

    if (mapw->world)
      draw_map_outline(mapw, (MapW *) (mapw->map->widget), FALSE);
    if (mapw->map->curunit)
      draw_current(mapw);
    if (mapw->blasttype >= 0)
      draw_blast_image(mapw, mapw->blastsx - mapw->rsx, mapw->blastsy - mapw->rsy,
		       mapw->blastsw, mapw->blastsh, mapw->blasttype);
}

static void
draw_map_overhead(MapW *mapw)
{
    int vylo, vyhi, y, x1, vx, len, t;

    if (mapw->vp->vcx < 0 || mapw->vp->vcy < 0) {
    	run_warning("doing a nasty hack");
	mapw->vp->vcx = mapw->vp->vcy = 2;
    }

    /* This is equivalent to the y calc in nearest_cell. */
    vylo = ((mapw->vp->totsh - (mapw->vp->sy + mapw->vp->pxh)) / mapw->vp->hch);
    /* Adjust downwards by a row, to cover overlap. */
    vylo -= 1;
    /* Now adjust the bottom row so it doesn't go outside the area. */
    vylo = limitn(0, vylo, area.height - 1);
    vyhi = ((mapw->vp->totsh - (mapw->vp->sy + 0)) / mapw->vp->hch);
    vyhi = limitn(0, vyhi, area.height - 1);
    /* Compute the leftmost "column". */
    vx = (mapw->vp->sx - (vylo * mapw->vp->hw) / 2) / mapw->vp->hw;
    if (mapw->vp->draw_terrain) {
	for (y = vyhi; y >= vylo; --y) {
	    if (!compute_x1_len(mapw, vx, vylo, y, &x1, &len))
	      continue;
	    if (DebugG) {
		int tmpsx1, tmpsy1, tmpsx2, tmpsy2;

		xform(mapw, x1, y, &tmpsx1, &tmpsy1);
		xform(mapw, x1 + len - 1, y, &tmpsx2, &tmpsy2);
		tmpsx1 += 0;             tmpsy1 += mapw->vp->hh / 2;
		tmpsx2 += mapw->vp->hw;  tmpsy2 += mapw->vp->hh / 2;
		if (mapw->rsw >= 0) {
		    tmpsx1 += mapw->rsx;  tmpsy1 += mapw->rsy;
		    tmpsx2 += mapw->rsx;  tmpsy2 += mapw->rsy;
		}
		XSetClipMask(mapw->display, mapw->gc, None);
		XSetForeground(mapw->display, mapw->gc,
			       dside->ui->badcolor->pixel);
		XDrawLine(mapw->display, Tk_WindowId(mapw->tkwin), mapw->gc,
			  tmpsx1, tmpsy1, tmpsx2, tmpsy2);
	    }
	    draw_terrain_row(mapw, x1, y, len, FALSE);
	    if (between(4, mapw->vp->power, 6) && mapw->draw_transitions) {
		draw_terrain_transitions(mapw, x1, y, len, FALSE);
	    }
	}
	/* Restore the fill style after tinkering with it for terrain. */
	XSetFillStyle(mapw->display, mapw->gc, FillSolid);
	if (any_aux_terrain_defined()) {
	    for_all_terrain_types(t) {
		if (t_is_border(t)
		    && aux_terrain_defined(t)
		    && bwid[mapw->vp->power] > 0) {
		    draw_borders(mapw, vx, vyhi, vylo, t);
		} else if (t_is_connection(t)
			   && aux_terrain_defined(t)
			   && cwid[mapw->vp->power] > 0) {
		    draw_connections(mapw, vx, vyhi, vylo, t);
		}
	    }
	}
	/* The relative ordering of these is quite important.  Note that
	   each should be prepared to run independently also, since the
	   other displays might have been turned off. */
	if (elevations_defined()
	    && mapw->vp->draw_elevations
	    && mapw->vp->angle == 90) {
	    for (y = vyhi; y >= vylo; --y) {
		if (!compute_x1_len(mapw, vx, vylo, y, &x1, &len))
		  continue;
		draw_contours(mapw, x1, y, len);
	    }
	}
	/* Fuzz out the edges of the known area. */
	if (!mapw->map->see_all
	    && !g_terrain_seen()  /* (should look at count of unseen cells) */
	    && between(4, mapw->vp->power, 6))
	  draw_unseen_fuzz(mapw, vx, vyhi, vylo);
    }
    /* Now draw the lat-long grid if asked to do so. */
    if (mapw->vp->draw_latlong && mapw->vp->latlong_interval > 0)
      draw_meridians(mapw);
    for (y = vyhi; y >= vylo; --y) {
	if (!compute_x1_len(mapw, vx, vylo, y, &x1, &len))
	  continue;
	draw_row(mapw, x1, y, len);
    }
}

/* Given a row in the viewport, compute the starting cell and length
   of the row of cells to draw. */

static int
compute_x1_len(MapW *mapw, int vx, int vy, int y, int *x1p, int *lenp)
{
    int x1, x2, vw, halfheight = area.height / 2;

    /* Compute the number of cells visible in this row. */
    vw = (mapw->vp->pxw + mapw->vp->hw - 1) / mapw->vp->hw;
    vw = min(vw, area.width);
    /* Adjust the right and left bounds to fill the viewport as much
       as possible, without going too far (the drawing code will clip,
       but clipped drawing is still expensive). */
    x1 = vx - (y - vy) / 2;
    x2 = x1 + vw;
    if (1 /* should be more precise */)
      --x1;
    if (1 /* should be more precise */)
      ++x2;
    if (area.xwrap) {
    } else {
	/* Truncate x's to stay within the area. */
	x1 = max(0, min(x1, area.width-1));
	x2 = max(0, min(x2, area.width));
	/* If this row is entirely in the NE corner, don't draw
	   anything. */
	if (x1 + y > area.width + halfheight)
	  return FALSE;
	/* If this row is entirely in the SW corner, don't draw
	   anything. */
	if (x2 + y < halfheight)
	  return FALSE;
	/* If the row ends up in the NE corner, shorten it. */
	if (x2 + y > area.width + halfheight)
	  x2 = area.width + halfheight - y;
	/* If the row starts out in the SW corner, shorten it. */
	if (x1 + y < halfheight)
	  x1 = halfheight - y;
    }
    *x1p = x1;
    *lenp = x2 - x1;
    return (*lenp > 0);
}

static void draw_terrain_iso(MapW *mapw, int x, int y);

static void
draw_map_isometric(MapW *mapw)
{
    int x0, y0, x, y, xw;
    int i, j, dx1a1, dy1a1, dx1a2, dy1a2, dx1b1, dy1b1, dx1b2, dy1b2, firstpart, dx2, dy2;
    int xfirstend, yfirstend, zigzaga, zigzagb, alternate;
    int over;
    XColor *color;
    enum whattouse drawmeth;
    Pixmap pat;
    GC gc = mapw->gc;
    Display *dpy = mapw->display;

    if (mapw->vp->power >= 3) {
	switch (mapw->vp->isodir) {
	  case NORTHEAST:
	    x0 = area.width - 1;  y0 = area.height - 1;
	    zigzaga = FALSE;
	    dx1a1 = -1;  dy1a1 =  0;
	    xfirstend = 0;  yfirstend = area.height - 1;
	    zigzagb = TRUE;
	    dx1b1 =  1;  dy1b1 = -1;
	    dx1b2 = -1;  dy1b2 = 0;
	    dx2 = 2;  dy2 = -1;
	    break;
	  case EAST:
	    x0 = area.width - 1;  y0 = area.height - 1;
	    zigzaga = TRUE;
	    dx1a1 =  0;  dy1a1 = -1;
	    dx1a2 = -1;  dy1a2 =  1;
	    xfirstend = 0;  yfirstend = area.height - 1;
	    zigzagb = FALSE;
	    dx1b1 =  0;  dy1b1 = -1;
	    dx2 = 1;  dy2 = -2;
	    break;
	  case SOUTHEAST:
	    x0 = area.width - 1;  y0 = 0;
	    zigzaga = FALSE;
	    dx1a1 = 0;  dy1a1 = 1;
	    xfirstend = 0;  yfirstend = area.height - 1;
	    zigzagb = FALSE;
	    dx1b1 = -1;  dy1b1 = 0;
	    dx2 = -1;  dy2 = -1;
	    break;
	  case SOUTHWEST:
	    x0 = 0;  y0 = 0;
	    zigzaga = FALSE;
	    dx1a1 = 1;  dy1a1 = 0;
	    xfirstend = area.width - 1;  yfirstend = 0;
	    zigzagb = TRUE;
	    dx1b1 = -1;  dy1b1 = 1;
	    dx1b2 =  1;  dy1b2 = 0;
	    dx2 = -2;  dy2 = 1;
	    break;
	  case WEST:
	    x0 = 0;  y0 = 0;
	    zigzaga = TRUE;
	    dx1a1 = 0;  dy1a1 =  1;
	    dx1a2 = 1;  dy1a2 = -1;
	    xfirstend = area.width - 1;  yfirstend = 0;
	    zigzagb = FALSE;
	    dx1b1 = 0;  dy1b1 = 1;
	    dx2 = -1;  dy2 = 2;
	    break;
	  case NORTHWEST:
	    x0 = 0;  y0 = area.height - 1;
	    zigzaga = FALSE;
	    dx1a1 = 0;  dy1a1 = -1;
	    xfirstend = 0;  yfirstend = 0;
	    zigzagb = FALSE;
	    dx1b1 = 1;  dy1b1 = 0;
	    dx2 = 1;  dy2 = 1;
	    break;
	  default:
	    break;
	}
	firstpart = TRUE;
	alternate = FALSE;
	for (i = 0; i < 2 * (area.width + area.height); ++i) {
	    x = x0;  y = y0;
	    for (j = 0; j < (area.width + area.height) / 2; ++j) {
		if (in_area(x, y)) {
		    xw = wrapx(x);
		    drawmeth = cell_drawing_info(mapw, xw, y, &pat, &color, &over);
		    if (drawmeth != dontdraw) {
			XSetForeground(dpy, gc, color->pixel);
			draw_terrain_iso(mapw, x, y);
			draw_row(mapw, x, y, 1);
		    }
		}
		x += dx2;  y += dy2;
	    }
	    if (x == xfirstend && y == yfirstend)
	      firstpart = FALSE;
	    if (firstpart) {
		if (zigzaga) {
		    if (!alternate) {
			x0 += dx1a1;  y0 += dy1a1;
		    } else {
			x0 += dx1a2;  y0 += dy1a2;
		    }
		    alternate = !alternate;
		} else {
		    x0 += dx1a1;  y0 += dy1a1;
		}
	    } else {
		if (zigzagb) {
		    if (!alternate) {
			x0 += dx1b1;  y0 += dy1b1;
		    } else {
			x0 += dx1b2;  y0 += dy1b2;
		    }
		    alternate = !alternate;
		} else {
		    x0 += dx1b1;  y0 += dy1b1;
		}
	    }
	}
    } else {
	/* (draw columns) */
    }
    XSetFillStyle(dpy, gc, FillSolid);
}

static int shading(int dir);

static void
draw_terrain_iso(MapW *mapw, int x, int y)
{
    int xw, t, sx, sy, elev, elev1, drop, drawcliffs, x1, x1w, y1, sx1, sy1, t1, dir;
    int viewdir = mapw->vp->isodir;
    int hw = mapw->vp->hw, hh = mapw->vp->hh;
    Image *timg;
    GC gc = mapw->gc;
    Display *dpy = mapw->display;
    extern int any_thickness;
    XPoint points[4];

    xw = wrapx(x);
    t = terrain_at(xw, y);
    timg = best_image(dside->ui->timages[t], hw, hh);
    XSetClipMask(dpy, gc, dside->ui->hexisopics[mapw->vp->power]);
    set_terrain_gc_for_image(mapw, gc, timg);
    xform(mapw, x, y, &sx, &sy);
    XSetClipOrigin(dpy, gc, sx, sy);
    XFillRectangle(dpy, mapw->d, gc, sx, sy, hh, hw / 2);
    if (elevations_defined() || any_thickness) {
	elev = (elevations_defined() ? elev_at(xw, y) : 0) + t_thickness(t);
	XSetClipMask(dpy, gc, None);
	XSetFillStyle(dpy, gc, FillSolid);
	for_all_directions(dir) {
	    drawcliffs = FALSE;
	    XSetForeground(dpy, gc,
			   dside->ui->cellshades[t][shading(dir)]->pixel);
	    if (point_in_dir(xw, y, dir, &x1, &y1)) {
		t1 = terrain_at(x1, y1);
		x1w = wrapx(x1);
		elev1 = ((elevations_defined() ? elev_at(x1w, y1) : 0)
			 + t_thickness(t1));
		drop = elev - elev1;
		if (drop > 0 /* (should be "> one pixel") */) {
		    xform(mapw, x1, y1, &sx1, &sy1);
		    if (dir == opposite_dir(viewdir)) {
			points[0].x = sx + hw / 4;  points[0].y = sy + hw / 2;
			points[1].x = sx + hh - hw / 4;  points[1].y = sy + hw / 2;
			points[2].x = sx + hh - hw / 4;  points[2].y = sy1;
			points[3].x = sx + hw / 4;  points[3].y = sy1;
			XFillPolygon(dpy, mapw->d, gc, points, 4,
				     Convex, CoordModeOrigin);
		    } else if (dir == left_dir(opposite_dir(viewdir))) {
			points[0].x = sx + hh - hw / 4;  points[0].y = sy + hw / 2;
			points[1].x = sx + hh;  points[1].y = sy + hw / 4;
			points[2].x = sx + hh;  points[2].y = sy1;
			points[3].x = sx + hh - hw / 4;  points[3].y = sy1 + hw / 4;
			XFillPolygon(dpy, mapw->d, gc, points, 4,
				     Convex, CoordModeOrigin);
		    } else if (dir == right_dir(opposite_dir(viewdir))) {
			points[0].x = sx;  points[0].y = sy + hw / 4;
			points[1].x = sx + hw / 4;  points[1].y = sy + hw / 2;
			points[2].x = sx + hw / 4;  points[2].y = sy1 + hw / 4;
			points[3].x = sx;  points[3].y = sy1;
			XFillPolygon(dpy, mapw->d, gc, points, 4,
				     Convex, CoordModeOrigin);
		    } else if (dir == viewdir) {
			XDrawLine(dpy, mapw->d, gc,
				  sx + hw / 4, sy, sx + hh - hw /4, sy);
				  
		    } else if (dir == left_dir(viewdir)) {
			XDrawLine(dpy, mapw->d, gc,
				  sx, sy + hw / 4, sx + hw /4, sy);
				  
		    } else if (dir == right_dir(viewdir)) {
			XDrawLine(dpy, mapw->d, gc,
				  sx + hh - hw / 4 - 1, sy, sx + hh - 1, sy + hw / 4);
				  
		    }
		}
	    }
	}
    }
}

static int
shading(int dir)
{
    switch (dir) {
	case NORTHEAST:
	  return 3;
	case EAST:
	  return 4;
	case SOUTHEAST:
	  return 3;
	case SOUTHWEST:
	  return 1;
	case WEST:
	  return 0;
	case NORTHWEST:
	  return 1;
    }
    return 1;
}

/* Temporary stashes for the meridian drawing callbacks. */

static MapW *tmpmapw;

/* Draw latitude and longitude lines & labels. */

static void
draw_meridians(MapW *mapw)
{
    Display *dpy = mapw->display;

    XSetClipMask(dpy, mapw->gc, None);
    XSetForeground(dpy, mapw->gc, dside->ui->meridian_color->pixel);
    XSetBackground(dpy, mapw->gc, dside->ui->bgcolor->pixel);
    tmpmapw = mapw;
    plot_meridians(mapw->vp, meridian_line_callback, meridian_text_callback);
}

static void
meridian_line_callback(int x1, int y1, int x1f, int y1f,
		       int x2, int y2, int x2f, int y2f)
{
    int sx1, sy1, sx2, sy2;

    xform_fractional(tmpmapw, x1, y1, x1f, y1f, &sx1, &sy1);
    xform_fractional(tmpmapw, x2, y2, x2f, y2f, &sx2, &sy2);
    XDrawLine(tmpmapw->display, tmpmapw->d, tmpmapw->gc, sx1, sy1, sx2, sy2);
}

static void
meridian_text_callback(int x1, int y1, int x1f, int y1f, char *str)
{
    int sx1, sy1;

    xform_fractional(tmpmapw, x1, y1, x1f, y1f, &sx1, &sy1);
    XSetClipMask(tmpmapw->display, tmpmapw->gc, None);
    XSetForeground(tmpmapw->display, tmpmapw->gc, dside->ui->fgcolor->pixel);
    Tk_DrawChars(tmpmapw->display, tmpmapw->d, tmpmapw->gc, tmpmapw->main_font,
		 str, strlen(str), sx1 + 1, sy1 + 1);
}

/* The basic map drawing routine does an entire row at a time, which yields
   order-of-magnitude speedups. */

static void
draw_row(MapW *mapw, int x0, int y0, int len)
{
    int x, i;

    if (clouds_defined() && mapw->vp->draw_clouds) {
	draw_clouds_row(mapw, x0, y0, len);
    }
    if (temperatures_defined() && mapw->vp->draw_temperature && mapw->vp->hw > 10) {
	draw_temperature_row(mapw, x0, y0, len);
    }
    if (winds_defined() && mapw->vp->draw_winds && mapw->vp->hw > 10) {
	draw_winds_row(mapw, x0, y0, len);
    }
    /* Skip the top and bottom rows if they are edge rows. */
    if (!between(1, y0, area.height - 2))
      return;
    /* Skip the rightmost and leftmost cells if on the edge. */
    if (!inside_area(x0 + len - 1, y0))
      --len;
    if (!inside_area(x0, y0)) {
	++x0;
	--len;
    }
    if (len <= 0)
      return;
    if (features_defined() && mapw->vp->draw_feature_boundaries) {
	for (x = x0; x < x0 + len; ++x) {
	    draw_feature_boundary(mapw, x, y0);
	}
    }
    if (features_defined() && mapw->vp->draw_feature_names && dside->ui->legends) {
	for (i = 0; i < dside->ui->numfeatures; ++i) {
	    if (dside->ui->legends[i].oy == y0) {
		draw_feature_name(mapw, i);
	    }
	}
    }
    /* Draw sparse things on top of the basic row. */
    if (((people_sides_defined() && mapw->vp->draw_people)
	 || (control_sides_defined() && mapw->vp->draw_control))
	&& mapw->vp->hw >= 8) {
	for (x = x0; x < x0 + len; ++x) {
	    draw_people(mapw, x, y0);
	}
    }
    /* Draw units. */
    /* (should do names separately, to prevent overlap problems.) */
    if (mapw->vp->draw_units) {
	for (x = x0; x < x0 + len; ++x) {
	    draw_units(mapw, x, y0);
	}
    }
    if (mapw->vp->draw_ai) {
	for (x = x0; x < x0 + len; ++x) {
	    draw_ai_region(mapw, x, y0);
	}
    }
}

char buffer[BUFSIZE];

static void
draw_feature_name(MapW *mapw, int f)
{
    Legend *legend = &dside->ui->legends[f];
    int x = legend->ox, y = legend->oy;
    int dist = ((legend->dx + 1) * mapw->vp->hw * 9) / 10;
    int sx0, sy0, sxc2, syc;
    char *name;
    Display *dpy = mapw->display;
    GC gc = mapw->gc;

    name = feature_desc(find_feature(f + 1), buffer);
    if (empty_string(name))
      return;

    xform(mapw, x, y, &sx0, &sy0);
    /* xform returns coordinates of the upper-left corner of the cell */
    sxc2 = 2 * sx0 + (legend->dx + 1) * mapw->vp->hw; /* twice center x */
    syc  = sy0 + mapw->vp->hh / 2;		  /* center y */
    if (sxc2 + 2 * dist < 0)
      return;

    XSetClipMask(dpy, gc, None);
    XSetForeground(dpy, gc, dside->ui->blackcolor->pixel);
    XSetFont(dpy, gc, Tk_FontId(mapw->main_font));
    Tk_DrawChars(dpy, mapw->d, gc, mapw->main_font, name, strlen(name),
		 sxc2 / 2 + 1, syc);
    Tk_DrawChars(dpy, mapw->d, gc, mapw->main_font, name, strlen(name),
		 sxc2 / 2, syc + 1);
    XSetForeground(dpy, gc, dside->ui->feature_color->pixel);
    XSetForeground(dpy, gc, dside->ui->graycolor->pixel);
    Tk_DrawChars(dpy, mapw->d, gc, mapw->main_font, name, strlen(name),
		 sxc2 / 2, syc);
}

static int
cell_drawing_info(MapW *mapw, int xw, int y, Pixmap *patp, XColor **colorp,
		  int *overp)
{
    int t;
    enum whattouse rslt;

    if (mapw->map->see_all || terrain_view(dside, xw, y) != UNSEEN) {
	t = terrain_at(xw, y);
	*patp = dside->ui->terrpics[mapw->vp->power][t];
	*colorp = dside->ui->cellcolor[t];
	if (*colorp == NULL)
	  *colorp = dside->ui->blackcolor;
	if (mapw->vp->draw_cover
	    && !mapw->map->see_all
	    && cover(dside, xw, y) == 0)
	  *overp = -2;
	else if (mapw->vp->draw_lighting && night_at(xw, y))
	  *overp = -1;
	else
	  *overp = 0;
	rslt = (mapw->vp->power >= 3 ? usepictures : useblocks);
	if (mapw->draw_polygons && mapw->vp->power >= 3)
	  rslt = usepolygons;
	if (mapw->vp->power == 3)
	  rslt = usepolygons;
    } else {
	*patp = None;
	*colorp = dside->ui->whitecolor;
	*overp = 0;
	rslt = dontdraw;
    }
#ifdef DESIGNERS
    /* Designing overrides the overlay choice. */
    if (dside->designer) {
	if (!mapw->map->see_all && terrain_view(dside, xw, y) == UNSEEN)
	  *overp = -2;
	else
	  *overp = 0;
    }
#endif
    return rslt;
}

/* Given a terrain image, set up the GC for tiling, stippling, or solid
   color, as determined by the image and desired display style. */

static void
set_terrain_gc_for_image(MapW *mapw, GC gc, Image *timg)
{
    TkImage *tkimg;
    Display *dpy = mapw->display;

    if (timg != NULL && timg->istile && mapw->draw_terrain_patterns) {
	tkimg = (TkImage *) timg->hook;
	if (tkimg != NULL) {
	    if (!dside->ui->monochrome && tkimg->colr != None) {
		XSetFillStyle(dpy, gc, FillTiled);
		XSetTile(dpy, gc, tkimg->colr);
		return;
	    } else if (tkimg->mono != None) {
		XSetFillStyle(dpy, gc, FillOpaqueStippled);
		XSetStipple(dpy, gc, tkimg->mono);
		return;
	    }
	}
    }
    /* The fallback case. */
    XSetFillStyle(dpy, gc, FillSolid);
}

/* This interfaces higher-level drawing decisions to the rendition of
   individual pieces of display.  The rendering technique chosen
   depends on what the init code has decided is appropriate given what
   it found during init and what magnification the display is at.

   This routine is performance-critical; any improvements will
   probably have a noticeable effect on the display.  But also note
   that X's main bottleneck is the network connection, so it's more
   useful to eliminate roundtrips to the server than anything else. */

/* (should draw overlay in a separate pass?) */

static void
draw_terrain_row(MapW *mapw, int x0, int y0, int len, int force)
{
    int x0w, x1, x1w, x, xw, t, sx, sy, i = 0, j;
    int w = mapw->vp->hw, h = mapw->vp->hh, p = mapw->vp->power;
    int dogrid = mapw->vp->draw_grid;
    int over, segover;
    XColor *color, *segcolor;
    enum whattouse drawmethod, segdrawmethod;
    Pixmap pat, segpat;
    Image *timg, *subimg;
    GC gc = mapw->gc;
    Display *dpy = mapw->display;
    enum grayshade shade;

    x0w = wrapx(x0);
    x1 = x0;
    x1w = wrapx(x1);
    segdrawmethod = cell_drawing_info(mapw, x0w, y0,
				      &segpat, &segcolor, &segover);
    for (x = x0; x < x0 + len + 1; ++x) {
	xw = wrapx(x);
	t = terrain_at(xw, y0);
	drawmethod = cell_drawing_info(mapw, xw, y0, &pat, &color, &over);
	/* Decide if the run is over and we need to dump some output. */
	if (x == x0 + len
	    || x == area.width
	    || drawmethod != segdrawmethod
	    || color != segcolor
	    || pat != segpat
	    || over != segover
	    || segdrawmethod == usepolygons
	    || force) {
	    /* Note: we might end up drawing something that matches
	       the background color, which wastes time, but apparently
	       the test "(segdrawmethod != dontdraw && segcolor !=
	       dside->ui->bgcolor)" is not completely sufficient.
	       (should figure this one out sometime) */
	    t = terrain_at(x1w, y0);
	    timg = dside->ui->besttimages[p][t];
	    xform(mapw, x1, y0, &sx, &sy);
	    XSetForeground(dpy, gc, segcolor->pixel);
	    switch (segdrawmethod) {
	      case dontdraw:
		/* Don't do anything. */
		break;
	      case useblocks:
		XSetClipMask(dpy, gc, None);
		set_terrain_gc_for_image(mapw, gc, timg);
		XFillRectangle(dpy, mapw->d, gc, sx, sy, i * w, h);
		if (segover < 0) {
		    shade = GRAY(segover);
		    XSetFillStyle(dpy, gc, FillStippled);
		    XSetStipple(dpy, gc, dside->ui->grays[shade]);
		    XSetForeground(dpy, gc, dside->ui->blackcolor->pixel);
		    XFillRectangle(dpy, mapw->d, gc, sx, sy, i * w, h);
		}
		break;
	      case usepictures:
		if (use_clip_mask) {
		    if (dogrid) {
			XSetClipMask(dpy, gc, dside->ui->bhexpics[p]);
		    } else {
			XSetClipMask(dpy, gc, dside->ui->hexpics[p]);
		    }
		}
		set_terrain_gc_for_image(mapw, gc, timg);
		for (j = 0; j < i; ++j) {
		    int quasirand = (11 * (x1 + j) + (13 * y0) + 7) * 41;
		    TkImage *tkimg;

		    xform(mapw, x1 + j, y0, &sx, &sy);
		    if (use_clip_mask)
		      XSetClipOrigin(dpy, gc, sx, sy);
		    if (mapw->draw_terrain_images) {
		      subimg = timg;
		      if (timg->numsubimages > 0 && timg->subimages)
			subimg =
			  timg->subimages[quasirand % timg->numsubimages];
		      tkimg = (TkImage *) subimg->hook;
		      if (subimg
			  && tkimg
			  && tkimg->colr != None
			  && !timg->istile) {
			if (!use_clip_mask)
			  XSetFunction(dpy, gc, GXor);
			XCopyArea(dpy, tkimg->colr, mapw->d, gc,
				  0, 0, w, h, sx, sy);
			if (!use_clip_mask)
			  XSetFunction(dpy, gc, GXcopy);
		      } else if (use_clip_mask) {
			XFillRectangle(dpy, mapw->d, gc, sx, sy, w, h);
		      } else {
			draw_hex_polygon(mapw, gc, sx, sy, p, segover, dogrid);
		      }
		    } else {
		      /* User doesn't want to see images; fill into a
			 rectangle (if we can clip) or draw a polygon. */
		      if (use_clip_mask) {
			XFillRectangle(dpy, mapw->d, gc, sx, sy, w, h);
		      } else {
			draw_hex_polygon(mapw, gc, sx, sy, p, segover, dogrid);
		      }
		    }
		}
		if (segover < 0) {
		    shade = GRAY(segover);
		    XSetFillStyle(dpy, gc, FillStippled);
		    XSetStipple(dpy, gc, dside->ui->grays[shade]);
		    XSetForeground(dpy, gc, dside->ui->blackcolor->pixel);
		    for (j = 0; j < i; ++j) {
			xform(mapw, x1 + j, y0, &sx, &sy);
			XSetClipOrigin(dpy, gc, sx, sy);
			XFillRectangle(dpy, mapw->d, gc, sx, sy, w, h);
		    }
		}
		break;
	      case usepolygons:
		XSetClipMask(dpy, gc, None);
		set_terrain_gc_for_image(mapw, gc, timg);
		draw_hex_polygon(mapw, gc, sx, sy, p, segover, dogrid);
	    }
	    /* Reset everything for the next run. */
	    i = 0;
	    x1 = x;
	    x1w = wrapx(x1);
	    segdrawmethod = drawmethod;
	    segpat = pat;
	    segcolor = color;
	    segover = over;
	}
	++i;
    }
}

static void
draw_terrain_transitions(MapW *mapw, int x0, int y0, int len, int force)
{
    int x1, y1, x, xw, t, t1, dir, sx, sy, sw, sh;
    int sx3, sy3, quasirand;
    int x2, y2, trite, tleft, overrite, overleft, offset;
    int w = mapw->vp->hw, h = mapw->vp->hh, hch = mapw->vp->hch;
    int mainover, adjover;
    XColor *color;
    enum whattouse drawmethod;
    Pixmap pat;
    Image *timg;
    Image *trimg, *subimg;
    TkImage *tkimg;
    GC gc = mapw->gc;
    Display *dpy = mapw->display;
    enum grayshade shade;

    trimg = best_image(generic_transition, w, h);
    if (trimg == NULL)
      return;
    for (x = x0; x < x0 + len + 1; ++x) {
	xw = wrapx(x);
	t = terrain_at(xw, y0);
	drawmethod = cell_drawing_info(mapw, xw, y0, &pat, &color, &mainover);
	if (drawmethod == dontdraw)
	  continue;
	for_all_directions(dir) {
	    if (point_in_dir(xw, y0, dir, &x1, &y1)) {
		t1 = terrain_at(x1, y1);
		if (t1 < t) {
		    if (point_in_dir(xw, y0, right_dir(dir), &x2, &y2))
		      trite = terrain_at(x2, y2);
		    else
		      trite = t;
		    overrite = (trite == t1);
		    if (point_in_dir(xw, y0, left_dir(dir), &x2, &y2))
		      tleft = terrain_at(x2, y2);
		    else
		      tleft = t;
		    overleft = (tleft == t1);
		    drawmethod = cell_drawing_info(mapw, x1, y1,
						   &pat, &color, &adjover);
		    if (drawmethod == dontdraw) {
			continue;
		    }
		    timg = best_image(dside->ui->timages[t1], w, h);
		    xform(mapw, xw, y0, &sx, &sy);
		    switch (dir) {
		      case NORTHEAST:
			sx3 = sx + w / 2;  sy3 = sy;
			sw = w / 2;  sh = h - hch + 1;
			if (overrite)
			  offset = 1;
			else if (overleft)
			  offset = 2;
			break;
		      case EAST:
			sx3 = sx + w / 2;  sy3 = sy + (h - hch) + 1;
			sw = w / 2;  sh = h - 2 * (h - hch) - 2;
			if (overrite)
			  offset = 2;
			else if (overleft)
			  offset = 1;
			break;
		      case SOUTHEAST:
			sx3 = sx + w / 2;  sy3 = sy + hch - 1;
			sw = w / 2;  sh = h - hch + 1;
			if (overrite)
			  offset = 1;
			else if (overleft)
			  offset = 2;
			break;
		      case SOUTHWEST:
			sx3 = sx;  sy3 = sy + hch - 1;
			sw = w / 2;  sh = h - hch + 1;
			if (overrite)
			  offset = 2;
			else if (overleft)
			  offset = 1;
			break;
		      case WEST:
			sx3 = sx;  sy3 = sy + (h - hch) + 1;
			sw = w / 2;  sh = h - 2 * (h - hch) - 2;
			if (overrite)
			  offset = 1;
			else if (overleft)
			  offset = 2;
			break;
		      case NORTHWEST:
			sx3 = sx;  sy3 = sy;
			sw = w / 2;  sh = h - hch + 1;
			if (overrite)
			  offset = 2;
			else if (overleft)
			  offset = 1;
			break;
		    }
#if 0 /* for debugging */
		    XSetClipMask(dpy, gc, None);
		    XSetFillStyle(dpy, gc, FillSolid);
		    XSetForeground(dpy, gc, dside->ui->badcolor->pixel);
		    XDrawRectangle(dpy, mapw->d, gc, sx3, sy3, sw, sh);
#endif
		    if (overrite && overleft)
		      offset = 0;
		    else if (!overrite && !overleft)
		      offset = 3;
		    quasirand = (11 * xw + y0 + 7) * 41;
		    subimg = trimg->subimages[(quasirand % 4) * 4 + offset];
		    tkimg = (TkImage *) subimg->hook;
		    if (use_clip_mask)
		      XSetClipMask(dpy, gc, tkimg->mask);
		    /* Draw a bit of shadow along coastlines. */
		    /* (should just be a default, for when no
		       coastline imagery available) */
		    if (use_clip_mask
			&& t_liquid(t) != t_liquid(t1)
			&& between(SOUTHWEST, dir, EAST)) {
			XSetClipOrigin(dpy, gc,
				       sx - 1, sy - (dir == EAST ? 0 : 1));
			XSetFillStyle(dpy, gc, FillSolid);
			XSetForeground(dpy, gc, dside->ui->blackcolor->pixel);
			XFillRectangle(dpy, mapw->d, gc,
				       sx3 - 1, sy3 - (dir == EAST ? 0 : 1),
				       sw, sh);
		    }
		    if (use_clip_mask) {
			XSetClipOrigin(dpy, gc, sx, sy);
			set_terrain_gc_for_image(mapw, gc, timg);
			XSetForeground(dpy, gc, color->pixel);
			XFillRectangle(dpy, mapw->d, gc, sx3, sy3, sw, sh);
		    } else {
			/* (should implement at some point) */
		    }
		    if (mainover < 0) {
			shade = GRAY(mainover);
			XSetFillStyle(dpy, gc, FillStippled);
			XSetStipple(dpy, gc, dside->ui->grays[shade]);
			XSetForeground(dpy, gc, dside->ui->blackcolor->pixel);
			XFillRectangle(dpy, mapw->d, gc, sx3, sy3, sw, sh);
		    }
		}
	    }
	}
    }
}

/* Draw the fuzzed-out edges of the seen terrain all at once, using the
   same general algorithm as for borders, but testing seen-ness. */

static void
draw_unseen_fuzz(MapW *mapw, int vx, int vyhi, int vylo)
{
    int x, y, x1, len, bitmask, sx, sy, halfside;
    int xw, yw, xne, yne, xnw, ynw;
    int hw = mapw->vp->hw, hch = mapw->vp->hch;
    int power = mapw->vp->power;
    Display *dpy = mapw->display;
    GC gc = mapw->gc;
    XColor *color;

    /* (should implement no-clip-mask case) */
    if (!use_clip_mask)
      return;
    halfside = halfsides[power];
    color = dside->ui->blackcolor;
    /* (should use alternate unseen color if requested) */
    XSetForeground(dpy, gc, color->pixel);
    XSetClipMask(dpy, gc, fuzzpics[power]);
    for (y = vyhi; y >= vylo; --y) {
	if (!compute_x1_len(mapw, vx, vylo, y, &x1, &len))
	  continue;
	for (x = x1; x < x1 + len; ++x) {
	    /* Compute coordinates of adjacent cells. */
	    if (!point_in_dir(x, y, NORTHEAST, &xne, &yne))
	      continue;
	    if (!point_in_dir(x, y, NORTHWEST, &xnw, &ynw))
	      continue;
	    if (!point_in_dir(x, y, WEST, &xw, &yw))
	      continue;
	    xform(mapw, x, y, &sx, &sy);
	    /* Draw the junction at the top of the x,y hex. */
	    bitmask = 0;
	    if (terrain_visible(dside, xne, yne)
		!= terrain_visible(dside, xnw, ynw))
	      bitmask |= 1;
	    if (terrain_visible(dside, x, y)
		!= terrain_visible(dside, xne, yne))
	      bitmask |= 2;
	    if (terrain_visible(dside, x, y)
		!= terrain_visible(dside, xnw, ynw))
	      bitmask |= 4;
	    if (bitmask != 0) {
		XSetClipOrigin(dpy, gc,
			       sx - (bitmask & 3) * hw,
			       sy - halfside - ((bitmask & 4) ? 3 : 1) * hch);
		XFillRectangle(dpy, mapw->d, gc, sx, sy - halfside, hw, hch);
	    }
	    /* Draw the junction at the top left corner of the x,y hex. */
	    bitmask = 0;
	    if (terrain_visible(dside, x, y)
		!= terrain_visible(dside, xw, yw))
	      bitmask |= 1;
	    if (terrain_visible(dside, x, y)
		!= terrain_visible(dside, xnw, ynw))
	      bitmask |= 2;
	    if (terrain_visible(dside, xw, yw)
		!= terrain_visible(dside, xnw, ynw))
	      bitmask |= 4;
	    if (bitmask != 0) {
		XSetClipOrigin(dpy, gc,
			       sx - (bitmask & 3) * hw - hw / 2,
			       sy - halfside - ((bitmask & 4) ? 2 : 0) * hch);
		XFillRectangle(dpy, mapw->d, gc,
			       sx - hw / 2, sy - halfside, hw, hch);
	    }
	}
    }
    XSetFillStyle(dpy, gc, FillSolid);
}

static void
draw_contours(MapW *mapw, int x0, int y0, int len)
{
    int xx, x, y;
    int i, sx, sy, numlines;
    LineSegment *lines;
    Display *dpy = mapw->display;
    GC gc = mapw->gc;

    if (mapw->vp->contour_interval < 1)
      return;
    XSetClipMask(dpy, gc, None);
    XSetForeground(dpy, gc, dside->ui->contour_color->pixel);
    y = y0;
    for (xx = x0; xx < x0 + len; ++xx) {
	x = wrapx(xx);
	if (terrain_visible(dside, x, y0)) {
	    xform(mapw, x, y, &sx, &sy);
	    contour_lines_at(mapw->vp, x, y, sx, sy, &lines, &numlines);
	    for (i = 0; i < numlines; ++i) {
		XDrawLine(dpy, mapw->d, gc,
			  lines[i].sx1, lines[i].sy1,
			  lines[i].sx2, lines[i].sy2);
	    }
	}
    }
}

static void
draw_clouds_row(MapW *mapw, int x0, int y0, int len)
{
    int x, xw, sx, sy;
    Display *dpy = mapw->display;

    for (x = x0; x < x0 + len - 1; ++x) {
	xw = wrapx(x);
	if (mapw->map->see_all || terrain_visible(dside, xw, y0)) {
	    sprintf(spbuf, "%d", cloud_view(dside, xw, y0));
	    xform(mapw, x, y0, &sx, &sy);
	    XSetClipMask(dpy, mapw->gc, None);
	    XSetForeground(dpy, mapw->gc, dside->ui->fgcolor->pixel);
	    Tk_DrawChars(dpy, mapw->d, mapw->gc, mapw->main_font,
			 spbuf, strlen(spbuf), sx + 5, sy + mapw->vp->uh / 2);
	}
    }
}

static void
draw_temperature_row(MapW *mapw, int x0, int y0, int len)
{
    int x, xw, sx, sy;
    Display *dpy = mapw->display;

    for (x = x0; x < x0 + len - 1; ++x) {
	xw = wrapx(x);
	if (mapw->map->see_all || terrain_visible(dside, xw, y0)) {
	    sprintf(spbuf, "%d", temperature_view(dside, xw, y0));
	    xform(mapw, x, y0, &sx, &sy);
	    XSetClipMask(dpy, mapw->gc, None);
	    XSetForeground(dpy, mapw->gc, dside->ui->fgcolor->pixel);
	    Tk_DrawChars(dpy, mapw->d, mapw->gc, mapw->main_font,
			 spbuf, strlen(spbuf), sx + 5, sy + mapw->vp->uh / 2);
	}
    }
}

static void
draw_winds_row(MapW *mapw, int x0, int y0, int len)
{
    int x, xw, sx, sy, rawwind, wdir, wforce, swforce;
    Display *dpy = mapw->display;
    GC gc = mapw->gc;

    for (x = x0; x < x0 + len - 1; ++x) {
	xw = wrapx(x);
	if (draw_winds_here(dside, xw, y0)) {
	    rawwind = wind_view(dside, xw, y0);
#ifdef DESIGNERS
	    /* Show designers an accurate view. (should test in kernel?) */
	    if (dside->designer)
	      rawwind = (winds_defined() ? raw_wind_at(xw, y0) : CALM);
#endif
	    wdir = wind_dir(rawwind);  wforce = wind_force(rawwind);
	    xform(mapw, x, y0, &sx, &sy);
	    sx += (mapw->vp->hw - 16) / 2;  sy += (mapw->vp->hh - 16) / 2;
	    if (wforce < 0) {
		DGprintf("negative wind force %d, substituting 0", wforce);
		wforce = 0;
	    }
	    swforce =
	      ((wforce - minwindforce) * 5) / (maxwindforce - minwindforce);
	    if (swforce > 4)
	      swforce = 4;
	    if (swforce == 0)
	      wdir = 0;
	    if (use_clip_mask) {
	      XSetClipMask(dpy, gc, windpics[wforce][wdir]);
	      XSetClipOrigin(dpy, gc, sx + 1, sy + 1);
	      /* Draw a white arrow, offset slightly, for contrast on dark
		 backgrounds. */
	      XSetForeground(dpy, gc, dside->ui->whitecolor->pixel);
	      XFillRectangle(dpy, mapw->d, gc, sx + 1, sy + 1, 16, 16);
	      /* Draw the main black arrow. */
	      XSetClipOrigin(dpy, gc, sx, sy);
	      XSetForeground(dpy, gc, dside->ui->blackcolor->pixel);
	      XFillRectangle(dpy, mapw->d, gc, sx, sy, 16, 16);
	    } else {
	    }
	}
    }
}

/* Draw all the units in the given cell. */

static void
draw_units(MapW *mapw, int x, int y)
{
    int xw = wrapx(x), sx, sy, sw, sh, uview, u, s, osx, osy;
    int uw = mapw->vp->uw, uh = mapw->vp->uh, didone;
    Unit *unit;
    Display *dpy = mapw->display;
    GC gc = mapw->gc;

    if ((mapw->map->see_all || units_visible(dside, xw, y))
	&& unit_at(xw, y) != NULL) {
	if (uw <= 16 || mapw->vp->isometric) {
	    /* At smaller mags, we choose a single best unit to display. */
	    unit = NULL;
	    for_all_stack(x, y, unit) {
		if (unit->side == dside)
		  break;
	    }
	    if (unit == NULL) {
		for_all_stack(x, y, unit) {
		    if (mapw->map->see_all
			|| unit_actually_visible(dside, unit))
		      break;
		}
	    }
	    /* If no actual unit found, fall down to unit view display. */
	    if (unit != NULL) {
		xform(mapw, x, y, &sx, &sy);
		/* Adjust to unit part of cell. */
		if (mapw->vp->isometric) {
		    sx += (mapw->vp->hh - uw) / 2; sy += (mapw->vp->hw / 2 - uh);
		} else {
		    sx += (mapw->vp->hw - uw) / 2;  sy += (mapw->vp->hh - uh) / 2;
		}
		if (unit->occupant != NULL
		    && uw >= 8
		    && !mapw->vp->isometric
		    && (side_controls_unit(dside, unit)
			|| mapw->map->see_all
			|| u_see_occupants(unit->type))) {
		    /* Draw a "grouping box", in white, but with no occs
		       actually drawn. */
		    XSetClipMask(dpy, gc, None);
		    XSetForeground(dpy, gc, dside->ui->whitecolor->pixel);
		    XFillRectangle(dpy, mapw->d, gc,
				   sx + 1, sy + 1, uw - 2, uh - 2);
		    /* Put a black border around it, for better
                           contrast. */
		    XSetForeground(dpy, gc, dside->ui->blackcolor->pixel);
		    XDrawRectangle(dpy, mapw->d, gc,
				   sx + 1, sy + 1, uw - 2, uh - 2);
		}
		draw_unit_image(mapw, sx, sy, uw, uh, unit->type,
				side_number(unit->side), !completed(unit));
		/* Indicate that other units are stacked here also. */
		if (unit->nexthere != NULL && uw > 8) {
		    osx = sx + uw / 2 - 6;  osy = sy + uh - 2;
		    /* (should be able to do with one fill?) */
		    if (use_clip_mask) {
		      XSetClipMask(dpy, gc, None);
		      XSetClipOrigin(dpy, gc, osx, osy - 1);
		      XSetForeground(dpy, gc, dside->ui->whitecolor->pixel);
		      XFillRectangle(dpy, mapw->d, gc, osx, osy - 1, 12, 4);
		      XSetForeground(dpy, gc, dside->ui->blackcolor->pixel);
		      XSetClipMask(dpy, gc, dside->ui->dots);
		      XFillRectangle(dpy, mapw->d, gc, osx, osy - 1, 12, 4);
		    } else {
		      XSetFillStyle(dpy, gc, FillOpaqueStippled);
		      XSetTSOrigin(dpy, gc, osx, osy - 1);
		      XSetStipple(dpy, gc, dside->ui->dots);
		      XSetBackground(dpy, gc, dside->ui->whitecolor->pixel);
		      XSetForeground(dpy, gc, dside->ui->blackcolor->pixel);
		      XFillRectangle(dpy, mapw->d, gc, osx, osy - 1, 12, 4);
		      XSetTSOrigin(dpy, gc, 0, 0);
		      XSetFillStyle(dpy, gc, FillSolid);
		    }
		}
		if (mapw->vp->draw_names)
		  draw_unit_name(mapw, unit, sx, sy, uw, uh);
		return;
	    }
	} else {
	    /* At 32x32 and up, we can display several units in the stack. */
	    didone = FALSE;
	    for_all_stack(x, y, unit) {
		if (unit_actually_visible(dside, unit)) {
		    x_xform_unit(mapw, unit, &sx, &sy, &sw, &sh);
		    draw_unit_and_occs(mapw, unit, sx, sy, sw, sh, TRUE);
		    didone = TRUE;
		}
	    }
	    if (didone)
	      return;
	}
    }
    /* Do this if we didn't get to look at any actual units. */
    if ((uview = unit_view(dside, xw, y)) != EMPTY) {
	u = vtype(uview);  s = vside(uview);
	xform(mapw, x, y, &sx, &sy);
	sx += (mapw->vp->hw - uw) / 2;  sy += (mapw->vp->hh - uh) / 2;
	draw_unit_image(mapw, sx, sy, uw, uh, u, s, 0);
    }
}

/* This is a recursive routine that can display occupants and
   suboccupants. */

static void
draw_unit_and_occs(MapW *mapw, Unit *unit, int sx, int sy, int sw, int sh,
		   int drawoccs)
{
    int u = unit->type, s = side_number(unit->side), sx2, sy2, sw2, sh2;
    Unit *occ;
    Display *dpy = mapw->display;
    GC gc = mapw->gc;

    /* If an occupant's side is the same as its transport's, then there's
       really no need to draw its side emblem, since the transport's emblem
       will also be visible. */
    if (unit->transport && unit->side == unit->transport->side)
      s = -1;
    if (unit->occupant == NULL
	|| sw <= 8
	|| !(side_controls_unit(dside, unit)
	     || mapw->map->see_all
	     || u_see_occupants(unit->type)
	     || side_owns_occupant(dside, unit))) {
	draw_unit_image(mapw, sx, sy, sw, sh, u, s, !completed(unit));
	if (mapw->vp->draw_names)
	  draw_unit_name(mapw, unit, sx, sy, sw, sh);
    } else {
	x_xform_occupant(mapw, unit, unit, sx, sy, sw, sh,
			 &sx2, &sy2, &sw2, &sh2);
	/* Draw a white box to indicate the grouping. */
	XSetClipMask(dpy, gc, None);
	XSetForeground(dpy, gc, dside->ui->whitecolor->pixel);
	XFillRectangle(dpy, mapw->d, gc, sx + 1, sy + 1, sw - 2, sh - 2);
	/* Put a black border around it, for better contrast. */
	XSetForeground(dpy, gc, dside->ui->blackcolor->pixel);
	XDrawRectangle(dpy, mapw->d, gc, sx + 1, sy + 1, sw - 2, sh - 2);
	/* Draw the transport's image. */
	draw_unit_image(mapw, sx2, sy2, sw2, sh2, u, s,	!completed(unit));
	if (mapw->vp->draw_names)
	  draw_unit_name(mapw, unit, sx2, sy2, sw2, sh2); 
	/* Maybe draw all of its occupants, recursively. */
	if (drawoccs) {
	    for_all_occupants(unit, occ) {
		x_xform_occupant(mapw, unit, occ, sx, sy, sw, sh,
				 &sx2, &sy2, &sw2, &sh2);
		draw_unit_and_occs(mapw, occ, sx2, sy2, sw2, sh2, TRUE);
	    }
	}
    }
}

static void
draw_unit_name(MapW *mapw, Unit *unit, int sx, int sy, int sw, int sh)
{
    char namebuf[BUFSIZE];
    Display *dpy = mapw->display;
    GC gc = mapw->gc;

    name_or_number(unit, namebuf);
    if (empty_string(namebuf))
      return;
    sx += sw;
    sy += sh / 2 + 5;
    XSetClipMask(dpy, gc, None);
    XSetFont(dpy, gc, Tk_FontId(mapw->main_font));
#if 0
    sy += (dside->ui->ulegendfonts[power][0])->max_bounds.ascent;
    XSetFont(dside->ui->dpy, dside->ui->ltextgc,
	     (dside->ui->ulegendfonts[power][0])->fid);
#endif
    XSetForeground(dpy, gc, dside->ui->blackcolor->pixel);
    /* Civ adds 2 to each, works if font is large... */
    Tk_DrawChars(dpy, mapw->d, gc, mapw->main_font, namebuf, strlen(namebuf),
		 sx + 1, sy);
    Tk_DrawChars(dpy, mapw->d, gc, mapw->main_font, namebuf, strlen(namebuf),
		 sx, sy + 1);
    XSetForeground(dpy, gc, dside->ui->unit_name_color->pixel);
    XSetForeground(dpy, gc, dside->ui->whitecolor->pixel);
    Tk_DrawChars(dpy, mapw->d, gc, mapw->main_font, namebuf, strlen(namebuf),
		 sx, sy);
}


/* Indicate what kind of people are living in the given cell. */

static void
draw_people(MapW *mapw, int x, int y)
{
    int xw, xw1, pop, sx, sy, sw, sh, ex, ey, ew, eh, dir, x1, y1, pop1;
    int	con, con1, cbitmask1, cbitmask2, pbitmask1, pbitmask2, drawemblemhere;

    xw = wrapx(x);
    if (!terrain_visible(dside, xw, y))
      return;
    pop = (people_sides_defined() ? people_side_at(xw, y) : NOBODY);
    con = (control_sides_defined() ? control_side_at(xw, y) : NOCONTROL);
    cbitmask1 = cbitmask2 = pbitmask1 = pbitmask2 = 0;
    drawemblemhere = FALSE;
    /* Decide which edges are borders of the country. */
    for_all_directions(dir) {
	if (point_in_dir(x, y, dir, &x1, &y1)) {
	    xw1 = wrapx(x1);
	    if (inside_area(xw1, y1) && terrain_visible(dside, xw1, y1)) {
		pop1 = (people_sides_defined() ? people_side_at(xw1, y1) : NOBODY);
		con1 = (control_sides_defined() ? control_side_at(xw1, y1) : NOCONTROL);
		if (mapw->vp->draw_control && con != con1) {
		    cbitmask1 |= 1 << dir;
		    if (con != NOCONTROL && con1 != NOCONTROL) {
			cbitmask2 |= 1 << dir;
		    }
		    drawemblemhere = TRUE;
		} else if (mapw->vp->draw_people && pop != pop1) {
		    pbitmask1 |= 1 << dir;
		    if (pop != NOBODY && pop1 != NOBODY) {
			pbitmask2 |= 1 << dir;
		    }
		    drawemblemhere = TRUE;
		}
	    } else {
		/* Draw just people in the cells right at the edge of
                   the known world. */
		drawemblemhere = TRUE;
	    }
	}
    }
    if (drawemblemhere) {
	xform(mapw, x, y, &sx, &sy);
	for_all_directions(dir) {
	    int mask = 1 << dir;

	    if (cbitmask1 & mask) {
		draw_country_border_line(mapw, sx, sy, dir, TRUE,
					 (cbitmask2 & mask));
	    } else if (pbitmask1 & mask) {
		draw_country_border_line(mapw, sx, sy, dir, FALSE,
					 (pbitmask2 & mask));
	    }
	}
	/* Draw an emblem for the people in the cell. */
	if (mapw->vp->draw_people && pop != NOBODY) {
	    /* (should make generic) */
	    sw = mapw->vp->hw;  sh = mapw->vp->hh;
	    ew = min(sw, max(8, sw / 2));  eh = min(sh, max(8, sh / 2));
	    ex = sx + sw / 2 - ew / 2;  ey = sy + sh / 2 - eh / 2;
	    /* Maybe offset emblem so it will be visible underneath
	       control emblem. */
	    if (mapw->vp->draw_control && con != pop) {
		ex += ew / 4;  ey += eh / 4;
	    }
	    draw_side_emblem(mapw, ex, ey, ew, eh, pop);
	}
	if (mapw->vp->draw_control && con != NOCONTROL) {
	    /* (should make generic) */
	    sw = mapw->vp->hw;  sh = mapw->vp->hh;
	    ew = min(sw, max(8, sw / 2));  eh = min(sh, max(8, sh / 2));
	    ex = sx + sw / 2 - ew / 2;  ey = sy + sh / 2 - eh / 2;
	    if (mapw->vp->draw_people && con != pop) {
		ex -= ew / 4;  ey -= eh / 4;
	    }
	    draw_side_emblem(mapw, ex, ey, ew, eh, con);
	}
    }
}

/* There are two ways to draw borders; as images taken from a special
   border type image that includes 16 subimages for different combinations,
   or as lines colored according to the border's terrain type. */

static void
draw_borders(MapW *mapw, int vx, int vyhi, int vylo, int b)
{
    int x, y, x1, len, xw, dir, bitmask, sx, sy, x3, y3, subi;
    int halfside;
    Image *timg, *subimg;
    TkImage *tkimg;
    Display *dpy = mapw->display;
    GC gc = mapw->gc;
    XColor *color;
    int power = mapw->vp->power;
    int wid = bwid[power], wid2, sx1, sy1, sx2, sy2;

    timg = dside->ui->besttimages[power][b];
    if (!mapw->draw_lines
	&& between(4, power, 6)
	&& timg->isborder
	&& timg->subimages != NULL) {
	/* We want to draw individual images for border segments. */
	halfside = halfsides[power];
	for (y = vyhi; y >= vylo; --y) {
	    if (!compute_x1_len(mapw, vx, vylo, y, &x1, &len))
	      continue;
	    for (x = x1; x < x1 + len; ++x) {
		xw = wrapx(x);
		/* Draw the junction at the top of the x,y hex. */
		bitmask = 0;
		if (point_in_dir(xw, y, NORTHEAST, &x3, &y3)
		    && border_at(x3, y3, WEST, b)
		    && seen_border(dside, x3, y3, WEST))
		  bitmask |= 1;
		if (border_at(xw, y, NORTHEAST, b)
		    && seen_border(dside, xw, y, NORTHEAST))
		  bitmask |= 2;
		if (border_at(xw, y, NORTHWEST, b)
		    && seen_border(dside, xw, y, NORTHWEST))
		  bitmask |= 4;
		if (bitmask != 0) {
		    xform(mapw, xw, y, &sx, &sy);
		    subi = (bitmask & 3) + ((bitmask & 4) ? 12 : 4);
		    subimg = timg->subimages[subi];
		    tkimg = (TkImage *) subimg->hook;
		    if (tkimg != NULL) {
			if (use_clip_mask) {
			    if (tkimg->mask != None) {
				XSetClipOrigin(dpy, gc, sx, sy - halfside);
				XSetClipMask(dpy, gc, tkimg->mask);
			    }
			} else {
			    if (tkimg->mask != None) {
				XSetFunction(dpy, gc, GXand);
				/* (should draw smaller part of image) */
				XCopyArea(dpy, tkimg->mask, mapw->d, gc,
					  0, 0, mapw->vp->hw, mapw->vp->hch,
					  sx, sy - halfside);
			    }
			    XSetFunction(dpy, gc, GXor);
			}
			/* (should draw smaller part of image) */
			XCopyArea(dpy, tkimg->colr, mapw->d, gc,
				  0, 0, mapw->vp->hw, mapw->vp->hch,
				  sx, sy - halfside);
			if (!use_clip_mask)
			  XSetFunction(dpy, gc, GXcopy);
		    }
		}
		/* Draw the junction at the top left corner of the x,y hex. */
		bitmask = 0;
		if (border_at(xw, y, WEST, b)
		    && seen_border(dside, xw, y, WEST))
		  bitmask |= 1;
		if (border_at(xw, y, NORTHWEST, b)
		    && seen_border(dside, xw, y, NORTHWEST))
		  bitmask |= 2;
		if (point_in_dir(xw, y, WEST, &x3, &y3)
		    && border_at(x3, y3, NORTHEAST, b)
		    && seen_border(dside, x3, y3, NORTHEAST))
		  bitmask |= 4;
		if (bitmask != 0) {
		    xform(mapw, xw, y, &sx, &sy);
		    subi = (bitmask & 3) + ((bitmask & 4) ? 8 : 0);
		    subimg = timg->subimages[subi];
		    tkimg = (TkImage *) subimg->hook;
		    if (tkimg != NULL) {
			if (use_clip_mask) {
			    if (tkimg->mask != None) {
				XSetClipOrigin(dpy, gc, sx - mapw->vp->hw / 2, sy - halfside);
				XSetClipMask(dpy, gc, tkimg->mask);
			    }
			} else {
			    if (tkimg->mask != None) {
				XSetFunction(dpy, gc, GXand);
				/* (should draw smaller part of image) */
				XCopyArea(dpy, tkimg->mask, mapw->d, gc,
					  0, 0, mapw->vp->hw, mapw->vp->hch,
					  sx - mapw->vp->hw / 2, sy - halfside);
			    }
			    XSetFunction(dpy, gc, GXor);
			}
			/* (should draw smaller part of image) */
			XCopyArea(dpy, tkimg->colr, mapw->d, gc,
				  0, 0, mapw->vp->hw, mapw->vp->hch,
				  sx - mapw->vp->hw / 2, sy - halfside);
			if (!use_clip_mask)
			  XSetFunction(dpy, gc, GXcopy);
		    }
		}
	    }
	}
	if (use_clip_mask)
	  XSetClipMask(dpy, gc, None);
    } else {
	/* We want to draw borders as solid-color line segments. */
	wid2 = wid / 2;
	XSetLineAttributes(dpy, gc, bwid[power],
			   LineSolid, CapButt, JoinMiter); 
	XSetFillStyle(dpy, gc, FillSolid);
	color = dside->ui->cellcolor[b];
	if (color == NULL)
	  color = dside->ui->blackcolor;
	XSetForeground(dpy, gc, color->pixel);
	XSetBackground(dpy, gc, dside->ui->whitecolor->pixel);
	XSetClipMask(dpy, gc, None);
	for (y = vyhi; y >= vylo; --y) {
	    if (!compute_x1_len(mapw, vx, vylo, y, &x1, &len))
	      continue;
	    for (x = x1; x < x1 + len; ++x) {
		xw = wrapx(x);
		if (!terrain_visible(dside, xw, y)
		    || !any_borders_at(xw, y, b))
		  continue;
		bitmask = 0;
		for_all_directions(dir) {
		    if (border_at(xw, y, dir, b)
			&& seen_border(dside, xw, y, dir)) {
			bitmask |= 1 << dir;
		    }
		}
		if (bitmask != 0) {
		    xform(mapw, xw, y, &sx, &sy);
		    for_all_directions(dir) {
			if (bitmask & (1 << dir)) {
			    sx1 = bsx[power][dir];  sy1 = bsy[power][dir];
			    sx2 = bsx[power][dir+1];  sy2 = bsy[power][dir+1];
			    XDrawLine(dpy, mapw->d, gc,
				      sx + sx1 - wid2, sy + sy1 - wid2,
				      sx + sx2 - wid2, sy + sy2 - wid2);
			}
		    }
		}
	    }
	}
	XSetLineAttributes(dpy, gc, 0, LineSolid, CapButt, JoinMiter); 
    }
}

/* Draw all the connections of the given type that are visible. */

static void
draw_connections(MapW *mapw, int vx, int vyhi, int vylo, int c)
{
    int x, y, x1, len, dir, bitmask, sx, sy;
    int power = mapw->vp->power;
    int wid = cwid[power], wid2, cx = hws[power] / 2, cy = hhs[power] / 2;
    Image *timg, *subimg;
    TkImage *tkimg;
    Display *dpy = mapw->display;
    GC gc = mapw->gc;
    XColor *color;

    timg = dside->ui->besttimages[power][c];
    if (mapw->draw_lines
	|| !between(4, power, 6)
	|| !timg->isconnection
	|| timg->subimages == NULL) {
	wid2 = wid / 2;
	XSetLineAttributes(dpy, gc, wid, LineSolid, CapButt, JoinMiter); 
	color = dside->ui->cellcolor[c];
	if (color < 0)
	  color = dside->ui->blackcolor;
	XSetForeground(dpy, gc, color->pixel);
	XSetBackground(dpy, gc, dside->ui->whitecolor->pixel);
	set_terrain_gc_for_image(mapw, gc, timg);
    }
    for (y = vyhi; y >= vylo; --y) {
	if (!compute_x1_len(mapw, vx, vylo, y, &x1, &len))
	  continue;
	for (x = x1; x < x1 + len; ++x) {
	    if (!terrain_visible(dside, wrapx(x), y)
		|| !tt_drawable(c, terrain_at(wrapx(x), y)))
	      continue;
	    /* Make a bitmask of all the dis that have a connection. */
	    bitmask = 0;
	    for_all_directions(dir) {
		if (connection_at(x, y, dir, c))
		  bitmask |= (1 << dir);
	    }
	    if (bitmask != 0) {
		xform(mapw, x, y, &sx, &sy);
		/* Choose whether to draw images or lines.  Unlike in
		   border drawing, the two algorithms are so similar it's
		   not worth writing a different loop for each case. */
		if (!mapw->draw_lines
		    && between(4, power, 6)
		    && timg->isconnection
		    && timg->subimages != NULL) {
		    subimg = timg->subimages[bitmask];
		    tkimg = (TkImage *) subimg->hook;
		    if (tkimg != NULL) {
			if (use_clip_mask) {
			    if (tkimg->mask != None) {
				XSetClipOrigin(dpy, gc, sx, sy);
				XSetClipMask(dpy, gc, tkimg->mask);
			    }
			} else {
			    if (tkimg->mask != None) {
				XSetFunction(dpy, gc, GXand);
				XCopyArea(dpy, tkimg->mask, mapw->d, gc,
					  0, 0, mapw->vp->hw, mapw->vp->hh,
					  sx, sy);
			    }
			    XSetFunction(dpy, gc, GXor);
			}
			XCopyArea(dpy, tkimg->colr, mapw->d, gc,
				  0, 0, mapw->vp->hw, mapw->vp->hh, sx, sy);
			if (!use_clip_mask)
			  XSetFunction(dpy, gc, GXcopy);
		    }
		} else {
		    for_all_directions(dir) {
			if (bitmask & (1 << dir)) {
			    XDrawLine(dpy, mapw->d, gc,
				      sx + cx - wid2, sy + cy - wid2,
				      sx + cx + lsx[power][dir] - wid2,
				      sy + cy + lsy[power][dir] - wid2);
			}
		    }
		}
	    }
	}
    }
    /* Clean up after ourselves. */
    if (mapw->draw_lines || !between(4, power, 6) || timg->subimages == NULL) {
	XSetFillStyle(dpy, gc, FillSolid);
	XSetLineAttributes(dpy, gc, 0, LineSolid, CapButt, JoinMiter);
    } else {
	XSetClipMask(dpy, gc, None);
    }
}

/* Cursor drawing also draws the unit in some other color if it's not the
   "top-level" unit in a cell. */

/* "color unders" here are not correct */

static void
draw_current(MapW *mapw)
{
    int sx, sy, sw, sh;
    int uw = mapw->vp->uw, uh = mapw->vp->uh;
    enum grayshade shade = black;
    Unit *unit = NULL;
    Display *dpy = mapw->display;
    GC gc = mapw->gc;

    unit = mapw->map->curunit;
    if (!in_play(unit) || unit->id != mapw->map->curunit_id)
      return;
    /* Compute the bounding box we're going to hilite. */
    if (mapw->vp->power >= 5) { /* should be same test as for stack draw */
	x_xform_unit_self(mapw, unit, &sx, &sy, &sw, &sh);
    } else {
	xform(mapw, unit->x, unit->y, &sx, &sy);
	/* Adjust to unit part of cell. */
	sx += (mapw->vp->hw - uw) / 2;  sy += (mapw->vp->hh - uh) / 2;
	sw = uw;  sh = uh;
    }
    /* Always redraw the unit that the cursor is showing. */
    draw_unit_image(mapw, sx, sy, sw, sh, unit->type, side_number(unit->side),
		    !completed(unit));
    if (mapw->vp->draw_names)
      draw_unit_name(mapw, unit, sx, sy, sw, sh);
    /* "Outdent" the curunit indicator slightly, so that it doesn't cover
       up some of the unit image. */
    if (sw >= 16) {
	sx -= 1;  sy -= 1;
	sw += 2;  sh += 2;
    }
    /* Black is for units that can still act, dark gray for actors
       that used all acp, gray if the unit can't do anything. */
    shade = gray;
    if (unit->act && unit->act->initacp > 0)
      shade = ((unit->act->acp > 0) ? black : darkgray);
    /* Draw the curunit indicator proper. */
    XSetClipMask(dpy, gc, None);
    if (sw >= 4 && sh >= 4) {
	XSetFillStyle(dpy, gc, FillSolid);
	XSetForeground(dpy, gc, dside->ui->blackcolor->pixel);
	if (shade != black) {
	    XSetFillStyle(dpy, gc, FillOpaqueStippled);
	    XSetStipple(dpy, gc, dside->ui->grays[shade]);
	    XSetForeground(dpy, gc, dside->ui->blackcolor->pixel);
	    XSetBackground(dpy, gc, dside->ui->whitecolor->pixel);
	}
#if 0
	XDrawRectangle(dpy, mapw->d, gc, sx + 1, sy + 1, sw - 3, sh - 3);
#endif
	XFillRectangle(dpy, mapw->d, gc, sx, sy, sw, 2);
	XFillRectangle(dpy, mapw->d, gc, sx + sw - 2, sy, 2, sh);
	XFillRectangle(dpy, mapw->d, gc, sx, sy + sh - 2, sw, 2);
	XFillRectangle(dpy, mapw->d, gc, sx, sy, 2, sh);
#if 0 /* this makes the curunit box too heavy... */
	/* If the box is not too small, thicken the inner rectangle. */
	if (sw >= 8 && sh >= 8
	    && (unit ?
		!(unit->plan && (unit->plan->asleep || unit->plan->reserve))
		: TRUE)) {
#if 0 /* ? */
	    if (sw > 8 && sh > 8 && unit == NULL)
	      XSetForeground(dpy, gc, dside->ui->whitecolor->pixel);
#endif
	    XDrawRectangle(dpy, mapw->d, gc, sx + 2, sy + 2, sw - 5, sh - 5);
	}
#endif
    }
    /* Draw an outer box, either a crawling ants line if we're in
       autoselect mode, or solid white if the main selection box is
       solid black, for contrast. */
    if (mapw->map->autoselect) {
	XSetBackground(dpy, gc, dside->ui->blackcolor->pixel);
	XSetFillStyle(dpy, gc, FillOpaqueStippled);
	XSetStipple(dpy, gc, antpic);
	XSetTSOrigin(dpy, gc, mapw->map->anim_state % 8, 0);
    }
    if (mapw->map->autoselect || shade == black) {
      XSetForeground(dpy, gc, dside->ui->whitecolor->pixel);
      if (sw >= 2 && sh >= 2) {
#if 0 /* doesn't work on windows */
	XDrawRectangle(dpy, mapw->d, gc, sx, sy, sw - 1, sh - 1);
#endif
#if 0 /* doesn't work on windows */
	XDrawLine(dpy, mapw->d, gc, sx, sy, sx + sw - 1, sy);
	XDrawLine(dpy, mapw->d, gc, sx + sw - 1, sy, sx + sw - 1, sy + sh - 1);
	XDrawLine(dpy, mapw->d, gc, sx + sw - 1, sy + sh - 1, sx, sy + sh - 1);
	XDrawLine(dpy, mapw->d, gc, sx, sy + sh - 1, sx, sy);
#endif
	XFillRectangle(dpy, mapw->d, gc, sx, sy, sw, 1);
	XFillRectangle(dpy, mapw->d, gc, sx + sw - 1, sy, 1, sh);
	XFillRectangle(dpy, mapw->d, gc, sx, sy + sh - 1, sw, 1);
	XFillRectangle(dpy, mapw->d, gc, sx, sy, 1, sh);
      } else {
	/* Just fill in the whole unit area, at small mags. */
	XFillRectangle(dpy, mapw->d, gc, sx, sy, sw, sh);
      }
    }
    /* Reset the tile/stipple origin if necessary. */
    if (mapw->map->autoselect)
      XSetTSOrigin(dpy, gc, 0, 0);
    XSetFillStyle(dpy, gc, FillSolid);
}

void
draw_blast(Map *map, Unit *unit, int blasttype)
{
    int sx, sy, sw, sh;
    MapW *mapw = (MapW *) map->widget;

    if (between(0, blasttype, 2)) {
	if (mapw->vp->hw > 10) {
	    x_xform_unit(mapw, unit, &sx, &sy, &sw, &sh);
	} else {
	    xform(mapw, unit->x, unit->y, &sx, &sy);
	    sw = mapw->vp->hw;  sh = mapw->vp->hh;
	}
	mapw->blastsx = sx;  mapw->blastsy = sy;
	mapw->blastsw = sw;  mapw->blastsh = sh;
	mapw->blasttype = blasttype;
	update_at_unit(map, unit);
	Tcl_Sleep(400);
	mapw->blasttype = -1;
	update_at_unit(map, unit);
    } else if (blasttype == 3) {
	x_xform_unit(mapw, unit, &sx, &sy, &sw, &sh);
	mapw->blastsx = sx - 100;  mapw->blastsy = sy - 100;
	mapw->blastsw = sw + 200;  mapw->blastsh = sh + 200;
	mapw->blasttype = blasttype;
	update_area(map, unit->x - 5, unit->y - 5, 10, 10);
	Tcl_Sleep(1000);
	mapw->blasttype = -1;
	update_area(map, unit->x - 5, unit->y - 5, 10, 10);
    }
}

static void
draw_blast_image(MapW *mapw, int sx, int sy, int sw, int sh, int blasttype)
{
    int sx2, sy2;
    Image *bimg;
    TkImage *tkimg;
    Display *dpy = mapw->display;
    GC gc = mapw->gc;

    XSetClipMask(dpy, gc, None);
    if (blasttype == 3) {
	/*	XSetFunction(dpy, gc, GXinvert); */
	XSetForeground(dpy, gc, dside->ui->badcolor->pixel);
	XFillRectangle(dpy, mapw->d, gc, sx, sy, sw, sh);
	XSetFunction(dpy, gc, GXcopy);
    } else if (sw >= 16 && sh >= 16) {
	if (dside->ui->blastimages[blasttype] != NULL) {
	    bimg = best_image(dside->ui->blastimages[blasttype], sw, sh);
	    if (bimg != NULL
		&& ((tkimg = (TkImage *) bimg->hook) != NULL)) {
		/* Offset the image to draw in the middle of its area,
		   whether larger or smaller than the given area. */
		sx2 = sx + (sw - bimg->w) / 2;  sy2 = sy + (sh - bimg->h) / 2;
		/* Only change the size of the rectangle being drawn if it's
		   smaller than what was passed in. */
		if (bimg->w < sw) {
		    sx = sx2;
		    sw = bimg->w;
		}
		if (bimg->h < sh) {
		    sy = sy2;
		    sh = bimg->h;
		}
		if (tkimg->mono != None) {
		  if (use_clip_mask) {
		    XSetClipOrigin(dpy, gc, sx2 + 1, sy2 + 1);
		    XSetForeground(dpy, gc, dside->ui->blackcolor->pixel);
		    XSetClipMask(dpy, gc, tkimg->mono);
		    XFillRectangle(dpy, mapw->d, gc, sx + 1, sy + 1, sw, sh);
		    XSetClipOrigin(dpy, gc, sx2, sy2);
		    XSetForeground(dpy, gc, dside->ui->badcolor->pixel);
		    XSetClipMask(dpy, gc, tkimg->mono);
		    XFillRectangle(dpy, mapw->d, gc, sx, sy, sw, sh);
		  } else {
		    if (tkimg->mask != None) {
			XSetFunction(dpy, gc, GXand);
			XCopyArea(dpy, tkimg->mask, mapw->d, gc,
				  0, 0, sw, sh, sx, sy);
			/* Make a shadow. */
			XCopyArea(dpy, tkimg->mask, mapw->d, gc,
				  0, 0, sw, sh, sx + 1, sy + 1);
		    }
		    XSetFunction(dpy, gc, GXor);
		    XCopyArea(dpy, tkimg->colr, mapw->d, gc,
			      0, 0, sw, sh, sx, sy);
		    XSetFunction(dpy, gc, GXcopy);
		  }
		} else {
		    printf("no tkimg mono\n");
		}
	    } else {
		printf("no bimg\n");
	    }
	} else {
	    printf("no blastimfs\n");
	}
    } else if (sw >= 4 && sh >= 4) {
	/* Draw misses as 1/2 the size of hits and kills. */
	if (blasttype == 0) {
	    sx += sw / 4;  sy += sh / 4;
	    sw /= 2;  sh /= 2;
	}
	/* Draw a red box with a black shadow. */
	XSetForeground(dpy, gc, dside->ui->blackcolor->pixel);
	XFillRectangle(dpy, mapw->d, gc, sx + 1, sy + 1, sw - 1, sh - 1);
	XSetForeground(dpy, gc, dside->ui->badcolor->pixel);
	XFillRectangle(dpy, mapw->d, gc, sx, sy, sw - 1, sh - 1);
    } else {
	/* At the smallest sizes, draw a red box only. */
	XSetForeground(dpy, gc, dside->ui->badcolor->pixel);
	XFillRectangle(dpy, mapw->d, gc, sx, sy, sw, sh);
    }
}

static void
draw_feature_boundary(MapW *mapw, int x, int y)
{
    int wid, p, dir, fid0, fid1, x1, y1, sx, sy, bitmask1, bitmask2;
    Display *dpy = mapw->display;
    GC gc = mapw->gc;

    if (!terrain_visible(dside, wrapx(x), y))
      return;
    fid0 = raw_feature_at(x, y);
    if (fid0 == 0)
      return;
    p = mapw->vp->power;
    wid = bwid[p];
    if (wid == 0)
      return;
    /* Compute which sides should have border lines drawn. */
    bitmask1 = bitmask2 = 0;
    for_all_directions(dir) {
	if (point_in_dir(x, y, dir, &x1, &y1)) {
	    fid1 = raw_feature_at(x1, y1);
	    if (fid1 != fid0 && terrain_visible(dside, x1, y1)) {
		if (dside->designer
		    && (fid0 == dside->ui->curfid
			|| fid1 == dside->ui->curfid))
		  bitmask2 |= (1 << dir);
		else
		  bitmask1 |= (1 << dir);
	    }
	}
    }
    if (bitmask1 != 0 || bitmask2 != 0) {
	xform(mapw, x, y, &sx, &sy);
	XSetClipMask(dpy, gc, None);
	XSetFillStyle(dpy, gc, FillSolid);
	/* Line width of 0 draws as width 1, but possibly more efficiently. */
	wid = (p >= 4 ? 2 : 0);
	if (wid > 0)
	  XSetLineAttributes(dpy, gc, wid, LineSolid, CapButt, JoinMiter); 
	if (bitmask1 != 0) {
	    XSetForeground(dpy, gc, dside->ui->graycolor->pixel);
	    for_all_directions(dir) {
		if (bitmask1 & (1 << dir)) {
		    XDrawLine(dpy, mapw->d, gc,
			      sx + bsx[p][dir], sy + bsy[p][dir],
			      sx + bsx[p][dir+1], sy + bsy[p][dir+1]);
		}
	    }
	}
	if (bitmask2 != 0) {
	    XSetForeground(dpy, gc, dside->ui->badcolor->pixel);
	    for_all_directions(dir) {
		if (bitmask2 & (1 << dir)) {
		    XDrawLine(dpy, mapw->d, gc,
			      sx + bsx[p][dir], sy + bsy[p][dir],
			      sx + bsx[p][dir+1], sy + bsy[p][dir+1]);
		}
	    }
	}
	if (wid > 0)
	  XSetLineAttributes(dpy, gc, 0, LineSolid, CapButt, JoinMiter);
    }
}

static void
draw_ai_region(MapW *mapw, int x, int y)
{
    int xw, thid, sx, sy, dir, x1, y1, thid1, bitmask = 0, wid;
    int p = mapw->vp->power;
    Display *dpy = mapw->display;
    GC gc = mapw->gc;

    xw = wrapx(x);
    thid = ai_region_at(dside, xw, y);
    /* Decide which edges are borders of the theater. */
    for_all_directions(dir) {
	/* Don't do anything about edge cells. */
	if (interior_point_in_dir(xw, y, dir, &x1, &y1)) {
	    thid1 = ai_region_at(dside, wrapx(x1), y1);
	    if (thid != thid1) {
		bitmask |= (1 << dir);
	    }
	}
    }
    if (bitmask != 0) {
	xform(mapw, x, y, &sx, &sy);
	XSetClipMask(dpy, gc, None);
	XSetForeground(dpy, gc, dside->ui->whitecolor->pixel);
	wid = bwid[p];
	if (wid > 0) {
	    if (wid == 1)
	      wid = 0;
	    if (wid > 0)
	      XSetLineAttributes(dpy, gc, wid, LineSolid, CapButt, JoinMiter); 
	    for_all_directions(dir) {
		if (bitmask & (1 << dir)) {
		    XDrawLine(dpy, mapw->d, gc,
			      sx + bsx[p][dir], sy + bsy[p][dir],
			      sx + bsx[p][dir+1], sy + bsy[p][dir+1]);
		}
	    }
	    if (wid > 0)
	      XSetLineAttributes(dpy, gc, 0, LineSolid, CapButt, JoinMiter); 
	} else {
	    XFillRectangle(dpy, mapw->d, gc, sx, sy, hws[p], hhs[p]);
	}
    }
}

/* Do the grody work of drawing very large polygons accurately. */

static void
draw_hex_polygon(mapw, gc, sx, sy, power, over, dogrid)
MapW *mapw;
GC gc;
int sx, sy, power, over, dogrid;
{
    XPoint points[6];
    int hw = hws[power], hh = hhs[power], delt = (hhs[power] - hcs[power]);
    int ew = (dogrid ? 1 : 0);
    enum grayshade shade;
    Display *dpy = mapw->display;

    points[0].x = sx + hw / 2;         points[0].y = sy;
    points[1].x = sx + hw - ew;        points[1].y = sy + delt /*- ew*/;
    points[2].x = sx + hw - ew;        points[2].y = sy + (hh - delt - ew);
    points[3].x = sx + hw / 2;         points[3].y = sy + (hh - ew);
    points[4].x = sx;                  points[4].y = sy + (hh - delt - ew);
    points[5].x = sx;                  points[5].y = sy + delt;
    XFillPolygon(dpy, mapw->d, gc, points, 6, Convex, CoordModeOrigin);
    if (over < 0) {
	shade = GRAY(over);
	XSetClipMask(dpy, gc, None);
	XSetFillStyle(dpy, gc, FillStippled);
	XSetStipple(dpy, gc, dside->ui->grays[shade]);
	XSetForeground(dpy, gc, dside->ui->blackcolor->pixel);
	XFillPolygon(dpy, mapw->d, gc, points, 6, Convex, CoordModeOrigin);
    }
}

/* Put a unit image in the widget's window. */

void
draw_unit_image(mapw, sx, sy, sw, sh, u, s2, mod)
MapW *mapw;
int sx, sy, sw, sh, u, s2, mod;
{
    char *ename;
    int sx2, sy2, ex, ey, ew, eh, desperate = FALSE;
    XColor *imagecolor = dside->ui->blackcolor;
    XColor *maskcolor = dside->ui->whitecolor;
    ImageFamily *eimf;
    Image *uimg;
    TkImage *tkimg;
    Display *dpy = mapw->display;
    GC gc = mapw->gc;

    /* Filter out very small images. */
    if (sw <= 0)
      return;
    XSetClipMask(dpy, gc, None);
    /* Just draw a small box at 4x4 and below. */
    if (sw <= 4) {
	/* (should draw in side color if possible) */
	XSetForeground(dpy, gc, imagecolor->pixel);
	XFillRectangle(dpy, mapw->d, gc, sx, sy, sw, sh);
	return;
    }
    uimg = best_image(dside->ui->uimages[u], sw, sh);
    if (uimg != NULL) {
	/* Offset the image to draw in the middle of its area,
	   whether larger or smaller than the given area. */
	sx2 = sx + (sw - uimg->w) / 2;  sy2 = sy + (sh - uimg->h) / 2;
	/* Only change the size of the rectangle being drawn if it's
	   smaller than what was passed in. */
	if (uimg->w < sw) {
	    sx = sx2;
	    sw = uimg->w;
	}
	if (uimg->h < sh) {
	    sy = sy2;
	    sh = uimg->h;
	}
	/* Figure out what colors to use. */
	if (mapw->map->colorize_units && dside->ui->numcolors[s2] > 0)
	  imagecolor = dside->ui->colors[s2][0];
	if (mapw->map->colorize_units && dside->ui->numcolors[s2] > 1)
	  maskcolor = dside->ui->colors[s2][1];
	tkimg = (TkImage *) uimg->hook;
	if (tkimg != NULL) {
	    if (!dside->ui->monochrome
		&& tkimg->colr != None) {
		if (use_clip_mask) {
		    if (tkimg->mask != None) {
			/* set the clip mask */
			XSetClipOrigin(dpy, gc, sx2, sy2);
			XSetClipMask(dpy, gc, tkimg->mask);
		    }
		} else {
		    if (tkimg->mask != None) {
			XSetFunction(dpy, gc, GXand);
			XCopyArea(dpy, tkimg->mask, mapw->d, gc,
				  0, 0, sw, sh, sx, sy);
		    }
		    XSetFunction(dpy, gc, GXor);
		}
		/* Draw the color image. */
		XCopyArea(dpy, tkimg->colr, mapw->d, gc, 0, 0, sw, sh, sx, sy);
		if (!use_clip_mask)
		  XSetFunction(dpy, gc, GXcopy);
	    } else if (tkimg->mono != None || tkimg->mask != None) {
	      if (use_clip_mask) {
		/* Set the origin for any subsequent clipping. */
		XSetClipOrigin(dpy, gc, sx2, sy2);
		/* Set the color we're going to use for the mask; use
		   the imagecolor if we'll be using the mask as the
		   only image. */
		XSetForeground(dpy, gc,
			       (tkimg->mono == None ? imagecolor : maskcolor)->pixel);
		/* Set the clip mask to be explicit mask or unit's image. */
		if (tkimg->mask)
		  XSetClipMask(dpy, gc, tkimg->mask);
		else
		  XSetClipMask(dpy, gc, tkimg->mono);
		/* Draw the mask. */
		XFillRectangle(dpy, mapw->d, gc, sx, sy, sw, sh);
		/* Draw the image proper. */
		if (tkimg->mono != None) {
		    XSetForeground(dpy, gc, imagecolor->pixel);
		    XSetClipMask(dpy, gc, tkimg->mono);
		    XFillRectangle(dpy, mapw->d, gc, sx, sy, sw, sh);
		}
	      } else {
		/* Use the mask to make a black hole. */
		if (tkimg->mask != None) {
		  XSetFunction(dpy, gc, GXand);
		  XCopyArea(dpy, tkimg->mask, mapw->d, gc,
			    0, 0, sw, sh, sx, sy);
		}
		/* OR the mono image into the hole. */
		if (tkimg->mono != None) {
		  XSetFunction(dpy, gc, GXor);
		  XCopyArea(dpy, tkimg->mono, mapw->d, gc,
			    0, 0, sw, sh, sx, sy);
		}
		XSetFunction(dpy, gc, GXcopy);
	      }
	    }
	} else {
	    desperate = TRUE;
	}
    } else {
	desperate = TRUE;
    }
    if (desperate) {
	/* If all else fails, draw a black box and maybe a white border. */
	XSetForeground(dpy, gc, dside->ui->blackcolor->pixel);
	XFillRectangle(dpy, mapw->d, gc, sx, sy, sw, sh);
	if (sw >= 16) {
	    XSetForeground(dpy, gc, dside->ui->whitecolor->pixel);
	    XDrawRectangle(dpy, mapw->d, gc, sx, sy, sw, sh);
	}
    }
    /* If modification was requested, add a 50% white stipple over the
       image. */
    if (mod != 0) {
	XSetFillStyle(dpy, gc, FillStippled);
	XSetStipple(dpy, gc, dside->ui->grays[gray]);
	XSetClipMask(dpy, gc, None);
	XSetForeground(dpy, gc, dside->ui->whitecolor->pixel);
	XFillRectangle(dpy, mapw->d, gc, sx, sy, sw, sh);
	XSetFillStyle(dpy, gc, FillSolid);
    }
    /* Draw a side emblem if one was requested. */
    if (between(0, s2, numsides)) {
	ename = (side_n(s2) ? side_n(s2)->emblemname : NULL);
	eimf = dside->ui->eimages[s2];
	if (emblem_position(uimg, ename, eimf, sw, sh, &ex, &ey, &ew, &eh)) {
	    draw_side_emblem(mapw, sx + ex, sy + ey, ew, eh, s2);
	}
    }
}

/* Draw an emblem identifying the given side.  If a side does not have a
   distinguishing emblem, fall back on some defaults. */

void
draw_side_emblem(mapw, ex, ey, ew, eh, s2)
MapW *mapw;
int ex, ey, ew, eh, s2;
{
    int ex2, ey2, ew2, eh2;
    XColor *imagecolor, *maskcolor;
    Image *eimg;
    TkImage *tkimg;
    Display *dpy = mapw->display;
    GC gc = mapw->gc;

    /* Draw the emblem's mask, or else an enclosing box. */
    eimg = best_image(dside->ui->eimages[s2], ew, eh);
    if (eimg != NULL) {
	ew2 = eimg->w;  eh2 = eimg->h;
	if (ew2 == 1 && eh2 == 1) {
	    ew2 = 8;  eh2 = 6;
	}
	/* Offset the image to draw in the middle of its area,
	   whether larger or smaller than the given area. */
	ex2 = ex + (ew - ew2) / 2;  ey2 = ey + (eh - eh2) / 2;
	/* Only change the size of the rectangle being drawn if it's
	   smaller than what was passed in. */
	if (ew2 < ew) {
	    ex = ex2;
	    ew = ew2;
	}
	if (eh2 < eh) {
	    ey = ey2;
	    eh = ew2;
	}
	tkimg = (TkImage *) eimg->hook;
	/* Decide on the colors to use with the emblem. */
	if (dside->ui->numcolors[s2] > 0) {
	    imagecolor = dside->ui->colors[s2][0];
	} else {
	    imagecolor = dside->ui->blackcolor;
	}
	if (dside->ui->numcolors[s2] > 1) {
	    maskcolor = dside->ui->colors[s2][1];
	} else {
	    maskcolor = dside->ui->whitecolor;
	}
	/* If the emblem is a solid color, fill the entire emblem area
	   with that color. */
	if (eimg->w == 1 && eimg->h == 1 && tkimg->solid != NULL) {
	    XSetClipMask(dpy, gc, None);
	    XSetForeground(dpy, gc, tkimg->solid->pixel);
	    XFillRectangle(dpy, mapw->d, gc, ex, ey, ew, eh);
	    return;
	}
	/* Draw the mask. */
	XSetForeground(dpy, gc, maskcolor->pixel);
	if (use_clip_mask)
	  XSetClipOrigin(dpy, gc, ex, ey);
	if (tkimg != NULL && tkimg->mask != None) {
	  if (use_clip_mask) {
	    XSetClipMask(dpy, gc, tkimg->mask);
	    XFillRectangle(dpy, mapw->d, gc, ex, ey, ew, eh);
	  } else {
	    XSetFunction(dpy, gc, GXand);
	    XCopyArea(dpy, tkimg->mask, mapw->d, gc, 0, 0, ew, eh, ex, ey);
	    XSetFunction(dpy, gc, GXor);
	  }
	} else {
	    XSetClipMask(dpy, gc, None);
	    XFillRectangle(dpy, mapw->d, gc, ex - 1, ey, ew + 1, eh + 1);
	}
	/* Now draw the emblem proper. */
	if (!dside->ui->monochrome
	    && dside->ui->dflt_color_embl_images
	    && tkimg != NULL
	    && tkimg->colr != None) {
	    /* Draw the color image. */
	    XCopyArea(dpy, tkimg->colr, mapw->d, gc, 0, 0, ew, eh, ex, ey);
	} else {
	    XSetForeground(dpy, gc, imagecolor->pixel);
	    if (use_clip_mask) {
	      XSetClipMask(dpy, gc, (tkimg != NULL ? tkimg->mono : None));
	      XFillRectangle(dpy, mapw->d, gc, ex, ey, ew, eh);
	    }
	}
	if (!use_clip_mask)
	  XSetFunction(dpy, gc, GXcopy);
    }
}

static void
draw_country_border_line(mapw, sx, sy, dir, con, heavy)
MapW *mapw;
int sx, sy, dir, con, heavy;
{
    int pow = mapw->vp->power;
    int wid = bwid[pow];
    Display *dpy = mapw->display;
    GC gc = mapw->gc;

    if (wid == 0)
      return;
    wid = max(1, wid / 2);
    if (!heavy)
      wid = max(1, wid / 2);
    XSetClipMask(dpy, gc, None);
    XSetLineAttributes(dpy, gc, wid, LineSolid, CapButt, JoinMiter);
    if (con)
      XSetForeground(dpy, gc, dside->ui->frontline_color->pixel);
    else
      XSetForeground(dpy, gc, dside->ui->country_border_color->pixel);
    if (!heavy) {
	/* Lighten the color by stippling with white. */
	XSetBackground(dpy, gc, dside->ui->whitecolor->pixel);
	XSetFillStyle(dpy, gc, FillOpaqueStippled);
	XSetStipple(dpy, gc, dside->ui->grays[gray]);
    }
    XDrawLine(dpy, mapw->d, gc,
	      sx + bsx[pow][dir], sy + bsy[pow][dir],
	      sx + bsx[pow][dir+1], sy + bsy[pow][dir+1]);
    XSetFillStyle(dpy, gc, FillSolid);
    XSetLineAttributes(dpy, gc, 0, LineSolid, CapButt, JoinMiter);
}

/* Draw an outline of the main map in the world map.  We draw directly on
   the screen and use inversion to avoid having to recalc anything
   underneath. */

static void
draw_map_outline(MapW *worldw, MapW *mapw, int update)
{
    int sx, sy, sw, sh;
    Display *dpy = worldw->display;
    Drawable d;
    GC gc = worldw->gc;

    if (update)
      d = Tk_WindowId(worldw->tkwin);
    else
      d = worldw->d;
    scale_vp(worldw->vp, mapw->vp, &sx, &sy, &sw, &sh);
    if (!update
	|| sx != worldw->last_wsx || sy != worldw->last_wsy
	|| sw != worldw->last_wsw || sh != worldw->last_wsh) {
	XSetClipMask(dpy, gc, None);
	XSetFunction(dpy, gc, GXinvert);
	/* Undo the last draw. */
	if (update && worldw->last_wsw > 0) {
	    XDrawRectangle(dpy, d, gc,
			   worldw->last_wsx, worldw->last_wsy,
			   worldw->last_wsw, worldw->last_wsh);
	}
	XDrawRectangle(dpy, d, gc, sx, sy, sw, sh);
	XFlush(dpy);
	XSetFunction(dpy, gc, GXcopy);
	if (worldw->rsw < 0) {
	    worldw->last_wsx = sx;  worldw->last_wsy = sy;
	    worldw->last_wsw = sw;  worldw->last_wsh = sh;
	}
    }
}

/* Put the point x, y in the center of the mapw, or at least as close
   as possible. */

void
recenter(Map *map, int x, int y)
{
    int oldsx, oldsy;
    VP *vp;

    if (map->widget == NULL)
      return;
    vp = widget_vp(map);
    oldsx = vp->sx;  oldsy = vp->sy;
    set_view_focus(vp, x, y);
    center_on_focus(vp);
    if (vp->sx != oldsx || vp->sy != oldsy) {
	redraw_map(map);
    }
}

/* Ensure that given location is visible on the front map.  We
   (should) also flush the input because any input relating to a
   different screen is probably worthless. */

void
put_on_screen(Map *map, int x, int y)
{
    /* Ugly hack to prevent extra boxes being drawn during init - don't ask!*/
    if (x == 0 && y == 0)
      return;
    if (!in_middle(map, x, y))
      recenter(map, x, y);
}

/* Decide whether given location is not too close to edge of screen.
   We do this because it's a pain to move units when half the adjacent
   places aren't even visible.  This routine effectively places a
   lower limit of 5x5 for the map window. (I think) */

int
in_middle(Map *map, int x, int y)
{
    int sx, sy, insetx1, insety1, insetx2, insety2;
    MapW *mapw = (MapW *) map->widget;

    if (mapw == NULL)
      return FALSE;

    xform(mapw, x, y, &sx, &sy);
    /* Adjust to be the center of the cell, more reasonable if large. */
    sx += mapw->vp->hw / 2;  sy += mapw->vp->hh / 2;
    insetx1 = min(mapw->vp->pxw / 4, 1 * mapw->vp->hw);
    insety1 = min(mapw->vp->pxh / 4, 1 * mapw->vp->hch);
    insetx2 = min(mapw->vp->pxw / 4, 2 * mapw->vp->hw);
    insety2 = min(mapw->vp->pxh / 4, 2 * mapw->vp->hch);
    if (sx < insetx2)
      return FALSE;
    if (sx > mapw->vp->pxw - insetx2)
      return FALSE;
    if (sy < (between(2, y, area.height-3) ? insety2 : insety1))
      return FALSE;
    if (sy > mapw->vp->pxh - (between(2, y, area.height-3) ? insety2 : insety1))
      return FALSE;
    return TRUE;
}

int mouse_is_down;

void
handle_mouse_down(Map *map, int sx, int sy, int button)
{
    int ax, ay;
    void (*fn)(Side *sidex, Map *mapx, int cancelledx);
    VP *vp = widget_vp(map);

    mouse_is_down = TRUE;
    if (map == NULL) {
	beep(dside);
	return;
    }
    if (!x_nearest_cell((MapW *) map->widget, sx, sy, &ax, &ay)) {
	beep(dside);
	return;
    }
    map->inpx = ax;  map->inpy = ay;
    x_nearest_unit((MapW *) map->widget, sx, sy, &(map->inpunit));
    /* Assume that last place clicked is a reasonable focus. */
    if (inside_area(ax, ay)) {
	set_view_focus(vp, ax, ay);
    }
#ifdef DESIGNERS
    if (dside->designer && dside->ui->curdesigntool != survey_mode) {
	handle_designer_mouse_down(dside, map, sx, sy);
	return;
    }
#endif /* DESIGNERS */
    if (map->modalhandler) {
	fn = map->modalhandler;
	map->modalhandler = NULL;
#if 0 /* why is this here? */
	move_look(map, sx, sy);
#endif
	(*fn)(dside, map, 0);
	return;
    }

    switch (map->mode) {
      case survey_mode:
	if (button == 1)
	  move_look(map, sx, sy);
	else if (button == 3) {
	    if (map->curunit && side_controls_unit(dside, map->curunit)) {
		move_the_selected_unit(map, map->curunit, sx, sy);
	    } else {
		beep(dside);
	    }
	}
	break;
      case move_mode:
	if (map->curunit && side_controls_unit(dside, map->curunit)) {
	    move_the_selected_unit(map, map->curunit, sx, sy);
	} else {
	    beep(dside);
	}
	break;
     default:
	/* error eventually */
	break;
    }
}

void
move_look(Map *map, int sx, int sy)
{
    int nx, ny;
    Unit *unit;
    MapW *mapw = (MapW *) map->widget;

    if (x_nearest_cell(mapw, sx, sy, &nx, &ny)) {
	if (inside_area(nx, ny)) {
	    x_nearest_unit(mapw, sx, sy, &unit);
	    if (unit != NULL && (!side_controls_unit(dside, unit)))
	      unit = NULL;
	    set_current_unit(map, unit);
	} else {
	    beep(dside);
	}
    }
}

void
move_the_selected_unit(Map *map, Unit *unit, int sx, int sy)
{
    int x, y;
    Unit *other = NULL;
    MapW *mapw = (MapW *) map->widget;

    x_nearest_cell(mapw, sx, sy, &x, &y);
    x_nearest_unit(mapw, sx, sy, &other);
    advance_into_cell(dside, unit, x, y, other);
}

void
handle_mouse_up(Map *map, int sx, int sy, int button)
{
    mouse_is_down = FALSE;
}

void
handle_world_mouse_down(Map *map, int sx, int sy, int button)
{
    int x, y;

    if (map == NULL) {
	beep(dside);
	return;
    }
    if (!x_nearest_cell((MapW *) map->worldw, sx, sy, &x, &y)) {
	beep(dside);
	return;
    }
    recenter(map, x, y);
}

void
handle_world_mouse_up(Map *map, int sx, int sy, int button)
{
}

VP *
widget_vp(Map *map)
{
    MapW *mapw = (MapW *) map->widget;

    return (mapw ? mapw->vp : NULL);
}

/* Force a refresh of the map widgets in a map. */

void
redraw_map(Map *map)
{
    MapW *mapw;

    mapw = (MapW *) map->widget;
    if (mapw == NULL)
      return;
    /* Make the viewport see_all match the map's value. */
    mapw->vp->see_all = map->see_all;
    eventually_redraw(mapw, -1, -1, -1, -1);
    mapw = (MapW *) map->worldw;
    if (mapw == NULL)
      return;
    /* Make the viewport see_all match the map's value. */
    mapw->vp->see_all = map->see_all;
    eventually_redraw(mapw, -1, -1, -1, -1);
    eval_tcl_cmd("update idletasks");
}

void
set_tool_cursor(Side *side, Map *map)
{
    MapW *mapw = (MapW *) map->widget;
    Tk_Cursor cursor;

    if (mapw == NULL)
      return;
    cursor = side->ui->cursors[map->mode];
    if (cursor == None)
      cursor = side->ui->cursors[survey_mode];
    Tk_DefineCursor(mapw->tkwin, cursor);
}

int painttype;
int paintctrl;
int paintpeop;
int paintfid;
int paintelev;
int painttview;
int paintuview;

void
handle_designer_mouse_down(Side *side, Map *map, int sx, int sy)
{
    int x, y, dir, oldt, oldctrl, oldpeop, oldfid;
    int oldtview;
    Unit *unit;

    nearest_boundary(widget_vp(map), sx, sy, &x, &y, &dir);
    switch (dside->ui->curdesigntool) {
      case cell_paint_mode:
	/* Choose to paint fg or bg type, depending on what's already there. */
	oldt = terrain_at(x, y);
	painttype = (dside->ui->curttype == oldt ? dside->ui->curbgttype : dside->ui->curttype);
	net_paint_cell(dside, x, y, dside->ui->curbrushradius, painttype);
	break;
      case bord_paint_mode:
	net_paint_border(dside, x, y, dir, dside->ui->curttype, -1);
	break;
      case conn_paint_mode:
	net_paint_connection(dside, x, y, dir, dside->ui->curttype, -1);
	break;
      case coat_paint_mode:
	/* (should do something) */
	break;
      case unit_paint_mode:
	unit = net_designer_create_unit(dside, dside->ui->curutype,
					dside->ui->curusidenumber, x, y);
	break;
      case people_paint_mode:
	/* Paint people or clear, inverting from what is already here. */
	oldpeop = (people_sides_defined() ? people_side_at(x, y) : NOBODY);
	paintpeop = (dside->ui->curpeoplenumber == oldpeop ? NOBODY : dside->ui->curpeoplenumber);
	net_paint_people(dside, x, y, dside->ui->curbrushradius, paintpeop);
	break;
      case control_paint_mode:
	/* Paint control or clear it, inverting from what is already here. */
	oldctrl = (control_sides_defined() ? control_side_at(x, y) : NOCONTROL);
	paintctrl = (dside->ui->curcontrolnumber == oldctrl ? NOCONTROL : dside->ui->curcontrolnumber);
	net_paint_control(dside, x, y, dside->ui->curbrushradius, paintctrl);
	break;
      case feature_paint_mode:
	oldfid = (features_defined() ? raw_feature_at(x, y) : 0);
	paintfid = (dside->ui->curfid == oldfid ? 0 : dside->ui->curfid);
	net_paint_feature(dside, x, y, dside->ui->curbrushradius, paintfid);
	break;
      case elevation_paint_mode:
	paintelev = dside->ui->curelevation;
	net_paint_elevation(dside, x, y, dside->ui->curbrushradius, paintelev);
	break;
      case winds_paint_mode:
	net_paint_winds(dside, x, y, dside->ui->curbrushradius,
			dside->ui->curwinddir, dside->ui->curwindforce);
	break;
      case view_paint_mode:
	if (dside->terrview == NULL || dside->unitview == NULL)
	  break;
	oldtview = terrain_view(dside, x, y);
	painttview = (oldtview == UNSEEN ? buildtview(terrain_at(x, y)) : UNSEEN);
	paintuview = EMPTY;
	net_paint_view(dside, x, y, dside->ui->curbrushradius, painttview, paintuview);
	/* (should paint other view layers also) */
	break;
      default:
	break;
    }
}

/* Call this repeatedly when dragging around the mouse while button
   is down, to drag-paint the current type of layer data. */

void
paint_on_drag(Map *map, int sx, int sy, int mods)
{
    int x, y, brush;

    if (!mouse_is_down)
      return;
    if (x_nearest_cell((MapW *) map->widget, sx, sy, &x, &y)) {
	brush = dside->ui->curbrushradius;
	switch (dside->ui->curdesigntool) {
	  case cell_paint_mode:
	    net_paint_cell(dside, x, y, brush, painttype);
	    break;
	  case unit_paint_mode:
	    /* Drag-painting is almost never useful for unit placement. */
	    break;
	  case people_paint_mode:
	    net_paint_people(dside, x, y, brush, paintpeop);
	    break;
	  case control_paint_mode:
	    net_paint_control(dside, x, y, brush, paintctrl);
	    break;
	  case feature_paint_mode:
	    net_paint_feature(dside, x, y, brush, paintfid);
	    break;
	  case elevation_paint_mode:
	    net_paint_elevation(dside, x, y, brush, paintelev);
	    break;
	  case winds_paint_mode:
	    net_paint_winds(dside, x, y, brush,
			    dside->ui->curwinddir, dside->ui->curwindforce);
	    break;
	  default:
	    break;
	}
    }
}

/* Add the given rectangle to the area that we plan to redraw at the
   next update. */

static void
eventually_redraw(MapW *mapw, int sx, int sy, int sw, int sh)
{
    int sx1, sy1, sx2, sy2;

    if (sw >= 0 && sh >= 0) {
	if (mapw->rsw == -2) {
	    mapw->rsx = sx;  mapw->rsy = sy;
	    mapw->rsw = sw;  mapw->rsh = sh;
	} else if (mapw->rsw == -1) {
	    /* do nothing, already drawing whole map */
	} else {
	    sx1 = mapw->rsx;  sy1 = mapw->rsy;
	    sx2 = mapw->rsx + mapw->rsw;  sy2 = mapw->rsy + mapw->rsh;
	    if (sx < sx1)
	      sx1 = sx;
	    if (sy < sy1)
	      sy1 = sy;
	    if (sx + sw > sx2)
	      sx2 = sx + sw;
	    if (sy + sh > sy2)
	      sy2 = sy + sh;
	    mapw->rsx = sx1;  mapw->rsy = sy1;
	    mapw->rsw = sx2 - sx1;  mapw->rsh = sy2 - sy1;
	}
    } else {
	mapw->rsx = mapw->rsy = mapw->rsw = mapw->rsh = -1;
    }
    if (!mapw->update_pending) {
	Tcl_DoWhenIdle(mapw_display, (ClientData) mapw);
	mapw->update_pending = TRUE;
    }
}

int limitx(int x, int y);

void
update_area(Map *map, int x, int y, int w, int h)
{
    int x2, y2, sx, sy, sx2, sy2;
    MapW *mapw, *worldw;

    if (!in_area(x, y))
      return;
    mapw = (MapW *) map->widget;
    if (mapw == NULL)
      return;
    x2 = x + w;  y2 = y + h;
    y = limity(y);
    x = limitx(x, y);
    y2 = limity(y2);
    x2 = limitx(x2, y2);
    xform(mapw, x, y, &sx, &sy);
    xform(mapw, x2, y2, &sx2, &sy2);
    eventually_redraw(mapw, sx, sy, sx2 - sx, sy2 - sy);
    worldw = (MapW *) map->worldw;
    if (worldw != NULL) {
	xform(worldw, x, y, &sx, &sy);
	xform(worldw, x2, y2, &sx2, &sy2);
	eventually_redraw(worldw, sx, sy, sx2 - sx, sy2 - sy);
    }
    eval_tcl_cmd("update idletasks");
}

int
limitx(int x, int y)
{
    if (area.xwrap) {
	x = wrapx(x);
    } else {
	x = limitn(0, x, area.width - 1);
	if (x + y < area.halfheight)
	  x = area.halfheight - y;
	if (x + y >= area.width + area.halfheight)
	  x = area.width - (area.halfheight + y) - 1;
    }
    return x;
}

void
update_at_cell(Map *map, int x, int y, int flags)
{
    int sx, sy;
    MapW *mapw, *worldw;

    if (!in_area(x, y))
      return;
    mapw = (MapW *) map->widget;
    if (mapw == NULL)
      return;
    if (!((flags & UPDATE_ALWAYS)
	  || ((flags & UPDATE_COVER) && mapw->vp->draw_cover)
	  || ((flags & UPDATE_TEMP) && mapw->vp->draw_temperature)
	  || ((flags & UPDATE_CLOUDS) && mapw->vp->draw_clouds)
	  || ((flags & UPDATE_WINDS) && mapw->vp->draw_winds)
	  ))
      return;
    xform(mapw, x, y, &sx, &sy);
    eventually_redraw(mapw, sx, sy, mapw->vp->hw, mapw->vp->hh);
    worldw = (MapW *) map->worldw;
    if (worldw != NULL) {
	xform(worldw, x, y, &sx, &sy);
	eventually_redraw(worldw, sx, sy, worldw->vp->hw, worldw->vp->hh);
    }
    eval_tcl_cmd("update idletasks");
}

void
update_at_unit(Map *map, Unit *unit)
{
    int sx, sy;
    MapW *mapw, *worldw;

    if (!in_area(unit->x, unit->y))
      return;
    mapw = (MapW *) map->widget;
    if (mapw == NULL)
      return;
    xform(mapw, unit->x, unit->y, &sx, &sy);
    eventually_redraw(mapw, sx, sy, mapw->vp->hw, mapw->vp->hh);
    worldw = (MapW *) map->worldw;
    if (worldw != NULL) {
	xform(worldw, unit->x, unit->y, &sx, &sy);
	eventually_redraw(worldw, sx, sy, worldw->vp->hw, worldw->vp->hh);
    }
    eval_tcl_cmd("update idletasks");
}

void
draw_fire_line(Map *map, Unit *unit, Unit *unit2, int x2, int y2)
{
    int i, sx1, sy1, sw1, sh1, sx2, sy2, sw2, sh2, dx, dy, xx, yy;
    MapW *mapw;

    mapw = (MapW *) map->widget;
    if (mapw == NULL)
      return;
    {
	Display *dpy = mapw->display;
	Drawable d = Tk_WindowId(mapw->tkwin);
	GC gc = mapw->gc;

	XSetClipMask(dpy, gc, None);
	XSetFunction(dpy, gc, GXinvert);
	for (i = 0; i < 32; ++i) {
	    x_xform_unit_self(mapw, unit, &sx1, &sy1, &sw1, &sh1);
	    if (unit2 != NULL) {
		x_xform_unit_self(mapw, unit2, &sx2, &sy2, &sw2, &sh2);
	    } else {
		xform(mapw, x2, y2, &sx2, &sy2);
		sw2 = mapw->vp->hw;  sh2 = mapw->vp->hh;
	    }
	    compute_fire_line_segment(sx1 + sw1 / 2, sy1 + sh1 / 2,
				      sx2 + sw2 / 2, sy2 + sh2 / 2,
				      i, 4, &xx, &yy, &dx, &dy);
	    XDrawLine(dpy, d, gc, xx, yy, xx + dx, yy + dy);
	    XFlush(dpy);
	    /* (should do a busy wait if necessary to ensure timing here) */
	    Tcl_Sleep(10);
	}
	XSetFunction(dpy, gc, GXcopy);
    }
}
