/*

  SECONDARY code file for C language X-Midas X-Windows graphics libraries
  This module contains code pertaining to X-Widgets.  Those X-Widgets that
  are used in the control panel (Menus, DFLVals and Prompts) are located
  in glibc3.c.

  $Revision: 1.3 $
  $Modtime:   01 May 1996 16:22:58  $
  Modification History
  MIB 19-April-2000 DR RRCcl00838
           added #if block for __DECC_VER to include <stdlib.h>
  CJB 24-Jun-2004 DR #504008 - xcall message timer hangs macro
  	   Modified section of mx_messageval that checks if time > 0.0
	   In the case of xcall, the macro would "hang" on any type of
	   event that satisfied NEWEXPOSEORRESIZE.  Changed the code to
	   only execute commands if NEWEXPOSEORRESIZE AND if widget is NOT
	   oneshot.

  ************************* GENERAL CONVENTIONS ********************

  Throughout the interactive X-Widgets code certain repeated conventions
  have been followed in reference to variable names.

  All global variables begin with a capital letter or are found in the global
  structure G.  If they are constants defined in X-Windows each word in the
  constant begins with a capital letter and there are no underscores, OR
  they begin with a capital X. These constants are fully spelled out.  To
  differentiate, our globals may contain underscores, abbreviations, or have
  extra letters added to the end so that they are not valid words and ONLY
  the first letter of the variable name is capitalized (except for BW).

  Examples:

  X-Windows Constants: ConfigureNotify, XK_Left, XYPixmap

  Our Globals:  GKeysym, G.D, G.gcm, Mcbreak

  Inline Macros: NEWEXPOSEORRESIZE, XKUP, STOREBACK

  For a full list of the globals see XMAREA_SYS:[inc]glibc.h

  xs,ys    refer to the overall width and height of the X-Widget box (all of
           them are rectangular) in pixels.
  xc,yc    refer to the coordinates of the upper left hand corner of the
           X-Widget box in pixels
  xss,yss  refer to the area within the X-Widget which we will be locating
           and drawing items and interpreting the events that activate
           them, this usually corresponds to the area inside the X-Widget
           borders.
  xcc,ycc  refer to the coordinates of the upper left hand corner of this
           inside area of size xss,yss.
  GBorder  the thickness of the X-Widget border.
  xsb,ysb  often times an X-Widget is logically divided up into boxes or
           areas, these indicate the width,height of these areas (for
           example, the menu X-Widget is divided into boxes for each item)
  xt,yt    indicate the current point at which text will be drawn (the
           lower left hand corner of the text)

  Certain labels are common in all X-Widgets that trap events:

  POLLEVENT:
  Here is where new events are polled for, such as ButtonPresses and
  KeyPresses, MotionNotifies etc. When Expose, ConfigureNotify or
  ClientMessage are received we trap them in getevent() so that they call
  mx_event and properly update the mx structure, or do a mx_teach. If a
  ClientMessage was received we go ahead and return FALSE since we don't need
  to process them anywhere but in mx_ifevent.  If a ConfigureNotify or Expose
  are received, we go ahead and process them in mx_ifevent and return TRUE in
  case the X-Widget needs to redraw itself (it does so by checking the
  GEvent.type field after each getevent()).

  POLLRELEASE:
  Once a button or key has been pressed to activate some operation, if it is
  held down we continually do that operation until we receive a button or key
  release.  This is where that is done.  Special care has been taken to avoid
  plugging in the initial time stall at the beginning of quickly repeated
  key/button presses.  Normally, when you press a key/button and hold it, we
  wait an initial time before we interpret it as a held key/button, we then
  process at a quicker repetition time.  Hence if the key has processed once
  through and is not specified as HELD, we look through the event queue and
  if we don't find a buttonrelease (or key release) we go ahead and pause for
  the initial time, then set HELD=True.

  PROCESSICON:
  This is the point at which the action specified by pressing a button or
  key, is actually processed and performed.  For held key/buttonpresses this
  may be the top of the loop.

  icon  this variable usually indicates some button or action that has
        been chosen or imlemented.  Each icon value is usually mapped
        out in a switch statement.  Normally in the POLLEVENT loop if
        icon is -1, no button or action has been selected.

  ip    indicates the pixmap that holds the image behind the X-Widget so
        that when we exit the X-Widget, we can redraw the background as
        it was when we entered.

  Each time we enter an X-Widget and call STOREBACK, it checks to see
  (a) if a resize has been encountered previously and
  (b) if a background pixmap has been created

  if (a) is true, then we don't need to save the area behind the X-Widget
  because a resize caused the screen to be cleared, thus we don't even bother
  creating the pixmap or copying the area into it.  Naturally when we first
  enter an X-Widget (b) is always false but if we encounter a resize or
  expose we will loop back up and hit this statement again

  Within an X-Widget, each time we call getevent, if the event is an expose,
  resize, or clientmessage (as for mx_teach) mx_ifevent is called and the
  Mx->resize or Mx->expose fields are set.  If the event is a clientmessage,
  our X-Widget routines can ignore it, so getevent returns false, as if it
  did not receive an event (but mx_ifevent was called to do the correct
  processing).  Otherwise the expose or resize return true from getevent()
  and we can check to see if GEvent.type is one of the two. If so we need to
  redraw the X-Widget.  If in a child X-Widget a resize or expose was
  encountered the MX fields were set there.  Upon returning we check these
  fields and redraw the parent X-Widget if necessary.

  Upon exiting an X-Widget, if the Mx->resize flag is set, or the pixmap was
  never created, we need not copy it back since the screen is blank. If on
  the other hand only an expose was encountered and we have created a pixmap,
  go ahead and copy it back. The X-Widget has not moved and at the time the
  area was copied the screen was valid.

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


#include "glibc.h"

static void setmap (short map[7][4], int ncolor, int pos);
static int color_menu (bool *exposure, bool *resize);
static bool wmessage (const char *str, float time);

bool redraw_widget (bool exposure, bool resize)
{
  bool redraw;
  redraw = (Mx->exposure || Mx->resize);
  Mx->exposure = (Mx->exposure || exposure) ? TRUE : FALSE;
  Mx->resize = (Mx->resize || resize) ? TRUE : FALSE;
  return redraw;
}

bool drag_widget (int x, int y, int w, int h, int y2,
                        int *xadj, int *yadj, Pixmap *ip)
{
  int_4 x4, y4;

  if (Mx->ypos < y2 && Mx->xpos > x && Mx->ypos > y && Mx->xpos < x+w) {
    x4 = x; y4 = y;
    mx_dragbox (&x4, &y4, w, h, 0, Mx->width-w-1, 0, Mx->height-h-1, NULL);
    if (Mx->button_release == 1) { *xadj = x4 - x; *yadj = y4 - y; }
    else *xadj = *yadj = 0;
    if (*xadj || *yadj) {
      COPYBACK (x, y, w, h, *ip);
      *ip = 0;
    }
    return True;
  }
  else return False;
}

/************************ COLOR MAP **************************

This implements the interactive Color Map X-Widget (if *pcolors is
sent in as a negative value), or allocates/resets a new color map
with *pcolors colors in it.  MAP is a 4X7 array of short integers
defining the colormap.  (HELP COLORMAP for more details) Black and
White displays do not support interactive mode, they also will
truncate all requested colormaps to two colors, Black in G.color[0]
and White in G.color[1]. (Referred to as 1 and 2 respectively in
Fortran.)

G.cells, indicates the current number of color cells allocated to
this particular routine in the G.color array.  You may not add more
colors to G.color but you may change them.  If you request more colors
a warning is printed and your colormap is truncated.  If no colors have
been allocated (G.cells==0), the *pcolors colors are requested from
the server.  We bound this to L_color, if status indicates a failure
to return the requested colors, we continue the attempt with one less
color until we succeed and a warning message is printed.

r,c     indicates the current row, column entry you are editing
rl,cl   indicate the last row, column entry that was highlighted
step    the increment step in repeated inc/dec's
oldmap  upon entry into the X-Widget, the original map is stored here to
        allow quitting without saving changes

************************* COLOR MAP **************************/

void mx_colormap (int_2 map[7][4], int_4 *pcolors)
{
  int    icon, cl, rl, i, j, n, step=0, ic;
  int    r, c, x, y, xc, yc, xs, ys, xcc, ycc, xss, yss, xsave=(-1), ysave;
  int    ysb, xsb, ycb, xcb, wcb, xt, yt, xl;
  bool   held=FALSE, exposure, resize;
  int_4  ncolor; 
  bool_4 autorepeat, set=FALSE;
  float  scale, cf;
  real_8 dtemp;
  char   string[32], lab[20], type;
  unsigned long planes[2];
  unsigned long pixels[L_color];
  int_2  oldmap[7][4];
  Status status;
  XKeyboardState  kbstate;
  Pixmap  ip=0;
  int rw_map;
  
#if _HEADLESS
  if (Mx->headless == 1 ) return;
#endif

  rw_map = (G.visual->class & 0x01); /* RDA: flag if this a R/W colormap */

#define COLORMAP_FILL(COL,ROW) \
  FILLRECTANGLE (G.gcm, xcc+COL*xsb+1, ycc+ROW*ysb+1, xsb-2, ysb-2)

#define COLORMAP_BOX(COL,ROW) \
    DRAWRECTANGLE (G.gcm, xcc+COL*xsb+1, ycc+ROW*ysb+1, xsb-3, ysb-3)

  if (*pcolors<0) {
    if (!G.BW) goto INTERACTIVE;
    mx_message ("Not supported for monochrome display", -1.0, 36);
    return;
  }

 STARTOVER:
  ncolor = max (1, *pcolors);
  /* Special trap for black and white displays */
  if (G.BW) {
    /* If B&W cells were previously allocated, do not overwrite them */
    if (G.cells==0) {
      ncolor = min(*pcolors,L_color);
      for (i=0; i<ncolor; i++) Mx->pixel[i] = G.color[i].pixel = Mx->white;
      ncolor = 1;
    }
  }
  /* Previously alloc'd shared colormap */
  else if (G.cells > 0) {
    if (ncolor>G.cells) {
      for (n=0; n<G.cells; n++) pixels[n] = G.color[n].pixel;
      XFreeColors(G.D, G.C_id, &pixels[0], G.cells, 0);
      G.cells = 0;
      goto STARTOVER;
    }
    else if (G.cells > ncolor) {
      for (n=ncolor; n<G.cells; n++) pixels[n] = G.color[n].pixel;
      XFreeColors(G.D, G.C_id, &pixels[ncolor], G.cells-ncolor, 0);
      if (ncolor==0 && Mx->shrcmap<0) (void)mx_colormap_id(oldmap,-1);
    }
    else set = TRUE;
  }
  else if (Mx->shrcmap>0 && Mx->shrcmap<=MAXSHARED &&
	   Gshr_cells[Mx->shrcmap-1]!=0) {
    free (G.color);
    free (G.colorp);
    ncolor = Gshr_cells[Mx->shrcmap-1];
    G.color = Gshr_color[Mx->shrcmap-1];
    G.colorp = Gshr_colorp[Mx->shrcmap-1];
    for (i=ncolor; --i>=0; Mx->pixel[i]=G.color[i].pixel);
    Mx->shrcmap = -Mx->shrcmap;
  }
  /* color cells allocated by another process, read from RESULTS */
  else if (Mx->shrcmap>0 && (i=mx_colormap_id(oldmap,0)) > 0) {
    for (ncolor=i; --i>=0; ){
      G.color[i].pixel = Mx->pixel[i];
      G.color[i].flags = DoRed | DoGreen | DoBlue;
    }
    XQueryColors(G.D, G.C_id, G.color, ncolor);
    Mx->shrcmap = -Mx->shrcmap;
    set = TRUE;
   }
  /* no color cells allocated yet */
  else {
    if (Mx->xcmax > 0) *pcolors = ncolor = min(Mx->xcmax, ncolor);
    else if (Mx->xcmax < 0) *pcolors = ncolor = -Mx->xcmax;
    ncolor = min (min (ncolor,L_color), DisplayCells(G.D, G.S));

    /* RDArendt: Fix for TrueColor & StaticColor.  These are simple
       read-only maps.  Colors are allocated as closest matches to
       realizable pixels.
       
       The Read/Write maps (PsuedoColor, DirectColor, GrayScale) are
       privately allocated and set to desired values.  These are
       consumable and run out.  (Better to use R/O maps if we can) The
       R/O maps (StaticColor, TrueColor, StaticGray) can't be changed.
       They look up close matches to desired colors - these are always
       shared.  Best situation is to get 24-bit TrueColor or
       StaticColor.
       
       DirectColor isn't supported - There's no need, since TrueColor
       provides a similar capability and it's easier to manage.
       
       Note: 8-bit StaticColor is better than 8-bit TrueColor, since
       the points in StaticColor are somewhat uniformly distributed in
       colorspace.  TrueColor will segregate by bit-planes - one color
       only gets 2 bits
       ***/
    if (rw_map) {               /* eat cells from R/W maps */
      while (ncolor>0 &&
	     !XAllocColorCells (G.D,G.C_id,FALSE,planes,0,pixels,ncolor))
	ncolor--;
      if (ncolor==0) {
	/* No more color cells are available, default to black and white */
	m_warning ("No more colors available from display; using black & white");
	G.BW = TRUE;
	goto STARTOVER;
      }
      for (n=0; n<ncolor; n++) {
	Mx->pixel[n] = G.color[n].pixel = pixels[n];
	G.color[n].flags = DoRed | DoGreen | DoBlue;
      }
      if (*pcolors>ncolor) {
	i = XSPRINTF (string, "Colormap truncated to %d colors", ncolor);
	m_warning (string);
      }
    } else {			/* R/O maps - set pixels in setmap() */
      for (n=0; n<ncolor; n++) {
	Mx->pixel[n] = G.color[n].pixel = 0;
	G.color[n].flags = DoRed | DoGreen | DoBlue;
      }
    }
    if (Mx->shrcmap > 0) {
      if (Mx->shrcmap <= MAXSHARED) {
	Gshr_cells[Mx->shrcmap-1] = ncolor;
	Gshr_color[Mx->shrcmap-1] = G.color;
	Gshr_colorp[Mx->shrcmap-1] = G.colorp;
      }
    }
    set = TRUE;
  }
  /*
     Actually figure out the percentage values of the colors that will be
     allocated (these percentage values are set in G.colorp, then figure the
     pixel values of each color and set them by calling setmap (the values
     are set into G.color array and stored).
     */
  *pcolors = G.cells = ncolor;
  Mx->glob = G;

  if (set) {
    cf = 100.0 / (max(2,ncolor) - 1);
    for (n=0; n<ncolor; n++) G.colorp[n] = (float)(cf*n) + .5;
    setmap (map, ncolor, -1);
    if (Mx->shrcmap!=0) (void)mx_colormap_id(map, ncolor);
  }

  if (rw_map) Mx->exposure = True; /* RDA: repaint with new pixels */
  return;

 INTERACTIVE:
  if (Mx->shrcmap!=0) G.cells = mx_colormap_id(map, 0);
  ncolor = G.cells;
  mx_setbar ("Colormap X-Widget: LM-Decrement MM-Menu RM-Increment/Exit",57);
  mx_autorepeat (&autorepeat, -2);

 DRAWBOX:
  xsb = min(4*Mx->text_w, (Mx->width-2*GBorder)/9 );
  xs = 2*GBorder + xsb*9;
  ysb = min(1.5*Mx->text_h, (Mx->height-2*GBorder)/6);
  ys = 2*GBorder + 6*ysb;

  if (xsave<0) {xsave = Mx->xpos; ysave = Mx->ypos;}
  xc = xsave - xs/2;  xc = bound( xc, 0, Mx->width-xs-2 );
  yc = ysave - ys/2;  yc = bound( yc, 0, Mx->height-ys-2 );

  xcc = xc + GBorder;   xss = xs - 2*GBorder;
  ycc = yc + GBorder;   yss = ys - 2*GBorder;

  STOREBACK (xc, yc, xs, ys, ip);
  mx_widgetbox (xc, yc, xs, ys, xcc, ycc, xss, yss, "", 0);

  /* Draw labels */
  xt = xcc + Mx->text_w; yt = ycc + .70*ysb;
  XDrawString(G.D, G.W, G.gctw, xt, yt + 2*ysb, "Posn", 4);
  XDrawString(G.D, G.W, G.gctw, xt, yt + 3*ysb, "Red", 3);
  XDrawString(G.D, G.W, G.gctw, xt, yt + 4*ysb, "Green", 5);
  XDrawString(G.D, G.W, G.gctw, xt, yt + 5*ysb, "Blue", 4);

  /* Draw color bar */
  xcb = xcc+2; y = ycc + 2; ycb = y+ysb; wcb = xss-4;
  scale = (float)ncolor / (float)wcb;
  setmap (map, ncolor, -1);
  for (i=0; i<wcb; i++) {
    ic = i * scale;
    XSetForeground (G.D, G.gcg, G.color[ic].pixel);
    XDrawLine (G.D, G.W, G.gcg, xcb+i, y, xcb+i, ycb);
  }
  Mx->clast = L_NoColor;

  xcc += 2*xsb;                 ycc += 2*ysb;
  xt = xcc + Mx->text_w*0.5;    yt += 2*ysb;
  xss -= 2*xsb;                 yss -= 2*ysb;
  xl = xcc + xsb*0.5;

  /* Write map[][] values */
  for (r=0; r<4; r++)
    for (c=0; c<7; c++) {
      oldmap[c][r] = map[c][r];
      i = XSPRINTF (string, "%3d", map[c][r]);
      DRAWTEXT (xt+c*xsb, yt+r*ysb, string, 3);
    }

  /* Draw lines from map[][] color columns to color bar */
  for (c=0; c<7; c++)
    XDrawLine (G.D,G.W,G.gcm,xl+c*xsb,ycc,xcb+wcb*map[c][0]/100,ycb);

  /* set initial cursor location and highlight it */
  r = rl = c = cl = 0;
  COLORMAP_FILL (c, r);

  goto POLLEVENT;

 PROCESSICON:
  switch (icon) {
  case 0:			/* changing a map[][] value */
    if (r==0) {			/* Changing the position field */
      XDrawLine (G.D,G.W,G.gcm,xl+c*xsb,ycc,xcb+wcb*map[c][0]/100,ycb);
      map[c][r] += step;
      switch (c) {
      case 0:  map[c][0] = bound ( map[c][0], 0, map[c+1][0] );   break;
      case 6:  map[c][0] = bound ( map[c][0], map[c-1][0], 100);  break;
      default:  map[c][0] = bound ( map[c][0], map[c-1][0], map[c+1][0]);}
      XDrawLine (G.D,G.W,G.gcm,xl+c*xsb,ycc,xcb+wcb*map[c][0]/100,ycb);
    }
    else {			/* Changing R, G or B */
      map[c][r] += step;
      map[c][r] = bound (map[c][r], 0, 100);
    }
    break;

  case 1:			/* change the cursor location (row) */
    r = (r-step+4) % 4;		/* with wrap-around at top/bottom */
    break;

  case 2:			/* change the cursor location (col) */
    c = (c+step+7) % 7;		/* with wrap-around at left/right */
    break;
  }

  /* check for highlight */
  if (r!=rl || c!=cl) {
    COLORMAP_FILL (cl, rl);
    cl = c; rl = r;
    COLORMAP_FILL (cl, rl);
  }

  /* Set new colors into color cells */
  setmap (map, ncolor, -1);

  /* Display the changed map[][] value */
  i = XSPRINTF (string, "%3d", map[c][r]);
  DRAWITEXT (xt+c*xsb, yt+r*ysb, string, 3);
  XSetForeground (G.D, G.gcg, G.color[ic].pixel);
  XDrawLine (G.D, G.W, G.gcg, xcb+i, y, xcb+i, ycb);
    
  /* RDA: If a read-only visual, must re-draw with new pixel value */
  if ( !rw_map ){
    int xcb = xc + GBorder + 2,   wcb = xs - 2*GBorder - 4;
    int y   = yc + GBorder + 2,   ycb = y + ysb;
    int i, ic;
    for (i=0; i<wcb; i++) {
      ic = i * scale;
      XSetForeground (G.D, G.gcg, G.color[ic].pixel);
      XDrawLine (G.D, G.W, G.gcg, xcb+i, y, xcb+i, ycb);
    }
    Mx->exposure = True;	/* Re-draw parent w/ new pixels */
  }
  
  XFlush (G.D);


 POLLRELEASE:
  if (step!=0) {
    /* do not hold if this keypress was generated by a send_event */
    if (GEvent.type == KeyPress && GEvent.xkey.send_event) goto POLLEVENT;
    if (held) m_pause (0.1);
    else {
      /* check for quick clicks and avoid initial hold pause if so */
      while (getevent()) {
        if (BUTTONORKEYRELEASE) goto POLLEVENT;
        if (NEWEXPOSEORRESIZE) goto DRAWBOX;
      }
      m_pause (0.3); if (icon==0) step*=5; held=TRUE;
    }
    while (getevent()) {
      if (BUTTONORKEYRELEASE) goto POLLEVENT;
      if (NEWEXPOSEORRESIZE) goto DRAWBOX;
    }
    goto PROCESSICON;
  }

 POLLEVENT:
  held=FALSE; i=0; step=0; icon = -1;
  while (TRUE) {
    while (!getevent()) { m_pause (POLLTIME); if (XM_BREAK) goto DONE; }
    if (NEWEXPOSEORRESIZE) goto DRAWBOX;
    switch (GEvent.type) {

    case ButtonPress:
      Mx->xpos = GEvent.xbutton.x;
      Mx->ypos = GEvent.xbutton.y;
      x = Mx->xpos - xcc; y = Mx->ypos - ycc;
      if (GEvent.xbutton.button == Button2) {
	switch (color_menu(&exposure,&resize)) {
	case 1: icon = 0; goto DRAWBOX;
	case 2: goto DONE;
	case 3: goto UNDO;
	}
      }
      else if (x>0 && x<xss && y>0 && y<yss) {
	r = y/ysb; c = x/xsb;
	icon = 0;
	switch (GEvent.xbutton.button) {
	case Button1: step = -1; break;
	case Button3: step = 1;  break;
	}
      }
      else if (GEvent.xbutton.button == Button1 &&
	       drag_widget(xc,yc,xs,ys,ycc,&i,&j,&ip)) {
	if (i || j) {
	  xc+=i; yc+=j;
	  xsave+=i; ysave+=j;
	  icon=0; goto DRAWBOX;
	}
      }
      else if (GEvent.xbutton.button == Button3) goto DONE;
      break;

    case KeyPress:
      switch (GKeysym) {
      case XK_Tab:
	icon = 0;
	COLORMAP_FILL (c, r);
	COLORMAP_BOX (c, r);
	x = xt + c*xsb; y = yt + r*ysb;
	(void)m_l2a(map[c][r], string, 3);
	for (n = -2; n < 0; n = m_a2d(string,&dtemp,3)) 
	  if ((n==-1 && wmessage("Bad input string - RETRY",1.0)) ||
	      mx_kbstring("",string,x,y,3,0,3)==L_AvalRedraw) goto DRAWBOX;
	COLORMAP_BOX (c, r);
	COLORMAP_FILL (c, r);
	step = (int)dtemp - map[c][r];
	GEvent.xkey.send_event = TRUE;
	break;
      case XK_Return: goto DONE;
      case XK_M: case XK_m:
	switch (color_menu(&exposure,&resize)) {
	case 1: icon = 0; goto DRAWBOX;
	case 2: goto DONE;
	case 3: goto UNDO;
	}
	break;
      case XK_Q: case XK_q: goto UNDO;
      case XK_question: case XK_Help: case XK_slash:
	Mx->xpos = GEvent.xkey.x;
	Mx->ypos = GEvent.xkey.y;
	CLEAR_EXPOSE_AND_RESIZE (exposure, resize);
	mx_mhelp ("~COLORMAP", "X-Widget", 9, 8);
	if (redraw_widget(exposure,resize)) {icon = 0; goto DRAWBOX;}
	break;
	/* icon 0: change value, 1: change row, 2: change column */
      default:
	/* CAUTION: step values for up/down arrow keys differ for row */
	/* move and change value functions, use flag and a separate */
	/* icon to catch this */
	switch (GKeysym) {
	case XKUP:     icon = 1; /* set to flag row change */
	case XKRIGHT:  step = 1;  break;
	case XKDOWN:   icon = 1; /* set to flag row change */
	case XKLEFT:   step = -1; break;
	}
	if (step !=0) {
	  /* if shift, change value if not set, change cols */
	  if (ARROWSHIFTORCONTROL) icon = 0;
	  else if (icon != 1) icon = 2;	/* use row change flag */
	}
      }

    }
    if (icon != -1) goto PROCESSICON;
  }

 UNDO:
  for (r=0; r<4; r++)
    for (c=0; c<7; c++) map[c][r] = oldmap[c][r];
  mx_colormap (map, &ncolor);
  goto CLEANUP;

 DONE:
  (void)mx_colormap_id(map, ncolor);

 CLEANUP:
  COPYBACK (xc, yc, xs, ys, ip);
  mx_unsetbar ();
  mx_autorepeat (&autorepeat, 0);
  XFlush (G.D);

}

/************************** SET MAP *************************

This routine actually figures out the values associated with
ncolors, spread out evenly across the colormap indicated in
map.  We can use this routine to refigure all of the colors
in the map (pos < 0) or just to refigure one color (pos>=0).
Colormap X-Widget uses the first option, Colortab the second.
ncolor indicates the number of colors in the colormap.

RDA: if the position argument is < 0, the color is fixed and
     will not be interpolated.  Necessary, since MX$COLORMAP
     retrieves all shared colormaps. MX$COLORTAB does not.
*************************** SET MAP *************************/

static void setmap (short map[7][4], int ncolor, int pos)
{

  int   iz, n;
  float z, zf, zf1;
  int rw_map=(G.visual->class & 0x01); /* RDA: Is this a R/W colormap? */

  /* start at highest 0 value column */
  for (iz=0; iz<6 && map[iz+1][0]==0; iz++);

  if (pos<0) n = 0;               /* if called for one color only pos>=0 */
  else { n = ncolor; ++ncolor; }

  /* figure color values from colormap and put them in G.color */
  for ( ;n<ncolor; n++) {
    if (pos<0) z = G.colorp[n]; else z = pos;
    while (z>map[iz][0] && iz<6) iz++;
    if (iz==0 || z>=map[iz][0]) {
      G.color[n].red   = PERC2PIXEL(map[iz][1]);    /* above, below range */
      G.color[n].green = PERC2PIXEL(map[iz][2]);    /* or directly on boundry */
      G.color[n].blue  = PERC2PIXEL(map[iz][3]);
    }
    else {
      zf = PERC2PIXEL((z-map[iz-1][0])/(map[iz][0]-map[iz-1][0]));
      zf1 = 655.35 - zf;
      G.color[n].red   = zf*map[iz][1] + zf1*map[iz-1][1];  /* interpolation */
      G.color[n].green = zf*map[iz][2] + zf1*map[iz-1][2];
      G.color[n].blue  = zf*map[iz][3] + zf1*map[iz-1][3];
    }
    G.color[n].flags = DoRed | DoGreen | DoBlue;
    if ( rw_map ) {
        XStoreColor (G.D, G.C_id, &G.color[n]);
    } else {
        XAllocColor (G.D, G.C_id, &G.color[n]);
	Mx->pixel[n] = G.color[n].pixel;
    }
  }
}

static int color_menu (bool *exposure, bool *resize)
{
  int item;

  CLEAR_EXPOSE_AND_RESIZE(*exposure, *resize);
  item = mx_menu(0,"~;OK,Undo",9);
  if (item) ++item;
  if (redraw_widget(*exposure,*resize)) item = max(1, item);
  return item;
}

/************************ COLOR TABLE **************************

This routine may be called as an interactive X-Widget, or to
set the value of one color (G.color[*icolor-1], *icolor is the
Fortran array index) with the color defined at *position of map.
If *position is negative, then we want the interactive X-Widget.
If the display is black and white, we just set the color to
the foreground color of the screen (Mx->white).

To easily implement the changes of several variables that are
not in an array, I used an array of pointers (target[]).  Icon
indicates which pointer value, and I change the value of
whatever that points to.  Since box 2 (from the top) has no
functionality or value to change (it is the colorbar), I use
it to indicate the current position of the cursor.  So target[1]
is ms (current cursor pos) 0-5 and by pressing the keys or mouse
I can increment, decrement this value.

ms, ml    the current cursor position (like in mx_mval), and the
          last one that was highlighted.
oldcolor  the original colormap that was present when we entered
          the X-Widget, we keep it in case we need to quit
oldcolorp the percentage values of the original colormap that was
          present when we entered the X-Widget

ic        the color (index into G.color) we are viewing
pos       the current position within the colormap of the color
red       the red intensity (as a percentage) of the color
green     the green intensity (as a percentage) of the color
blue      the blue intensity (as a percentage) of the color

************************* COLOR TABLE **************************/

void mx_colortab (int ic, int_2 map[7][4], int pos)
{
  int    i, j, icon=0, step=0, red, green, blue;
  int    x, y, xc, yc, xs, ys, xcc, ycc, xss, yss, ysb, xt, yt, ms=2, ml=2;
  int    *target[6], oldcolorp[L_color], xsave=(-1), ysave;
  bool   held=FALSE, exposure, resize;
  bool_4 autorepeat;
  real_8 dtemp;
  char   string[10];
  XColor oldcolor[L_color];
  Pixmap ip=0;
  int rw_map;

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

  rw_map = (G.visual->class & 0x01); /* RDA: Is this a R/W colormap? */
#define COLORTAB_FILL(ROW) \
  FILLRECTANGLE ((ROW==1)?G.gcg:G.gcm, xcc+1, ycc+ROW*ysb+1, xss-2, ysb-2)

#define COLORTAB_BOX(ROW) \
    DRAWRECTANGLE (G.gcm, xcc+1, ycc+ROW*ysb+1, xss-3, ysb-3)

  --ic;
  switch (pos) {

  case -1:			/* Interactive X-Widget mode */
    if (G.BW) {
      mx_message ("Not supported for monochrome display",-1.0,36);
      return;
    }
    else if (G.cells <=0) {
      mx_message ("Colors not properly allocated for Colortab X-Widget",
		  -1.0, 52);
      return;
    }
    break;

  case -2:			/* Set color from map[] */
    if (G.BW) {
      if (ic<0) {ic=0; j=L_color;} else j=1;
      for (i=0; i<j && ic<L_color; i++, ic++) {
	if (map[i][1]==0 && map[i][2]==0 && map[i][3]==0)
	  Mx->pixel[ic] = G.color[ic].pixel = Mx->black;
	else
	  Mx->pixel[ic] = G.color[ic].pixel = Mx->white;
      }
    } else {
      if (ic<0) {ic=0; j=G.cells;} else j=1;
      for (i=0; i<j && ic<G.cells; i++, ic++) {
	G.colorp[ic]      = bound(map[i][0],-1,100);
	G.color[ic].red   = PERC2PIXEL(bound(map[i][1],0,100));
	G.color[ic].green = PERC2PIXEL(bound(map[i][2],0,100));
	G.color[ic].blue  = PERC2PIXEL(bound(map[i][3],0,100));
	G.color[ic].flags = DoRed | DoGreen | DoBlue;
	if (rw_map) {		/* RDA */
	  XStoreColor (G.D, G.C_id, &G.color[ic]);
	} else {
	  XAllocColor (G.D, G.C_id, &G.color[ic]);
	  Mx->pixel[ic] = G.color[ic].pixel; /* RDA */
        }
      }
      if (!rw_map){
	/* RDA: The GCG colors changed - reset clast so  
	 *      SETGCGFOREGROUND will pick it up */
	Mx->clast = L_NoColor;
	Mx->exposure = True;	/* RDA: repaint with new pixels */
      }
    }
    return;

  case -3:			/* Put color into map[] */
    if (G.BW) {
      if (G.color[0].pixel == Mx->black) i=0; else i=100;
      map[0][0] = -1; map[0][1] = map[0][2] = map[0][3] = i;
    }
    else {
      if (ic<0) {ic=0; j=G.cells;} else j=1;
      for (i=0; i<j && ic<L_color; i++, ic++) {
	map[i][0] = G.colorp[ic];
	map[i][1] = PIXEL2PERC(G.color[ic].red);
	map[i][2] = PIXEL2PERC(G.color[ic].green);
	map[i][3] = PIXEL2PERC(G.color[ic].blue);
      }
    }
    return;

  default:			/* Set color by percentage value */
    if (G.BW) Mx->pixel[0] = G.color[0].pixel = Mx->white;
    else if (pos>=0 && ic>=0 && ic<G.cells)
      { setmap (map, ic, pos); G.colorp[ic] = pos; }
    if (!rw_map) {
      XSetForeground (G.D, G.gcg, G.color[ic].pixel); /* RDA */
      Mx->exposure = True;	/* RDA: repaint with new pixels */
    }
    return;
  }
  
  target[0] = &ic;      target[1] = &ms;      target[2] = &pos;
  target[3] = &red;     target[4] = &green;   target[5] = &blue;
  for (i=0; i<G.cells; i++) {
    oldcolorp[i] = G.colorp[i];
    oldcolor[i] = G.color[i];
  }

  mx_setbar ("Colortab X-Widget: LM-Decrement MM-Menu RM-Increment/Exit",57);
  mx_autorepeat (&autorepeat, -2);

 DRAWBOX:
  xs = 2*GBorder + 11*Mx->text_w;
  ysb = min(1.5*Mx->text_h, Mx->height/6);
  ys = 2*GBorder + 6*ysb;

  if (xsave<0) {xsave = Mx->xpos; ysave = Mx->ypos;}
  xc = xsave - xs/2;  xc = bound( xc, 0, Mx->width-xs-2 );
  yc = ysave - ys/2;  yc = bound( yc, 0, Mx->height-ys-2 );

  xcc = xc + GBorder;   xss = xs - 2*GBorder;
  ycc = yc + GBorder;   yss = ys - 2*GBorder;

  STOREBACK (xc, yc, xs, ys, ip);

  mx_widgetbox (xc, yc, xs, ys, xcc, ycc, xss, yss, "", 0);

 DRAWLABELS:
  xt = xcc + Mx->text_w;
  yt = ycc + ysb*.70;
  XDrawString(G.D, G.W, G.gctw, xt, yt, "COLOR", 5);
  XDrawString(G.D, G.W, G.gctw, xt, yt + 2*ysb, "Posn", 4);
  XDrawString(G.D, G.W, G.gctw, xt, yt + 3*ysb, "Red", 3);
  XDrawString(G.D, G.W, G.gctw, xt, yt + 4*ysb, "Green", 5);
  XDrawString(G.D, G.W, G.gctw, xt, yt + 5*ysb, "Blue", 4);
  /* highlight defaulted beginning item */
  COLORTAB_FILL (ms);

  xt = xcc + 7*Mx->text_w;

 PROCESSICON:
  *(target[icon]) += step;
  if (icon>1) *(target[icon]) = bound ( *(target[icon]), 0, 100);

  switch (icon) {
  case 0: ic = bound( ic, 0, G.cells-1 );
    pos = G.colorp[ic];
    red   = PIXEL2PERC(G.color[ic].red);
    green = PIXEL2PERC(G.color[ic].green);
    blue  = PIXEL2PERC(G.color[ic].blue);
    XSetForeground (G.D, G.gcg, G.color[ic].pixel);
    COLORTAB_FILL (1);
    break;

  case 1: if (step<0) { if(ms<0) ms = 5; else if (ms == 1) ms=0; }
  else { if(ms>5) ms = 0; else if (ms == 1) ms=2; }
    break;

  case 2: setmap(map, ic, pos);
    G.colorp[ic] = pos;
    red   = PIXEL2PERC(G.color[ic].red);
    green = PIXEL2PERC(G.color[ic].green);
    blue  = PIXEL2PERC(G.color[ic].blue);
    break;

  case 3: G.color[ic].red = red*655.35;     break;
  case 4: G.color[ic].green = green*655.35; break;
  case 5: G.color[ic].blue = blue*655.35;   break;
  }
  if (icon>2 && pos != -1) { G.colorp[ic] = -1, pos = -1; }
  if (icon!=2 && ! G.BW) {
    if (rw_map) {
      XStoreColors ( G.D , G.C_id , G.color , G.cells );
    } else {
      int i; 
      for(i=0; i<G.cells; i++){
	XAllocColor (G.D, G.C_id, &G.color[i]);
	Mx->pixel[i] = G.color[i].pixel;
      }
      /* RDA: Need to redraw using new pixel value */
      XSetForeground (G.D, G.gcg, G.color[ic].pixel);
      COLORTAB_FILL (1);
      Mx->exposure = True;	/* have parent redraw with new pixels */
    }
  }

  if (ms != ml) {
    COLORTAB_FILL (ml);
    ml = ms;
    COLORTAB_FILL (ml);
  }

 WRITEVALUE:
  if (icon==0) {
    j = XSPRINTF (string, "%3d", ic+1);
    if (ms==0) DRAWITEXT (xt, yt, string, 3);
    else DRAWTEXT (xt, yt, string, 3);
  }
  for (i=2; i<6; i++) {
    j = XSPRINTF (string, "%3d", *(target[i]) );
    if (ms==i) DRAWITEXT (xt, yt+i*ysb, string, 3);
    else DRAWTEXT (xt, yt+i*ysb, string, 3);
  }

  XFlush (G.D);

 POLLRELEASE:
  if (step!=0) {
    /* do not hold if this keypress was generated by a send_event */
    if (GEvent.type == KeyPress && GEvent.xkey.send_event) goto POLLEVENT;
    if (held) m_pause (0.1);
    else {
      /* check for quick clicks and avoid initial hold pause if so */
      while (getevent()) {
	if (BUTTONORKEYRELEASE) goto POLLEVENT;
	if (NEWEXPOSEORRESIZE) {icon = 0; goto DRAWBOX;}
      }
      m_pause (0.3); if (icon>1) step*=5; held=TRUE;
    }
    while (getevent()) {
      if (BUTTONORKEYRELEASE) goto POLLEVENT;
      if (NEWEXPOSEORRESIZE) {icon = 0; goto DRAWBOX;}
    }
    goto PROCESSICON;
  }

 POLLEVENT:
  held=FALSE; icon= -1; step = 0;
  while (TRUE) {
    while (!getevent()) { m_pause (POLLTIME); if (XM_BREAK) goto DONE; }
    if (NEWEXPOSEORRESIZE) {icon = 0; goto DRAWBOX;}
    switch (GEvent.type) {

    case ButtonPress:
      Mx->xpos = GEvent.xbutton.x; Mx->ypos = GEvent.xbutton.y; i=0;
      x = Mx->xpos - xcc; y = Mx->ypos - ycc;
      if (GEvent.xbutton.button == Button2) {
	switch (color_menu(&exposure,&resize)) {
	case 1: icon = 0; goto DRAWBOX;
	case 2: goto DONE;
	case 3: goto UNDO;
	}
      }
      else if (x>0 && x<xss && y>0 && y<yss) {
	icon = y/ysb;
	if (icon == 1) {
	  icon = -1;
	  if (GEvent.xbutton.button==Button1 &&
	      drag_widget(xc,yc,xs,ys,yc+ys,&i,&j,&ip) && (i || j))
	    { xc+=i; yc+=j; xsave+=i; ysave+=j; icon=0; goto DRAWBOX; }
	}
	else {
	  ms = icon;
	  switch (GEvent.xbutton.button) {
	  case Button1: step = -1; break;
	  case Button3: step = 1;  break;
	  }
	}
      }
      else if (GEvent.xbutton.button==Button3) goto DONE;
      break;

    case KeyPress:
      switch (GKeysym) {
      case XK_Tab:
	icon = 0;
	COLORTAB_FILL (ms);
	COLORTAB_BOX (ms);
	y = yt + ms*ysb;
	(void)m_l2a(*(target[ms]), string, 3);
	for (i = -2; i < 0; i = m_a2d(string,&dtemp,3))
	  if ((i==-1 && wmessage("Bad input string - RETRY",1.0)) ||
	      mx_kbstring("",string,xt,y,3,0,3)==L_AvalRedraw) goto DRAWBOX;
	COLORTAB_BOX (ms);
	COLORTAB_FILL (ms);
	step = (int)dtemp - *(target[ms]);
	GEvent.xkey.send_event = TRUE;
	icon = ms;
	break;
      case XK_Return:       goto DONE;
      case XK_Q: case XK_q: goto UNDO;
      case XK_M: case XK_m:
	switch(color_menu(&exposure,&resize)) {
	case 1: icon = 0; goto DRAWBOX;
	case 2: goto DONE;
	case 3: goto UNDO;
	}
	break;
      case XK_question: case XK_Help:  case XK_slash:
	Mx->xpos = GEvent.xkey.x;
	Mx->ypos = GEvent.xkey.y;
	CLEAR_EXPOSE_AND_RESIZE (exposure, resize);
	mx_mhelp ("~COLORTAB", "X-Widget", 9, 8);
	if (redraw_widget(exposure,resize)) { icon = 0; goto DRAWBOX; }
	break;
      default:
	/* CAUTION: step values for up/down arrow keys differ for */
	/* move/change value functions, use flag to catch this */
	switch (GKeysym) {
	case XKUP:     icon = 1; /* flag for position move */
	case XKRIGHT:  step = 1;          break;
	case XKDOWN:   icon = 1; /* flag for position move */
	case XKLEFT:   step = -1; break;
	}
	if (step !=0) {
	  /* if shift, change value, otherwise move */
	  if (ARROWSHIFTORCONTROL) icon = ms;
	  else { if (icon==1) step = -step; else icon = 1; } /* use flag */
	}
      }

    } /* switch on events */

    if (icon != -1) goto PROCESSICON;
  }

 UNDO:
  for (i=0; i<G.cells; i++)
    { G.colorp[i] = oldcolorp[i]; G.color[i] = oldcolor[i];}
  if (!G.BW) {
    int i;
    for(i=0; i<G.cells; i++)
      if (rw_map) {		/* RDA */
	XStoreColor (G.D, G.C_id, &G.color[i]);
      } else {
	XAllocColor (G.D, G.C_id, &G.color[i]);
	Mx->pixel[i] = G.color[i].pixel;
      }
  }

 DONE:
  COPYBACK (xc, yc, xs, ys, ip);
  mx_unsetbar ();
  mx_autorepeat (&autorepeat, 0);
  XFlush (G.D);
  Mx->clast = L_NoColor;
}

/*************************** MESSAGE **************************/

void mx_message (const char *text, float time, int length)
{
  XWIDGET xw;

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

  xw.wtype = XW_MESSAGE;
  mx_widget (&xw, XW_INIT);
  xw.title.p = (M_PTR_T) text;
  xw.title_len = length;
  xw.pause = time;
  mx_widget (&xw, XW_ONESHOT);
}

void mx_messageval (XWIDGET *xw, XWIDGET_OP op)
{
  int    beg[MESSLINES], len[MESSLINES];
  int    xc, yc, xs, ys, i, j, xcc, ycc, xss, yss, xsave=(-1), ysave;
  int    brk, end, bg, cur, linel, lines, lastline, winlines;
  int    ls, le, rs, re, length;
  bool   center, endinreturn=FALSE, warn;
  real_8 vl,vr;
  float time;
  Pixmap ip=0;
  char   *text, optype;

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

  if (op == XW_INIT || op == XW_FREE || op == XW_RESET) return;

  text = (char *) xw->title.p;
  length = xw->title_len;
  time = xw->pause;

  DRAWBOX:
  linel = min( (((Mx->width-2*GBorder)/Mx->text_w) - 2), length);
  if (linel <= 0) return;
  while (linel>MESSWIDTH && 2.5*Mx->text_h*length<Mx->height*linel) linel-=5;

  center = TRUE; bg = 0; ls = re = 0;
  if (time==-3.0 || time==-4.0) {
    bg = m_lengthd(text,length) + 1;
    if (text[0]=='(') { ls = 1; le = re = bg-2; }
    else { ls = 0; le = re = bg-1; }
    for (rs=ls,vl=vr=0.0,optype='!'; rs<le; ++rs) switch (text[rs]) {
      case '<': if (text[rs+1] == '>') { optype = '!'; le = rs++; }
                else if (text[rs+1] == '=') { optype = '-'; le = rs++; }
                else { optype = '<'; le = rs; }
                break;
      case '=': if (text[rs+1] == '=') { optype = '='; le = rs++; }
                else { optype = '='; le = rs; }
                break;
      case '>': if (text[rs+1] == '<') { optype = '!'; le = rs++; }
                else if (text[rs+1] == '=') { optype = '+'; le = rs++; }
                else { optype = '>'; le = rs; }
                break;
    }
  }

  i=j=0; beg[0]=bg;
  while (bg < length && i < MESSLINES) {
    end = bg + linel - 1;
    brk = end = min(end,length-1);
    endinreturn = FALSE;
    for (cur=bg; cur <= end && !endinreturn; cur++) {
      switch (text[cur]) {
        case ',': case ';': case ' ': case ':':
          brk = cur; break;
        case'-':  case '/':
          if (brk != cur-1) brk = cur; break;
        case '@': case 10: case 13:
          center = FALSE; endinreturn = TRUE; brk = cur; break;
      }
    }
    if (cur==length) brk = end;
    beg[++i] = bg;
    if (endinreturn) {  /* line ends in return */
      len[i] = brk-bg;
      bg = brk+1;
    }
    else {                      /* line doesn't end in return */
      len[i] = brk-bg+1;
      /* cut leading spaces*/
      for (bg=brk+1; bg<length && text[bg]==' '; ++bg);
    }
    j = max(j,len[i]);
  }
  /* if last line is a return */
  if (endinreturn && i<MESSLINES) {beg[++i]=0; len[i]=0;}

  lines = i; linel = j;
  if (lines > 6) center = FALSE;
  /* denotes the # of lines that can fit vertically in window */
  winlines = max( 1, Mx->height/Mx->text_h);   
  if (lines > winlines) {
    i = max( 1, (lines-1)/winlines + 1);
    winlines = lines / i;
  }
  cur = 1;                          /* current line to draw to screen */

  lastline = min (lines, cur+winlines-1);
  xss = (linel+2)*Mx->text_w;
  yss = (lastline-cur+2)*Mx->text_h;
  xs = xss + 2*GBorder; ys = yss + 2*GBorder;

  if (xsave>=0 || (xw->x == -1 && xw->y == -1)) {
    if (xsave<0) {xsave = Mx->xpos; ysave = Mx->ypos;}
    xc = xsave - xs/2;
    yc = ysave - ys;
  } else {
    xc = xw->x;
    yc = xw->y;
  }
  xc = bound( xc, 0, Mx->width-xs );
  yc = bound( yc, 0, Mx->height-ys );
  xcc = xc + GBorder; ycc = yc + GBorder;
  xw->x = xc; xw->y = yc; xw->w = xs; xw->h = ys;

  if (op == XW_SETUP) return;

  if (op == XW_ONESHOT) STOREBACK (xc, yc, xs, ys, ip);
  mx_widgetbox (xc, yc, xs, ys, xcc, ycc, (G.BW) ? xss : 0, yss, "", 0);

  DRAWMESSAGE:
  j = ycc + Mx->text_h/3;    /* j = ytext pos, i = xtextpos */
  i = xcc + Mx->text_w;
  while(cur<=lastline) {
    j += Mx->text_h;
    if (center) i = xc + xs/2 - (len[cur] * Mx->text_w / 2);
    XDrawString (G.D, G.W, G.gctw, i, j, text+beg[cur], len[cur]);
    cur++;
  }
  lastline = min (lines, cur+winlines-1);
  XFlush (G.D);

  if (time > 0.0 && cur>lines) {
/*  DR #504008  */
    m_pause (time);
    while (getevent())
    {
      if (NEWEXPOSEORRESIZE && op != XW_ONESHOT)
      {
        cur=1;
	goto DRAWBOX;
      }
    }
/*  DR #504008  */
  }
  else if (time == -2.) {
    do { geteventw();}
    while (GEvent.type != ButtonRelease);
    Mx->xpos = GEvent.xbutton.x; Mx->ypos = GEvent.xbutton.y;
  }
  else if (time <= 0.0 || cur<=lines) {
    warn = TRUE;
    do {
      for (i=0; !getevent(); i++) {
        if (re>ls) {
          if (le > ls) j = m_a2d(text+ls,&vl,le-ls); else j = 0;
          if (j >= 0 && re > rs) j = m_a2d(text+rs,&vr,re-rs);
          if (j >= 0) switch (optype) {
            case '>': if (vl<=vr) goto DONE; break;
            case '<': if (vl>=vr) goto DONE; break;
            case '+': if (vl<vr)  goto DONE; break;
            case '-': if (vl>vr)  goto DONE; break;
            case '!': if (vl==vr) goto DONE; break;
            case '=': if (vl!=vr) goto DONE; break;
          }
          else if (warn) {
            m_warning ("Untranslateable condition string in MX$MESSAGE");
            warn = FALSE;
          }
          m_pause (POLLTIME);
        }
        else if (i<20) m_pause (0.05);
        else m_pause (POLLTIME);
        if (XM_BREAK) goto DONE;
      }
      if (NEWEXPOSEORRESIZE) { cur=1; goto DRAWBOX; }
      if (GEvent.type == KeyPress) {
        switch (GKeysym) {
          case XK_Control_R: case XK_Control_L:
            GEvent.type = KeyRelease; break;
          case XK_C: case XK_c:
            if (GEvent.xkey.state & ControlMask) { *Mcbreak=TRUE; goto DONE; }
        }
      }
      if (re>ls && time==-4.0 && cur>lines &&
        (GEvent.type == ButtonPress || GEvent.type == KeyPress))
          { XBell(G.D, 50); GEvent.type = ButtonRelease; }
    } while (GEvent.type != ButtonPress && GEvent.type != KeyPress);
    if (cur<=lines && 
      (GEvent.type != ButtonPress || GEvent.xbutton.button != Button3)) {
      if (G.BW) XClearArea (G.D, G.W, xcc, ycc, xss, yss, FALSE);
      else mx_shadowbox (xcc, ycc, xss, yss, 1, 0, "", 0);
      goto DRAWMESSAGE;
    }
    switch (GEvent.type) {
      case ButtonPress:
        Mx->xpos = GEvent.xbutton.x;
	Mx->ypos = GEvent.xbutton.y;
	break;
      case KeyPress:
        Mx->xpos = GEvent.xkey.x;
	Mx->ypos = GEvent.xkey.y;
	break;
    }
  }

  DONE:
  if (op == XW_ONESHOT) COPYBACK (xc, yc, xs, ys, ip);
  XFlush (G.D);
}

/************************* PIPE MONITOR **************************/

void mx_pipe_monitor (const char *name, const HEADER *hcb, int length)
{
  int    i, j, xc, yc, xs, ys, xt, yt, xsave=(-1), ysave;
  int    xw, wx, rx, ytw, ytr, yts, wy, ry, lwy, lry, pdw;
  int    x, y, xo, yo, xcc, ycc, xss, yss, xsmin, ysmin, xsmax, ysmax;
  real_8 psize, pread, pwrite, lpread, lpwrite, factor;
  char   string[40];
  Pixmap ip=0;

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

  xsmin = 2*GBorder + Mx->text_w * max(length,12);
  xsmax = 2*GBorder + Mx->text_w * max(length,16);
  ysmin  = Mx->text_h*9  + 3*GBorder;
  ysmax  = Mx->text_h*20 + 3*GBorder;

  DRAWBOX:
  xs = bound (Mx->width, xsmin, xsmax);
  ys = bound (Mx->height, ysmin, ysmax);
  if (xsave<0) {xsave = Mx->xpos; ysave = Mx->ypos;}
  xc = bound (xsave-xs/2, 0, Mx->width-xs);
  yc = bound (ysave-ys/2, 0, Mx->height-ys);

  STOREBACK (xc, yc, xs, ys, ip);

  xcc = xc + GBorder;   ycc = yc + 2*Mx->text_h + 2*GBorder;
  xss = xs - 2*GBorder; yss = ys - (2*Mx->text_h + 3*GBorder);
  mx_widgetbox(xc, yc, xs, ys, xcc, ycc, xss, (G.BW) ? yss : 0, name, length);

  xw = (xss - 3*Mx->text_w)/2;
  wx = xcc + Mx->text_w;
  rx = wx + xw + Mx->text_w;
  xt = wx;
  pdw = Mx->text_w-1;
  yts = ycc + yss - Mx->text_h*.5;
  ytr = yts - Mx->text_h;
  ytw = ytr - Mx->text_h;
  yss = yss - 4.5*Mx->text_h;
  ycc = ycc + .5*Mx->text_h;
  if (!G.BW) XClearArea(G.D, G.W, wx-GBorder, ycc-GBorder,
    rx+xw-wx+2*GBorder, yss+2*GBorder, FALSE);
  XDrawLine (G.D, G.W, G.gcm, xc+xs/2, ycc, xc+xs/2, ycc+yss);
  XFlush (G.D);

  m_pindices(hcb, &psize, &pread, &pwrite);
  factor = (double)yss/psize;
  lpwrite = lpread = -1.0;
  lwy = lry = -1;

  while (!XM_BREAK) {
    m_pindices(hcb, &psize, &pread, &pwrite);
    if (pwrite != lpwrite || pread != lpread) {

      wy = fmod(pwrite,psize) * factor + ycc;
      ry = fmod(pread,psize) * factor + ycc;

      if (lwy != wy || lpwrite-lpread != pwrite-pread) {
/*+++                                                        */
/* DR 10142                                                  */
/* Old                                                       */
/*	if (lwy != lry || lpwrite > lpread) {                */
/* New                                                       */
        if (lwy != lry || (pwrite > (pread + 2/factor))) {
/* ---                                                       */
       	  XClearArea(G.D, G.W, wx+xw+1, ycc, pdw, yss, False);
	  XDrawLine (G.D, G.W, G.gct, xc+xs/2, ycc, xc+xs/2, ycc+yss);
	}
	if (ry != lry) {
	  XDrawLine(G.D, G.W, G.gcm, rx, lry, rx+xw, lry);
	  lry = ry;
	  XDrawLine(G.D, G.W, G.gcm, rx, lry, rx+xw, lry);
	}
	if (wy != lwy) {
	  XDrawLine(G.D, G.W, G.gcm, wx, lwy, wx+xw, lwy);
	  lwy = wy;
	  XDrawLine(G.D, G.W, G.gcm, wx, lwy, wx+xw, lwy);
	}
	if (wy > ry) FILLRECTANGLE(G.gct, wx+xw+1, ry, pdw, wy-ry);
	else if (wy < ry) {
	  FILLRECTANGLE(G.gct, wx+xw+1, ycc, pdw, wy-ycc);
	  FILLRECTANGLE(G.gct, wx+xw+1, ry, pdw, ycc+yss-ry);
	}
/*+++                                                        */
/* DR 10142                                                  */
/* Old                                                       */
/*	else if (pwrite > pread)                             */
/* New                                                       */
        else if (pwrite > (pread + 2/factor))
/* ---                                                       */
	  FILLRECTANGLE(G.gct, wx+xw+1, ycc, pdw, yss);
      }

      i = XSPRINTF(string, "W %8.0lf", pwrite);
      XDrawImageString(G.D, G.W, G.gctw, xt, ytw, string, i);
      i = XSPRINTF(string, "R %8.0lf", pread);
      XDrawImageString(G.D, G.W, G.gctw, xt, ytr, string, i);
      i = XSPRINTF(string, "S %8.0lf", psize);
      XDrawImageString(G.D, G.W, G.gctw, xt, yts, string, i);

      lpread = pread;
      lpwrite = pwrite;
    }

    while (getevent()) {
      if (NEWEXPOSEORRESIZE) goto DRAWBOX;
      if (GEvent.type==KeyPress) goto DONE;
      if (GEvent.type==ButtonPress) {
        Mx->xpos = GEvent.xbutton.x; Mx->ypos = GEvent.xbutton.y;
        if (GEvent.xbutton.button==Button3) goto DONE;
        if (GEvent.xbutton.button==Button1 &&
          drag_widget(xc,yc,xs,ys,ycc,&i,&j,&ip) && (i || j)) 
           { xc+=i; yc+=j; xsave+=i; ysave+=j; goto DRAWBOX; }
      }
    }
    m_pause (POLLTIME);
  }

  DONE:
  COPYBACK (xc, yc, xs, ys, ip);
  XFlush (G.D);
}

/************************ SCROLLBOX **************************

  type = 0:     Normal, does not fill screen, allows selection
  type = 1:     Fills screen, allows hiliting/selection
  type = 2:     Fills screen, no hiliting - used for reading text
  type = 3:     Does not fill screen, no hiliting/selection

*************************************************************/

void mx_scrollbox (const char *text, const int_4 line[],
	      int_4 *lines, int_4 *maxlen, int type)
{
  int    i, j, k, l, len, icon=0, pos=1, npos, maxpos, select, last;
  int    x, y, xc, yc, xs, ys, xcc, ycc, xss, yss, xsave=(-1), ysave;
  int    xsb, ysb, xsc, ysc, xt, yt, dx, dy, bw=GBorder;
  int    xboxx, xboxw, xbarh, xbarw, yboxy, yboxh, ybarh, ybarw, xfw;
  int	 xfw2, xfy2;
  int    maxline, lineh, beg=0, nbeg, maxbeg, linew, ysbtot;
  int    lfi=0, lf=0, find=0, lfx, lfy, lfw, arrow, yhi, maxsel;
  int	 xtack, ytack, btnx[6];
  bool   redraw, held=FALSE, exposure, resize, selectable, multi, fullwin;
  bool   okcancel;
  bool_4 autorepeat;
  char   search[20];
  float  yboxd, xboxd;
  XPoint pix[3];
  Pixmap ip=0;
  int_1 *sel;
  XRectangle rect;

#define SCROLLBOX_FILL(LINE_INDEX) \
          FILLRECTANGLE (G.gcm, xt, yhi+(LINE_INDEX)*ysb, xsb*linew, ysb)

#define SCROLLBOX_FIND_FILL \
          FILLRECTANGLE (G.gcm, lfx+1, lfy+1, lfw-2, ysb-2)

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

  if (*lines<=0 || *maxlen<=0) return;
  if (type >= 0 && type <= 3) {
    fullwin = (type==1 || type==2);
    selectable = (type < 2);
    multi = FALSE;
  }
  else {
    fullwin = (type & 1);
    selectable = (type & 2);
    multi = (type & 4);
  }
  maxsel = 1;
  maxline = max (1, *lines);
  okcancel = FALSE;
  if (multi) {
    sel = (int_1 *)malloc(maxline * sizeof(int_1));
    for (i=0; i < maxline; i++) sel[i] = 0;
    for (i=0, maxsel=lines[1], okcancel=(maxsel>1); (j=lines[i+2]); i++) {
      if (j > 0) { sel[j-1] = 1; okcancel = TRUE; }
      else sel[-j-1] = -1;
    }
  }
  mx_setbar ("ScrollBox: LM/MM-Select or Scroll RM-NoSelect Exit",45);
  mx_autorepeat (&autorepeat, 2);

  DRAWBOX:
  xsb = Mx->text_w;
  ysb = Mx->text_h;
  arrow = ysb;

  if (fullwin) lineh = maxline;
  else lineh = min (maxline, 20);

  linew = line[1] + 12;		/* length of "Find", "Next" and title */
  if (okcancel) linew += 14; /* "All" and "Clear" */
  linew = max (linew, *maxlen);

  xss = (linew+2) * xsb;
  yss = (lineh+1) * ysb;
  xtack = 3*GBorder + 2*xsb;
  ytack = 6*GBorder + 2*ysb;
  if (okcancel) ytack += 3*GBorder + ysb;

  xs = xss + xtack;            /* dimensions with scrollbars */
  ys = yss + ytack;

  if (xs>Mx->width && linew > 10) {   /* X-Widget won't fit in x dimension */
    i = (xs - Mx->width)/xsb + 1;
    i = min(i, linew-1);
    linew -= i;
    xss -= i*xsb;
    xs = xss + xtack;
  }                             /* lines not shortened, no xscrollbar */
  if (linew >= *maxlen) {
    ytack -= GBorder + ysb;
    if (okcancel) ytack -= GBorder;
    ys = yss + ytack;
  }

  if (ys>Mx->height && lineh > 1) {   /* X-Widget won't fit in y dimension */
    i = (ys - Mx->height)/ysb + 1;
    i = min(i, lineh-1);
    lineh -= i;
    yss -= i*ysb;
    ys = yss + ytack;
  }                             /* all lines in view, no y scrollbar */
  if (lineh >= *lines) {
    xtack -= GBorder + 2*xsb;
    if (okcancel) xtack -= GBorder;
    xs = xss + xtack;
  }

  /* fill the entire screen */
  if (fullwin) {
    xc = 1; xs = Mx->width-2;
    i = (Mx->width-xs) / xsb;
    linew += i;
    yc = 1; ys = Mx->height-2;
    i = (Mx->height-ys) / ysb;
    lineh += i;
  }
  else {
    if (xsave<0) {xsave = Mx->xpos; ysave = Mx->ypos;}
    xc = xsave - xs/2;  xc = bound( xc, 1, Mx->width-xs-2 );
    yc = ysave - ys/2;  yc = bound( yc, 1, Mx->height-ys-2 );
  }
  maxbeg = max (0, *maxlen - linew);
  maxpos = 1 + max (0, maxline - lineh);

  STOREBACK (xc, yc, xs, ys, ip);

  xcc = xc + GBorder;
  ycc = yc + 4*GBorder + ysb;
  xt = xcc + xsb;
  yt = ycc + ysb;
  yhi = yt - 0.8*ysb + 1;

  xss = xs - xtack;
  yss = ys - ytack;

  /* main scrollbox X-Widget with text box carved out */
  mx_widgetbox (xc,yc,xs,ys,xcc+GBorder,ycc,xss-2*GBorder,yss-GBorder,
    (line[1]<=0) ? "SCROLLBOX" : &text[line[0]-1],
    (line[1]<=0) ? 9 : line[1]);

  if (lineh < maxline) {                        /* draw yscrollbar */
    xsc = xcc + xss + 1;
    yboxy = ycc + arrow + 4;
    yboxh = max(1,yss-2*(arrow+4)-GBorder);
    yboxd = (float) yboxh / (float) maxline;
    ybarh = yboxd*lineh+1;
    ybarw = max(1, 2*xsb-4);
    XClearArea (G.D, G.W, xsc, ycc, 2*xsb, yss-GBorder, FALSE);
    xsc += 2;
    mx_shadowbox (xsc, ycc+2, ybarw, arrow, L_ArrowUp, 2, "", 0);
    mx_shadowbox (xsc, ycc+yss-GBorder-arrow-2,
      ybarw, arrow, L_ArrowDown, 2, "", 0);
  }
  else xsc = 0;

  if (linew < *maxlen) {                        /* draw xscrollbar */
    ysc = ycc + yss + 1;
    xboxx = xcc + GBorder + arrow + 4;
    xboxw = max(1,xss-2*(GBorder+arrow+4));
    xboxd = (float) xboxw / (float) *maxlen;
    xbarw = xboxd*linew+1;
    xbarh = max(1, ysb-4);
    XClearArea (G.D, G.W, xcc+GBorder, ysc, xss-2*GBorder, ysb, FALSE);
    ysc += 2;
    mx_shadowbox (xcc+GBorder+2, ysc, arrow, xbarh, L_ArrowLeft, 2, "", 0);
    mx_shadowbox (xcc+xss-arrow-GBorder-2, ysc,
      arrow, xbarh, L_ArrowRight, 2, "", 0);
  }
  else ysc = 0;

  /* Draw buttons and extra bevelling */
  {
    int i, j, y, w, bw;
    int minwidth, margin;
    char *btns[6] = { "Find", "Next", "All", "Clear", "OK", "Cancel" };
    bw = GBorder;

    y = yc + 2*bw;
    xfw = xsb*5 + 2*bw;
    minwidth = ((xs - 2*bw - 2*xsb - line[1]*xsb) / ((okcancel)?4:2)) - 1;
    minwidth = max(2*xsb, minwidth);
    xfw = min(xfw, minwidth);
    btnx[0] = xcc + bw;
    btnx[1] = xc + xs - xfw - bw - 2;

    GBorder = 1;
    btnx[2] = btnx[3] = -xfw;
    if (okcancel) {
      if (maxsel > 1) {
	margin = (xs - 4*xfw - line[1]*xsb - 4*bw) / 4;
	margin = max(1, margin);
	btnx[2] = btnx[0] + xfw + bw + margin;
	btnx[3] = btnx[1] - btnx[2] + btnx[0];
      }
      xfy2 = yc + ys - 3*bw - ysb;
      xfw2 = xsb*7 + 2*bw;
      i = (xfw2*3 > xs-2*bw) ? 2 : 3;
      minwidth = (xs-2*bw)/i - i - 1;
      xfw2 = min(minwidth, xfw2);
      margin = (xs - i*xfw2 - 2*bw) / (i+1);
      btnx[4] = xcc + margin + (i-2)*(xfw2 + margin);
      btnx[5] = btnx[4] + xfw2 + margin;
    }
    else xfw2 = 0;

    for (i=0,w=xfw; i<6; i++) {
      if (w>0 && btnx[i] > -w) {
	mx_shadowbox(btnx[i], y, w, ysb+bw, 1, 2, btns[i], strlen(btns[i]));
	if (!G.BW) DRAWRECTANGLE(G.gcti, btnx[i]-1, y-1, w+1, ysb+bw+1);
      }
      if (i==3) { y = xfy2; w = xfw2; }
    }
    rect.x = xcc+bw+1;
    rect.y = ycc+1; 
    rect.width = xss-2*bw-2;
    rect.height = yss-bw-2;
    /* Extra bevelling around the text window and scrollbars */
    if (!G.BW) {
      GBorder = 1;
      mx_shadowbox(rect.x-1, rect.y-1, rect.width+2, rect.height+2, 1,-1,"",0);
      if (ysc) mx_shadowbox(rect.x-1, ysc-2, rect.width+2, ysb, 1,-1,"",0);
      if (xsc) mx_shadowbox(xsc-2, rect.y-1, 2*xsb, rect.height+2, 1,-1,"",0);
    }
    GBorder = bw;
  }

  npos = bound(pos,1,maxpos); pos = -1;
  nbeg = bound(beg,0,maxbeg); beg = -1;
  ysbtot = lineh*ysb;

  PROCESSICON:
  select = 0; last = 0;
  if (xsc) {
    switch (icon) {
      case 1: case -1: case -11: npos = pos - 1; break;
      case 2: case -2: case -12: npos = pos + 1; break;
      case 3: npos = pos - lineh + 1; break;
      case 4: npos = pos + lineh - 1; break;
    }
    npos = bound(npos,1,maxpos);
    if (icon==-11) select = npos;
    else if (icon==-12) select = npos + lineh - 1;
  }

  if (ysc) {
    switch (icon) {
      case 6: case -6: nbeg = beg - 1; break;
      case 7: case -7: nbeg = beg + 1; break;
      case 8: nbeg = beg - linew + 1; break;
      case 9: nbeg = beg + linew - 1; break;
    }
    nbeg = bound(nbeg,0,maxbeg);
  }

  WRITETEXT:
  if (npos!=pos || nbeg!=beg || icon==-3) {
    if (xsc && npos != pos) {
      if (pos>=1)  XClearArea (G.D, G.W, 
	xsc, yboxy+(int)(yboxd*(pos-1)), ybarw, ybarh, FALSE);
      mx_shadowbox (xsc,yboxy+(int)(yboxd*(npos-1)), ybarw,ybarh, 1,2, "",0);
    }
    if (ysc && nbeg != beg) {
      if (beg>=0) XClearArea (G.D, G.W, 
	xboxx+(int)(xboxd*beg), ysc, xbarw, xbarh, FALSE);
      mx_shadowbox (xboxx+(int)(xboxd*nbeg),ysc, xbarw,xbarh, 1,2, "",0);
    }
    pos = npos; beg = nbeg;
    XClearArea (G.D, G.W, rect.x, rect.y, rect.width, rect.height, FALSE);
    XSetClipRectangles (G.D, G.gct, 0, 0, &rect, 1, Unsorted);
    XSetClipRectangles (G.D, G.gcm, 0, 0, &rect, 1, Unsorted);
    y = yt;   k = 2*pos;  lfw = 0;
    for (i=0;i<lineh+1;i++) {
      j = line[k]+beg-1;
      l = bound (line[k+1]-beg, 0, linew+1);
      if (i+pos<=maxline) {
        DRAWTEXT (xt, y, text+j, l);
        if (i+pos==find && lfi<j+l && lfi>j-abs(lf)) {/* highlight find text */
          x = max(lfi,j); lfw = xsb * (min (j+l-x, lfi+abs(lf)-x));
          lfx = xt + (x-j)*xsb; lfy = yhi + (find-pos)*ysb;
          SCROLLBOX_FIND_FILL;
        }
	if (okcancel && sel[i+pos-1]>0) SCROLLBOX_FILL(i);
      }
      y += ysb;   k += 2;
    }
    XSetClipMask(G.D, G.gct, None);
    XSetClipMask(G.D, G.gcm, None);
    XFlush (G.D);
  }

  if (GEvent.type!=KeyPress && icon > 0) {
    if (held) { if (icon!=5 && icon!=10) m_pause (0.05); }
    else {
      for (i=0; i<5; i++) {
	while (getevent()) {
	  if (GEvent.type==ButtonRelease) goto POLLEVENT;
	  if (NEWEXPOSEORRESIZE) {icon=0; goto DRAWBOX;}
	}
	m_pause (0.05);
      }
      held=TRUE;
    }
    while (getevent()) {
      if (GEvent.type==ButtonRelease) goto POLLEVENT;
      if (NEWEXPOSEORRESIZE) {icon=0; goto DRAWBOX;}
      if (GEvent.type==MotionNotify) {
	if (icon==5)  {npos = (GEvent.xmotion.y-dy)/yboxd; goto PROCESSICON;}
	if (icon==10) {nbeg = (GEvent.xmotion.x-dx)/xboxd; goto PROCESSICON;}
      }
    }
    goto PROCESSICON;
  }

  POLLEVENT:
  held=FALSE; icon=0;
  while (icon==0) {
    for (i=0; !(XM_BREAK) && !getevent(); i++) {
      if (i<20) m_pause (0.05); else m_pause (POLLTIME);
    }
    if (XM_BREAK) icon = -998;
    else if (NEWEXPOSEORRESIZE) goto DRAWBOX;
    else switch (GEvent.type) {

      case MotionNotify:
        x = GEvent.xmotion.x - xcc; y = GEvent.xmotion.y - yt + ysb;
        if (x>0 && x<xss && y>0 && y<ysbtot) {
          select = pos + (lineh*y)/ysbtot;
        }
        else select = 0;
        break;

      case ButtonPress:
        Mx->xpos = GEvent.xbutton.x;
        Mx->ypos = GEvent.xbutton.y;
        switch (GEvent.xbutton.button) {
          case Button3:         /* quit */
	    select = 0;
	    icon = -998;
	    break;
          case Button2:
          case Button1:
	    {
	      int y = yc + 2*GBorder;
	      int w = xfw;
	      int h = ysb+bw;
	      int button, nbuttons = (okcancel) ? 6 : 2;
	      for (button=0; button<nbuttons && icon==0; button++) {
		if (button==4) { y = xfy2; w = xfw2; }
		if (Mx->ypos >= y && Mx->ypos<y+h &&
		    Mx->xpos >= btnx[button] && Mx->xpos < btnx[button]+w) 
		  switch (button) {
		    case 0: GKeysym = XK_f; goto FINDSTRING;
		    case 1: GKeysym = XK_n; goto FINDSTRING;
		    case 2: case 3:
		      for (i=0, j=3-button; i<maxline; i++)
			if (sel[i]>=0) sel[i] = j;
		      icon = -3;
		      break;
		    case 4: icon = -999; break;
		    case 5: icon = -998; break;
		  }
	      }
	    }
            x = Mx->xpos - xcc;
	    y = Mx->ypos - ycc;
	    if (icon!=0) { }
            else if (xsc && x>xss && x<xs && y>0 && y<yss) {    /* yscrollbar */
              y = Mx->ypos - yboxy;
	      icon = 5;
              if (y<0 && pos>1)                 icon = 1;
              else if (y>yboxh && pos<maxpos)   icon = 2;
	      else if (GEvent.xbutton.button == Button1) {
		if (y<yboxd*(pos-1))         icon = 3;
		else if (y>yboxd*(pos-1)+ybarh)   icon = 4;
	      }
              if (icon==5) {
		held=TRUE;
		if (GEvent.xbutton.button==Button1) npos = pos;
		else npos = (y-ybarh/2)/yboxd;
		dy = Mx->ypos - yboxd*npos;
	      }
            }
            else if (ysc && y>yss && y<ys && x>0 && x<xss) { /* xscrollbar */
              x = Mx->xpos - xboxx;
	      icon = 10;
              if (x<0 && beg>0)                 icon = 6;
              else if (x>xboxw && beg<maxbeg)   icon = 7;
	      else if (GEvent.xbutton.button == Button1) {
		if (x<xboxd*beg)                icon = 8;
		else if (x>xboxd*beg+xbarw)     icon = 9;
	      }
              if (icon==10) {
		held=TRUE;
		if (GEvent.xbutton.button==Button1) nbeg = beg;
		else nbeg = (x-xbarw/2)/xboxd;
		dx = Mx->xpos - xboxd*nbeg;
	      }
            }
            else if (drag_widget(xc,yc,xs,ys,ycc,&i,&j,&ip)) {
	      if (i ||j) { xc+=i; yc+=j; xsave+=i; ysave+=j; goto DRAWBOX; }
            }
            else if ((x>0 && x<xss && y>0 && y<yss) ||
                      Mx->xpos>xc+xs+5 || Mx->xpos<xc-5 || 
                      Mx->ypos>yc+ys+5 || Mx->ypos<yc-5) {
	      if (selectable) {
		if (!multi) icon = -999;
		else if (select && sel[select-1]>=0) {
		  sel[select-1] = 1 - sel[select-1];
		  SCROLLBOX_FILL(select-pos);
		  if (!okcancel) icon = -999;
		  else if (!(GEvent.xbutton.state&(ControlMask|ShiftMask))) {
		    for (i=0; i<maxline; i++) if (sel[i] > 0) sel[i] = 0;
		    sel[select-1] = 1;
		    icon = -3;
		  }
		}
	      }
	    }
        }
        break;

      case KeyPress:
        switch (GKeysym) {
          case XK_Return: icon = -999; break;             /* select */
          case XK_Q: case XK_q: select = 0; icon = -998;  break; /* quit */
          /* moving highlighting */
          case XKUP:    if (selectable) {
                           if (select == 0) select = pos+lineh-1;
                           else if (select <= pos) {
			     if (xsc && pos>1) icon = -11;
			   }
                           else --select;
                         }
                         else if (xsc && pos>1) icon = -1;
                         break;
          case XKDOWN:  if (selectable) {
                           if (select == 0) select = pos;
                           else if (select >= pos+lineh-1) {
			     if (xsc && pos<maxpos) icon = -12;
			   }
                           else ++select;
                         }
                         else if (xsc && pos<maxpos) icon = -2;
                         break;
          /* scrolling */
          case XKLEFT:  if (ysc && beg>0) icon = -6;                    break;
          case XKRIGHT: if (ysc && beg<maxbeg) icon = -7;       break;
	  case XK_Prior: icon = 3; break;
	  case XK_Next: icon = 4; break;
	  case XK_Find: GKeysym = XK_f;
          case XK_f: case XK_F:
          case XK_n: case XK_N:
	    FINDSTRING:
	    Mx->xpos = xcc + xss/2;
	    Mx->ypos = ycc + yss/2;
	    redraw = FALSE;
	    if (GKeysym==XK_F || GKeysym==XK_f || lf==0) {   /* find prompt */
	      for (i=abs(lf); i<20; i++) search[i] = ' ';
	      i = GKeysym;
	      CLEAR_EXPOSE_AND_RESIZE (exposure, resize);
	      mx_prompt ("Search String: ",search,15,19);
	      redraw = (redraw_widget(exposure,resize));
	      GKeysym = i;
	      /* prep search string by trimming and upcasing it */
	      for (j=20; j>0 && search[j-1]==' '; --j);
	      for (lf=0; lf<j; lf++) search[lf] = toupper(search[lf]);
	      if (GKeysym == XK_F || GKeysym == XK_N) lf = -lf;
	    }
	    /*********************************
	    * Here begins the actual search for the search string. If
	    * found, lfi is the index into text where the current match
	    * begins, find is the line number it is on, and abs(lf) is the
	    * length of the search string, if lf<0 we're in a bkwrd search
	    **********************************/

	    if (lf==0) { find=0; lfi=0; if (redraw) goto DRAWBOX; }
	    else {
	      /* Here k is the index of text[] where we're going to end our
		 search and j is the index in line[] of the current line
		 we're looking at. lfi is the index into text where we start
		 our search.  We begin at either the first character in the
		 scrollbox, or if we've currently found an item, at the
		 second character of that item.  We must set the end of our
		 search (k) to at least one char before this starting point
		 so we don't fail the (lfi!=k) test the first time through.
		 When we've hit the end of the text and loop back to the
		 beginning, we can move k up to its original position so that
		 the (lfi!=k) test will fail properly (the ++k when j>l). */

	      /* forward search */
	      len = abs(lf);
	      if ((lf>0 && (GKeysym==XK_n||GKeysym==XK_f)) ||
		  (lf<0 && GKeysym==XK_N) ) {
		if (lfi==0) { /* start at screen top */
		  j = 2*pos;
		  lfi = line[j]-1;
		}
		else {       /* start after cur find */
		  j = 2*find;
		  ++lfi;
		}
		k = lfi - 1; find = 0; l = 2 * maxline;
		while (find==0 && lfi!=k) {
		  if (line[j+1] >= len) {
		    x = line[j] + line[j+1] - len;
		    for (i=0; lfi<x && lfi!=k && i<len; lfi++)
		      if (search[0] == toupper(text[lfi]))
			for (i=1;i<len && search[i]==toupper(text[lfi+i]);i++);
		    if (i==len) {find = j/2; --lfi;}
		  }
		  if (find==0 && lfi!=k) {
		    if ((j+=2)>l) {j=2; ++k;}
		    lfi=line[j]-1;
		  }
		}
	      }
	      else {                  /* backward search */
		if (lfi==0) {                  /* start at bottom of screen */
		  j = 2*(pos+lineh-1);
		  lfi = line[j] + line[j+1] - len - 1;
		}
		else {                        /* start before cur find */
		  j = 2*find;
		  --lfi;
		}
		k = lfi + 1; find = 0; l = 2 * maxline;
		while (find==0 && lfi!=k) {
		  if (line[j+1] >= len) {
		    x = line[j]-2;
		    for (i=0; lfi>x && lfi!=k && i<len; lfi--)
		      if (search[0] == toupper(text[lfi]))
			for (i=1;i<len && search[i]==toupper(text[lfi+i]);i++);
		    if (i==len) {find = j/2; ++lfi;}
		  }
		  if (find==0 && lfi!=k) {
		    if ((j-=2)<2) {j=l; --k;}
		    lfi=line[j] + line[j+1]-len-1;
		  }
		}
	      }
	      if (find==0) { lfi=0; redraw|=wmessage("String not found",1.0); }
	      else {
		if (find<pos || find>=pos+lineh) {    /* Can't see line */
		  npos = find - lineh/2; icon = -5; /* scroll text up,dn */
		  if (redraw) pos = npos;
		}
		j = line[2*find]-1;                   /* Can't see word */
		if (lfi<j+beg || lfi+len>j+beg+linew) {
		  if (lfi<j+beg) nbeg=lfi-j; else nbeg=lfi+len-(j+linew);
		  icon = -10; if (redraw) beg = nbeg;
		}
	      }
	      if (redraw) goto DRAWBOX;
	    }
	    if (lfw!=0) { SCROLLBOX_FIND_FILL; lfw = 0; }
	    if (icon==0 && find>=pos && find<pos+lineh) {
	      k=find*2; j=line[k]+beg-1; l=bound (line[k+1]-beg,0,linew);
	      if (lfi<j+l && lfi>j-len) {
		x = max(lfi,j); lfw = xsb * min (j+l-x, lfi+len-x);
		lfx = xt + (x-j)*xsb; lfy = yhi + (find-pos)*ysb;
		SCROLLBOX_FIND_FILL;
	      }
	    }

	    break;
	  case XK_Select:
	  case XK_space:
	    if (selectable) {
	      if (!multi) icon = -999;
	      else if (multi && sel[select-1]>=0) {
		sel[select-1] = 1 - sel[select-1];
		SCROLLBOX_FILL (select-pos);
		if (!okcancel) icon = -999;
	      }
	    }
	    break;
          case XK_h: case XK_H:
            if (select==find) select = 0;
            else if (find>=pos && find<pos+lineh) select = find;
            break;
          case XK_question: case XK_Help:  case XK_slash:
            Mx->xpos = GEvent.xkey.x;
            Mx->ypos = GEvent.xkey.y;
	    CLEAR_EXPOSE_AND_RESIZE (exposure, resize);
            mx_mhelp ("~SCROLLBOX","X-Widget",10,8);
            if (redraw_widget(exposure,resize)) goto DRAWBOX;
            break;
          default:
            if (xsc && GKey[0]>='0' && GKey[0]<='9') {
              icon = -5;
              i = GKey[0]-'0';
              npos = maxline*i/9 - lineh/2;
            }
        }

    } /* switch on GEvent.type */

    if (select != last && selectable) {
      for (i=0; i<2; i++) {
        if (last!=0 && (!multi || sel[last-1] >= 0)) {
	  if (okcancel)
	    DRAWRECTANGLE (G.gcm,xt-2,yhi+(last-pos)*ysb-2,xss-2*xsb+4,ysb+3);
	  else
	    FILLRECTANGLE (G.gcm,xt,yhi+(last-pos)*ysb,xss-2*xsb,ysb);
	}
        last = select;
      }
    }
  }
  if (icon != -999 && icon != -998) goto PROCESSICON;

  if (selectable) {
    if (multi) {
      j = 0;
      if (icon == -999) for (i=0; i<maxline; i++) {
	if (sel[i] > 0) {
	  if (j<maxsel) {
	    lines[j+2] = i+1;
	    ++j;
	  }
	  else if (!XM_BREAK) {
	    int le;
	    char err_msg[80];
	    CLEAR_EXPOSE_AND_RESIZE (exposure, resize);
	    le = XSPRINTF(err_msg,
	      "Exceeded maximum allowable selections: %d", maxsel);
	    mx_message(err_msg, -1.0, le);
            if (redraw_widget(exposure,resize)) goto DRAWBOX;
	    else goto POLLEVENT;
	  }
	}
      }
      *maxlen = j;
    }
    else if (icon==-999 && select>0 && select <= maxline) {
      *lines = line[2*select];
      *maxlen = line[2*select+1];
    }
    else {
      *lines = 1;
      *maxlen = 0;
    }
  }
  if (multi) free(sel);

  COPYBACK (xc, yc, xs, ys, ip);
  mx_unsetbar ();
  mx_autorepeat (&autorepeat, 0);
  XFlush (G.D);
}

/**************************** WIDGETBOX *****************************/

void mx_widgetbox (int xc, int yc, int xs, int ys, int xcc, int ycc,
        int xss, int yss, const char *name, int length)
{
  int xt, yt;

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

  if (G.BW) FILLRECTANGLE (G.gct, xc, yc, xs, ys);
  else mx_shadowbox (xc, yc, xs, ys, 1, 2, "", 0);
  if (G.BW || Mx->xwbg==Mx->xwlo) DRAWRECTANGLE(G.gcti, xc, yc, xs-1, ys-1);
  /* Title X-Widget */
  if (length>0) {
    length = min(length, xs/Mx->text_w);
    length = max(length, 1);
    xt = xc + (xs - length*Mx->text_w) / 2;
    yc += GBorder;
    yt = yc + (ycc - yc + .7*Mx->text_h)/2;
    if (G.BW) XDrawString (G.D, G.W, G.gcm, xt, yt, name, length);
    else XDrawString (G.D, G.W, G.gctw, xt, yt, name, length);
  }
  if (xss>0 && yss>0) XClearArea(G.D, G.W, xcc, ycc, xss, yss, FALSE);

}

#define FILLPOLY(GC,PIX,NPTS) \
XFillPolygon (G.D, G.W, GC, (XPoint *)PIX, NPTS, Nonconvex, CoordModeOrigin)

#define DRAWPOLY(GC,PIX,NPTS) \
XDrawLines (G.D, G.W, GC, (XPoint *)PIX, NPTS, CoordModeOrigin)

#define FILLPIX(DX0,DY0,DX1,DY1,DX2,DY2) \
pix[0].x = x+DX0;		pix[0].y = y+DY0; \
pix[1].x = x+DX1;		pix[1].y = y+DY1; \
pix[2].x = x+DX2;		pix[2].y = y+DY2; \
pix[3] = pix[0]

void mx_shadowbox (int x, int y, int w, int h, int shape, int func,
  const char *label, int length)
{ 
  int n, m, xt, yt, bw=0;
  bool fill;
  XPoint pix[11];

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

  fill = !(func==1 || func==-1);
  
  if (G.BW) {
    if (shape==1) {
      if (fill) {
	if (func==0) FILLRECTANGLE(G.gct, x, y, w, h);
	else DRAWRECTANGLE(G.gcm, x, y, w-1, h-1);
      }
      if (!fill || func<0) {
	if (func!=0 && GBorder>0) bw = 2;
	FILLRECTANGLE(G.gcm, x+bw, y+bw, w-2*bw, h-2*bw);
      }
    }
    else if (fill) {
      switch (shape) {
	case L_ArrowLeft:  FILLPIX(w,0, 0,h/2, w,h); break;
	case L_ArrowRight: FILLPIX(0,h, w,h/2, 0,0); break;
	case L_ArrowUp:    FILLPIX(0,h, w/2,0, w,h); break;
	case L_ArrowDown:  FILLPIX(w,0, w/2,h, 0,0); break;
      }
      if (func==0) FILLPOLY(G.gct, pix, 4);
      else DRAWPOLY(G.gct, pix, 4);
    }
  }
  else {
    int j = (shape==L_ArrowLeft || shape==L_ArrowUp) ? 1 : 2;

    if (func!=0 && GBorder>0) {
      bw = min(w,h)/3;
      bw = max (1, min (bw, GBorder));
    }

    if (bw > 0) {

      /* outer shape */
      pix[0].x = pix[1].x = x;
      pix[8].x = pix[9].x = x+w;
      pix[1].y = pix[8].y = y;
      pix[0].y = pix[9].y = y+h;

      switch (shape) {
	case L_ArrowLeft:  pix[0].y = pix[1].y = y+h/2; x+=2; --w; break;
	case L_ArrowRight: pix[8].y = pix[9].y = y+h/2; --x; --w; break;
	case L_ArrowUp:    pix[1].x = pix[8].x = x+w/2; y+=2; --h; break;
	case L_ArrowDown:  pix[0].x = pix[9].x = x+w/2; --y; --h; break;
      }
      pix[2] = pix[8];
      pix[10] = pix[0];

      x += bw;
      y += bw;
      w -= 2*bw;
      h -= 2*bw;

    }

    /* inner shape */
    pix[4].x = pix[5].x = x;
    pix[3].x = pix[6].x = x+w;
    pix[3].y = pix[4].y = y;
    pix[5].y = pix[6].y = y+h;
    switch (shape) {
      case L_ArrowLeft:  pix[4].y = pix[5].y = y+h/2; break;
      case L_ArrowRight: pix[3].y = pix[6].y = y+h/2; break;
      case L_ArrowUp:    pix[3].x = pix[4].x = x+w/2; break;
      case L_ArrowDown:  pix[5].x = pix[6].x = x+w/2; break;
    }
    pix[7] = pix[3];

    if (bw > 0) {
      XSetForeground(G.D, G.gctw, (func>0) ? Mx->xwts : Mx->xwbs);
      FILLPOLY(G.gctw, pix, 6);
      if (shape!=1) DRAWPOLY(G.gctw, pix, 6);

      XSetForeground(G.D, G.gctw, (func<0) ? Mx->xwts : Mx->xwbs);
      FILLPOLY(G.gctw, &pix[5], 6);
      if (shape!=1) DRAWPOLY(G.gctw, &pix[5], 6);
    }

    if (fill) {
      XSetForeground(G.D, G.gctw, Mx->xwbg);
      FILLPOLY(G.gctw, &pix[3], 5);
      if (shape!=1) DRAWPOLY(G.gctw, &pix[3], 5);
    }

    XSetForeground(G.D, G.gctw, Mx->xwfg);
  }


  if (fill && length>0) {
    length = min(length, w/Mx->text_w);
    length = max(length, 1);
    xt = x + (w - length*Mx->text_w) / 2;
    yt = y + (h + .7*Mx->text_h) / 2;
    XDrawString (G.D, G.W, (G.BW)?G.gcm:G.gctw, xt, yt, label, length);
  }


}

bool wmessage (const char *str, float time)
{
  bool exposure, resize;
  CLEAR_EXPOSE_AND_RESIZE (exposure, resize);
  mx_message (str, time, strlen(str));
  return redraw_widget(exposure,resize);
}
