/* Image functions for XANT

   To use these functions, the host system must send the special XANT
   order code for images (x'2B5C02') followed by an image command in
   one of these formats:

      +--------+--------+--------+--------+--------+
      | F_INIT |      Width      |      Height     |
      +--------+--------+--------+--------+--------+

      +--------+--------+
      | F_LUT  |LUT type|
      +--------+--------+

      +--------+--------+--------+--------+--------+--------+-------->
      | F_LLUT |  No. of Colors  |    LUT Data ...                   >
      +--------+--------+--------+--------+--------+--------+-------->

      +--------+--------+--------+--------+--------+--------+-------->
      | F_DATA |    Start Row    | Number of Rows  |    Data ...     >
      +--------+--------+--------+--------+--------+--------+-------->

      +--------+--------+
      | F_MAP  | 0 or 1 |
      +--------+--------+


   This module can support several image windows at once.  When the first
   F_INIT is done, a "working" window is created.  Data can be loaded
   into this window with F_DATA, or the color lookup table can be changed
   with F_LUT or F_LLUT.  If another F_INIT is done with a different
   size, the working window is destroyed and re-created with the new size.
   If the image-save() action routine is called (usually by pressing the 
   left mouse button), the working window is changed into a "saved" window.
   Saved windows cannot be changed by the host.  After an image-save(),
   the next F_INIT will create a new working window.

   The F_LUT and F_LLUT functions set the values in the color lookup
   table.  If the working window exists, the colors will be changed
   immediately.  Otherwise, the values will be saved and used later
   when the window is created.  The size of the lookup table is limited
   to 256 in the current implementation.

   The F_MAP function sets the type of color map that will be used by
   the next F_LUT or F_LLUT.  "F_MAP 0" means that the default color map
   of the screen should be used, and "F_MAP 1" means that a new color map
   should be created for the window (which usually results in strange
   colors outside the image window when the cursor is in the image window,
   and vice versa).  */

#include <stdio.h>
#include <memory.h>
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Shell.h>
#include <X11/Xatom.h>
#include "Im3270.h"


/* Function codes */
#define F_INIT	1		/* Initialize */
#define F_LUT 	2		/* Load pre-defined look-up table */
#define F_LLUT 	3		/* Load look-up table from data */
#define F_DATA	4		/* Write image data */
#define F_MAP	5		/* Set newmap value */

/* Pre-defined LUT types */
#define LUT_GRAY 1		/* Gray scale (default) */
#define LUT_RGB7 2		/* IAX style RGB7 color map */
#define LUT_595  3		/* 5 red, 9 green, 5 blue */

/* Internal LUT types */
#define LUT_NONE 0		/* No LUT specified yet */
#define LUT_LOAD 255		/* Use loaded LUT data */

/* Other definitions */
#define GRAY 16			/* Number of gray shades */

#define RGB7_R 4		/* Number of red shades in LUT_RGB7 color */
#define RGB7_G 8		/* Number of green shades in LUT_RGB7 color */
#define RGB7_B 4		/* Number of blue shades in LUT_RGB7 color */

#define L595_R 5		/* Number of red shades in LUT_595 color */
#define L595_G 9		/* Number of green shades in LUT_595 color */
#define L595_B 5		/* Number of blue shades in LUT_595 color */


/* Input states */
enum states
{
  st_init, st_doinit, st_width, st_height, st_initdone, st_lut, st_llut,
  st_lutlen, st_lutdata, st_data, st_row, st_numr, st_numr2, st_getdata,
  st_map
};


/* Information about each image window (working or saved) */
typedef struct _image_info {
  struct _image_info *next;	/* Link to next entry */
  struct _image_info *prev;	/* Link to previous entry */
  Boolean inwindow;		/* True if the pointer is in this window */
  Boolean mapset;		/* True if color map has been set */
  Widget toplevel;		/* Pop-up shell widget */
  Colormap cmap;		/* Color map used by this window */
  GC gc;			/* Graphics context */
  XImage *image;		/* The image data */
} image_info;


/* Global variables */
static Display *dpy;		/* The current display */
static int scr;			/* The screen to use */
static Colormap cmap;		/* Default color map */
static char title[100];		/* Title string for window */
static char *image_geometry;	/* Geometry for windows */
static enum states state;	/* Input state */
static Boolean newmap;		/* True if we should create a new color map */
static image_info *imwork;	/* Info for "working" image window */
static image_info *imlist;	/* List of saved image windows */


/* Information from host about a LUT to be loaded */
static unsigned char host_lut;	/* LUT type requested */
static Boolean host_new;	/* True if new map should be used */
static unsigned char *host_ldata; /* Data for LUT_LOAD */
static int host_len;		/* Length of LUT data in host_ldata */


/* Information about LUT that is currently loaded in the default map */
static unsigned char cur_lut;	/* LUT type */
static unsigned char *cur_ldata; /* Data if cur_lut is LUT_LOAD */
static int cur_len;		/* Length of cur_ldata */
static Pixel cur_pixval[256];	/* Internal color to pixel value translation */


/* Additional information about the "working" image window.  Not valid
   until after F_INIT is done (i.e. imwork is not NULL).  */

static Dimension width, height;	/* Size of image */
static Widget imwin;		/* The image window */
static unsigned char *data;	/* Data as originally sent by host */
static Pixel impixels[256];	/* Pixel values returned by XAllocColorCells */
static Pixel pixval[256];	/* Internal color to pixel value translation */


/* Forward and external routine declarations */
static void do_init(), new_lut(), load_lut(), dokey(),
  expose(), action(), destroy();
static XtEventHandler image_event();
extern Boolean s3270_key();


/* Initialization */
image_init(display, screen, titlestring, geometry)
     Display *display;
     int screen;
     char *titlestring;
     char *geometry;
{
  dpy = display;
  scr = screen;
  cmap = DefaultColormap(dpy, scr);
  (void) strcpy(title, "Image-");
  strncat(title, titlestring, sizeof title - 10);
  image_geometry = geometry;
  state = st_init;
  newmap = False;
  imwork = imlist = NULL;
  host_lut = LUT_GRAY;
  host_new = False;
  host_ldata = NULL;
  cur_lut = LUT_NONE;
  cur_ldata = NULL;
  width = height = 0;
  imwin = NULL;
}


/* Process image data.  Return length remaining or -1 if more data needed.  */
int image_write(buff, len)
     register unsigned char *buff;
     register int len;
{
  register unsigned char c;
  static int row, numrows, pos, endpos;
  static Dimension newwidth, newheight;
  static int datalen;
  static unsigned char *lutdata, *lutptr;

  /* Check for special end-of-data signal from image_end() */
  if (!buff)
    {
      /* Release any partially-filled LUT */
      if (state == st_lutdata) XtFree(lutdata);
      return 0;
    }
  
  while (len > 0)
    {
      c = *buff++;
      len--;

      switch (state) 
	{
	case st_init:
	  /* +-------------------------+ */
	  /* | Get image function code | */
	  /* +-------------------------+ */
	  switch (c)
	      {
	      case F_INIT:
		state = st_doinit;
		break;

	      case F_LUT:
		state = st_lut;
		break;

	      case F_LLUT:
		state = st_llut;
		break;

	      case F_DATA:
		state = st_data;
		break;

	      case F_MAP:
		state = st_map;
		break;
	      }
	  break;
	  
	case st_doinit:
	  /* +---------------+ */
	  /* | Handle F_INIT | */
	  /* +---------------+ */
	  newwidth = c << 8;
	  state = st_width;
	  break;

	case st_width:
	  /* Reading width */
	  newwidth += c;
	  state = st_height;
	  break;

	case st_height:
	  /* Reading height */
	  newheight = c << 8;
	  state = st_initdone;
	  break;

	case st_initdone:
	  /* Done with F_INIT */
	  newheight += c;
	  do_init(newwidth, newheight);
	  state = st_init;
	  return len;

 	case st_lut:
	  /* +--------------+ */
	  /* | Handle F_LUT | */
	  /* +--------------+ */
	  if (c == LUT_GRAY || c == LUT_RGB7 || c == LUT_595)
	    new_lut(c, (unsigned char *) NULL, 0);
	  state = st_init;
	  return len;
	  
	case st_llut:
	  /* +---------------+ */
	  /* | Handle F_LLUT | */
	  /* +---------------+ */
	  datalen = c << 8;
	  state = st_lutlen;
	  break;
	  
	case st_lutlen:
	  /* Reading LUT length */
	  datalen += c;
	  if (datalen < 1) return len;
	  lutdata = (unsigned char *) XtMalloc(3 * datalen);
	  lutptr = lutdata;
	  state = st_lutdata;
	  break;

	case st_lutdata:
	  /* Reading LUT data */
	  *lutptr++ = c;
	  if (lutptr - lutdata >= 3 * datalen) 
	    {
	      new_lut(LUT_LOAD, lutdata, datalen);
	      state = st_init;
	      return len;
	    }
	  break;

	case st_data:
	  /* +----------------+ */
	  /* | Process F_DATA | */
	  /* +----------------+ */
	  row = c << 8;
	  state = st_row;
	  break;
	  
	case st_row:
	  row += c;
	  state = st_numr;
	  break;

	case st_numr:
	  numrows = c << 8;
	  state = st_numr2;
	  break;

	case st_numr2:
	  numrows += c;
	  pos = row * width;
	  datalen = numrows * width;
	  if (row + numrows > height) numrows = height - row;
	  endpos = pos + numrows * width;
	  state = st_getdata;
	  break;

	case st_getdata:
	  buff--; len++;	/* Put back the character we peeked at */
	  datalen -= len;

	  /* If F_INIT wasn't done then just skip over the data */
	  if (!imwork)
	    {
	      if (datalen <= 0)
		{
		  state = st_init;
		  return -datalen;
		}
	      return -1;
	    }
	  
	  /* Copy data to image */
	  for (; len > 0 && pos < endpos; len--, pos++, buff++)
	    {
	      data[pos] = *buff;
	      (void) XPutPixel(imwork->image, pos % width, pos / width,
			       pixval[*buff]);
	    }

	  /* When we get it all, write it out to the screen */
	  if (datalen <= 0)
	    {
	      if (XtIsRealized(imwin))
		XPutImage(dpy, XtWindow(imwin), imwork->gc, imwork->image,
			  0, row, 0, row, (unsigned int) width,
			  (unsigned int) numrows);
	      state = st_init;
	      return -datalen;
	    }
	  break;

	case st_map:
	  /* +---------------+ */
	  /* | Process F_MAP | */
	  /* +---------------+ */
	  newmap = c;
	  state = st_init;
	  return len;
	}
    }
  return -1;
}


/* End of image order reached */
image_end()
{
  (void) image_write((unsigned char *) NULL, 0); /* Signal end of data */
  state = st_init;
}


/* Handle F_INIT */
static void do_init(newwidth, newheight)
     Dimension newwidth, newheight;
{
  register int i;
  Arg arglist[20];
  XGCValues gcv;
  Pixel background;
  int x, y;
  static XtCallbackRec keycalls[] =
    { {(XtCallbackProc) dokey, NULL}, {NULL, NULL} };
  static XtCallbackRec exposecalls[] =
    { {(XtCallbackProc) expose, NULL}, {NULL, NULL} };
  static XtCallbackRec actioncalls[] =
    { {(XtCallbackProc) action, NULL}, {NULL, NULL} };
  static XtCallbackRec destroycalls[] =
    { {(XtCallbackProc) destroy, NULL}, {NULL, NULL} };

  /* If a window already exists with the right size and colormap, use it */
  if (imwork && newwidth == width && newheight == height &&
      (host_new && imwork->cmap != cmap || !host_new && imwork->cmap == cmap))
    return;

  /* Destroy any existing image and initialize new image info */
  if (imwork) XtDestroyWidget(imwork->toplevel);
  imwork = (image_info *) XtNew(image_info);
  exposecalls[0].closure = (caddr_t) imwork;
  actioncalls[0].closure = (caddr_t) imwork;
  destroycalls[0].closure = (caddr_t) imwork;
  imwork->next = imwork->prev = imwork;
  imwork->inwindow = imwork->mapset = False;
  imwork->image = NULL;

  /* Set up the color map */
  if (host_new)
    {
      int maplen;

      imwork->cmap = XCreateColormap(dpy, RootWindow(dpy, scr),
				     DefaultVisual(dpy, scr), AllocNone);
      /*...Should search for the best visual instead of using default */

      /* Grab all the cells in the map */
      maplen = DisplayCells(dpy, scr);
      if (maplen > 256) maplen = 256;
      if (!XAllocColorCells(dpy, imwork->cmap, False, (Pixel *) NULL, 0,
			    impixels, maplen))
	error("Can't allocate all color cells");
    }
  else imwork->cmap = cmap;
  load_lut();

  /* Create new shell */
  width = newwidth;
  height = newheight;
  i = 0;
  XtSetArg(arglist[i], XtNminWidth, width); i++;
  XtSetArg(arglist[i], XtNminHeight, height); i++;
  XtSetArg(arglist[i], XtNtitle, title); i++;
  XtSetArg(arglist[i], XtNiconName, title); i++;
  XtSetArg(arglist[i], XtNcolormap, imwork->cmap); i++;
  if (image_geometry)
    { XtSetArg(arglist[i], XtNgeometry, image_geometry); i++; }
  imwork->toplevel = XtAppCreateShell((String) NULL, "Xant",
				      topLevelShellWidgetClass, dpy,
				      arglist, i);

  /* Create the graphics window as a child of the shell */
  i = 0;
  XtSetArg(arglist[i], XtNwidth, width); i++;
  XtSetArg(arglist[i], XtNheight, height); i++;
  XtSetArg(arglist[i], XtNkeyProc, keycalls); i++;
  XtSetArg(arglist[i], XtNexposeProc, exposecalls); i++;
  XtSetArg(arglist[i], XtNactionProc, actioncalls); i++;
  XtSetArg(arglist[i], XtNdestroyProc, destroycalls); i++;
  XtSetArg(arglist[i], XtNcolormap, imwork->cmap); i++;
  if (host_new)
    {
      /* Be sure no colors are used so that read-only cells won't be grabbed */
      XtSetArg(arglist[i], XtNforeground, "XtDefaultForeground"); i++;
      XtSetArg(arglist[i], XtNbackground, "XtDefaultBackground"); i++;
      XtSetArg(arglist[i], XtNforeColor,  "XtDefaultForeground"); i++;
      XtSetArg(arglist[i], XtNbackColor,  "XtDefaultBackground"); i++;
      XtSetArg(arglist[i], XtNmouseColor, "XtDefaultForeground"); i++;
    }
  imwin = XtCreateManagedWidget("im3270", im3270WidgetClass, imwork->toplevel,
				arglist, i);

  imwork->gc = XtGetGC(imwin, 0, &gcv);

  /* Allocate memory for the image data */
  data = (unsigned char *) XtMalloc(width * height);
  memset(data, 0, width * height);
  imwork->image = XCreateImage(dpy, DefaultVisual(dpy, scr),
			       DefaultDepth(dpy, scr), ZPixmap, 0,
			       (char *) XtMalloc(width * height *
						 sizeof(Pixel)),
			       width, height, 8, 0);
  XtSetArg(arglist[0], XtNbackground, &background);
  XtGetValues(imwin, arglist, 1);
  for (x = 0; x < width; x++)
    for (y = 0; y < height; y++)
      (void) XPutPixel(imwork->image, x, y, background);

  /* Handle events */
  XtAddEventHandler(imwin, EnterWindowMask | LeaveWindowMask |
		    StructureNotifyMask, False, image_event, imwork);

  /* Map the window */
  XtRealizeWidget(imwork->toplevel);
}


/* Set new LUT values.  If image exists, load the new values into colormap.  */
static void new_lut(lutval, ldata, llen)
     unsigned char lutval, *ldata;
     int llen;
{
  if (host_ldata) XtFree(host_ldata);
  host_lut = lutval;
  host_ldata = ldata;
  host_len = llen;
  host_new = newmap;

  /* If a working window exists and it has the wrong type of color map,
     then destroy it (user will have to do another F_INIT and re-load
     the data).  */

  if (imwork && (host_new && imwork->cmap == cmap ||
		 !host_new && imwork->cmap != cmap))
    {
      XtDestroyWidget(imwork->toplevel);
      imwork = NULL;
    }
  load_lut();
}


/* Load new LUT values (from host_xxx variables) into the colormap */
static void load_lut()
{
  register int r, g, b, i;
  int startp, ndefs;
  XColor defs[256];
  static Pixel pixels[256];	/* Pixels that we allocated in default map */
  static int npixels;		/* Number of entries in pixels array */

  /* If using a new map, don't load unless the window exists.  For
     the default map, load the new colors unless they match what is
     already loaded.  */

  if (host_new)
    {
      if (!imwork) return;
    }
  else
    {
      Boolean match = False;
      if (host_lut == LUT_LOAD && cur_lut == LUT_LOAD && host_len == cur_len)
	{
	  for (i = 0; i < 3 * host_len; i++)
	    if (host_ldata[i] != cur_ldata[i]) break;
	  if (i == 3 * host_len) match = True;
	}
      if (match || cur_lut == host_lut && cur_lut != LUT_LOAD)
	{
	  for (i = 0; i < 256; i++) pixval[i] = cur_pixval[i];
	  return;
	}
    }

  /* If we're loading the default map, destroy any saved windows that
     also use the default map and then free the colors.  */

  if (!host_new)
    {
      image_info *im;
      for (im = imlist; im; )
	{
	  if (im->cmap == cmap)
	    {
	      /* This saved window uses the default map, so zap it */
	      if (im == im->next && im == imlist) imlist = NULL;
	      else
		{
		  if (im == imlist) imlist = im->next;
		  im->prev->next = im->next;
		  im->next->prev = im->prev;
		}
	      XtDestroyWidget(im->toplevel);
	      im = imlist;
	    }
	  else
	    {
	      im = im->next;
	      if (im == imlist) break;
	    }
	}
      if (cur_lut != LUT_NONE)
	XFreeColors(dpy, cmap, pixels, npixels, 0);
    }

  /* Load the defs array with the desired color values */
  switch (host_lut)
    {
    case LUT_GRAY:
      ndefs = GRAY;
      startp = host_new ? 256 - ndefs : 0;
      for (i = 0; i < GRAY; i++)
	{
	  defs[startp + i].flags = DoRed | DoGreen | DoBlue;
	  defs[startp + i].red =
	  defs[startp + i].green =
	  defs[startp + i].blue = (i * 255 / (GRAY - 1)) << 8;
	}
      break;

    case LUT_RGB7:
      ndefs = RGB7_R * RGB7_G * RGB7_B;
      startp = host_new ? 256 - ndefs : 0;
      i = 0;
      for (r = 0; r < RGB7_R; r++) 
	for (g = 0; g < RGB7_G; g++)
	  for (b = 0; b < RGB7_B; b++)
	    {
	      defs[startp + i].flags = DoRed | DoGreen | DoBlue;
	      defs[startp + i].red   = (r * 255 / (RGB7_R - 1)) << 8;
	      defs[startp + i].green = (g * 255 / (RGB7_G - 1)) << 8;
	      defs[startp + i].blue  = (b * 255 / (RGB7_B - 1)) << 8;
	      i++;
	    }
      break;

    case LUT_595:
      ndefs = L595_R * L595_G * L595_B;
      startp = host_new ? 256 - ndefs : 0;
      i = 0;
      for (r = 0; r < L595_R; r++) 
	for (g = 0; g < L595_G; g++)
	  for (b = 0; b < L595_B; b++)
	    {
	      defs[startp + i].flags = DoRed | DoGreen | DoBlue;
	      defs[startp + i].red   = (r * 255 / (L595_R - 1)) << 8;
	      defs[startp + i].green = (g * 255 / (L595_G - 1)) << 8;
	      defs[startp + i].blue  = (b * 255 / (L595_B - 1)) << 8;
	      i++;
	    }
      break;

    case LUT_LOAD:
      ndefs = host_len > 256 ? 256 : host_len;
      startp = host_new ? 256 - ndefs : 0;
      for (i = 0; i < ndefs; i++)
	{
	  defs[startp + i].flags = DoRed | DoGreen | DoBlue;
	  defs[startp + i].red =   host_ldata[i] << 8;
	  defs[startp + i].green = host_ldata[i + host_len] << 8;
	  defs[startp + i].blue =  host_ldata[i + 2 * host_len] << 8;
	}
      break;

    default:
      return;
    }

  /* Allocate the color cells */
  if (host_new)
    {
      /* Copy as many colors as possible from the default map so 
         that we minimize the number of strange colors that appear
	 in other windows when our map is loaded (this assumes that
	 new colors are allocated from the front of the map).  */

      for (i = 0; i < 256; i++)
	defs[i].pixel = (i >= DisplayCells(dpy, scr) ?
			 impixels[0] : impixels[i]);
      if (startp > 0)
	XQueryColors(dpy, cmap, defs, startp);

      XStoreColors(dpy, imwork->cmap, defs, 256);
    }
  else
    {
      /* We're using the default map, so allocate read-only cells */
      for (i = 0; i < ndefs; i++)
	{
	  if (!XAllocColor(dpy, cmap, &defs[i]))
	    error("Not enough color map entries are available");
	  pixels[i] = defs[i].pixel;
	}
      npixels = ndefs;
    }

  /* Set up pixval array as translation from internal color to pixel value */
  switch (host_lut)
    {
    case LUT_GRAY:
      for (r = 0; r < GRAY; r++)
	for (g = 0; g < 256 / GRAY; g++)
	  /* Low order bits are ignored */
	  pixval[r * GRAY + g] = defs[startp + r].pixel;
      break;

    case LUT_RGB7:
      i = 0;
      for (r = 0; r < RGB7_R; r++) 
	for (g = 0; g < RGB7_G; g++)
	  for (b = 0; b < RGB7_B; b++)
	    {
	      /* Low order of green bit is ignored */
	      pixval[(((g << 1) + 0) * RGB7_R + r) * RGB7_B + b] =
	      pixval[(((g << 1) + 1) * RGB7_R + r) * RGB7_B + b] =
		defs[startp + i++].pixel;
	    }
      break;

    case LUT_595:
      i = 0;
      for (r = 0; r < L595_R; r++) 
	for (g = 0; g < L595_G; g++)
	  for (b = 0; b < L595_B; b++)
	    pixval[(r * L595_G + g) * L595_B + b] = defs[startp + i++].pixel;
      for (i = ndefs; i < 256; i++)
	pixval[i] = defs[0].pixel;
      break;

    case LUT_LOAD:
      for (i = 0; i < 256; i++)
	if (i < ndefs)
	  pixval[i] = defs[startp + i].pixel;
        else
	  pixval[i] = defs[0].pixel;
      break;
    }

  /* The "cur_lut" variable keeps track of what type of LUT is loaded in
     the default color map.  The LUT can stay loaded even after the working
     window is destroyed (otherwise, the colors would have to be re-allocated
     just because an F_INIT was done with a new size).

     If a window doesn't use the default colormap then it must always
     have its colors re-allocated.  */

  if (!host_new)
    {
      cur_lut = host_lut;
      if (cur_ldata)
	{
	  XtFree(cur_ldata);
	  cur_ldata = NULL;
	}
      if (host_lut == LUT_LOAD)
	{
	  cur_ldata = (unsigned char *) XtMalloc(3 * host_len);
	  memcpy(cur_ldata, host_ldata, 3 * host_len);
	  cur_len = host_len;
	}
      for (i = 0; i < 256; i++) cur_pixval[i] = pixval[i];
    }
  
  /* Since the internal color to pixel value mapping has changed, reload
     the X pixmap with new pixel values.  */

  if (imwork && imwork->image)
    {
      for (i = 0; i < width * height; i++)
	(void) XPutPixel(imwork->image, i % width, i / width, pixval[data[i]]);
      if (XtIsRealized(imwin))
	XPutImage(dpy, XtWindow(imwin), imwork->gc, imwork->image,
		  0, 0, 0, 0, (unsigned int) width, (unsigned int) height);
    }
}


/* Handle graphics window events */
/*ARGSUSED*/ static XtEventHandler image_event(w, client_data, event)
     Widget w;
     caddr_t client_data;
     XEvent *event;
{
  image_info *im = (image_info *) client_data;

  switch (event->type)
    {
    case EnterNotify:
      im->inwindow = True;
      x_inwindow(True);
      break;
      
    case LeaveNotify:
      im->inwindow = False;
      x_inwindow(False);
      break;

    case MapNotify:
      /* The first time a window is mapped, set its colormap to the
	 correct value.  Another way to do this would be to create a
	 new widget which is a sub-class of topLevelShell.  The new
	 widget would inherit everything except that its realize
	 procedure would set the colormap attribute before doing the
	 XtCreateWindow (just like Im3270).  However, I don't think
	 it's worth the trouble to create a new widget just for that.  */

      if (im->mapset || im->cmap == cmap || !XtIsRealized(im->toplevel))
	break;
      XSetWindowColormap(dpy, XtWindow(im->toplevel), im->cmap);
      im->mapset = True;
      break;
    }
  return (XtEventHandler) NULL;
}


/* Callback routine for key press events */
/*ARGSUSED*/ static void dokey(w, client_data, keybuff)
     Widget w;
     caddr_t client_data;
     caddr_t keybuff;
{
  (void) s3270_key(&keybuff[1], (int) keybuff[0]);
}


/* Callback routine for expose events */
static void expose(w, client_data, event)
     Widget w;
     caddr_t client_data;
     XEvent *event;
{
  image_info *im = (image_info *) client_data;
  if (XtIsRealized(w))
    XPutImage(dpy, XtWindow(w), im->gc, im->image,
	      event->xexpose.x, event->xexpose.y,
	      event->xexpose.x, event->xexpose.y,
	      (unsigned int) event->xexpose.width,
	      (unsigned int) event->xexpose.height);
}


/* Callback routine for window actions */
static void action(w, client_data, call_data)
     Widget w;
     caddr_t client_data;
     caddr_t call_data;
{
  XtWin3270DataPtr cdata = (XtWin3270Data *) call_data;
  image_info *im = (image_info *) client_data;

  /* If a working window exists, imwork will point to its info.  If any
     saved windows exist, they'll be in a list pointed to by imlist.
     The working window is never in that list.  */

  switch (cdata->function)
    {
    case XtFimageSave:
      /* Save image (button1) */
      if (w != imwin || im != imwork) break;
      if (imlist)
	{
	  im->next = imlist;
	  im->prev = imlist->prev;
	  imlist->prev->next = im;
	  imlist->prev = im;
	  imlist = im;
	}
      else imlist = im;
      imwork = NULL;
      XtFree(data);
      break;

    case XtFimageDestroy:
      /* Destroy image (button2) */
      if (im == im->next && im == imlist) imlist = NULL;
      else
	{
	  if (im == imlist) imlist = im->next;
	  im->prev->next = im->next;
	  im->next->prev = im->prev;
	}
      XtDestroyWidget(im->toplevel);
      if (im == imwork) imwork = NULL;
      break;
    }
}


/* Callback routine for window destroy */
/*ARGSUSED*/ static void destroy(w, client_data, event)
     Widget w;
     caddr_t client_data;
     XEvent *event;
{
  image_info *im = (image_info *) client_data;

  /* Unlink this window from the chain */
  if (im == im->next && im == imlist) imlist = NULL;
  else
    {
      if (im == imlist) imlist = im->next;
      im->prev->next = im->next;
      im->next->prev = im->prev;
    }

  if (im->inwindow) x_inwindow(False);
  XtDestroyGC(im->gc);
  XDestroyImage(im->image);
  if (im->cmap != cmap)
    XFreeColormap(dpy, im->cmap);
  XtFree(im);
  if (im == imwork) imwork = NULL;
}
