/*
  MAIN code file for C language X-Midas X-Windows graphics libraries

  $Revision: 1.3 $
  $Modtime:   08 Sep 1997 13:11:12  $

   Revision:
   12-May-98 RDArendt Added non-default Visual and CDE workspace enhancements
   15-Dec-98 JGSchoen Added "if" statements before calling postscript in some
             high profile routines to improve performance
   08-Mar-99 RDArendt Revisions to avoid Digital Unix compiler messages.
   08-Mar-99 JWestmiller Change XcmsCCCofColormap to XcmsCCCOfColormap.
   06-May-99 ASchibig added conditional compile for Linux
   10-May-99 ASchibig/LMartin added type declarations in
             XGetWindowProperty(9,12) and XChangeProperty(7) 
             for Linux build.
  Modification History
  MIB 19-April-2000 DR RRCcl00837
            added #if block for __DECC_VER to include <stdlib.h>
            changed "unsigned log actFmt to "int actFmt" (arg #9) 
            in function set_CDE_workspace as required in XLIB function
            XGetWindowProperty's call signature.
  RDA 12-Sep-2001
            Set window property StaticGravity so the workstation
				windowmanager doesn't try tp reposition the frame.
				See Inter-Client Communication Conventions Manual ver 2.0
				(ICCCM2.0), X Consortium Standard X11R6.4 Section 4.1.2.3 
				regarding win-gravity
				For CDE, nust set this X-resource in /etc/dt/config/C/sys.resources
				  Dtwm*positionIsFrame:   False
				  on the workstation.  Seeman page for dtwm(1)
				This fix submitted by RDA was integrated by EAS for MF Bug #2715
*/

/* RDA: 12-May-98 

   Workaround inherent X11 problem.  Need to associate a visual with
   the imported, non-standard colormap, or face long delays in setting
   colors from colornames on Xservers with many heads/visuals.  Problem
   identified in ICCCM Inter-Client Communications Manual ver 2.0,
   (X11R6) Appendix B:

	  "The protocol needs to be changed to provide some way of
           identifying the Visual and the Screen of a Colormap"

  Currently DEC and Sun workaround this by generating trial
  windows for all visuals on all screens until a BadMatch
  doesn't occur (thus screen and visual identified).  This can
  take a take a long time for multi-heads with advanced video
  cards that support many visuals.  Clocked at 8 sec for second
  head on 37th visual ...
  	
  I work around this by creating new temp colormap to establish the
  internal strucutures, set the colormap to the shared colormap ID,
  then delete the temp colormap.  Have to access internal portions
  accesses the colormap ID of the first non-default cms structure.

  Reverse engineered the method CDE uses to hint a window onto a specific
  workspace.  The window creates a property "_DT_WORKSPACE_HINTS" with
  a list of workspace atoms.  In order get the list of workspaces on a
  screen, need to query the CDE window manager property "_DT_WORKSPACE_LIST",
  and run through the the properties of "_DT_WORKSPACE_INFO_ws0" ... to get
  the name of each workspace.  The window manager window ID is the second
  item in the property list returned by "_MOTIF_WM_INFO" on the root window
  on the screen in question.  If the atoms don't exist the routine silently
  exits, leaving the window on the main screen - perhaps the user wasn't
  running under CDE anyway.

  Yes, there's a nifty API called DtWsmSetWorkspacesOccupied(), but then
  XMIDAS would need a CDE library to link, which seemed a bad requirement.

  Added routines:
    set_CDE_workspace()  Sets CDE workspace, by name or number
    set_visual()         Sets up for non-default screen visual
    set_colormap()       Reuses or maps to existing colormap for visual

*/
/* RDA: 16-Mar-03

  Fixed bug in set_CDE_workspace(), where it would create the atoms
  if not already existant (not good when testing for existence).
  Used to cause a SEGV when used with Linux WM's

  Added support for most Linux desktop, following the guidelines in
  the Extended Window Manager Hints from http://www.freedesktop.org

  mx_open() will try both methods (CDE first) if the /WKSP switch is set.

  Added routines:
    set_EWM_workspace()  Sets workspace for most Linux desktops
*/

/* JLR: DR 501921 18-Jun-04

   In client_msg() when moving window only correct for border if parent window.
      
*/

#if defined(__DECC_VER)          /* MIB 19-April-2000 */
#   if(__DECC_VER >= 60290003) 
#include <stdlib.h>
#  endif
#endif

/* Sets up for accessing XDisplay internals for nasty Colomap-CMS sneak */
#ifdef __DECC
#ifdef __VMS		/* OpenVMS */
#define XLIB_ILLEGAL_ACCESS /* access DEC  struct _XDisplay for "sneak" */
#include <X11/Xlib.h>
#define CMS_COLORMAP(DISP) (*(((Colormap **)&(DISP->mode_switch)) + 6))
#endif

#ifdef _OSF		/* Digital UNIX */
#define NEED_EVENTS
#include <X11/Xlibint.h>
#define CMS_COLORMAP(DISP) ((Colormap *)(DISP->cms.clientCmaps))
#endif
#endif

/*+++                                                            */
/* Added jdh                                                     */
#ifdef _SOL
#include <X11/Xlib.h>
#include <X11/Xlibint.h>
#define CMS_COLORMAP(DISP) ((Colormap *)(DISP->cms.clientCmaps))
#endif
/*---                                                            */

#ifdef _HPUX
#include <X11/Xlib.h>
#include <X11/Xlibint.h>
#define CMS_COLORMAP(DISP) ((Colormap *)(DISP->cms.clientCmaps))
#endif

#ifdef _LINX
#include <X11/Xlibint.h>
#define CMS_COLORMAP(DISP) ((Colormap *)(DISP->cms.clientCmaps))
#endif

#ifndef CMS_COLORMAP    /* Trick may not work ... */
#include <X11/Xlib.h>
#define CMS_COLORMAP(DISP) ((Colormap *)(DISP->cms.clientCmaps))
#endif

#include "glibc.h"

/* Imported Variables */

attr    extern  FILE    *f_PostScript;
attr    extern  char    TextDash[];
attr    extern  char    LineDash[];
attr    extern  int     PSfontsize;

static int error_handler (Display *display, XErrorEvent *error);
static void list_menu (const char *title, const char *ditem,
		char **list, int *dcoff, int *lcoff,
		int *item1, int *item2, const char *endchar);
static int client_msg (XEvent *event);
static bool window_resize (int_4 *l, int_4 *t, int_4 *w, int_4 *h);
static void xform_xy (int_4 *x, int_4 *y, int width, int height);
static unsigned int xmidas2xmask (unsigned int mask);
static unsigned int x2xmidasmask (unsigned int mask);
Bool learn_match (const Display *D, const XEvent *E, const int *junk);

attr	Atom	GXMidasMsgAtom;
attr	Atom	GDeleteWinAtom;
attr	Atom	GProtocolsAtom;

typedef int (*CMP_FUNC)(const void *,const void*);

void mx_arrow (int x, int y, int d, int orientation)
{
  int r,rr;

#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  r = d/2;
  rr = r+r+1;
  switch (orientation) {
    case 1: case L_ArrowRight:
      mx_shadowbox (x-rr, y-r, rr, rr, L_ArrowRight, 2, "", 0);
      break;

    case 2: case L_ArrowUp:
      mx_shadowbox (x-r, y, rr, rr, L_ArrowUp, 2, "", 0);
      break;

    case 3: case L_ArrowLeft:
      mx_shadowbox (x, y-r, rr, rr, L_ArrowLeft, 2, "", 0);
      break;

    case 4: case L_ArrowDown:
      mx_shadowbox (x-r, y-rr, rr, rr, L_ArrowDown, 2, "", 0);
      break;

  }

}

/*************************** QUERY  **************************/

int mx_query (char object)
{
#if _HEADLESS
  if (Mx->headless == 1 ) return 0;
#endif

  switch (object) {
    case 'A': return (int) G.gcta;
    case 'b': return (int) GBorder;
    case 'B': return (int) G.BW;
    case 'C': return (int) G.color;
    case 'c': return (int) G.cells;
    case 'D': return (int) G.D;
    case 'd': return (int) G.depth;
    case 'G': return (int) G.gcg;
    case 'I': return (int) G.gcti;
    case 'M': return (int) G.gcm;
    case 'P': return (int) G.colorp;
    case 'R': return (int) G.gcr;
    case 'r': return (int) G.gcr2;
    case 'S': return (int) G.S;
    case 's': return (int) G.S_ptr;
    case 'T': return (int) G.gct;
    case 'W': return (int) G.W;
    case 'V': return (int) G.visual;

    default : m_warning ("Unrecognized query flag in MX$QUERY"); return (-1);
  }
}

/*************************** XFLUSH **************************/

void mx_xflush (void)
{
#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  XFlush (G.D);
}

/*************************** DEFINE CURSOR **************************/

void mx_cursor (int shape)
{
#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  if (shape<0) XDefineCursor (G.D, G.W, None);
  else XDefineCursor (G.D, G.W, XCreateFontCursor(G.D,shape));
  XFlush (G.D);
}

/*************************** DRAW ARC **************************/

void mx_draw_arc (int ic, int x, int y, int w, int h, int a1, int a2)
{
#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  if (w<=0 || h<=0) return;
  if (ic!=L_XORColor) {
    SETGCGFOREGROUND (ic);
    XDrawArc (G.D, G.W, G.gcg, x, y, w, h, a1, a2);
  }
  else {
    XDrawArc (G.D, G.W, G.gcr, x, y, w, h, a1, a2);
    if (Mx->rmode) XDrawArc (G.D, G.W, G.gcr2, x-1, y-1, w+2, h+2, a1, a2);
  }
  XFlush (G.D);
  if (f_PostScript) mx_psdraw_arc(ic, x, y, w, h, a1, a2, FALSE);
}

/*************************** FILL ARC **************************/

void mx_fill_arc (int ic, int x, int y, int w, int h, int a1, int a2)
{
#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  if (w<=0 || h<=0) return;
  if (ic!=L_XORColor) {
    SETGCGFOREGROUND (ic);
    XFillArc (G.D, G.W, G.gcg, x, y, w, h, a1, a2);
  }
  else {
    XFillArc (G.D, G.W, G.gcr, x, y, w, h, a1, a2);
    if (Mx->rmode) XFillArc (G.D, G.W, G.gcr2, x-1, y-1, w+2, h+2, a1, a2);
  }
  XFlush (G.D);
  if (f_PostScript) mx_psdraw_arc(ic, x, y, w, h, a1, a2, TRUE);
}

void mx_getrgb (int ic, int_4 *red, int_4 *green, int_4 *blue)
{
#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  if (ic <= L_NoColor || ic > L_color) *red = *green = *blue = -1;
  else {
    XColor xc;
    xc.pixel = Mx->pixel[ic-1];
    XQueryColor(G.D, G.C_id, &xc);
    *red = PIXEL2PERC(xc.red);
    *green = PIXEL2PERC(xc.green);
    *blue = PIXEL2PERC(xc.blue);
  }
}

/*************************** COLOR LABEL **************************

This X-Widget throws a prompt X-Widget up requesting a color name.
This string is sent to XAllocNamedColor and if the status indicates
that the color was not found an MX_MESSAGE is called to indicate that
and the original color name is loaded back in again.

ic  a pointer to the integer index indicating an element in the G.color color
    array that the new color should be set into.  Normally, this is just a
    temporary holder for the color.  If the pixel value in this color element
    was set to L_NoColor before COLORLAB was called, suppress the MX_MESSAGE
    warning if the color is not found. (This avoids multiple messages when
    annotate files are all loaded in at once and the colors are all
    unrecognized.)

If the display is Black and White, automatically set the color to the
foreground color of the display (Mx->white)

**************************** COLOR LABEL **************************/

void mx_colorlab (int ic, const char *text, int length)
{

  char string[41];
  XColor c_used, c_named;
  Status status;
  int i, j;
  bool empty;

#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  if (ic<L_NoColor || ic>L_color) return;
  empty = (ic == L_NoColor);
  if (empty) ic = L_XORColor;
  --ic;
  if (G.BW) { 
    Mx->pixel[ic] = Mx->white;
    if (ic>=0) G.color[ic].pixel = Mx->white;
    return;
  }

  strf2c (text, length, string, 41, F2C_FLAG_TRIM);

  /* if string begins with a #, RGB is specified as HEX values */
  switch (string[0]) {
    case '#':		/* parse out hex RGB's */
      sscanf(string+1,"%4hx%4hx%4hx",&c_used.red,&c_used.green,&c_used.blue);
      c_used.flags = DoRed|DoGreen|DoBlue;
      status = XAllocColor(G.D, G.C_id, &c_used);
      break;
    case '%':		/* parse out % RGB's */
      for (i=j=0; j<3; ++j) {
	unsigned short val;
	while (!isdigit(string[i]) && string[i]) ++i;
	for (val=0; isdigit(string[i]); i++) val = val*10 + string[i] - '0';
	val = min(val, 100);
	switch (j) {
	  case 0: c_used.red = PERC2PIXEL(val);
	  case 1: c_used.green = PERC2PIXEL(val);
	  case 2: c_used.blue = PERC2PIXEL(val);
	}
      }
      c_used.flags = DoRed|DoGreen|DoBlue;
      status = XAllocColor(G.D, G.C_id, &c_used);
      break;
    default:
      if (!strcmp(string,"+") || !strcmp(string,"-")) {
	c_used.pixel = (string[0]=='+') ? Mx->white : Mx->black;
	XQueryColor(G.D, G.C_id, &c_used);
	status = 1;
      }
      else
        status = XAllocNamedColor (G.D, G.C_id, string, &c_used, &c_named);
  }

  /* color found - flag success and set pixel */
  if (ic+1!=L_XORColor) Mx->tpix = status;
  if (status != 0) {
    if (c_used.pixel != Mx->pixel[ic]) {
      /*
	This is the place we might have considered freeing up the old pixel
        that was allocated in this slot. Unfortunately, X-Windows isn't too
        friendly when you try to do this and spews a BadValue error when
        your next color allocation calls XAllocNamedColor and gets the slot
        you just freed.  Furthermore, when this happens, you get the previous
	color, not the new color you asked for.
      */
      if (ic+1 == Mx->clast) Mx->clast = L_NoColor;
      Mx->pixel[ic] = c_used.pixel;
      if (ic >= 0) {
	G.color[ic] = c_used;
	G.colorp[ic] = -1;
      }
    }
  }

  /* color not found & no color is set yet - set to white */
  else if (empty) {
    Mx->pixel[ic] = Mx->white;
    if (ic>=0) G.color[ic].pixel = Mx->white;
  }

  /* color not found & another color is there */
  else if (ic+1==L_XORColor || ic >= 0)
    mx_message ("Color not found", 1.0, 15);

}

/*************************** PUT IMAGE **************************/
/* RDA
 *   Map to the color table HERE, since scanline_pad could be 32
 *   Keeps color manipulation hidden, as it should be
 */

void mx_put_image (const char data[], int nx, int ny, int nex, int ney,
                   int xd, int yd, int level)
{
  int w, h, chunk, x, y, z, x1, x2, y1, y2, bw=4, i, j, l, m, r;
  Pixmap ip;
  XImage *img;
  int_u4 *pixc=&(Mx->pixel[0]) - 1; /* correct for Fortran 1-based arrays */
  int_u4 rev_pixel[L_color]; /* for TrueColor, may have to byte-swap pixels */
  int bytes_per_pixel = (Mx->bits_per_pixel + 7) / 8;

#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  if (nex > 0) w = nx * nex;
  else w = -nex;
  h = ny * ney;
  img = G.img_ptr;
  img->bytes_per_line = w *  bytes_per_pixel;
  img->width = w;
  img->height = ny;
  chunk = img->bytes_per_line * ny;
  
  if (chunk>GExbuf_size) {
    if (GExbuf_size>0) free(GExbuf_ptr);
    GExbuf_size = chunk;
    GExbuf_ptr = (char *) malloc(GExbuf_size);
  }
  /* RDA: If server byte order reversed, generate a reversed pixelmap */
  if (G.depth > 8 ){
    union {int i; char c[4];} test;
    test.i = 1;
    if ( ImageByteOrder(G.D) == test.c[0] ) {
      if (Mx->bits_per_pixel > 16) {
	for (i=0; i<G.cells; i++) {
	  register unsigned char *a=(unsigned char *)(&Mx->pixel[i]);
	  register unsigned char *b=(unsigned char *)(&rev_pixel[i]);
	  b[0] = a[3];
	  b[1] = a[2];
	  b[2] = a[1];
	  b[3] = a[0];
	}
      } else {
	for (i=0; i<G.cells; i++) {
	  register unsigned char *a=(unsigned char *)(&Mx->pixel[i]);
	  register unsigned char *b=(unsigned char *)(&rev_pixel[i]);
	  b[0] = a[1];
	  b[1] = a[0];
	  b[2] = a[3];
	  b[3] = a[2];
	}
      }
      pixc = &rev_pixel[0] - 1;	/* point to reversed pixels */
    }
  }
  
  /* RDA: replicated sections based on bytes_per_pixel for speed */
  if (nex != 1) {		/* expand data */
    if (nex < 0) {
      float rind, rdelta;
      int nind, modulo;
      nex = w / nx;
      for (modulo = w; modulo >= nx; modulo -= nx);
      if (modulo > 0) {
	rdelta = (float)nx / (float)modulo;
	if (bytes_per_pixel == 1 ) {
	  int_u1 *Gptr = (int_u1 *) GExbuf_ptr;
	  for (i=j=y=0; y<ny; y++) {
	    for (x=0, nind=rind=(rdelta/2.0); x<nx; x++, j++) {
	      if (x==nind) {
		Gptr[i++] = (int_u1) pixc[data[j]];
		nind = (rind+=rdelta);
	      }
	      for (z=0; z<nex; z++) Gptr[i++] = (int_u1) pixc[data[j]];
	    }
	  }
	} else if (bytes_per_pixel == 4 ) {
	  int_u4 *Gptr = (int_u4 *) GExbuf_ptr;
	  for (i=j=y=0; y<ny; y++) {
	    for (x=0, nind=rind=(rdelta/2.0); x<nx; x++, j++) {
	      if (x==nind) {
		Gptr[i++] = (int_u4) pixc[data[j]];
		nind = (rind+=rdelta);
	      }
	      for (z=0; z<nex; z++) Gptr[i++] = (int_u4) pixc[data[j]];
	    }
	  }
	} else {		/* == 2 */
	  int_u2 *Gptr = (int_u2 *) GExbuf_ptr;
	  for (i=j=y=0; y<ny; y++) {
	    for (x=0, nind=rind=(rdelta/2.0); x<nx; x++, j++) {
	      if (x==nind) {
		Gptr[i++] = (int_u2) pixc[data[j]];
		nind = (rind+=rdelta);
	      }
	      for (z=0; z<nex; z++) Gptr[i++] = (int_u2) pixc[data[j]];
	    }
	  }
	}
	nex = -w;
      }
    }
    if (nex > 0) {
      if (bytes_per_pixel == 1 ) {
        int_u1 *Gptr = (int_u1 *) GExbuf_ptr;
	for (i=j=y=0; y<ny; y++)
	  for (x=0; x<nx; x++,j++)
	    for (z=0; z<nex; z++) Gptr[i++] = (int_u1) pixc[data[j]];
      } else if (bytes_per_pixel == 4 ) {
        int_u4 *Gptr = (int_u4 *) GExbuf_ptr;
	for (i=j=y=0; y<ny; y++)
	  for (x=0; x<nx; x++,j++)
	    for (z=0; z<nex; z++) Gptr[i++] = (int_u4) pixc[data[j]];
      } else {			/* == 2 */
        int_u2 *Gptr = (int_u2 *) GExbuf_ptr;
	for (i=j=y=0; y<ny; y++)
	  for (x=0; x<nx; x++,j++)
	    for (z=0; z<nex; z++) Gptr[i++] = (int_u2) pixc[data[j]];
      }
    }
  } else {
    if (bytes_per_pixel == 1 ) {
      int_u1 *Gptr = (int_u1 *) GExbuf_ptr;
      for (j=0; j < (w*ny) ; j++)
	Gptr[j] = (int_u1) pixc[data[j]];
    } else if (bytes_per_pixel == 4 ) {
      int_u4 *Gptr = (int_u4 *) GExbuf_ptr;
      for (j=0; j < (w*ny) ; j++)
	Gptr[j] = (int_u4) pixc[data[j]];
    } else {			/* ==2 */
      int_u2 *Gptr = (int_u2 *) GExbuf_ptr;
      for (j=0; j < (w*ny) ; j++)
	Gptr[j] = (int_u2) pixc[data[j]];
    } 
  }
  img->data = GExbuf_ptr;

  if (level==Mx->zlevel) {	/* writing to top zoom level */
    if (Mx->zlevel!=0) {
      xd += (Mx->zoom[Mx->zlevel-1].x + bw);
      yd += (Mx->zoom[Mx->zlevel-1].y + bw);
    }
    if (ney==1) XPutImage (G.D, G.W, G.gcg, img, 0, 0, xd, yd, w, h);
    else for (y=0; y<ny; y++) {
      XPutImage (G.D, G.W, G.gcg, img, 0, y, xd, yd++, w, 1);
      for (i=1; (j=min(i,ney-i)) > 0; i+=j, yd+=j)
        XCopyArea (G.D, G.W, G.W, G.gcg, xd, yd-j, w, j, xd, yd);
    }
  } /* writing to bottom level with zooms on top */
  else if (level <= 0) {
    y1 = Mx->zoom[0].y;  y2 = Mx->zoom[0].y + Mx->zoom[0].h;
    x1 = Mx->zoom[0].x;  x2 = Mx->zoom[0].x + Mx->zoom[0].w;
    if (yd<y2 && yd+h>y1 ) {
      l = min(x1,xd+w) - xd;          l = max(0,l);
      m = min(x2,xd+w) - max(x1,xd);  m = max(0,m);  x1 = max(xd-x1,0);
      r = w-l-m; x2 = xd+w-r;
      ip = Mx->zoom[0].ip;
    }
    for (y=0; y<ny; y++) {
      for (j=yd+ney; yd<j; yd++) {
        if (yd<y1 || yd>=y2) XPutImage (G.D,G.W,G.gcg,img, 0,y, xd,yd, w,1);
        else {                  /* writing to zoom BG pixmap */
          if (l>0) XPutImage (G.D,G.W,G.gcg,img,   0,y,  xd,yd,    l,1);
          if (m>0) XPutImage (G.D, ip,G.gcg,img,   l,y,  x1,yd-y1, m,1);
          if (r>0) XPutImage (G.D,G.W,G.gcg,img, w-r,y,  x2,yd,    r,1);
        }
      }
    }
  }
  else m_warning ("Cannot put image to intermediate zoom level");

}

/*************************** MAP ***************************/

void mx_map (MAPSTRUCT *cnv, int x, int y, int w, int h, int func)
{
#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  if (func & L_NewMap) {
    if (cnv->ip && (w!=cnv->w || h!=cnv->h)) {
      XFreePixmap(G.D,cnv->ip); cnv->ip=0;
    }
    if (w <= 0 || h <= 0)
      m_warning ("Illegal area specified in MX$MAP, PIXMAP not created");
    else {
      cnv->x = x; cnv->y = y; cnv->w = w; cnv->h = h;
      if (!cnv->ip) cnv->ip = XCreatePixmap (G.D, G.W, w, h, G.depth);
      if (cnv->ip) XCopyArea (G.D, G.W, cnv->ip, G.gct, x, y, w, h, 0, 0);
    }
  }
  else if (cnv->ip) {
    if (func & L_PutMap)
      XCopyArea (G.D,cnv->ip,G.W,G.gct,0,0,cnv->w,cnv->h,cnv->x,cnv->y);
    else if (func & L_MoveMap)
      XCopyArea (G.D, cnv->ip, G.W, G.gct, 0, 0, cnv->w, cnv->h, x, y);
    else if (func & L_ClipMap) {
      x = max (0,min(x,cnv->w)); w = max (0,min(w,cnv->w-x));
      y = max (0,min(y,cnv->h)); h = max (0,min(h,cnv->h-y));
      if (w && h) XCopyArea (G.D,cnv->ip,G.W,G.gct,x,y,w,h,cnv->x,cnv->y);
    }
    if (func & L_FreeMap) { XFreePixmap (G.D, cnv->ip); cnv->ip = 0; }
  }
  XFlush (G.D);
}

/*************************** ZOOM **************************/

void mx_zoom (int x, int y, int w, int h, int xd, int yd, int nx, int ny)
{
  int xs, ys, i, j, wx, wy, px, py, bw=4, xsb;
  Pixmap ip;

#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  if (nx<=0 || ny<=0 || w<=0 || h<=0 || Mx->zlevel>=L_zoommax)
    { m_warning ("Illegal values in MX$ZOOM"); return; }

  xsb = w*nx; xs = xsb + bw*2; ys = h*ny + bw*2;
  mx_map (&(Mx->zoom[Mx->zlevel]), xd-bw, yd-bw, xs, ys, L_NewMap);
  if (Mx->zoom[Mx->zlevel].ip != 0) ++Mx->zlevel; else return;

  if (nx==1 && ny==1) {
    if (x!=xd || y!=yd) XCopyArea (G.D, G.W, G.W, G.gct, x, y, w, h, xd, yd);
  }
  else {
    /* temp pixmap needed */
    ip = XCreatePixmap (G.D, G.W, xsb, h, G.depth);

    /************
      Copy, or stretch source horizontally into pixmap if necessary.  Expand
      in binary chunks, i.e. copy 1 from the window, then copy that one next
      to itself, then copy those 2 and put them next to themselves, then
      those 4, etc. until all expansion is done. This copies the area much
      more quickly than copying 1 line at a time.
      nx - expansion factor
      i  - number of lines currently expanded out for one window line (i<=nx)
      j  - number of lines to duplicate in the next binary chunk
      wx - X location on window of the line currently being expanded
      px - X location in the pixmap to copy the next line(s) to
    ************/
    if (nx==1) XCopyArea (G.D, G.W, ip, G.gct, x, y, w, h, 0, 0);
    else for (wx=x,px=0; wx<x+w; wx++) {
      XCopyArea (G.D, G.W, ip, G.gct, wx, y, 1, h, px++, 0);
      for (i=1; (j=min(i,nx-i)) > 0; i+=j, px+=j)
        XCopyArea (G.D, ip, ip, G.gct, px-j, 0, j, h, px, 0);
    }

    /***********
      Copy or stretch buffer vertically onto window if necessary
      ny - expansion factor
      i  - number of lines currently expanded out for one pixmap line (i<=ny)
      j  - number of lines to duplicate in the next binary chunk
      py - Y location in pixmap of the line currently being expanded
      wy - Y location in window to copy next line(s) from pixmap
    ************/
    if (ny==1) XCopyArea (G.D, ip, G.W, G.gct, 0, 0, xsb, h, xd, yd);
    else for (py=0,wy=yd; py<h; py++) {
      XCopyArea (G.D, ip, G.W, G.gct, 0, py, xsb, 1, xd, wy++);
      for (i=1; (j=min(i,ny-i)) > 0; i+=j, wy+=j)
        XCopyArea (G.D, G.W, G.W, G.gct, xd, wy-j, xsb, j, xd, wy);
    }
    XFreePixmap (G.D, ip);
  }

  /* Draw border */
  if (bw>0) DRAWRECTANGLE (G.gcti, (xd-=bw), (yd-=bw), --xs, --ys);
  for (i=1; i<bw; ++i) DRAWRECTANGLE (G.gct, ++xd, ++yd, (xs-=2), (ys-=2));
  XFlush (G.D);
}

/*************************** UNZOOM **************************/

void mx_unzoom (void)
{
#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  if (Mx->zlevel>0)
    mx_map (&(Mx->zoom[--Mx->zlevel]), 0, 0, 0, 0, L_PutMap+L_FreeMap);
}

/************************** ERROR HANDLER ***********************/

static int error_handler (Display *display, XErrorEvent *error)
{
  char msg[73];
  if (GErrorVerbose) {
    strcpy(msg, "X-Windows error - ");
    XGetErrorText(display, error->error_code, &msg[18], 73-18);
    m_warning (msg);
  }
  return 0;
}

/************************* SET VISUAL ***************************/

/* RDA: pick a visual.  Tries to get StaticColor, 8-bit depth (speed,
   can't run out of colors). Otherwise tries for max depth, prefering
   StaticColor over TrueColor over PseudoColor.  If frustrated, it
   uses the default visual.  Also scans server Pixmap formats for one
   with a  depth that matches the visual.
   
   Uses the visClass, visDepth, and visualID to restrict the set of
   visuals considered.
   
   Sets G.visual, G.depth, and G.C_id.  If a non-default visual is
   used, G.C_id is set to zero as a flag to call set_colormap().
   Sets Mx->scanline_pad and Mx->bits_per_pixel based on matching
   pixmap formats.
*/

void set_visual ( void )
{
  XVisualInfo *visList, visTmpl;
  XPixmapFormatValues *pList;
  long visMask;
  int ibest=-1;
  int i,n=0;
  static int rank[]={		/* ranking of visual classes */
    1,				/* StaticGray  */
    2,				/* GrayScale   */
    5,				/* StaticColor */
    3,				/* PseudoColor */
    4,				/* TrueColor   */
    0};				/* DirectColor */

  /* Get a list of visuals (obtained when client connects to server */
  visTmpl.screen = G.S;
  visMask = VisualScreenMask;
  if ( Mx->visClass > -1) {	/* Override default behaviour w/ switches? */
    visTmpl.class = Mx->visClass;
    visMask |= VisualClassMask;
  }
  if ( Mx->visDepth > -1) {
    visTmpl.depth = Mx->visDepth;
    visMask |= VisualDepthMask;
  }
  if ( Mx->visID > -1) {
    visTmpl.visualid = Mx->visID;
    visMask |= VisualIDMask;
  }
  visList = XGetVisualInfo ( G.D, visMask, &visTmpl, &n );

  /* Search list visuals - select the best */
  for(i=0; i < n; i++) {
    int class, depth;
    class = visList[i].class;
    depth = visList[i].depth;
    if ( Mx->visDebug >= 0)
      printf("VISUALDEBUG: visualid=0x%2x class=%d depth=%2d cmap_size=%3d bits/RGB=%d\n",
	     visList[i].visualid , visList[i].class , visList[i].depth ,
	     visList[i].colormap_size , visList[i].bits_per_rgb );

    /* Don't bother with the following visuals: */
    /* if ( class == StaticGray )  continue; */
    if ( class == DirectColor ) continue;
    if ( depth < 8 )            continue;
/*    if ( class == TrueColor && depth == 8 ) continue; */

    /* Now try to upgrade the "best" visual class */
    if ( class == StaticColor && depth == 8) {
      ibest = i;
      break;			/* Just what I want! */
    }
    if (ibest < 0) {		/* Better than nothing ... */
      ibest = i;
      continue;
    }
    if ( depth > visList[ibest].depth ) {
      ibest = i;		/* better depth */
      continue;
    }
    if ( depth == visList[ibest].depth )
      if ( rank[class] > rank[visList[ibest].class] )
	ibest = i;		/* better class */
  }

  if ( ibest >= 0 ) {
    if (visList[ibest].visualid==DefaultVisualOfScreen(G.S_ptr)->visualid) 
      ibest = -1;		/* life is easy */
  }

  if ( ibest < 0 ) {		/* Use default visual */
    G.visual = DefaultVisualOfScreen (G.S_ptr);
    G.depth  = DefaultDepthOfScreen (G.S_ptr);
    G.C_id   = DefaultColormapOfScreen (G.S_ptr);
    if ( Mx->visDebug >= 0)
      printf("VISUALDEBUG: using default screen visual 0x%x\n",
	     G.visual->visualid);
  } else {
    G.visual = visList[ibest].visual;
    G.depth  = visList[ibest].depth;
    G.C_id   = 0;		/* Need to set this later */
    if ( Mx->visDebug >= 0)
      printf("VISUALDEBUG: using non-default screen visual 0x%x\n",
	     G.visual->visualid);
  }
  XFree( visList );

  pList = XListPixmapFormats ( G.D , &n );
  for (i=0; i<n; i++) {
    if ( Mx->visDebug >= 0)
      printf( "VISUALDEBUG: %d: depth=%d  bits/pixel=%d  scanlinePad=%d\n", i,
	     pList[i].depth, pList[i].bits_per_pixel , pList[i].scanline_pad );
    if ( pList[i].depth == G.depth ) {
      Mx->scanline_pad    = BitmapPad (G.D);	/* RDA: pList[i].scanline_pad; */
      Mx->bits_per_pixel  = pList[i].bits_per_pixel;
      break;
    }
  }
  if ( Mx->visDebug >= 0){
    printf("VISUALDEBUG: Using PixMap format %d\n", i );
    printf("VISUALDEBUG: ImageByteOrder = %d\n", ImageByteOrder(G.D) );
    printf("VISUALDEBUG: BitmapBitOrder = %d\n", BitmapBitOrder(G.D) );
    printf("VISUALDEBUG: BitmapPad      = %d\n", BitmapPad     (G.D) );
    printf("VISUALDEBUG: BitmapUnit     = %d\n", BitmapUnit    (G.D) );
  }
  XFree ( pList );
}

/************************* SET COLORMAP ***************************/
/*
   RDA: If using a non-default visual, try to get the RGB_DEFAULT_MAP
   property on the root-screen window.  If it doesn't exist, create
   and install a permanent colormap, and update RGB_DEFAULT_MAP.  This
   non-default colormap will be shared by all future XMIDAS displays
   using the same visualID.
   
   There's a X11 protocol "feature" where colormap looses it's CMS
   (Color Management System) association with a Visual (see comments
   at top of file).  As a workaround, a new temporary colormap is
   allocated to establish the CMS structures, then fake it out by
   overlaying the new colormapID with the old one.  Delete the temp
   colormap.  Give a warning if it detects a possible memory
   misalignment...
   
   Other utils (like Netscape) allocate a new colormap with each
   window.  If different visual, may have different black and white
   pixels!
*/
void set_colormap( char *node )
{
  int i,nc=0;
  XStandardColormap *oldList=NULL, *newList=NULL;
  Colormap Ctmp;
  Window root = DefaultRootWindow ( G.D );
  XcmsCCC ccc;

  /* Look for a default Cmap list on screen root window */
  if ( XGetRGBColormaps ( G.D, root, &oldList, &nc, XA_RGB_DEFAULT_MAP )){
    /* List exists, see if a colormap is listed */
    for (i=0; i < nc ; i++){
      if ( oldList[i].visualid == G.visual->visualid ) {
	G.C_id = oldList[i].colormap;
	if ( Mx->visDebug >= 0)
	  printf("VISUALDEBUG: using existing colormap: 0x%x\n", G.C_id);
	break;
      }
    }
  }

  if ( G.C_id == 0 ) {		/* Need to create a colormap and register it */
    /* Make a permanent addition to the X11 server */
    Display *rD;
    Window  rW;
    XColor c;

    rD = XOpenDisplay ( node );
    rW = DefaultRootWindow ( rD );
    G.C_id = XCreateColormap ( rD , rW , G.visual , AllocNone );
    newList = (XStandardColormap *) calloc( nc+1 , sizeof(XStandardColormap) );
    newList[0].colormap = G.C_id;
    newList[0].visualid = G.visual->visualid;

#if _SUN
    if (nc) memcpy( (char *)&newList[1] , (const char *)oldList , 
              sizeof(XStandardColormap)*nc );
#else
    if (nc) memcpy( &newList[1] , oldList , sizeof(XStandardColormap)*nc );
#endif

    XSetRGBColormaps ( rD , rW , newList , nc+1 , XA_RGB_DEFAULT_MAP );
    if ( G.visual->class & 1 ) { /* Store black, white cells for R/W Cmaps */
      unsigned long  planes[2],pix[2];
      XAllocColorCells ( rD , G.C_id , False , planes , 0 , pix , 2 );
      c.flags = DoRed | DoGreen | DoBlue;
      c.pixel=pix[0]; c.red=0;       c.green=0;       c.blue=0;
      XStoreColor ( rD , G.C_id , &c );
      c.pixel=pix[1]; c.red=0xFFFF;  c.green=0xFFFF;  c.blue=0xFFFF;
      XStoreColor ( rD , G.C_id , &c );
    }
    if ( Mx->visDebug >= 0) 
      printf("VISUALDEBUG: Created new colormap 0x%x; Now %d maps\n",
	     G.C_id , nc+1 );
    XSetCloseDownMode ( rD , RetainPermanent );
    XCloseDisplay ( rD );
    free ( newList );
  }
  XFree ( oldList );

  /* Tricky bit - make sure the CMS structures map the colormap -> visual */
  Ctmp = XCreateColormap ( G.D , root , G.visual , AllocNone );
  ccc = XcmsCCCOfColormap ( G.D , Ctmp );
  if ( CMS_COLORMAP ( G.D ) == 0 ) { if ( Mx->visDebug >= 0) 
      printf("VISUALDEBUG: ColormapCMS sneak failed! (zero pointer)\n");
  } else if ( *CMS_COLORMAP ( G.D ) != Ctmp ) { if ( Mx->visDebug >= 0) 
      printf("VISUALDEBUG: ColormapCMS sneak failed! (CmapID didn't match)\n");
  } else {
    *CMS_COLORMAP ( G.D ) = G.C_id; /* overload with old colormapID */
  }
  XFreeColormap ( G.D , Ctmp );
}

/*************************** DISPLAY **************************/
void mx_display (MX *mx_ptr, bool_4 *b_ptr, bool_4 *unl_ptr)
{
  int i, m, n;
  char node[41];
  int left, top, width, height;
  char dashes[4];
  int_u4 black,white;		/* RDA */
  XColor c_used, c_named;	/* RDA */
  
  Mx = mx_ptr;
  Mcsysunlock = unl_ptr;
  Mcbreak = b_ptr;
  strf2c (Mx->node, 40, node, 41, F2C_FLAG_TRIM);

  if (GWcount==0) {
    GExbuf_ptr=0; GExbuf_size=0;
    for (i=0; i<MAXSHARED; i++) Gshr_cells[i] = 0;
    for (i=0; i<MAXDISPLAYS; i++) D[i].count = 0;
  }

  n = -1;
  for (i=MAXDISPLAYS-1; i>=0; i--)
    if (D[i].count>0 && strcmp(D[i].node,node)==0 ) n=i;

#if _HEADLESS
  if (Mx->headless == 0 ) {
#endif
    if (n == -1) {
      for (i=MAXDISPLAYS-1; i>=0; i--) if (D[i].count==0) n=i;
      if (n == -1) m_error ("Too many displays for one image");
      D[n].id = XOpenDisplay (node);
      if (D[n].id==NULL) m_error ("Opening display server");
      strcpy (D[n].node,node);
    }

    GWcount++; D[n].count++;

    G.D      = D[n].id;
    G.W      = 0;
    G.S      = DefaultScreen (G.D);
    G.S_ptr  = DefaultScreenOfDisplay (G.D);

    set_visual ();			/* RDA */
    if (G.C_id == 0)		/* RDA */
      set_colormap ( node );

    i = XAllocNamedColor (G.D, G.C_id, "Black", &c_used, &c_named); /* RDA */
    black = c_used.pixel;
    i = XAllocNamedColor (G.D, G.C_id, "White", &c_used, &c_named); /* RDA */
    white = c_used.pixel;

    G.cells = 0;
    G.color = (XColor *) malloc((L_color)*sizeof(XColor));
    G.colorp = (short *) malloc((L_color)*sizeof(short));

    G.img_ptr = XCreateImage (G.D, G.visual, G.depth, ZPixmap,
			      0,0,0,0, Mx->scanline_pad, 0);

    Mx->screen_width   = DisplayWidth  (G.D, G.S);
    Mx->screen_height  = DisplayHeight (G.D, G.S);

  /* Need to define the GXMidasMsgAtom here so a routine need not call
     mx_open to call mx_client_event.  They will still need to open a
     connection to the display server. */
    GXMidasMsgAtom = XInternAtom(G.D, "_XMIDAS_CLIENT_MESSAGE", False);

  /* Initialize color arrays, allocate graphics contexts */
    for (i=0; i<L_color; i++) Mx->pixel[i] = G.color[i].pixel = Mx->white;
    for (i=L_NoColor; i<0; i++) Mx->pixel[i] = Mx->white;

  /* RDArendt 10-Apr-98  Fix for TrueColor.  Simple R/O map, so no cells
   *                     are alloc'd.  Uses straight 24-bit color.
   */
#if _HEADLESS
  }
  else {
    black = 1;
    white = 2;
    Mx->screen_width   = 1280;
    Mx->screen_height  = 1024;
  }
#endif

  G.BW = abs(Mx->xi)>1;
  if (Mx->xi < 0) {
    Mx->black = black;
    Mx->white = white;
  } else {
    Mx->black = white;
    Mx->white = black;
  }
  G.gct = G.gcti = G.gcg = G.gcr = G.gcr2 = G.gcm = G.gcta = G.gctw = 0;
  /* creates and sets bg/fg colors for GXCopy GC's */

  mx_setbgfg(L_BGColor, L_FGColor, 0);

  /* dash values for outlines, 2 pxl on, 2 off */
  dashes[0] = 2; dashes[1] = 2;
  mx_psdashes (LineDash, 2, dashes, 2, 2);
#if _HEADLESS
  if (Mx->headless == 0 ) {
#endif
    XSetDashes (G.D, G.gcr, 2, dashes, 2);
    XSetDashes (G.D, G.gcr2, 2, dashes, 2);
#if _HEADLESS
  }
#endif

  /* dash values for grid, 1 pxl on, 3 off */
  dashes[0] = 1; dashes[1] = 3;
#if _HEADLESS
  if (Mx->headless == 0 )
#endif
    XSetDashes (G.D, G.gct, 2, dashes, 2);

  mx_psdashes (TextDash, 2, dashes, 2, 1);

  mx_setfont(9);

  G.AutoRepeat = TRUE;
  GBorder = 3;			/* Border width in pixels for all X-Widgets */

  for (i=0; i<L_zoommax; i++) Mx->zoom[i].ip = 0;
  Mx->zlevel = 0;
  Mx->rmode = FALSE;
  Mx->noevent = FALSE;
  Mx->no_arrow_move = FALSE;
  Mx->clast = L_NoColor;
  Mx->breakflag = FALSE;
  if (Mx->origin == 0) Mx->origin = 4;

  /* convert desired window position and size to pixel measurements */
  xform_xy(&Mx->left, &Mx->top, Mx->screen_width, Mx->screen_height);
  xform_xy(&Mx->width, &Mx->height, Mx->screen_width, Mx->screen_height);

  /* limit desired window to screen size */
  Mx->width  = min(Mx->width, Mx->screen_width-10);
  Mx->height = min(Mx->height, Mx->screen_height-10);
  Mx->left   = min(Mx->left, Mx->screen_width-Mx->width-5);
  Mx->top    = min(Mx->top, Mx->screen_height-Mx->height-5);

#if _HEADLESS
  if (Mx->headless == 0 )
#endif
    XSetErrorHandler (error_handler);

  GErrorVerbose = TRUE;

#if _HEADLESS
  if (Mx->headless == 0 )
#endif
    GMaxLines = ( XMaxRequestSize (G.D) - 3) / 2;
#if _HEADLESS
  else
    GMaxLines = 10;
#endif
}

/**************************** AUTOREPEAT **************************/

void mx_autorepeat (bool_4 *autorepeat, int mode)
{
  Window focuswin;
  int revert;

#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  XGetInputFocus (G.D, &focuswin, &revert);
  switch (mode) {

    case -2: *autorepeat = G.AutoRepeat;                 /* save orig state */
             G.AutoRepeat = FALSE;
    case -1: if (focuswin==G.W) XAutoRepeatOff (G.D);   /* turn OFF */
             break;

    case 0:  G.AutoRepeat = *autorepeat; /* restore to specified state */
             if (focuswin==G.W) {
               if (G.AutoRepeat) XAutoRepeatOn (G.D);
               else XAutoRepeatOff (G.D);
             }
             break;

    case 2:  *autorepeat = G.AutoRepeat;                 /* save orig state */
             G.AutoRepeat = TRUE;
    case 1:  if (focuswin==G.W) XAutoRepeatOn (G.D);    /* turn ON */
             break;

  }
  XFlush (G.D);
}


/*RDA: changed "root" to "tmpPix" */
#define GALTERCREATEGC(GCON) \
  if (GCON) XChangeGC(G.D, GCON, valuemask, &GCvalues); \
  else GCON = XCreateGC(G.D, tmpPix, valuemask, &GCvalues)

void mx_setbgfg (int black, int white, int xi)
{
  unsigned int valuemask;
  XSetWindowAttributes setwinattr;
  XGCValues GCvalues;
  /* RDA:  Window root; */
  int i;
  int_u4 fg, bg;		/* RDA - must be 4 bytes!!! */

  /* RDA: Create a pixmap "drawable" to create GC's from, since the selected
   *      visual may not have the same depth as the root screen.  The pixmap
   *      can be small - only the depth (not clip region) is used
   */
  Pixmap tmpPix;

#if _HEADLESS
  if (Mx->headless == 0 ) {
#endif

    tmpPix = XCreatePixmap ( G.D , RootWindow(G.D,G.S) , 2, 2, G.depth );

    fg = Mx->pixel[white-1];
    bg = Mx->pixel[black-1];

    if (fg != Mx->white || bg != Mx->black) {
    /* swap all color entries that were FG to BG and vise versa */
      if (G.gcm) {
        for (i=0; i<L_color; i++) {
	  if (Mx->pixel[i]==Mx->black) G.color[i].pixel = Mx->pixel[i] = bg;
	  else if (Mx->pixel[i]==Mx->white) G.color[i].pixel = Mx->pixel[i] = fg;
        }
      }
      Mx->white = fg;
      Mx->black = bg;
    }

    if (xi) Mx->xi = xi;

    if (G.W) {
      valuemask = CWBackPixel | CWBorderPixel;
      setwinattr.background_pixel = Mx->black;
      setwinattr.border_pixel = Mx->white;
      XChangeWindowAttributes(G.D, G.W, valuemask, &setwinattr);
    }

    valuemask = GCForeground | GCBackground | GCFunction | GCGraphicsExposures;

    GCvalues.foreground = Mx->white;
    GCvalues.background = Mx->black;
    GCvalues.function = GXcopy;
    GCvalues.graphics_exposures = FALSE;
    GALTERCREATEGC(G.gct);
    GALTERCREATEGC(G.gcta);
    GALTERCREATEGC(G.gcg);
    Mx->clast = L_FGColor;

    Mx->xwfg = Mx->white;
    Mx->xwbg = Mx->black;
    Mx->xwts = Mx->black;
    Mx->xwbs = Mx->black;
    Mx->xwlo = Mx->white;
  /* Use shadow color as background for widget text */
    if (!G.BW) {
    /* get shadow colors from environment variable, or use defaults */
      int ls, j, cs, ce, soff;
      real_8 dval;
      char shadow[256];
      char *dshade[10] = { "white", "%35.35.30",
			   "%60.60.55", "%25.25.20", "%15.15.10",
			   "black", "%60.60.55",
			   "%80.80.75", "%40.40.35", "%70.70.65" };

      if (m_rfind("XWIDGET_COLORS", shadow, &dval, 14, 256) == 'A') ls = dval;
      else ls = 0;

      soff = (Mx->xi > 0) ? 5 : 0;
      for (i=0; i<ls; i++) shadow[i] = toupper(shadow[i]);
      for (i=0, j=L_XWFGColor, ce=cs=0; j>L_NoColor; i++, j--, cs = ++ce) {
        while (ce<ls && shadow[ce]!=',' && shadow[ce]!='|') ++ce;
        if (ce<ls && shadow[ce]=='|' && soff >= 5)
	  for (cs = ++ce; ce<ls && shadow[ce]!=','; ++ce);
        Mx->tpix = 0;
        if (ce>cs) mx_colorlab(j, &shadow[cs], ce-cs);
        if (!Mx->tpix) mx_colorlab(j, dshade[soff+i], strlen(dshade[soff+i]));
        if (ce<ls && shadow[ce]=='|' && soff < 5)
	  while (ce<ls && shadow[ce]!=',') ++ce;
      }
      GCvalues.foreground = Mx->xwfg;
      GCvalues.background = Mx->xwbg;
    }
    GALTERCREATEGC(G.gctw);

    GCvalues.foreground = Mx->black;
    GCvalues.background = Mx->white;
    GALTERCREATEGC(G.gcti);

    GCvalues.foreground = Mx->white ^ Mx->black;
    GCvalues.background = 0;
    GCvalues.function = GXxor;
    GALTERCREATEGC(G.gcm);
    GALTERCREATEGC(G.gcr);

  /* Only create the second XOR GC the first time, otherwise leave it alone */
    if (!G.gcr2) {
      GCvalues.foreground = 8;
      G.gcr2 = XCreateGC(G.D, tmpPix, valuemask, &GCvalues); /* RDA */
    }

    XFreePixmap ( G.D , tmpPix );
#if _HEADLESS
  }
  else {
    Mx->white = Mx->pixel[white-1];
    Mx->black = Mx->pixel[black-1];
    if (xi) Mx->xi = xi;
    Mx->clast = L_FGColor;

    Mx->xwfg = Mx->white;
    Mx->xwbg = Mx->black;
    Mx->xwts = Mx->black;
    Mx->xwbs = Mx->black;
    Mx->xwlo = Mx->white;
  }
#endif
}

/*** RDA: set EWM workspace **********************************/
/* If the workspace switch is set, try to tell the window manager
 * that we're want to be on specific workspaces.  
 * This is for window managers implementing the
 * Extended Window Manager Hints from http://www.freedesktop.org
 * This should work for enlightenment, Gnome, and KDE desktops
 *
 * The only options are -1 (all workspaces), 1..#Dtop.
 * Workspace names are not uniformly implemented at this time.
 *
 * Assumptions that may be wrong:
 *   - All screens have same # desktops
 *   - cannot put on multiple Wspace
 *   - Can put *all* workspaces
 *
 * Returns True if the the window is on the current workspace
 *         False  if the window isn't on the current workspace
 */
static int set_EWM_workspace(void)
{
  int i, ndt, iws, cws;
  unsigned long bytes_after, nitem;
  Atom ANum, ACur, AWin;
  int  actFmt;
  Atom actType;
  Window r;
  char *wmtype;
  int onWS = True;		/* return value */
  unsigned char *prop;          /* not int aligned!! */

  /* If default workspace, skip this mess */
  if ( sscanf( Mx->wspace, "%d", &iws ) != 1 ) return( onWS );

  /* Intern all the atoms first - if they don't exist, it's not valid WM */
  if (((ANum=XInternAtom(G.D, "_NET_NUMBER_OF_DESKTOPS", True))==None) ||
      ((ACur=XInternAtom(G.D, "_NET_CURRENT_DESKTOP"   , True))==None) ||
      ((AWin=XInternAtom(G.D, "_NET_WM_DESKTOP"        , True))==None)) {
    /* Not EWM - try enlightenment */
    if (((ANum=XInternAtom(G.D, "_WIN_WORKSPACE_COUNT", True))==None) ||
	((ACur=XInternAtom(G.D, "_WIN_WORKSPACE"      , True))==None)) {
      return( onWS ); /* Unsupported WM - punt */
    } else {
      wmtype = "Old ExtWM";
      AWin = ACur;
    } 
  } else {
    wmtype = "New ExtWM";
  }
      

      /* Get # of desktops for bounds checking */
  r = RootWindowOfScreen(G.S_ptr);
  i=XGetWindowProperty ( G.D , r, ANum ,
		      0, 1, False, XA_CARDINAL ,
		      &actType, &actFmt , &nitem, &bytes_after,
                      &prop);
  if ( i != Success ) goto exit_EWM_workspace;
  memcpy ( &ndt, prop, sizeof(ndt));
  XFree ( prop );

  /* check range and adjust */
  if ( iws < -1 || iws == 0 || iws > ndt ) return( onWS );
  if ( iws > 0 ) iws--;

  /* Get current workspace */
  i=XGetWindowProperty ( G.D , r, ACur ,
			 0, 1, False, XA_CARDINAL,
			 &actType, &actFmt , &nitem, &bytes_after,
			 &prop);
  if ( i != Success ) goto exit_EWM_workspace;

  /* If on iws is current workspace, just return */
  memcpy ( &cws, prop, sizeof(cws) );
  XFree ( prop );
  if ( Mx->visDebug >= 0) 
    printf("VISUALDEBUG: %s #WS=%d curWS=%d onWS=%d\n", wmtype, ndt, cws, iws );
  if ( cws == iws ) return( onWS );

  /* Set the workspace hints on the window */
  onWS = False;
  XChangeProperty ( G.D , G.W , AWin , XA_CARDINAL , 32 ,
		   PropModeReplace , (unsigned char *)&iws , 1 );

  /* Don't leak - release memory */
 exit_EWM_workspace:
  return ( onWS );
}

/*** RDA: set CDE workspace **********************************/
/* If the workspace switch is set, try to tell the window manager
 * that we're want to be on specific workspaces.  A CDE API exists,
 * but I wrote the functionality in Xlib to avoid having CDE libraries
 * to build XMIDAS.
 *
 * Returns True if the the window is on the current workspace
 *         False  if the window isn't on the current workspace
 *                (though the window can be on multiple workspaces)
 */
static int set_CDE_workspace(void)
{
#define MAXWS 32
  int i, n;
  char  wsAtomName[32] , *ws, *tmp;
  unsigned long bytes_after,nitem,nWS;                                /*MIB*/
  int actFmt;                                                         /*MIB*/
  Atom AMotif, AwsList, AwsCurr, ADtHint, Atmp;
  Atom SetWS[MAXWS+3], actType, *Ap;
  int onWS = True;		/* return value */
  /* RDA: NULL the pointers so XFree will safely ignore them */
  Window *motifID=NULL;
  Atom *ListWS=NULL;
  Atom *curWS=NULL;	
  char *(ListNames[MAXWS]);	/* Names of workspaces */

#if _SUN
  memset( (char *)ListNames , 0 , sizeof(ListNames) );
#else
  memset( ListNames , 0 , sizeof(ListNames) );
#endif

  /* Intern all the atoms first - if they don't exist, it's not CDE */
  if ((AwsList=XInternAtom(G.D, "_DT_WORKSPACE_LIST", True))==None)
    return( onWS );
  if ((AwsCurr=XInternAtom(G.D, "_DT_WORKSPACE_CURRENT", True))==None)
    return( onWS );
  if ((ADtHint=XInternAtom(G.D, "_DT_WORKSPACE_HINTS", True))==None)
    return( onWS );
  if ((AMotif =XInternAtom(G.D, "_MOTIF_WM_INFO", True))==None)
    return( onWS );

  /* Get windowID of window manager */
  i=XGetWindowProperty ( G.D , RootWindowOfScreen(G.S_ptr) , AMotif ,
		      1,1,False, AMotif ,
		      &actType, &actFmt , &nitem, &bytes_after,/*MIB*/ 
                      (unsigned char **)&motifID );
  if ( i != Success ) goto exit_CDE_workspace;

  /* Get the list of workspace atoms for this display */
  i=XGetWindowProperty ( G.D , *motifID , AwsList ,
		      0,MAXWS,False, XA_ATOM ,
		      &actType, &actFmt , &nWS, &bytes_after,/*MIB*/
                      (unsigned char **) &ListWS );
  if ( i != Success ) goto exit_CDE_workspace;

  /* Get the atom of the current workspace */
  i=XGetWindowProperty ( G.D , *motifID , AwsCurr ,
		      0,1,False, XA_ATOM ,
		      &actType, &actFmt , &nitem, &bytes_after,/*MIB*/ 
                      (unsigned char **)&curWS );
  if ( i != Success ) goto exit_CDE_workspace;

  /* Get the name of each workspace */
  for (i=0; i<nWS; i++) {
    tmp = XGetAtomName ( G.D , ListWS[i] );
    sprintf(wsAtomName, "_DT_WORKSPACE_INFO_%s", tmp );
    XFree ( tmp );
    if ((Atmp = XInternAtom(G.D, wsAtomName, False))==None)
      goto exit_CDE_workspace;
    XGetWindowProperty ( G.D , *motifID , Atmp ,
			0,16,False, AnyPropertyType ,
			&actType, &actFmt , &nitem, &bytes_after,/*MIB*/
			(unsigned char **)&(ListNames[i]) );
    if ( Mx->visDebug >= 0) 
      printf("VISUALDEBUG: wsAtom[%d]=0x%x wsName=%s\n",
	     i+1, ListWS[i], ListNames[i] );
 }

  /* Form new list of workspaces; See if any are current workspace */
  Ap = SetWS;
  *Ap++ = 1L; *Ap++ = 2L; *Ap++ = 0L; /* magic header - it works ... */
  onWS = False;
  for(n=0,ws=strtok( Mx->wspace, ","); ws!=NULL; ws=strtok(NULL,","),n++) {
    if ( Mx->visDebug >= 0) 
      printf("VISUALDEBUG: Put on Workspace=%s\n", ws );
    if (isdigit( *ws )) {	/* set the screen by number - zero based */
      sscanf( ws , "%d" , &i );
      if ( 1 <= i  &&  i <= nWS ) {
	*++Ap = ListWS[i-1];
	if ( ListWS[i-1] == *curWS ) onWS = True;
      }
    } else {			/* match the screen title */
      for (i=0; i < nWS ; i++ ) {
	if ( strcasecmp( ws , ListNames[i] ) == 0 ) {
	  *++Ap = ListWS[i];
	  if ( ListWS[i] == *curWS ) onWS = True;
	  break;
	}
      }
    }
  }
  SetWS[3] = n;

  /* Set the workspace hints on the window */
  XChangeProperty ( G.D , G.W , ADtHint , ADtHint , 32 ,
		   PropModeReplace , (unsigned char *)SetWS , n+4 );

  if ( Mx->visDebug >= 0) 
    printf("VISUALDEBUG: curWS=0x%x onWS=0x%x\n", *curWS, onWS );

  /* Don't leak - release memory */
 exit_CDE_workspace:
  XFree ( motifID );
  XFree ( ListWS );
  XFree ( curWS );
  for(i=0; i < nWS ; i++) XFree( ListNames[i] );
#undef MAXWS
  return ( onWS );
}

/*************************** OPEN **************************/

void mx_open (const char *name, int lname)
{
  int i, r_x, r_y, x, y;
  unsigned int valuemask, keys;
  Window root, child;
  XSetWindowAttributes setwinattr;
  XGCValues GCvalues;
  XSizeHints Shints;
  XWMHints WMhints;
  int onCurWorkspace;	/* RDA */
  char node[41];        /* DR 11244 */

#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

/* RDA  +++ start fix for MF Bug #2715 */
  valuemask = CWEventMask | CWBackPixel | CWBorderPixel | CWColormap | CWWinGravity; /* RDA */
  setwinattr.background_pixel = Mx->black;
  setwinattr.border_pixel = Mx->white;
  setwinattr.colormap = G.C_id;	/* RDA */
  setwinattr.event_mask =   KeyPressMask      | KeyReleaseMask
    | ButtonPressMask     | ButtonReleaseMask | PointerMotionMask
    | StructureNotifyMask | ExposureMask      | FocusChangeMask
    | EnterWindowMask;
  setwinattr.win_gravity = StaticGravity;   /* RDA */
  root = 0;
  if (Mx->parent == 0);
  else if (Mx->parent > 0) root = mx_window_id(Mx->parent,0);	/* XS value */
  if ((int)root <= 0) root = RootWindowOfScreen(G.S_ptr);    /*MIB 18-April-2000*/

  x = Mx->left; y = Mx->top;

/* commented out  * handle any Window Manager specific adjustments *
                  if (strstr(ServerVendor(G.D),"XFree86") != NULL) {
                  * this WM uses the outside border location *
                  if (root == RootWindowOfScreen(G.S_ptr)) { x-=5; y-=23; }
                  }
                  else if (strstr(ServerVendor(G.D),"DECWINDOWS") != NULL) {
                  }
                  else if (strstr(ServerVendor(G.D),"OPENWINDOWS") != NULL) {
                  }
 * --- end fix for MF Bug #2715 */						

  G.W = XCreateWindow (G.D, 
          root, x, y, Mx->width, Mx->height, 0, G.depth,
	  InputOutput, G.visual, valuemask, &setwinattr);

  GDeleteWinAtom = XInternAtom(G.D, "WM_DELETE_WINDOW", False);
  XSetWMProtocols(G.D, G.W, &GDeleteWinAtom, 1);
  GProtocolsAtom = XInternAtom(G.D, "WM_PROTOCOLS", False);

  if ( *Mx->wspace != '\0' ) {
    onCurWorkspace = set_CDE_workspace();
    if (onCurWorkspace)
      onCurWorkspace = set_EWM_workspace();
  }

  Shints.flags = USPosition | USSize /* | PMinSize | PMaxSize */;
  Shints.x = x;
  Shints.y = y;
  Shints.width = /* Shints.min_width = Shints.max_width = */ Mx->width;
  Shints.height = /* Shints.min_height = Shints.max_height = */ Mx->height;
  XSetNormalHints (G.D, G.W, &Shints);

  /* 
     Explicitly set the icon name to the application name.  If we do not
     do this the icon is named the first string set into mx_setbar AFTER
     the window is mapped.  This is usually a temporary message like
     "X-Raster reading from file" or "Scrollbox...."
  */
  {
    char icon_name[41];
    strf2c(name, lname, icon_name, 41, F2C_FLAG_TRIM);
    XSetIconName(G.D, G.W, icon_name);
  }

  for (i=sizeof(Mx->title); i>0 && Mx->title[i-1]==' '; --i);
  if (i > 0) mx_title(Mx->title, 3, i);
  else mx_title(name, 3, lname);

  WMhints.flags = InputHint;
  WMhints.input = TRUE;
  if (Mx->icon > 0) {
    WMhints.flags |= StateHint;
    WMhints.initial_state = IconicState;
  }
  /* this code left here as an example of how to position the icon
     this is only a suggestion however, if there is already an icon there
     the window manager ignores the suggestion and puts it where it wants
  WMhints.flags |= IconPositionHint;
  WMhints.icon_x = 100;
  WMhints.icon_y = 100;
  */

  XSetWMHints (G.D, G.W, &WMhints);

  XMapWindow (G.D, G.W);
  XFlush (G.D);

  if (!Mx->icon && onCurWorkspace ) {
    /* Some servers do not give you a MapNotify if you are mapped as an icon */
    /* RDA: or on a non-visible workspace ... */
    do { geteventw();} while (GEvent.type != MapNotify);
    /* The window will not get an expose event if it is brought up iconified */
    /* and may not get an Expose if it is a child window (if it is obscured) */
    if (Mx->parent == 0) 
      do { geteventw();} while (GEvent.type != Expose);
    else
      while (getevent() && GEvent.type != Expose);
    /* Setting input focus when window is an icon causes a BadMatch error */
    if (Mx->xs <= 0) XSetInputFocus (G.D, G.W, RevertToParent, CurrentTime);
  }

  XQueryPointer (G.D, G.W, &root, &child, &r_x, &r_y, &x, &y, &keys);
  Mx->xpos = x;
  Mx->ypos = y;

/*+++                                       */
/* DR 11244                                 */
/* Old
  if (Mx->xs > 0) mx_window_id(Mx->xs, 1);  */
/* New                                      */
  if (Mx->xs > 0) {
    (void)mx_window_id((int_4)Mx->xs, 1);
    strf2c (Mx->node, 40, node, 41, F2C_FLAG_TRIM);
    if (strlen(node) == 0) {
      memset(node,' ',40);
      node[40] = 0;
    }
    (void)mx_display_id((int_4)Mx->xs, 1, node, strlen(node));
  }
/*---                                       */

  Mx->glob = G;
}

/*************************** DRAW LINES **************************/

void mx_draw_lines (int ic, const XPoint pix[], int npix)
{
  float slope;
  int i, j, n, x1, x2, y, y1, y2;
  GC gc;

#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  if (npix <= 1) return;
  SETGCGFOREGROUND (ic);
  gc = (ic==L_XORColor) ? G.gcr : G.gcg;

  /* test for overlapped points to see if we should use an algorithm to */
  /* reduce the number of line draws */
  if (npix > 10)
    for (i=1,j=0; i<10 && (j+=abs(pix[i].x-pix[i-1].x))<2; i++);
  else
    i = 0;

  /*
   The following algorithm speeds up plotting of lines that have bunches
   of points next to one another with the same X pixel coordinate.
   Instead of letting X-Windows draw all of these lines, compact them
   into a single vertical line.
  */

  if (i == 10) {
    int k, gml=GMaxLines-3;
    XPoint *tpix;
    if (sizeof(XPoint)*GMaxLines > GExbuf_size) {
      if (GExbuf_size>0) free(GExbuf_ptr);
      GExbuf_size = sizeof(XPoint) * GMaxLines;
      GExbuf_ptr = (char *)malloc(GExbuf_size);
    }
    tpix = (XPoint *)GExbuf_ptr;
    for (i=j=k=0, n=npix-1; j <= n; j=i) {

      /* condense multiple lines on the same X pixel vertical line */
      /* keeping track of the max and min y pixel coordinate values */
      x1 = pix[i].x;
      y1 = y2 = pix[i].y;
      while (++j<n && x1==pix[j].x) {
        y = pix[j].y;
        if (y < y1) y1 = y;
        else if (y > y2) y2 = y;
      }
      /* only worth doing if we have more than 2 points on the same */
      /* vertical line, otherwise draw them below */
      if (j-i > 2) {
	tpix[k].x = x1;
	tpix[k++].y = y1;
	tpix[k].x = x1;
	tpix[k++].y = y2;
        i = j-1;
      }

      /* draw lines between points not on the same X pixel or between */
      /* less than 3 points on the same line */
      while (j<n && pix[j].x!=pix[j+1].x) ++j;
      j = min(j,n);
      while (i<=j) {
	tpix[k++] = pix[i++];
	if (k>=gml) {
	  XDrawLines(G.D, G.W, gc, tpix, k, CoordModeOrigin);
          if (f_PostScript) mx_psdraw_vector(ic, tpix, k, "S");
	  tpix[0] = tpix[k-1];
	  k = 1;
	}
      }
    }
    if (k>1) {
      XDrawLines(G.D, G.W, gc, tpix, k, CoordModeOrigin);
      if (f_PostScript) mx_psdraw_vector(ic, tpix, k, "S");
    }
  } 
  else {
    for (i=0; i<npix; i += GMaxLines-1) {
      XDrawLines(G.D, G.W, gc, (XPoint *)&pix[i],
        min (npix-i, GMaxLines), CoordModeOrigin);
    }
    if (f_PostScript) mx_psdraw_vector(ic, pix, npix, "S");
  }

  if (ic == L_XORColor && Mx->rmode) {
    for (i=0; i<npix-1; i++) {
      x1 = pix[i].x;
      y1 = pix[i].y;
      x2 = pix[i+1].x;
      y2 = pix[i+1].y;
      if (x1 == x2)      XDrawLine (G.D, G.W, G.gcr2, x1+1, y1, x1+1, y2);
      else if (y1 == y2) XDrawLine (G.D, G.W, G.gcr2, x1, y1+1, x2, y1+1);
      else {
        slope = (float)(y2 - y1) / (float)(x2 - x1);
        if (slope < 0) slope = - slope;
        if  (slope < .4)    XDrawLine (G.D, G.W, G.gcr2, x1, y1+1, x2, y2+1);
        else if (slope<2.4) XDrawLine (G.D, G.W, G.gcr2, x1+2, y1, x2+2, y2);
        else                XDrawLine (G.D, G.W, G.gcr2, x1+1, y1, x2+1, y2);
      }
    }
  }

}

/*************************** DRAW_DOTS **************************/

void mx_draw_dots (int ic, const XPoint pix[], int npix, int rr)
{
#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  if (rr < 0) mx_draw_symbols(ic, pix, npix, L_SquareSymbol, -rr);
  else if (rr > 0) mx_draw_symbols(ic, pix, npix, L_CircleSymbol, rr);
  else mx_draw_symbols(ic, pix, npix, L_PixelSymbol, 0);
}

/*************************** DRAW_SYMBOL **************************/

void mx_draw_symbol (int ic, int x, int y, int symbol, int rr)
{
  XPoint pix[1];

#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  pix[1].x = x; pix[0].y = y;
  mx_draw_symbols(ic, pix, 1, symbol, rr);
}

/*************************** DRAW_SYMBOLS **************************/

void mx_draw_symbols (int ic, const XPoint pix[], int npix, int symbol, int rr)
{
  int i, r, d, x, y;
  bool rmode, fill;
  XPoint tri[4];
  GC gc;
  char c;

#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  fill = (rr < 0);
  r = abs(rr);
  d = r*2;

  SETGCGFOREGROUND (ic);
  if (ic == L_XORColor) {
    rmode = Mx->rmode;
    gc = (rmode) ? G.gcr2 : G.gcr;
  }
  else {
    rmode = FALSE;
    gc = G.gcg;
  }

  switch (symbol) {

    case L_CircleSymbol:

      if (fill) {
	if (f_PostScript) mx_psdraw_objects(ic, pix, npix, r, "Dot fill");
	for (i=0; i<npix; i++)
	  XFillArc(G.D, G.W, gc, pix[i].x-r, pix[i].y-r, d, d, 0, 23040);
      }
      else {
	if (f_PostScript) mx_psdraw_objects(ic, pix, npix, r, "Dot S");
	for (i=0; i<npix; i++)
	  XDrawArc(G.D, G.W, gc, pix[i].x-r, pix[i].y-r, d, d, 0, 23040);
	if (rmode && (gc=G.gcr)) {
	  ++r; d += 2;
	  for (i=0; i<npix; i++)
	    XDrawArc(G.D, G.W, gc, pix[i].x-r, pix[i].y-r, d, d, 0, 23040);
	}
      }
      break;

    case L_SquareSymbol:

      if (fill) {
	if (f_PostScript) mx_psdraw_objects(ic, pix, npix, r, "sq fill");
	for (i=0; i<npix; i++)
	  FILLRECTANGLE(gc, pix[i].x-r, pix[i].y-r, d, d);
      }
      else {
	if (f_PostScript) mx_psdraw_objects(ic, pix, npix, r, "sq S");
	for (i=0; i<npix; i++)
	  DRAWRECTANGLE(gc, pix[i].x-r, pix[i].y-r, d, d);
	if (rmode && (gc=G.gcr)) {
	  ++r; d += 2;
	  for (i=0; i<npix; i++)
	    DRAWRECTANGLE(gc, pix[i].x-r, pix[i].y-r, d, d);
	}
      }
      break;

    case L_PixelSymbol:
      if (f_PostScript) mx_psdraw_objects(ic, pix, npix, rr, "Dot S");
      d = 2*GMaxLines;
      for (i=0; i<npix; i+=d) {
	r = min ( d, npix-i);
	XDrawPoints (G.D, G.W, gc, (XPoint *)&pix[i], r, CoordModeOrigin);
      }
      break;

    case L_ITriangleSymbol:
      r = -r;
    case L_TriangleSymbol:
      d = r * 1.5;
      x = r * .80;

      tri[1].x = -x;
      tri[1].y = d;
      tri[2].x = x*2;
      tri[2].y = 0;
      tri[3].x = -x;
      tri[3].y = -d;

      if (fill) {
	if (f_PostScript) mx_psdraw_objects(ic, pix, npix, r, "Tri fill");
	for (i=0; i<npix; i++) {
	  tri[0].x = pix[i].x;
	  tri[0].y = pix[i].y - r;
	  XFillPolygon(G.D, G.W, gc, tri, 4, Convex, CoordModePrevious);
	}
      }
      else {
	if (f_PostScript) mx_psdraw_objects(ic, pix, npix, r, "Tri S");
	for (i=0; i<npix; i++) {
	  tri[0].x = pix[i].x;
	  tri[0].y = pix[i].y - r;
	  XDrawLines(G.D, G.W, gc, tri, 4, CoordModePrevious);
	}
	if (rmode && (gc=G.gcr)) {
	  if (r >= 0) { ++r; ++x; d += 2; }
	  else { --r; --x; d -= 2; }
	  tri[1].x = -x;
	  tri[1].y = d;
	  tri[2].x = x*2;
	  tri[2].y = 0;
	  tri[3].x = -x;
	  tri[3].y = -d;

	  for (i=0; i<npix; i++) {
	    tri[0].x = pix[i].x;
	    tri[0].y = pix[i].y - r;
	    XDrawLines(G.D, G.W, gc, tri, 4, CoordModePrevious);
	  }
	}
      }
      break;

    case L_PlusSymbol:

      if (f_PostScript) mx_psdraw_objects(ic, pix, npix, r, "Plus S");
      for (i=0; i<npix; i++) {
	x = pix[i].x;
	y = pix[i].y;
	XDrawLine(G.D, G.W, gc, x, y+r, x, y-r);
	XDrawLine(G.D, G.W, gc, x+r, y, x-r, y);
      }
      if (rmode && (gc=G.gcr)) {
	for (i=0; i<npix; i++) {
	  x = pix[i].x - 1;
	  y = pix[i].y + 1;
	  XDrawLine(G.D, G.W, gc, x, y+r, x, y-r);
	  XDrawLine(G.D, G.W, gc, x+r, y, x-r, y);
	}
      }
      break;

    case L_XSymbol:

      if (f_PostScript) mx_psdraw_objects(ic, pix, npix, r, "XSym S");
      for (i=0; i<npix; i++) {
	x = pix[i].x;
	y = pix[i].y;
	XDrawLine(G.D, G.W, gc, x-r, y-r, x+r, y+r);
	XDrawLine(G.D, G.W, gc, x+r, y-r, x-r, y+r);
      }
      if (rmode && (gc=G.gcr)) {
	d = r-1; ++r;
	for (i=0; i<npix; i++) {
	  x = pix[i].x;
	  y = pix[i].y;
	  XDrawLine(G.D, G.W, gc, x-r, y-d, x+d, y+r);
	  XDrawLine(G.D, G.W, gc, x+d, y-r, x-r, y+d);
	}
      }
      break;

    default:
      c = (char) symbol;
      if (isprint(c)) {
	r = Mx->text_w / 2;
	if (f_PostScript) {
	  char astr[8];
	  sprintf(astr, "(%c) Char", c);
	  mx_psdraw_objects(ic, pix, npix, r, astr);
	}
	if (fill && !rmode) {
	  for (i=0; i<npix; i++)
	    XDrawImageString(G.D, G.W, gc, pix[i].x-r, pix[i].y+r, &c, 1);
	}
	else {
	  for (i=0; i<npix; i++)
	    XDrawString(G.D, G.W, gc, pix[i].x-r, pix[i].y+r, &c, 1);
	}

      }
      else m_warning ("Unrecognized symbol type in MX$DRAW_SYMBOLS");
      break;
  }

}

/*************************** CONTEXT **************************/

void mx_setcontext (MX *mx_ptr)
{
  Mx = mx_ptr;
  G = Mx->glob;
}


/*************************** GRAPHICSINIT **************************/

void mx_graphicsinit (void)
{
  GWcount = 0;
}

/*************************** CLOSE **************************/

void mx_close (void)
{
  int i;

#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  if (G.W != 0) {
    XUnmapWindow (G.D, G.W);
    while (mx_ifevent());
    XDestroyWindow (G.D, G.W);
    XFlush (G.D);
  }

  G.img_ptr->data = 0;
  XDestroyImage(G.img_ptr);
  mx_tfont(NULL, L_FreeFonts);
  if (G.W == 0 || Mx->shrcmap >= 0 || Mx->shrcmap <= -MAXSHARED) {
    free(G.color);
    free(G.colorp);
  }

  if (G.gcm != 0) {
    XFreeGC (G.D, G.gcg);
    XFreeGC (G.D, G.gct);
    XFreeGC (G.D, G.gcta);
    XFreeGC (G.D, G.gctw);
    XFreeGC (G.D, G.gcm);
    XFreeGC (G.D, G.gcr);
    XFreeGC (G.D, G.gcr2);
    XFreeGC (G.D, G.gcti);
  }

  GWcount--;
  for (i=0; i<MAXDISPLAYS; i++)
  if (D[i].count > 0 && G.D == D[i].id) {
    if (--D[i].count == 0) XCloseDisplay (D[i].id);
  }

  /* remove window id entry from results table */

/*+++                                             */
/* DR 11244                                       */
/* Old
  if (Mx->xs > 0) (void)mx_window_id(Mx->xs, -1); */
/* New                                            */
  if (Mx->xs > 0) {
    (void)mx_window_id((int_4)Mx->xs, -1);
    (void)mx_display_id((int_4)Mx->xs, -1, NULL, 0);
  }
/*---                                            */
  if (Mx->shrcmap > 0) (void)mx_colormap_id(NULL, -1);
}

/*************************** QUEUED **************************/

int mx_queued (void)
{
#if _HEADLESS
  if (Mx->headless == 1 ) return 0;
#endif

  return XPending(G.D);
}

/*************************** EVENT **************************/

Bool mx_match (const Display *D, const XEvent *E, const int *type)
{
  /* MappingNotify is the only event that does not use the window field */
  return (E->xany.window==G.W || E->type==MappingNotify);
}

void mx_event (void)
{
  int pending=0;

#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  while ( ! mx_ifevent() && ! XM_BREAK ) {
    /* check for bogus event on the queue */
    if (pending>0) { mx_bogus_event(); return; }
    m_pause (POLLTIME);
    pending = XPending(G.D);
  }
}

/*********************** IFEVENT *****************************/

bool mx_ifevent (void)
{
  int x, y;
  unsigned int w, h, border_width, depth;
  Window root;

#if _HEADLESS
  if (Mx->headless == 1 ) return FALSE;
#endif

  Mx->exposure = FALSE;
  Mx->resize = FALSE;
  Mx->motion_notify = FALSE;
  Mx->button_press = 0;
  Mx->button_release = 0;
  Mx->key_press  = 0;
  Mx->key_char   = 0;
  Mx->key_upchar = 0;
  Mx->state_mask = 0;

  if ( !XCheckIfEvent(G.D, &GEvent, (Bool(*)())mx_match, 0) ) return FALSE;

  NEXT:
  if (GEvent.type==ClientMessage) GEvent.type = client_msg(&GEvent);

  switch (GEvent.type) {
    case MotionNotify:
      if (!EVENTS_ON) break;
      Mx->motion_notify = TRUE;
      (void)peelevents(MotionNotify);
      Mx->xpos = bound (GEvent.xmotion.x, 0, Mx->width);
      Mx->ypos = bound (GEvent.xmotion.y, 0, Mx->height);
      Mx->state_mask = x2xmidasmask(GEvent.xmotion.state);
      break;

    case ButtonPress:
      if (!EVENTS_ON) break;
      Mx->xpos = bound (GEvent.xbutton.x, 0, Mx->width);
      Mx->ypos = bound (GEvent.xbutton.y, 0, Mx->height);
      switch (GEvent.xbutton.button) {
        case Button1: Mx->button_press = 1; break;
        case Button2: Mx->button_press = 2; break;
        case Button3: Mx->button_press = 3;
      }
      Mx->state_mask = x2xmidasmask(GEvent.xbutton.state);
      break;

    case ButtonRelease:
      if (!EVENTS_ON) break;
      Mx->xpos = bound (GEvent.xbutton.x, 0, Mx->width);
      Mx->ypos = bound (GEvent.xbutton.y, 0, Mx->height);
      switch (GEvent.xbutton.button) {
        case Button1: Mx->button_release = 1; break;
        case Button2: Mx->button_release = 2; break;
        case Button3: Mx->button_release = 3;
      }
      Mx->state_mask = x2xmidasmask(GEvent.xbutton.state);
      break;

    case KeyPress:
      if (!EVENTS_ON) break;
      (void)mx_pointer ();
      switch (GKeysym) {
        case XKLEFT:   Mx->key_press = L_ArrowLeft;  return TRUE;
        case XKRIGHT:  Mx->key_press = L_ArrowRight; return TRUE;
        case XKUP:     Mx->key_press = L_ArrowUp;    return TRUE;
        case XKDOWN:   Mx->key_press = L_ArrowDown;  return TRUE;
        case XK_c:
        case XK_C:     if (GEvent.xkey.state&ControlMask) *Mcbreak=TRUE; break;
	case XK_i: case XK_I:
        case XK_r: case XK_R:
	  if (GEvent.xkey.state&ControlMask) {
	    if (GKeysym==XK_i||GKeysym==XK_I)
	      mx_setbgfg(L_FGColor, L_BGColor, -Mx->xi);
	    else
	      mx_setbgfg(L_BGColor, L_FGColor, 0);
	    XClearWindow(G.D, G.W);
	    Mx->resize = TRUE;
	    return TRUE;
	  }
	  break;
        case XK_Help:
        case XK_slash: GKey[0] = '?'; break;
      }
      Mx->xpos = bound (GEvent.xkey.x, 0, Mx->width);
      Mx->ypos = bound (GEvent.xkey.y, 0, Mx->height);
      Mx->key_press = (int)GKey[0];
      Mx->key_char   = GKey[0];
      Mx->key_upchar = toupper(GKey[0]);
      Mx->state_mask = x2xmidasmask(GEvent.xkey.state);
      break;

    case MapNotify:
      Mx->icon = FALSE;
    case Expose:
      (void)peelevents(Expose);
      Mx->exposure = TRUE;
      break;

    case UnmapNotify:
      Mx->icon = TRUE;
      break;

    case ConfigureNotify:
      if (window_resize(&Mx->left,&Mx->top,&Mx->width,&Mx->height))
	Mx->resize = TRUE;
      /* pare off any expose events that come on the back of a resize */
      (void)peelevents(Expose);
      break;

    case EnterNotify:
      XGetGeometry (G.D, G.W, &root, &x, &y, &w, &h, &border_width, &depth);
      if (Mx->height != h || Mx->width != w)
        { Mx->height = h; Mx->width = w; Mx->resize = TRUE; }
      break;

    case FocusIn:
      if (G.AutoRepeat) XAutoRepeatOn (G.D);
      else XAutoRepeatOff (G.D);
      break;
    case FocusOut:
      if (!G.AutoRepeat) XAutoRepeatOn (G.D);
      break;

    case SelectionRequest:
      {
	XEvent sevent;
	sevent.type                  = SelectionNotify;
	sevent.xselection.display    = GEvent.xselectionrequest.display;
	sevent.xselection.requestor  = GEvent.xselectionrequest.requestor;
	sevent.xselection.selection  = GEvent.xselectionrequest.selection;
	sevent.xselection.target     = GEvent.xselectionrequest.target;
	sevent.xselection.property   = None;
	sevent.xselection.time       = GEvent.xselectionrequest.time;
	XSendEvent(GEvent.xselectionrequest.display,
		   GEvent.xselectionrequest.requestor, False, 0, &sevent);

	if (XGetSelectionOwner(G.D,XA_PRIMARY)==G.W)
	  XSetSelectionOwner(G.D, XA_PRIMARY, None, CurrentTime);
      }
      /* Fall through */

    case MappingNotify:

    default:   /* non-recognized event - look again */
      if ( XCheckIfEvent(G.D, &GEvent, (Bool(*)())mx_match, 0) ) goto NEXT;
  }
  return TRUE;
}

static void xform_xy(int_4 *x, int_4 *y, int width, int height)
{
  *x = (*x>=0) ? (0.5 + width * ((double)(*x) / 1000.0)) : -(*x);
  *y = (*y>=0) ? (0.5 + height * ((double)(*y) / 1000.0)) : -(*y);
}

/************************** BOGUS_EVENT **************************
 * Peels events off of the X event queue that are NOT for the current
 * window.  These events should never be there in the first place, so
 * it may be helpful to see exactly what the events are, who they are
 * for, etc.  mx_bogus_event prints information about each event and
 * returns the number of events peeled off.
 *
 * Note: graphics primitives running with DS support all run in the same
 * image and therefore share the same X event queue, so it is very likely
 * that events will appear on the event queue that are not addressed to the
 * individual primitive running at the time.  These events are not bogus
 * and will be peeled off when the DS graphics primitive they are addressed
 * to gets its next event processing run-through by the datastreams manager.
 */

static Bool mx_notmatch (const Display *D, const XEvent *E, const int *type)
{
  return (E->xany.window!=G.W && E->type!=MappingNotify);
}

int mx_bogus_event(void)
{
  int i;
  XEvent event;
  char *s;

#if _HEADLESS
  if (Mx->headless == 1 ) return 0;
#endif

  for (i=0; XCheckIfEvent(G.D,&event,(Bool(*)())mx_notmatch,0); i++ ) {
    printf("Window %d at display %d received a bogus event:\n", G.W, G.D);
    switch (event.type) {
      case KeyPress:		s = "KeyPress"; break;
      case KeyRelease:		s = "KeyRelease"; break;
      case ButtonPress:		s = "ButtonPress"; break;
      case ButtonRelease:	s = "ButtonRelease"; break;
      case MotionNotify:	s = "MotionNotify"; break;
      case Expose:		s = "Expose"; break;
      case FocusIn:		s = "FocusIn"; break;
      case FocusOut:		s = "FocusOut"; break;
      case EnterNotify:		s = "EnterNotify"; break;
      case CirculateNotify:	s = "CirculateNotify"; break;
      case ConfigureNotify:	s = "ConfigureNotify"; break;
      case DestroyNotify:	s = "DestroyNotify"; break;
      case GravityNotify:	s = "GravityNotify"; break;
      case MapNotify:		s = "MapNotify"; break;
      case ReparentNotify:	s = "ReparentNotify"; break;
      case UnmapNotify:		s = "UnmapNotify"; break;
      case MappingNotify:	s = "MappingNotify"; break;
      case ClientMessage:	s = "ClientMessage"; break;
      case SelectionClear:	s = "SelectionClear"; break;
      case SelectionNotify:	s = "SelectionNotify"; break;
      case SelectionRequest:	s = "SelectionRequest"; break;
      default:			s = "Not registered for it, see HELP XEVENTS";
    }
    printf("  Event type: %d (%s)\n", event.type, s);
    printf("  Addressed to window: %d\n", event.xany.window);
    printf("  Read from display: %d\n", event.xany.display);
    printf("  # of last event processed by server: %ld\n", event.xany.serial);
    printf("  %s from a SendEvent request.\n",
      (event.xany.send_event) ? "Came" : "Did not come");
  }
  return i;
}


/**************************** GRAPHICS_BREAK ***********************/

void mx_graphics_break (bool_4 *breakflag)
{
  *breakflag = FALSE;
  Mcbreak = Mcsysunlock = breakflag;
}

static int client_msg (XEvent *event_ptr)
{
  int mesg, button;
  int_4 x, y;
  unsigned int kpress, mask;
  XEvent event, teach;
  char error[81];

  event = *event_ptr;		/* so we don't write over ourselves */

  if (event.xclient.message_type == GXMidasMsgAtom) {
    switch (mesg = event.xclient.data.l[0]) {

      case L_SendButtonPress:
      case L_SendButtonRelease:
	if (mesg==L_SendButtonPress) GEvent.type = ButtonPress;
	else GEvent.type = ButtonRelease;
	switch (event.xclient.data.l[1]) {
	  case 1: GEvent.xbutton.button = Button1; break;
	  case 2: GEvent.xbutton.button = Button2; break;
	  case 3: GEvent.xbutton.button = Button3; break;
	}
	x = event.xclient.data.l[2];
	y = event.xclient.data.l[3];
        GEvent.xbutton.state = xmidas2xmask(event.xclient.data.l[4]);
	xform_xy(&x, &y, Mx->width, Mx->height);
	GEvent.xbutton.x = x;
	GEvent.xbutton.y = y;
	break;

      case L_SendKeyPress:
      case L_SendKeyRelease:
	if (mesg==L_SendKeyPress) GEvent.type = KeyPress;
	else GEvent.type = KeyRelease;
	kpress = event.xclient.data.l[1];

	/* keypresses without preset ASCII values */
	if (kpress > 127) {
	  switch (kpress) {
	    case L_ArrowLeft:  kpress = XK_Left;    break;
	    case L_ArrowRight: kpress = XK_Right;   break;
	    case L_ArrowUp:    kpress = XK_Up;      break;
	    case L_ArrowDown:  kpress = XK_Down;    break;
	    case L_ReturnKey:  kpress = XK_Return;  break;
	    case L_InsertKey:  kpress = XK_Insert;  break;
	    case L_DeleteKey:  kpress = XK_Delete;  break;
	    case L_F14Key:     kpress = XK_F14;     break;
	    case L_TabKey:     kpress = XK_Tab;     break;
	    case L_HelpKey:    kpress = XK_Help;    break;
	    default:           kpress = None;
	  }
	}
	GEvent.xkey.keycode = (unsigned int) kpress;
	x = event.xclient.data.l[2];
	y = event.xclient.data.l[3];
	GEvent.xkey.state = xmidas2xmask(event.xclient.data.l[4]);
	xform_xy(&x, &y, Mx->width, Mx->height);
	GEvent.xkey.x = x;
	GEvent.xkey.y = y;
	break;

      case L_SendMotion:
	GEvent.xmotion.type = MotionNotify;
	x = event.xclient.data.l[1];
	y = event.xclient.data.l[2];
	GEvent.xmotion.state = xmidas2xmask(event.xclient.data.l[3]);
	xform_xy(&x, &y, Mx->width, Mx->height);
	GEvent.xmotion.x = x;
	GEvent.xmotion.y = y;
	break;

      case L_Deiconify_Msg:
	if (Mx->icon) XMapWindow(G.D, G.W);
	XRaiseWindow(G.D, G.W);
	break;

      case L_Iconify_Msg:
	XIconifyWindow(G.D, G.W, G.S);
	break;

      case L_Move_Msg:
      case L_Resize_Msg:
      case L_MoveResize_Msg:
	{
	  XSizeHints        Shints;
	  int_4             x, y, w, h;
	  int               xo, yo, xi, yi;
	  unsigned int      bw, d, wl, hl;
	  Window            root, child;

	  /* This section is a kludge for OpenWindows and any other       */
	  /* window mngr that subtracts the border width from x and y     */
	  /* in the call to XMoveResizeWindow later on...         */
	  (void) XGetGeometry(G.D, G.W, &root, &xo, &yo, &wl, &hl, &bw, &d);
	  w = wl;
	  h = hl;
	  Shints.flags = 0;

	  switch (mesg) {
	    case L_MoveResize_Msg:
	      Shints.flags |= USSize;
	      w = event.xclient.data.l[3];
	      h = event.xclient.data.l[4];
	      xform_xy(&w, &h, Mx->screen_width, Mx->screen_height);
	    case L_Move_Msg:
	      Shints.flags |= USPosition;
	      x = event.xclient.data.l[1];
	      y = event.xclient.data.l[2];
	      xform_xy(&x, &y, Mx->screen_width, Mx->screen_height);
	      if ((Mx->parent <= 0) && (xo != -1 || yo != -1)) { x += xo; y += yo; }
	      break;

	    case L_Resize_Msg:
	      Shints.flags |= USSize;
	      if (xo != -1 || yo != -1)
		XTranslateCoordinates(G.D,G.W,root, xo,yo, &xi,&yi, &child);
	      else
		XTranslateCoordinates(G.D,G.W,root, -xo,-yo, &xi,&yi, &child);
	      x = xi;
	      y = yi;
	      w = event.xclient.data.l[1];
	      h = event.xclient.data.l[2];
	      xform_xy(&w, &h, Mx->screen_width, Mx->screen_height);
	      break;
	      
	  }

	  XMoveResizeWindow(G.D, G.W, x, y, w, h);

	  /* These lines are recommended by O'Rs Xlib Ref. Manual */
	  Shints.x = x;
	  Shints.y = y;
	  Shints.width = w;
	  Shints.height = h;
	  XSetNormalHints (G.D, G.W, &Shints);
	}
	break;

      case L_Learn_Msg:
	teach.xclient.type = ClientMessage;
	teach.xclient.display = G.D;
	teach.xclient.window = (Window) event.xclient.data.l[1];
	teach.xclient.message_type = GXMidasMsgAtom;
	teach.xclient.format = 32;

	teach.xclient.data.l[0] = L_Teach_Msg;
	(void) window_resize(&Mx->left, &Mx->top, &Mx->width, &Mx->height);
	teach.xclient.data.l[1] =
	  (int) (0.5 + (double)(1000*Mx->left)/(double)Mx->screen_width);
	teach.xclient.data.l[2] =
	  (int) (0.5 + (double)(1000*Mx->top)/(double)Mx->screen_height);
	teach.xclient.data.l[3] =
	  (int) (0.5 + (double)(1000*Mx->width)/(double)Mx->screen_width);
	teach.xclient.data.l[4] =
	  (int) (0.5 + (double)(1000*Mx->height)/(double)Mx->screen_height);

	GErrorVerbose = FALSE;
	if (!XSendEvent (G.D, teach.xclient.window, False, 0, &teach)) {
	  (void) XSPRINTF (error, "Unable to send reply to window %d", 
	    teach.xclient.window);
	  m_warning (error);
	}
	GErrorVerbose = TRUE;
	break;
      
      case L_Focus_Msg:
	if (!Mx->icon) XSetInputFocus (G.D, G.W, RevertToParent, CurrentTime);
	break;

      case L_UserEvents_Msg:
	Mx->noevent = (event.xclient.data.l[1]) ? FALSE : TRUE;
	break;

    } /* switch */

  }
  else if (event.xclient.message_type == GProtocolsAtom) {
    if (event.xclient.data.l[0] == GDeleteWinAtom && Mx->noexit < 3)
      Mx->breakflag = TRUE;
  }
  return GEvent.type;
}

static unsigned int xmidas2xmask (unsigned int mask)
{
  unsigned int xmask = L_ClientEventMask;
  if (mask) {
    if (mask & L_ShiftKeyMask)    xmask |= ShiftMask;
    if (mask & L_ControlKeyMask)  xmask |= ControlMask;
    if (mask & L_Mod1KeyMask)     xmask |= Mod1Mask;
    if (mask & L_Mod2KeyMask)     xmask |= Mod2Mask;
    if (mask & L_Mod3KeyMask)     xmask |= Mod3Mask;
    if (mask & L_Mod4KeyMask)     xmask |= Mod4Mask;
    if (mask & L_Mod5KeyMask)     xmask |= Mod5Mask;
    if (mask & L_Button1Mask)     xmask |= Button1Mask;
    if (mask & L_Button2Mask)     xmask |= Button2Mask;
    if (mask & L_Button3Mask)     xmask |= Button3Mask;
  }
  return xmask;
}

static unsigned int x2xmidasmask (unsigned int mask)
{
  unsigned int xmask = 0;
  if (mask) {
    if (mask & ShiftMask)         xmask |= L_ShiftKeyMask;
    if (mask & ControlMask)       xmask |= L_ControlKeyMask;
    if (mask & Mod1Mask)          xmask |= L_Mod1KeyMask;
    if (mask & Mod2Mask)          xmask |= L_Mod2KeyMask;
    if (mask & Mod3Mask)          xmask |= L_Mod3KeyMask;
    if (mask & Mod4Mask)          xmask |= L_Mod4KeyMask;
    if (mask & Mod5Mask)          xmask |= L_Mod5KeyMask;
    if (mask & Button1Mask)       xmask |= L_Button1Mask;
    if (mask & Button2Mask)       xmask |= L_Button2Mask;
    if (mask & Button3Mask)       xmask |= L_Button3Mask;
    if (mask & L_ClientEventMask) xmask |= L_ClientEventMask;
  }
  return xmask;
}

/**************************** CONFIGMESS ***********************/

static bool window_resize (int_4 *left, int_4 *top, int_4 *width, int_4 *height)
{
  int x, y;
  unsigned int w, h, border_width, depth;
  Window root, child;

  XGetGeometry (G.D, G.W, &root, &x, &y, &w, &h, &border_width, &depth);
  /* This pair of XTranslateCoordinates calls is a kludge for */
  /* window mngrs that compute offsets for border widths      */
  if (x == -1 && y == -1)
    XTranslateCoordinates (G.D, G.W, root, x, y, &x, &y, &child);
  else
    XTranslateCoordinates (G.D, G.W, root, -x, -y, &x, &y, &child);

  *left = x;
  *top = y;
  if (w != *width || h != *height) {
    *width = w;
    *height = h;
    return TRUE;
  }
  else return FALSE;
}

bool getevent (void)
{
  if ( XCheckIfEvent(G.D, &GEvent, (Bool(*)())mx_match, 0)) {
    if (GEvent.type == ClientMessage) GEvent.type = client_msg(&GEvent);

    switch (GEvent.type) {
      case MotionNotify:
	Mx->xpos = bound (GEvent.xmotion.x, 0, Mx->width);
	Mx->ypos = bound (GEvent.xmotion.y, 0, Mx->height);
	break;
      case ButtonPress:
      case ButtonRelease:
	Mx->xpos = bound (GEvent.xbutton.x, 0, Mx->width);
	Mx->ypos = bound (GEvent.xbutton.y, 0, Mx->height);
	break;
      case KeyPress:
	(void)lookupstring(&GEvent, GKey, &GKeysym);
	Mx->xpos = bound (GEvent.xkey.x, 0, Mx->width);
	Mx->ypos = bound (GEvent.xkey.y, 0, Mx->height);
	switch (GKeysym) {
	  case XK_c: case XK_C:
	    if (GEvent.xkey.state&ControlMask) *Mcbreak=TRUE;
	    break;
	  case XK_r: case XK_R:
	  case XK_i: case XK_I:
	    if (GEvent.xkey.state&ControlMask) {
	      if (GKeysym==XK_i||GKeysym==XK_I)
		mx_setbgfg(L_FGColor, L_BGColor, -Mx->xi);
	      else
		mx_setbgfg(L_BGColor, L_FGColor, 0);
	      XClearWindow(G.D, G.W);
	      GEvent.type = Expose;
	      Mx->resize = TRUE;
	    }
	    break;
	}
	break;
      case KeyRelease:
	Mx->xpos = bound (GEvent.xkey.x, 0, Mx->width);
	Mx->ypos = bound (GEvent.xkey.y, 0, Mx->height);
	break;
      case ConfigureNotify:
        if (window_resize(&Mx->left,&Mx->top,&Mx->width,&Mx->height))
	  Mx->resize = TRUE;
	/* pare off any expose events that come on the back of a resize */
	(void)peelevents(Expose);
        break;
      case Expose:
        (void)peelevents(Expose);
        GEvent.type = Expose;
        Mx->exposure = TRUE;
        break;
      case MapNotify:
        Mx->icon = FALSE;
        break;
      case UnmapNotify:
        Mx->icon = TRUE;
        break;
      case FocusIn:
        if (G.AutoRepeat) XAutoRepeatOn (G.D);
        else XAutoRepeatOff (G.D);
	break;
      case FocusOut:
        if (!G.AutoRepeat) XAutoRepeatOn (G.D);
	break;

    }
    return (EVENTS_ON) ? TRUE : FALSE;
  }
  else return FALSE;
}

void geteventw (void)
{
  while ( ! getevent() && ! XM_BREAK ) m_pause (POLLTIME);
}

bool peelevents (int type)
{
  XEvent event;
  int got=0;
  for (got=0; XCheckIfEvent(G.D,&event,(Bool(*)())mx_match,0); ++got) {
    if (event.type==type) GEvent = event;
    else { XPutBackEvent(G.D, &event); return (got > 0); }
  }
  return (got > 0);
}

void mx_title (const char *text, int mask, int length)
{
  char *title;

  title = (char *)malloc(sizeof(char) * (length+1));

  /* copy text into Mx->title field for future reference */
  if (mask & 1) strc2f(text, length, Mx->title, sizeof(Mx->title), C2F_FILL);

  /* copy text to a C string so we can send it to X */
  /* trim off the initial '~' if present, this is a flag for mx_setbar */
  if (text[0]=='~') strf2c (text+1, length-1, title, length, F2C_FLAG_TRIM);
  else strf2c (text, length, title, length+1, F2C_FLAG_TRIM);

  if (mask & 1) XStoreName (G.D, G.W, title);
  if (mask & 2) XSetIconName (G.D, G.W, title);
  XFlush (G.D);

  free(title);
}

/*************************** SET TITLE BAR **************************/

void mx_setbar (const char *text, int length)
{
  char title[61];

#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  if (Mx->title[0] != '~') strf2c (text, length, title, 61, F2C_FLAG_TRIM);
  else strf2c (Mx->title+1, 39, title, 61, F2C_FLAG_TRIM);
  XStoreName (G.D, G.W, title);
  XFlush (G.D);
}

/*************************** UNSET TITLE BAR **************************/

void mx_unsetbar (void)
{
  char title[41];

#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  if (Mx->title[0] != '~') strf2c (Mx->title, 40, title, 41, F2C_FLAG_TRIM);
  else strf2c (Mx->title+1, 39, title, 41, F2C_FLAG_TRIM);
  XStoreName (G.D, G.W, title);
  XFlush (G.D);
}

/*************************** SET FONT **************************/

void mx_setfont (int width)
{

  char fontstring[50];
  int fwidth;
  int old_width;
  Font f_id;

  old_width = Mx->text_w;

  if (abs(Mx->text_size_lock) > 1) width = abs(Mx->text_size_lock);
  if      (width < 6) { fwidth = 80;  Mx->text_w = 5; Mx->text_h = 10; }
  else if (width < 7) { fwidth = 100; Mx->text_w = 6; Mx->text_h = 12; }
  else if (width < 9) { fwidth = 120; Mx->text_w = 7; Mx->text_h = 14; }
  else if (width < 11) { fwidth = 140; Mx->text_w = 9; Mx->text_h = 16; }
  else if (width < 15) { fwidth = 180; Mx->text_w = 11; Mx->text_h = 18; }
  else                 { fwidth = 240; Mx->text_w = 15; Mx->text_h = 24; }

  if (Mx->text_size_lock > 0)
    XSPRINTF (fontstring, "*COURIER-BOLD-R-NORMAL--*-%d-75-75-M-*",fwidth);
  else
    XSPRINTF (fontstring, "*COURIER-MEDIUM-R-NORMAL--*-%d-75-75-M-*",fwidth);

#if _HEADLESS
  if (Mx->headless == 0 ) {
#endif
    if (Mx->text_w != old_width) {
      if (Mx->font_id != 0)
        XUnloadFont(G.D,Mx->font_id);

      f_id = XLoadFont (G.D, fontstring);
      Mx->font_id = f_id;

      XSetFont (G.D, G.gcg,  f_id);
      XSetFont (G.D, G.gcr,  f_id);
      XSetFont (G.D, G.gcr2, f_id);
      XSetFont (G.D, G.gct,  f_id);
      XSetFont (G.D, G.gcti, f_id);
      XSetFont (G.D, G.gctw, f_id);
      XSetFont (G.D, G.gcm,  f_id);
    }
#if _HEADLESS
  }
#endif

  PSfontsize = fwidth / 10;

}

int lookupstring (const XEvent *event, char key[10], KeySym *keysym)
{

  if (event->xkey.state & L_ClientEventMask) {
    *keysym = (unsigned int) event->xkey.keycode;
    key[0] = *keysym;
    return 1;
  }
  else {
    key[0] = 0;
    return XLookupString ((XKeyEvent *)event,key,9,keysym,NULL);
  }

}

/*************************** PUTBACKEVENT **************************/

void mx_putbackevent (void)
{
#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  XPutBackEvent(G.D,&GEvent);
}

void mx_wakeup (Window window_id, int mode, int value)
{
  int_4 vals[5];
  int nvals=0, mesg;

#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  switch (mode) {
    case 0:
      mesg = L_Learn_Msg;
      break;
    case 1:
    case -1:
      mesg = (mode> 0) ? L_SendButtonPress : L_SendButtonRelease;
      vals[0] = value;
      vals[1] = Mx->xpos;
      vals[2] = Mx->ypos;
      nvals = 3;
      break;
    case 2:
    case -2:
      mesg = (mode> 0) ? L_SendKeyPress : L_SendKeyRelease;
      vals[0] = value;
      vals[1] = Mx->xpos;
      vals[2] = Mx->ypos;
      vals[3] = 0;
      nvals = 4;
      break;
    case 3:
      mesg = L_SendMotion;
      vals[0] = Mx->xpos;
      vals[1] = Mx->ypos;
      nvals = 2;
      break;
    case 4:
    case -4:
      mesg = L_UserEvents_Msg;
      vals[0] = (mode > 0) ? 1 : 0;
      nvals = 1;
      break;
    case 5:
      mesg = L_Deiconify_Msg;;
      break;
    case -5:
      mesg = L_Iconify_Msg;;
      break;
    case 6:
      mesg = L_Focus_Msg;;
      break;
    default:
      return;

  }

  mx_client_event(window_id, mesg, vals, nvals);
}

/*************************** CLIENT_EVENT **************************/

bool mx_client_event (Window window_id, int mesg,
			const int_4 value[], int nvalues) 
{
  Window sw_id;
  XEvent cmevent;
  char error[80];
  int i, ls, status = FALSE;

#if _HEADLESS
  if (Mx->headless == 1 ) return FALSE;
#endif

  if (!window_id) sw_id = G.W; /* MIB was window_id==0 */
  else sw_id = window_id;
  if (!sw_id) return FALSE;    /* MIB was sw_id==0 */ 

  cmevent.type = ClientMessage;
  cmevent.xclient.display = G.D;
  cmevent.xclient.window = sw_id;
  cmevent.xclient.message_type = GXMidasMsgAtom;
  cmevent.xclient.format = 32;
  cmevent.xclient.data.l[0] = mesg;
  for (i=1; i<5; i++)
    cmevent.xclient.data.l[i] = (i<=nvalues) ? value[i-1] : 0;

  /* In the special case where we want iconify/deiconify/move or give
   * focus to ourself, do  not send an event, just do it.  The caller
   * does not expect that they should have to call MX$EVENT for it to
   * take place. If we're sending to another window, send the event.
   */
  if (sw_id == G.W) switch (mesg) {
    case L_Deiconify_Msg:
    case L_Iconify_Msg:
    case L_Move_Msg:
    case L_Resize_Msg:
    case L_MoveResize_Msg:
    case L_Focus_Msg:
      (void) client_msg(&cmevent);
      status = TRUE;
      break;
  }
  /* In the case where we simply want to Iconify or Deiconify a window
   * we don't need to know anything special about the window except for
   * its window id.  Rather than sending it an event, we can simply tell
   * the window from right here.  This ensures that windows not listening
   * to XMIDAS client events can at least still be iconified/deiconified
   * as long as they publish their window id to us.  In the case of all
   * other events we need to know more about the window (i.e. screen
   * width/height, the dimensions of the window itself, etc.) and we can't
   * get these values from here (I've tried) so we have to send the event
   * over to that window and let them do it.
   */
  else switch (mesg) {
    case L_Deiconify_Msg:
      XMapWindow(G.D, sw_id);
      XRaiseWindow(G.D, sw_id);
      status = TRUE;
      break;
    case L_Iconify_Msg:
      XIconifyWindow(G.D, sw_id, G.S);	/* G.S is OUR screen, not theirs */
      status = TRUE;			/* but this still works */
      break;
    case L_Focus_Msg:
      XSetInputFocus (G.D, sw_id, RevertToParent, CurrentTime);
      break;
  }

  if (!status) {
    GErrorVerbose = FALSE;
    if (XSendEvent(G.D, sw_id, False, 0, &cmevent)) status = TRUE;
    else {
      status = FALSE;
      ls = XSPRINTF (error, "Unable to send message to window %d", sw_id);
      m_warning (error);
    }
    GErrorVerbose = TRUE;
  }

  XFlush(G.D);

  return status;

}

bool mx_learn (Window wid, int_4 *left, int_4 *top,
               int_4 *width, int_4 *height)
{

  Window root, child;
  int_4 data[1];
  XEvent e;

#if _HEADLESS
  if (Mx->headless == 1 ) return FALSE;
#endif

  if (wid == 0) wid = G.W;
  data[0] = G.W;	/* tells other window where to send response */

  if (mx_client_event(wid, L_Learn_Msg, data, 1)) {
    while (True) {
      if (XCheckIfEvent(G.D, &GEvent, (Bool(*)())learn_match, NULL)) {
	if (GEvent.xclient.data.l[0]==L_Teach_Msg) {
	  *left   = GEvent.xclient.data.l[1];
	  *top    = GEvent.xclient.data.l[2];
	  *width  = GEvent.xclient.data.l[3];
	  *height = GEvent.xclient.data.l[4];
	  return TRUE;
	}
	(void) client_msg(&GEvent);
      }
      else if (XM_BREAK) return FALSE;
      else m_pause (POLLTIME);
    }
  }
  else return FALSE;
}

Bool learn_match (const Display *D, const XEvent *E, const int *junk)
{
  XEvent e;
  if (E->xany.window == G.W && E->xany.type == ClientMessage && 
      E->xclient.message_type == GXMidasMsgAtom && E->xclient.format==32) {
    switch (E->xclient.data.l[0]) {
      case L_Teach_Msg:
	return TRUE;
      case L_Learn_Msg:
	return (E->xclient.data.l[1] == G.W);
    }
  }
  return False;
}


/*************************** CLEAR AREA **************************/

void mx_clear_area (int x, int y, int w, int h)
{
#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  if (w>=0) XClearArea (G.D, G.W, x, y, w, h, FALSE);
  else XFillRectangle (G.D, G.W, G.gct, x, y, -w, abs(h));
  if (f_PostScript)
    fprintf(f_PostScript,
      "GS bg %d %d %d %d R fill GR ", w, h, x, y);
}


/*************************** CLEAR WINDOW **************************/

void mx_clear_window (void)
{
#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  XClearWindow (G.D, G.W);
  if (f_PostScript)
    fprintf(f_PostScript,
      "GS bg %d %d %d %d R fill GR ",
                        Mx->width, Mx->height, 0, 0);
}


/*************************** DRAW RECTANGLE **************************/

void mx_draw_box (int ic, int x, int y, int w, int h)
{
#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  if (f_PostScript) mx_psdraw_rect(ic, x, y, w, h, "S");
  if (ic!=L_XORColor) {
    SETGCGFOREGROUND (ic);
    DRAWRECTANGLE (G.gcg, x, y, w, h);
  }
  else {
    if (w==0 || h==0) XDrawLine (G.D, G.W, G.gcr, x, y, x+w, y+h);
    else DRAWRECTANGLE (G.gcr, x, y, w, h);
    if (Mx->rmode) DRAWRECTANGLE (G.gcr2, x-1, y-1, w+2, h+2);
  }
  XFlush (G.D);
}


/*************************** FILL RECTANGLE **************************/

void mx_fill_box (int ic, int x, int y, int w, int h)
{
#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  if (f_PostScript) mx_psdraw_rect(ic, x, y, w, h, "fill");
  if (ic==L_XORColor) {
    if (Mx->rmode) FILLRECTANGLE (G.gcr2, x, y, w, h);
    else           FILLRECTANGLE (G.gcr, x, y, w, h);
  }
  else {
    SETGCGFOREGROUND (ic);
    FILLRECTANGLE (G.gcg, x, y, w, h);
  }
  XFlush (G.D);
}


/*************************** CLIP **************************/

void mx_clip (int x, int y, int w, int h)
{
  XRectangle rect;

#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  if (f_PostScript) mx_psclip(1, x, y, w, h);

  if (w==0 && h==0)
    XSetClipMask(G.D, G.gcg, None);             /* Unset clip */
  else {
    rect.x = x;
    rect.y = y;                         /* Set clip */
    rect.width = w;
    rect.height = h;

    XSetClipRectangles (G.D, G.gcg, 0, 0, &rect, 1, Unsorted);
  }
}


/*************************** RCLIP **************************/

void mx_rclip (int x, int y, int w, int h)
{
  XRectangle rect;

#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  if (f_PostScript) mx_psclip(L_XORColor, x, y, w, h);

  if (w==0 && h==0) {
    XSetClipMask (G.D, G.gcr, None);            /* Unset clip */
    XSetClipMask (G.D, G.gcr2, None);
  }
  else {
    rect.x = x;
    rect.y = y;
    rect.width = w;
    rect.height = h;

    XSetClipRectangles (G.D, G.gcr, 0, 0, &rect, 1, Unsorted);
    XSetClipRectangles (G.D, G.gcr2, 0, 0, &rect, 1, Unsorted);
  }
}


/*************************** RUBBER DOT **************************/

void mx_rubberdot (int x, int y, int rr)
{
  int r, d;
  XPoint pix[1];

#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  pix[0].x = x; pix[0].y = y;
  if (rr > 0)
    mx_draw_symbols(L_XORColor, pix, 1, L_CircleSymbol, rr);
  else if (rr < 0)
    mx_draw_symbols(L_XORColor, pix, 1, L_SquareSymbol, -rr);
  else 
    mx_draw_symbols(L_XORColor, pix, 1, L_PixelSymbol, 0);
  XFlush (G.D);
}


/*************************** RUBBER LINE **************************/

void mx_rubberline (int x1, int y1, int x2, int y2)
{
  float slope;
  XPoint pix[2];

#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  pix[0].x = x1; pix[0].y = y1;
  pix[1].x = x2; pix[1].y = y2;
  mx_draw_lines(L_XORColor, pix, 2);
  XFlush (G.D);
}

/*************************** RUBBER BOX **************************/

void mx_rubberbox (int_4 *x1, int_4 *y1, int_4 *x2, int_4 *y2, void (*func)())
{
#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  mx_warpbox (x1, y1, x2, y2, 0, 0, Mx->width, Mx->height, func, 0);
}


/*************************** RUBBER WARP BOX **************************/

void mx_rubberwarpbox (int_4 *x1, int_4 *y1, int_4 *x2, int_4 *y2,
                       void (*func)())
{
#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  mx_warpbox (x1, y1, x2, y2, 0, 0, Mx->width, Mx->height, func, 1);
}

/*************************** DRAG BOX **************************/

void mx_dragbox (int_4 *x, int_4 *y, int w, int h,
                int xmin, int xmax, int ymin, int ymax, void (*func)())
{
  int_4 x2, y2;

#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  x2 = *x + w;
  y2 = *y + h;
  mx_warpbox (x, y, &x2, &y2, xmin, ymin, xmax, ymax, func, 2);
}

/*************************** WARP BOX **************************/

void mx_warpbox (int_4 *x1, int_4 *y1, int_4 *x2, int_4 *y2,
        int xmin, int ymin, int xmax, int ymax, void (*func)(), int mode)
{
  int  x, y, w, h, xd=0, yd=0;
  bool done=FALSE, xwarp=TRUE, ywarp=TRUE;
  int *temp;
/*
   C treats function pointers in a funky way so set the address into a
   legit void pointer and test the value for NULL (this indicates no
   function to call)
*/
#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  temp = (int *) func;

  if (*x1 > *x2) {x = *x1; *x1 = *x2; *x2 = x;}
  if (*y1 > *y2) {y = *y1; *y1 = *y2; *y2 = y;}

  switch (mode) {
    case 0: *x1 = *x2 = Mx->xpos; *y1 = *y2 = Mx->ypos; break;
    case 1: xwarp = FALSE; ywarp = FALSE;               break;
    case 2: xd = Mx->xpos - *x1; yd = Mx->ypos - *y1;
            w = abs (*x2-*x1); h = abs (*y2-*y1);       break;
  }

  if (mode) SETLINE (G.gcr, 0, LineOnOffDash);
  if (mode && Mx->rmode) SETLINE (G.gcr2, 0, LineOnOffDash);

  while (!done) {
    if (mode==2) { x = *x1; y = *y1; }
    else {
      x = min (*x1,*x2); y = min (*y1,*y2);
      w = abs (*x2-*x1); h = abs (*y2-*y1);
    }
    mx_draw_box (L_XORColor, x, y, w, h);
    geteventw();
    switch (GEvent.type) {
      case MotionNotify:
        Mx->xpos = GEvent.xmotion.x; Mx->ypos = GEvent.xmotion.y;
        if (!xwarp && (Mx->xpos < *x1 || Mx->xpos > *x2))
          { xwarp = TRUE; if (Mx->xpos > *x2) *x2 = *x1; }
        if (!ywarp && (Mx->ypos < *y1 || Mx->ypos > *y2))
          { ywarp = TRUE; if (Mx->ypos > *y2) *y2 = *y1; }
        if (temp && *temp) (*func) ();
	break;
      case KeyPress:
        (void)mx_pointer ();
        switch (GKeysym) {
         case XK_Return: Mx->button_release = Mx->button_press;
         case XK_Q: case XK_q:  done = TRUE;
        }
	break;
      case ButtonRelease:
        switch (GEvent.xbutton.button){
          case Button1: Mx->button_release = 1; break;
          case Button2: Mx->button_release = 2; break;
          case Button3: Mx->button_release = 3;
        }
	done = TRUE;
	break;
      case Expose: case ConfigureNotify: case ClientMessage:
        XPutBackEvent (G.D, &GEvent);
	done = TRUE;
	break;
    }
    mx_draw_box (L_XORColor, x, y, w, h);
    if (xwarp) *x1 = bound(Mx->xpos - xd, xmin, xmax);
    if (ywarp) *y1 = bound(Mx->ypos - yd, ymin, ymax);
  }

  if (mode) SETLINE (G.gcr, 0, LineSolid);
  if (mode && Mx->rmode) SETLINE (G.gcr2, 0, LineSolid);

  if (mode != 2) {
    if (*x1 > *x2) {x = *x1; *x1 = *x2; *x2 = x;}
    if (*y1 > *y2) {y = *y1; *y1 = *y2; *y2 = y;}
  }
}


/*************************** DRAW MODE **************************/

void mx_draw_mode (int mode)
{
  int width;

#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  switch (mode) {

    case L_solid:
      SETLINE (G.gct, 0, LineSolid);
      break;

    case L_dashed:
      SETLINE (G.gct, 0, LineOnOffDash);
      break;

    case L_RModeOff:
      Mx->rmode = FALSE;
      break;

    case L_RModeOn:
      XSetForeground (G.D, G.gcr2, G.cells/2);
      Mx->rmode = TRUE;
      break;

    default:
      width = abs(mode);
      if (width<800) {
        if (width==1) width = 0;
        if (mode<0) SETLINE (G.gcg, width, LineOnOffDash);
        else SETLINE (G.gcg, width, LineSolid);
      }
      else if (width>=1000 && width<1100) {
        GBorder = width - 1000;
      }
  }

}


/*************************** DRAW LINE **************************/

void mx_draw_line (int ic, int x1, int y1, int x2, int y2)
{
  XPoint pix[2];

#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  pix[0].x = x1; pix[0].y = y1;
  pix[1].x = x2; pix[1].y = y2;
  mx_draw_lines(ic, pix, 2);
}


/************************* FILL POLY **************************/

void mx_fill_poly (int ic, const XPoint pix[], int npix, int mode)
{
  int shape;

#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  SETGCGFOREGROUND (ic);

  if (mode == 1) shape = Convex;
  else if (mode == -1) shape = Nonconvex;
  else shape = Complex;

  if (npix>2) {
    if (ic != L_XORColor)
      XFillPolygon (G.D,G.W,G.gcg,(XPoint *)pix,npix,shape,CoordModeOrigin);
    else if (Mx->rmode)
      XFillPolygon (G.D,G.W,G.gcr2,(XPoint *)pix,npix,shape,CoordModeOrigin);
    else
      XFillPolygon (G.D,G.W,G.gcr,(XPoint *)pix,npix,shape,CoordModeOrigin);
    if (f_PostScript) mx_psdraw_vector(ic, pix, npix, "CP fill");
  }
}

/*********************** COLORTEXT *************************/

void mx_colortext (int ic, int x, int y, const char *text, int length)
{
  int fg=ic, bg=0;

#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  if (ic==0) { fg = L_BGColor; bg = L_FGColor; }
  else if (fg < L_NoColor || fg > L_color) {
    bg = floor ((float)(ic - L_NoColor) / 1000.0);
    fg -= 1000*bg;
    if (fg <= L_NoColor || fg > L_color) fg = L_FGColor;
    if (bg <= L_NoColor || bg > L_color) bg = L_BGColor;
  }

  if (fg == L_XORColor) {
    if (Mx->rmode) XDrawString (G.D, G.W, G.gcr2, x, y, text, length);
    else XDrawString (G.D, G.W, G.gcr, x, y, text, length);
    if (f_PostScript) mx_pstext(L_XORColor, x, y, text, length, FALSE);
  }
  else {
    if (fg <= L_color) { SETGCGFOREGROUND (fg) }
    else XSetForeground(G.D, G.gcg, Mx->pixel[(Mx->clast=fg)-1]);
    if (bg == L_XORColor) {
      XDrawString (G.D, G.W, G.gcg, x, y, text, length);
      if (f_PostScript) mx_pstext(1, x, y, text, length, FALSE);
    }
    else {
      if (bg != L_BGColor) XSetBackground(G.D, G.gcg, Mx->pixel[bg-1]);
      XDrawImageString (G.D, G.W, G.gcg, x, y, text, length);
      if (f_PostScript) mx_pstext(1, x, y, text, length, TRUE);
      if (bg != L_BGColor) XSetBackground(G.D, G.gcg, Mx->black);
    }
  }
}

/*************************** TEXT **************************/

void mx_text (int x, int y, const char *text, int length)
{
#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  if (length>0) XDrawImageString (G.D, G.W, G.gct, x, y, text, length);
  if (f_PostScript) mx_pstext(L_FGColor, x, y, text, length, TRUE);
}

/************************ RUBBERTEXT ***********************/

void mx_rubbertext (int x, int y, const char *text, int length)
{
#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  if (length>0) {
    if (Mx->rmode) XDrawString (G.D, G.W, G.gcr2, x, y, text, length);
    else XDrawString (G.D, G.W, G.gcr, x, y, text, length);
  }
  if (f_PostScript) mx_pstext(L_XORColor, x, y, text, length, FALSE);
}

/************************ TEXT LINE ************************/

void mx_textline (int x1, int y1, int x2, int y2)
{
#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  XDrawLine (G.D, G.W, G.gct, x1, y1, x2, y2);
  if (f_PostScript) mx_pstextline(x1, y1, x2, y2);
}

/*************************** GET IMAGE **************************/

void mx_get_image (int_4 data[], int xc, int yc, int w, int h, int ncolors)
{
  int i, m, x, y;
  unsigned int k;
  unsigned char indx[256];
  XImage *getimage_ptr=NULL;
  union p_struct {
    unsigned long ul;
    unsigned char b0,b1,b2,b3;
  } v,*vp;

#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  GErrorVerbose = FALSE;
  if (xc>=0 && yc>=0 && xc+w<=Mx->width && yc+h<=Mx->height)
    getimage_ptr = XGetImage (G.D, G.W, xc, yc, w, h, AllPlanes, ZPixmap);
  GErrorVerbose = TRUE;

    /* RDA: TrueColor displays will have long pixel values - need to hash
     *      the 3-byte word into a byte - avoid huge indx[] tables
     */
  if (getimage_ptr==NULL) for (i=0,m=w*h; i<m; i++) data[i] = 0;
  else {
    for (i=0; i<256; i++) indx[i] = 1;
    for (i=1; i<ncolors; i++) {
      k = Mx->pixel[i];
      indx[(k ^ (k>>8) ^ (k>>16)) & 0x0FF] = i+1;
    }

    if (w>1) for (i=y=0; y<h; y++)
      for (x=0; x<w; x++) {
        k = XGetPixel(getimage_ptr,x,y);
        data[i++] = indx[(k ^ (k>>8) ^ (k>>16)) & 0x0FF];
      }
    else 
      for (i=y=0; y<h; y++) {
	k = XGetPixel(getimage_ptr,0,y);
	data[i++] = indx[(k ^ (k>>8) ^ (k>>16)) & 0x0FF];
      }

    XDestroyImage (getimage_ptr);
  }
}

#ifdef SHIFTAREA
void mx_shift_area (int x, int y, int w, int h, int ny, int level, bool clear)
{
  int ys, hs;

  if (ny==0) return;
  if (level==Mx->zlevel) {              /* writing to top zoom level */
    if (abs(ny) >= h) {
      if (ny>0) XClearArea (G.D, G.W, x, y, w, h, FALSE);
    }
    else {
      ys = bound (y-ny, y, y+h); hs = h - abs(ny);
      XCopyArea (G.D, G.W, G.W, G.gcg, x, ys, w, hs, x, ys+ny);
      if (clear) {
        if (ny>0) XClearArea (G.D, G.W, x, y, w, ny, FALSE);
        else if (ny<0) XClearArea (G.D, G.W, x, y+h-ny, w, ny, FALSE);
      }
    }
  }
  else if (level <= 0) {
    y1 = Mx->zoom[0].y;  y2 = Mx->zoom[0].y + Mx->zoom[0].h;
    x1 = Mx->zoom[0].x;  x2 = Mx->zoom[0].x + Mx->zoom[0].w;
    t = min(y+h, y1) - y + ny;
    m = min(y+h, y2+ny) - max(y,y2);
    b = y+h - min(y+h, y2+ny);
    l = max (0, x1 - x); r = max (0, x+w-x2);
    tp = max (0, min (-ny, y2 - y1));
    mp = max (0, y2 - y1 + ny);
    bp =
    for (i=0; i<7; i++) {
      switch (i) {
        case 0: if (t>0) XCopyArea (G.D,G.W,G.W,G.gcg,x,y+ny,w,t,x,y);
      }
    }
  }
}
#endif

/*************************** PUTLAT IMAGE **************************/

void mx_putlat_image (const char data[], int nx, int ny,
                        int nex, int ney, int xd, int yd, int level)
{
  int size, chunk, i, j, x, y, z;
  int x1, x2, y1, y2, xp, yp, bw=4;
  int_u4 *pixc=&(Mx->pixel[0]) - 1; /* correct for Fortran 1-based arrays */
  int_u4 rev_pixel[L_color]; /* for TrueColor, may have to byte-swap pixels */
  int bytes_per_pixel = (Mx->bits_per_pixel + 7) / 8;

#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  size = ny * ney;
  chunk = bytes_per_pixel * size * nx;
  G.img_ptr->width = nx;
  G.img_ptr->height = size;
  G.img_ptr->data   = (char *)data;
  G.img_ptr->bytes_per_line = nx * bytes_per_pixel;

  /* RDA: If server byte order reversed, generate a reversed pixelmap */
  if (G.depth > 8 ){
    union {int i; char c[4];} test;
    test.i = 1;
    if ( ImageByteOrder(G.D) == test.c[0] ) {
      if (Mx->bits_per_pixel > 16) {
        for (i=0; i<G.cells; i++) {
          register unsigned char *a=(unsigned char *)(&Mx->pixel[i]);
          register unsigned char *b=(unsigned char *)(&rev_pixel[i]);
          b[0] = a[3];
          b[1] = a[2];
          b[2] = a[1];
          b[3] = a[0];
        }
      } else {
        for (i=0; i<G.cells; i++) {
          register unsigned char *a=(unsigned char *)(&Mx->pixel[i]);
          register unsigned char *b=(unsigned char *)(&rev_pixel[i]);
          b[0] = a[1];
          b[1] = a[0];
          b[2] = a[3];
          b[3] = a[2];
        }
      }
      pixc = &rev_pixel[0] - 1;    /* point to reversed pixels */
    }
  }

  if (ney > 1) {		/* expand data */
    if (chunk>GExbuf_size) {
      if (GExbuf_size>0) free(GExbuf_ptr);
      GExbuf_size = chunk;
      GExbuf_ptr = (char *) malloc(GExbuf_size);
    }
    i = 0; j = 0;
    /* RDA: replicated sections based on bytes_per_pixel for speed */
    if (bytes_per_pixel == 1 ) {
      int_u1 *Gptr = (int_u1 *) GExbuf_ptr;
      for (y=0;y<ny;y++) {
	for (z=0;z<ney;z++) {
	  for (x=0;x<nx;x++) Gptr[i++] = (int_u1) pixc[data[j++]];
	  j -= nx;
	}
	j += nx;
      }
    } else if (bytes_per_pixel == 4 ) {
      int_u4 *Gptr = (int_u4 *) GExbuf_ptr;
      for (y=0;y<ny;y++) {
	for (z=0;z<ney;z++) {
	  for (x=0;x<nx;x++) Gptr[i++] = (int_u4) pixc[data[j++]];
	  j -= nx;
	}
	j += nx;
      }
    } else {			/* bytes_per_pixel == 2 */
      int_u2 *Gptr = (int_u2 *) GExbuf_ptr;
      for (y=0;y<ny;y++) {
	for (z=0;z<ney;z++) {
	  for (x=0;x<nx;x++) Gptr[i++] = (int_u2) pixc[data[j++]];
	  j -= nx;
	}
	j += nx;
      }
    }

    G.img_ptr->data = GExbuf_ptr;
  }

  xp = xd; yp = yd;

  if (level==Mx->zlevel) {              /* writing to top zoom level */
    if (Mx->zlevel!=0) {
      xp += (Mx->zoom[Mx->zlevel-1].x + bw);
      yp += (Mx->zoom[Mx->zlevel-1].y + bw);
    }
    if (nex==1) {
      XPutImage (G.D, G.W, G.gcg, G.img_ptr, 0, 0, xp,yp, nx, size); }
    else {                      /* stretch buffer horizontally onto window */
      for (x=0; x< nx; x++) {
        XPutImage (G.D, G.W, G.gcg, G.img_ptr, x, 0, xp++, yp, 1, size);
        for (i=1; i*2 < nex; i += i ) {
        XCopyArea (G.D, G.W, G.W, G.gcg, xp-i, yp, i, size, xp, yp); xp+=i;}
        i = nex-i; XCopyArea (G.D,G.W,G.W,G.gcg,xp-i,yp,i,size,xp,yp); xp+=i;
      }
    }
  }                             /* writing to bottom level with zooms on top */
  else if (level == 0) {
    x1 = Mx->zoom[0].x;  x2 = Mx->zoom[0].x + Mx->zoom[0].w;
    y1 = Mx->zoom[0].y;  y2 = Mx->zoom[0].y + Mx->zoom[0].h;
    for (x=0; x<nx; x++,xp++) {
      if (xp < x1 || xp >= x2) {
        XPutImage (G.D, G.W, G.gcg, G.img_ptr, x, 0, xp,yp, 1, size);
      }
      else {                    /* writing to zoom BG pixmap */
        z = y1 - yp;
        if (z>0) XPutImage (G.D, G.W, G.gcg, G.img_ptr, x, 0, xp,yp, 1, z);
        y = max(0,-z); j = min(yp+size,y2) - y1 - y;
        if (j>0) XPutImage(G.D,Mx->zoom[0].ip,G.gcg,G.img_ptr,x,z,xp-x1,y,1,j);
        z = size - j - z;
        if (z>0) XPutImage (G.D,G.W,G.gcg,G.img_ptr,x,size-z,y2,xp,1,z);
      }
    }
  }
  else { m_warning ("Cannot put image to intermediate zoom level"); }
}


/******************** TEXT ANNOTE DRAW **************************/

void mx_tdraw (XFANNOTATION *tn)
{
  int xt, yt;

#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  if (tn->f_id == 0) mx_tfont (tn, L_LoadOneFont); /* Load font */
  XSetFont (G.D, G.gcta, tn->f_id);

  if (tn->h == 0) mx_tfont (tn, L_GetTextExtents); /*  Figure text size */

  if (tn->color[0]==' ') XSetForeground (G.D, G.gcta, Mx->white);
  else {
    if (tn->pixel == L_NotSet) mx_tfont (tn, L_SetPixel); /* Set color */
    XSetForeground (G.D, G.gcta, tn->pixel);
  }

  if (tn->mode & L_XFANoGravity) {
    xt = tn->x;
    yt = tn->y;
  }
  else {
    xt = floor(0.5 + Mx->width * tn->x / 1000.0);
    yt = floor(0.5 + Mx->height * tn->y / 1000.0);
  }

  if (tn->mode&L_XFAMidAnchor) xt -= tn->w / 2;
  else if (tn->mode&L_XFAEndAnchor) xt -= tn->w;

  tn->xc = xt;
  tn->yc = yt - tn->a;

  if (!(tn->mode & L_XFANoBS)) {
    if (tn->p_id != 0) XFreePixmap (G.D, tn->p_id);
    tn->p_id = XCreatePixmap (G.D, G.W, tn->w+10, tn->h+10, G.depth);
    XCopyArea (G.D,G.W,tn->p_id,G.gct,tn->xc-5,tn->yc-5,tn->w+10,tn->h+10,0,0);
  }

  if (tn->mode & L_XFAFill) {
    XDrawImageString (G.D, G.W, G.gcta, xt, yt, tn->text, tn->size);
    if (f_PostScript) mx_pstdraw(tn, xt, yt, TRUE);
  }
  else  {
    XDrawString (G.D, G.W, G.gcta, xt, yt, tn->text, tn->size);
    if (f_PostScript) mx_pstdraw(tn, xt, yt, FALSE);
  }
}


/******************** TEXT ANNOTE SELECT **************************/

void mx_tselect (XFANNOTATION *tn, int task)
{
  int xc, yc;

#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  xc = tn->xc - 5;
  yc = tn->yc - 5;

  switch (task) {

    /* select text, draw a rectangle around it */
    case L_DeselectText: case L_SelectText:
      mx_draw_box (L_XORColor, xc-2, yc-2, tn->w+14, tn->h+14);
      break;

    /* remove text from current location and free bg pixmap */
    /* backing store must be turned on in order to do this */
    case L_RemoveText:
      if (L_XFANoBS & tn->mode) return;
      if (tn->p_id != 0) {
        XCopyArea (G.D,tn->p_id,G.W,G.gct, 0,0, tn->w+10,tn->h+10, xc,yc);
        XFreePixmap (G.D, tn->p_id);
        tn->p_id = 0;
      }
      break;

    /* move text to a different location */
    /* backing store must be turned on in order to do this */
    case L_MoveText:
      if (L_XFANoBS & tn->mode) return;
      XCopyArea (G.D,tn->p_id,G.W,G.gct, 0,0, tn->w+10,tn->h+10, xc,yc);
      if (L_XFANoGravity & tn->mode) {
        tn->x = Mx->xpos;
        tn->y = Mx->ypos;
      }
      else {
        tn->x = floor (0.5 + 1000.0 * Mx->xpos / Mx->width);
        tn->y = floor (0.5 + 1000.0 * Mx->ypos / Mx->height);
      }
      mx_tdraw (tn);

  }
}

/********************** TEXT ANNOTATE FONT **************************/

/* front end for quicksort for fontlist */
int compare (const void *s, const void *t)
{
  return (strcmp (*((char **) s)+7, *((char **) t)+7));
}

/* front end for quicksort for fontlist point sizes */
int scompare (const void *s, const void *t)
{
  return (atoi (strstr (*((char **) s)+7, "--") + 2) -
          atoi (strstr (*((char **) t)+7, "--") + 2));
}


void mx_tfont (XFANNOTATION *tn, int task)
{
  char s[80], font[129];
  int i, k, cd, cf, i1, i2;
  int dir, asc, desc;
  char **fontname;
  static char **xfontnames=NULL, **fontnames=NULL;
  static int  nfonts=0;
  XCharStruct overall;
  XColor colr;

#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  switch (task) {

    case L_GetTextExtents:              /* return text width and height */
      if (tn->f_id == 0) mx_tfont (tn, L_LoadOneFont);
      if (tn->size > 0) {
	XQueryTextExtents (G.D, tn->f_id, tn->text, tn->size, &dir,
	  &asc, &desc, &overall);
	tn->w = overall.width;
	tn->a = asc;
	tn->h = asc + desc;
      }
      else tn->w = tn->a = tn->h = 1;
      break;

    case L_SetPixel:                    /* locate and set pixel color cell */
      if (tn->color[0] == ' ') tn->pixel = Mx->white;
      else {  
	/*
	 * We need to call mx_colorlab to load our color, but mx_colorlab
	 * always loads the color into our Mx->pixel array or our system
	 * colors table.  One of the entries is unused, that of the L_XORColor
	 * slot (which never uses colors).  We'll use this slot so all others
	 * are left undisturbed.  The L_NoColor color id tells mx_colorlab
	 * to load the pixel into Mx->tpix if found, and if not load in
	 * Mx->white.
	 */
        mx_colorlab(L_NoColor, tn->color, sizeof(tn->color));
        tn->pixel = Mx->tpix;   /* copy pixel value into font struct */
      }
      break;

    case L_FreeFontPtr:                 /* free pointer to font list */
      if (tn->f_id) XFreeFontNames ((char **)tn->f_id);
      break;

    case L_GetFontPtr:                  /* return pointer to font list */
      strf2c (tn->font, 128, font, 129, F2C_FLAG_TRIM);
      tn->f_id = (int) XListFonts (G.D, font, tn->size, &i);
      qsort ((char **)tn->f_id,i,sizeof(char *),(CMP_FUNC) compare);
      tn->size = i;
      break;

    case L_LoadAllFonts:                /*  load font list into local var */
      xfontnames = XListFonts (G.D,"-Adobe-*-*-*-*-*-75-75-*", 200, &nfonts);
      if (xfontnames) {
        if (nfonts<1) { XFreeFontNames (xfontnames); xfontnames = NULL; }
        /*
        The order of the string pointers in the xfontnames array is important
        to XFreeFontNames for accounting purposes.  We cannot, therefore,
        change this order without risking messing up XFreeFontNames.  Hence,
        we will sort the pointers in our own malloced space.
        */
        else {
          i = nfonts * sizeof (char *);
          fontnames = (char **) malloc (i);
          m_move (xfontnames, fontnames, i);
          qsort (fontnames,nfonts,sizeof(char *),(CMP_FUNC) compare);
        }
      }
      break;

    case L_FreeFonts:                   /*  free font list  */
      if (xfontnames) { free (fontnames); XFreeFontNames (xfontnames); }
      xfontnames = fontnames = NULL;
      nfonts = 0;
      break;

    case L_LoadOneFont:                 /*  load font id into tn */
      strf2c (tn->font, 128, font, 129, F2C_FLAG_TRIM);
      fontname = XListFonts (G.D, font, 1, &i);
      tn->f_id = XLoadFont (G.D, fontname[0]);
      strc2f (fontname[0], -1, tn->font, 128, C2F_FILL);
      XFreeFontNames (fontname);
      tn->h = tn->w = tn->a = 0;
      break;

    case L_FontAttributes:              /*  font attributes menus  */
      if (xfontnames==NULL) mx_tfont (tn, L_LoadAllFonts);
      if (xfontnames==NULL)
        { mx_message ("No fonts available to list.", 27, 1.0); return; }
      if (tn->f_id == 0) mx_tfont(tn, L_LoadOneFont);
      strf2c (tn->font, 128, font, 129, F2C_FLAG_TRIM);

      i1 = 0;
      i2 = nfonts;
      cd = cf = 7;
      list_menu ("~FONT FAMILY;", font, fontnames, &cd, &cf, &i1, &i2, "-");
      list_menu ("~FONT STYLE;", font, fontnames, &cd, &cf, &i1, &i2, "--");
      qsort(fontnames+i1, i2-i1, sizeof(char *), (CMP_FUNC) scompare);
      list_menu ("~FONT SIZE;", font, fontnames, &cd, &cf, &i1, &i2, "-");

      strcpy(font,fontnames[i1]);
      tn->f_id = XLoadFont(G.D,font); tn->h = 0;
      strc2f (font, -1, tn->font, 128, C2F_FILL);
      break;

    case L_ChangeBG:                    /*  change bg mode of tn text  */
      if (tn->mode & L_XFAFill) i = 2; else i = 1;
      k = mx_menu (i,"~BACKGROUND;No Fill,Fill",24);
      if (k==1) tn->mode &= ~L_XFAFill; else tn->mode |= L_XFAFill;
      break;

    case L_ChangeColor:                 /*  change color of tn text  */
      Mx->tpix = tn->pixel;
      strncpy(s, tn->color, sizeof(tn->color));/* try to load new color */
      mx_prompt("Text Color: ", s, 12, sizeof(tn->color));
      mx_colorlab(L_XORColor, s, sizeof(tn->color));
      if (Mx->tpix != tn->pixel) { /* if changed, change tn name */
        tn->pixel = Mx->tpix;
        strncpy(tn->color, s, sizeof(tn->color));
      }
      break;

    case L_ChangeAnchor:                /*  change anchor pt of tn text  */
      if (tn->mode & L_XFAMidAnchor) i = 2;
      else if (tn->mode & L_XFAEndAnchor) i = 3;
      else i = 1;
      k = mx_menu (i, "~ANCHOR POINT;Beginning,Center,End",34);
      if (k != i) {
        if (tn->mode & L_XFANoGravity) tn->x += floor(0.5 + tn->w * (k-i) / 2);
        else tn->x += floor(0.5 + tn->w * (k-i) * 500.0 / Mx->width);
        if (k==2) tn->mode |= L_XFAMidAnchor; else tn->mode &= ~L_XFAMidAnchor;
        if (k==3) tn->mode |= L_XFAEndAnchor; else tn->mode &= ~L_XFAEndAnchor;
      }
      break;
  }

}

void list_menu (const char *title, const char *ditem,
		char **list, int *dcoff, int *lcoff,
		int *item1, int *item2, const char *endchar)
{
  int i, j, j2, ms;
  int ld, ll, lm, lc, fni[31];
  char *s1, *s2, menu[160];

  strcpy (menu,title);

  lm = strlen(menu);		/* length of menu string */
  s1 = (char *)ditem + *dcoff;	/* default item substring pointer */
  ld = strstr(s1,endchar) - s1;	/* length of default substring */
  lc = *lcoff;			/* char offset in fonts list */
  ms = 0;			/* default menu item */
  j2 = *item2;			/* start of next matched font group */

  for (i=0,j=(*item1); i<30 && j<j2; ++i) {
    s2 = (char *)list[j] + lc;
    ll = strstr(s2,endchar) - s2;
    strncpy(menu+lm, s2, ll);
    menu [lm+ll] = ',';
    lm += ll + 1;
    if (ms==0 && ll==ld && strncmp(s1,s2,ld)==0) ms = i+1;
    fni[i] = j;
    while (++j<j2 && !strncmp(s2,list[j]+lc,ll));
  }
  fni[i] = j;

  i = mx_menu (max(1,ms),menu,lm) - 1;

  /* Output the new range of valid items due to this selection */
  *item1 = fni[i];
  *item2 = fni[i+1];
  s2 = (char *)list[*item1] + lc;

  /* Output the new character offsets in the default and list */
  *lcoff += strstr(s2,endchar) - s2 + strlen(endchar);
  *dcoff += ld + strlen(endchar);

}

/*************************** POINTER **************************/

bool mx_pointer (void)
{
  int i, r_x, r_y, x, y;
  Window root, child;
  unsigned int keys;

#if _HEADLESS
  if (Mx->headless == 1 ) return TRUE;
#endif

  (void)lookupstring (&GEvent,GKey,&GKeysym);
  if (Mx->no_arrow_move) return FALSE;
  if (ARROWSHIFTORCONTROL) i=10; else i=1;
  XQueryPointer (G.D, G.W, &root, &child, &r_x, &r_y, &x, &y, &keys);
  switch (GKeysym) {
    case XKUP:
      i = max(-y, -i);
      XWarpPointer (G.D, G.W, None, 0, 0, 0, 0, 0, i);
      break;
    case XKDOWN:
      i = min(Mx->height-y-1, i);
      XWarpPointer (G.D, G.W, None, 0, 0, 0, 0, 0, i);
      break;
    case XKLEFT:
      i = max(-x, -i);
      XWarpPointer (G.D, G.W, None, 0, 0, 0, 0, i, 0);
      break;
    case XKRIGHT:
      i = min(Mx->width-x-1, i);
      XWarpPointer (G.D, G.W, None, 0, 0, 0, 0, i, 0);
      break;
    case XK_Return: Mx->button_release = Mx->button_press;
    default: return FALSE;
  }
  return TRUE;
}
