


/*
 * Internal procedures for the above code.
 * ----------------------------------------
 */

/* get_colormap -- Get a private colormap.  On all calls after the first
 * this just returns the existing gterm widget colormap.  On the first call
 * we query the server for the named custom colormap, and if the colormap
 * exists we modify the gterm widget to use it.  If the custom colormap has
 * not yet been created by some other client, we create it.
 *
 * This code creates a custom colormap using the "standard colormap"
 * facilities provided by XLIB.  Although we do not use any of the predefined
 * standard colormaps, use of the standard colormap facilities allows any
 * number of clients to share the same custom colormap.  Use of a custom
 * colormap helps avoid running out of space in the default colormap, ensures
 * that the gterm widget will get the color cells it needs, and  makes it
 * easier for several imaging clients which share the same colormap to
 * simultaneously display their windows.
 *
 * To minimize colormap flashing we try to avoid using the full colormap,
 * setting the unused cells to the colors set in the default colormap.  In
 * most cases this will prevent the rest of the screen from changing color
 * when the custom colormap is installed.
 */
static Colormap
get_colormap (GtermWidget w)
{
    int i, j;
    Display *display = w->gterm.display;
    Screen *screen = w->gterm.screen;
    XColor def_colors[SZ_STATIC_CMAP], *cp, *c1, *c2;
    XStandardColormap cm, *cm_p;
    XColor colors[MAX_SZCMAP];
    int base_pixel, p1, p2;
    Colormap colormap;
    char property[128];
    int ncmap, nitems;
    Pixel pixel;
    Atom atom;


    if (DBG_TRACE && DBG_CM_VERB)
 	fprintf (stderr, "get_colormap: have=%d ncols=%d maxcols=%d base=%lu\n",
	    w->gterm.haveColormap, w->gterm.ncolors, w->gterm.maxColors,
	    w->gterm.base_pixel);

    if (w->gterm.haveColormap || w->gterm.useGlobalCmap)
	return (w->core.colormap);

    /* Map custom colormap name to atom. */
    sprintf (property, "GT_%s", w->gterm.cmapName);
    atom = XInternAtom (display, property, False);
    w->gterm.cmapAtom = atom;


    /* Get custom colormap.
     */
    if (DBG_TRACE && DBG_CM_VERB)
 	fprintf (stderr, "get_colormap: cmapInitialize=%d GetRGB=%d of %d\n",
	    w->gterm.cmapInitialize,
	    XGetRGBColormaps (display, w->gterm.root, &cm_p, &ncmap, atom),
	    ncmap);


    if (!w->gterm.cmapInitialize &&
	    XGetRGBColormaps (display, w->gterm.root, &cm_p, &ncmap, atom)) {

	/* Colormap aleady exists, just use it.
	 */
	cm = *cm_p;
	colormap = cm.colormap;
	w->gterm.base_pixel = cm.base_pixel;

	if (DBG_TRACE)
 	    fprintf (stderr, "get_colormap: use existing cmap=0x%lx; base=%lu\n",
	        colormap, w->gterm.base_pixel);


    } else if (w->gterm.w_depth > 8) {

	/*  Setup for TrueColor visual.
	 */
	if (DBG_TRACE && DBG_CM_VERB)
 	    fprintf (stderr, "get_colormap: TrueColor gt.ncolors=%d base=%lu\n",
	        w->gterm.ncolors, w->gterm.base_pixel);

        nitems = MAX_SZCMAP;
        w->gterm.ncolors = SZ_STATIC_CMAP + w->gterm.maxColors;

        /* Get a private colormap.
	*/
        for (i=0;  i < MAX_SZCMAP;  i++)
	    colors[i].pixel = i;

    	for (i = SZ_STATIC_CMAP;  i < w->gterm.ncolors;  i++) {
	    w->gterm.color[i].pixel = w->gterm.cmap[i] = pixel =
	        min (nitems - 1, w->gterm.base_pixel + i - SZ_STATIC_CMAP);

	    w->gterm.color[i] = colors[pixel];
    	}

	if (DBG_TRACE)
	    fprintf (stderr,
		"\n\nget_colormap:  gt.ncolors = %d  maxcols = %d\n\n\n", 
		w->gterm.ncolors, w->gterm.maxColors);

	goto use_default;

    } else {
	/* Create or reinitialize a global colormap.
	 */
	XVisualInfo template, *vi;
	Display *d;
	Screen *s;
	Window root;
	long mask;


	if (DBG_TRACE)
 	    fprintf (stderr, "get_colormap: ...creating colormap, ncols=%d\n",
		w->gterm.ncolors);


	if (!(d = XOpenDisplay (DisplayString(display))))
	    goto use_default;
	s = DefaultScreenOfDisplay (d);
	root = DefaultRootWindow (d);

	/* Try to get a pseudocolor visual. */
	mask = 0;
	template.screen = DefaultScreen(d);	mask |= VisualScreenMask;
	template.depth  = RasterDepth;		mask |= VisualDepthMask;
	template.class  = PseudoColor;		mask |= VisualClassMask;

	if (!(vi = XGetVisualInfo (d, mask, &template, &nitems))) {
	    XCloseDisplay (d);
	    goto use_default;
	}

	/* Create custom colormap with all cells allocated read/write */
	colormap = XCreateColormap (d, root, vi->visual, AllocAll);

	/* Initialize colormap to be same as default colormap. */
	nitems = min (MAX_SZCMAP, CellsOfScreen(s));
	for (i=0;  i < nitems;  i++)
	    colors[i].pixel = i;
	XQueryColors (d, DefaultColormapOfScreen(s), colors, nitems);
	XStoreColors (d, colormap, colors, nitems);

	/* Globally define permanent server custom colormap. */
	memset ((char *)&cm, 0, sizeof(cm));
	cm.colormap = colormap;
	cm.base_pixel = w->gterm.base_pixel;
	cm.red_max = 0;
	cm.visualid = vi->visualid;
	cm.killid = 1;
	XSetRGBColormaps (d, root, &cm, 1, atom);

	XSetCloseDownMode (d, RetainPermanent);
	XCloseDisplay (d);
	w->gterm.cmapInitialize = False;

	/* Free the XVisualInfo struct. */
	if (vi)
	    XFree ((void *)vi);					/* MF040 */


	if (DBG_TRACE && DBG_CM_VERB)
 	    fprintf (stderr, "get_colormap: .............creating done\n");
    }

    /* Save default color assignments for static colors. */
    for (i=0;  i < SZ_STATIC_CMAP;  i++)
	def_colors[i] = w->gterm.color[i];

    nitems = min (MAX_SZCMAP, CellsOfScreen(screen));
    w->gterm.ncolors = SZ_STATIC_CMAP + w->gterm.maxColors;
    base_pixel = w->gterm.base_pixel;

    if (DBG_TRACE && DBG_CM_VERB)
	fprintf (stderr,
	    "\n\nget_colormap:  gt.ncolors = %d  maxcols = %d\n\n\n", 
	    w->gterm.ncolors, w->gterm.maxColors);

    /* Get the private colormap. */
    for (i=0;  i < nitems;  i++)
	colors[i].pixel = i;
    XQueryColors (display, colormap, colors, nitems);

    /* Initialize the raster pixel to display pixel mapping and set the
     * color assigned to each pixel value in the private colormap.
     */
    for (i = SZ_STATIC_CMAP;  i < w->gterm.ncolors;  i++) {
	w->gterm.color[i].pixel = w->gterm.cmap[i] = pixel =
	    min (nitems - 1, base_pixel + i - SZ_STATIC_CMAP);
	w->gterm.color[i] = colors[pixel];
    }

    /* Check the static part of the cmap to make sure that the pixel numbers
     * aren't aliased to pixels in the dynamic part of the custom colormap.
     * If this happens, reassign these color numbers to the pixels just
     * preceeding the dynamic part of the custom colormap.  The red_max
     * field of the colormap descriptor is used to keep track of the number
     * of static colors allocated by different clients.  These static colors
     * are shared, hence the same color will not be stored twice.
     */
    p1 = p2 = base_pixel - cm.red_max;
    for (i=0;  i < SZ_STATIC_CMAP;  i++) {
	pixel = w->gterm.cmap[i];
	if (pixel >= base_pixel && pixel < base_pixel+DEF_MAXCOLORS && p1 > 2) {
	    /* First check to see if we already have a static entry reserved
	     * for this color.
	     */
	    c1 = &def_colors[i];
	    for (j=p1, cp=NULL;  j < base_pixel;  j++) {
		c2 = &colors[j];
		if (c1->red == c2->red && c1->green == c2->green &&
			c1->blue == c2->blue) {
		    cp = c2;
		    break;
		}
	    }

	    /* Assign a new screen pixel value. */
	    if (cp)
		w->gterm.cmap[i] = cp->pixel;
	    else {
		cp = &colors[--p1];
		*cp = def_colors[i];
		cp->flags = (DoRed | DoGreen | DoBlue);
		cp->pixel = w->gterm.cmap[i] = p1;
		cm.red_max++;
	    }
	    w->gterm.color[i].pixel = w->gterm.cmap[i];
	}
    }
    if (p1 < p2) {
	XStoreColors (display, colormap, &colors[p1], p2 - p1);
	XSetRGBColormaps (display, w->gterm.root, &cm, 1, atom);
    }

    /* Assign the new colormap to the gterm widget's window. */
    XtVaSetValues ((Widget)w, XtNcolormap, (XtArgVal)colormap, NULL);
    w->gterm.haveColormap++;

    /* If the pointer is in the window, advise window manager to load the
     * colortable for the window.
     */
    if (w->gterm.in_window)
	request_colormap_focus (w);

    return (colormap);

use_default:
    /* Unable to create custom colormap. */
    w->gterm.useDefaultCM++;
    w->gterm.haveColormap++;
    return (w->core.colormap);
}


/* request_colormap_focus -- Modify the WM_COLORMAP_WINDOWS property on a
 * widget's top level shell window to advise the window manager that the
 * widget's window should have its colormap loaded.  This should only be
 * used for windows that have a colormap different than that of the top
 * level window.
 */
static void
request_colormap_focus (GtermWidget w)
{
    Widget p;


    if (!w || !XtIsRealized ((Widget)w))
	return;

    /* Find the top level window. */
    for (p = XtParent(w);  !XtIsShell(p);  p = XtParent(p))
	;

    /* Modify WM_COLORMAP_WINDOWS to give the current window priority.
     */
    if (p) {
	Window window = XtWindow (p);
	Window *wl = NULL, n_wl[MAX_WMWIN+1];
	int n_nw, i;
	int nw;

	/* If WM_COLORMAP_WINDOWS is already set save its value, otherwise
	 * start a list initially containing only the top level window.
	 */
	w->gterm.wmTop = window;
	if (XGetWMColormapWindows (w->gterm.display, window, &wl, &nw)) {
	    memmove (w->gterm.wmWindows, (char *)wl, nw * sizeof(int));
	    w->gterm.n_wmWindows = nw = min (nw, MAX_WMWIN);
	    free ((char *)wl);
	} else {
	    w->gterm.wmWindows[0] = window;
	    w->gterm.n_wmWindows = nw = 1;
	}

	n_nw = 0;
	wl = w->gterm.wmWindows;
	n_wl[n_nw++] = XtWindow(w);

	for (i=0;  i < nw;  i++)
	    if (wl[i] != XtWindow(w))
		n_wl[n_nw++] = wl[i];

	XSetWMColormapWindows (w->gterm.display, window, n_wl, n_nw);
    }
}


/* restore_colormap_focus -- Reset WM_COLORMAP_WINDOWS.  Retain the window
 * that had the focus in the list, but drop its priority one notch.  This
 * should follow a prior call to request_colormap_focus.
 */
static void
restore_colormap_focus (GtermWidget w)
{
    int nw, n_nw, i;
    Window *wl, n_wl[MAX_WMWIN+1], old;

    if (!w || !XtIsRealized ((Widget)w))
	return;

    old = XtWindow(w);
    wl = w->gterm.wmWindows;
    if ((nw = w->gterm.n_wmWindows) == 0 || (nw == 1 && wl[0] == old))
	return;

    n_nw = 0;
    if (wl[0] != old)
	n_wl[n_nw++] = wl[0];
    n_wl[n_nw++] = old;

    for (i=1;  i < nw;  i++)
	if (wl[i] != old)
	    n_wl[n_nw++] = wl[i];

    XSetWMColormapWindows (w->gterm.display, w->gterm.wmTop, n_wl, n_nw);
}


/* inherit_default_colormap -- Set any unused cells of the custom colormap
 * to the colors defined for the corresponding cells of the default colormap.
 * This minimizes colormap flashing when using a custom colormap, but only
 * works if a few unused cells can be reserved, e.g., at the beginning of
 * the colormap (which is usually where X allocates its colors).
 */
static void
inherit_default_colormap (GtermWidget w)
{
    XColor *cp, *ap;
    int ncolors, i;
    Display *display = w->gterm.display;
    Screen *screen = w->gterm.screen;
    Window root = w->gterm.root;
    Atom atom = w->gterm.cmapAtom;
    XColor colors[MAX_SZCMAP];
    XStandardColormap *cm;
    int first, nitems, ncmap;



    if (DBG_TRACE)
 	fprintf (stderr, "inherit_default_cmap: ncols=%d maxcols=%d base=%lu\n",
	    w->gterm.ncolors, w->gterm.maxColors, w->gterm.base_pixel);

    if (!w || !XtIsRealized ((Widget)w))
	return;
    if (w->gterm.useDefaultCM || !w->gterm.haveColormap)
	return;
    if (w->gterm.base_pixel <= 0)
	return;				/* fully allocated colormap */

    /* We have to read the colormap property again as another client could
     * have reserved more static colors (i.e.,changed red_max), and we don't
     * want to clobber these colors.
     */
    if (XGetRGBColormaps (display, root, &cm, &ncmap, atom)) {
	/* Make sure we have the right colormap. */
	if (w->core.colormap != cm->colormap)
	    XtVaSetValues ((Widget)w,XtNcolormap,(XtArgVal)cm->colormap,NULL);

	/* Get lower part of default colormap. */
	ncolors = cm->base_pixel - cm->red_max;
	for (cp=colors, i=0;  i < ncolors;  i++, cp++) {
	    cp->flags = (DoRed | DoGreen | DoBlue);
	    cp->pixel = i;
	}

	/* Get upper part of default colormap. */
	first = cm->base_pixel + w->gterm.ncolors - SZ_STATIC_CMAP;
	ncolors = min (MAX_SZCMAP, CellsOfScreen(screen)) - first;
	for (i=0;  i < ncolors;  i++, cp++) {
	    cp->flags = (DoRed | DoGreen | DoBlue);
	    cp->pixel = first + i;
	}

	/* Inherit values from default colormap. */
	ncolors = cp - colors;
	XQueryColors (display, DefaultColormapOfScreen(screen),
	    colors, ncolors);
	XStoreColors (display, w->core.colormap, colors, ncolors);

	/* The global gterm colormap may have changed.  Compare widget's
	 * version of color table with the global colortable and update
	 * the widget's state if the global colortable has changed.
	 */
	ncolors = w->gterm.ncolors;
	memmove (colors, w->gterm.color, ncolors * sizeof(*cp));
	XQueryColors (display, w->core.colormap, colors, ncolors);
	for (i=ncolors, cp=colors, ap=w->gterm.color;  --i >= 0;  cp++, ap++)
	    if (cp->red != ap->red || cp->green != ap->green ||
		    cp->blue != ap->blue) {
		memmove (w->gterm.color, colors, ncolors * sizeof(*cp));
		invalidate_cmap (w);
	    }

    } else {
        if (DBG_TRACE)
 	    fprintf (stderr, "inherit_default_cmap: else XGetRGBColormaps\n");
    }
}


/* update_default_colormap -- Update the default colormap so that any
 * unallocated cells mirror the widget's custom colormap.  This increases
 * the chance that the widget's contents will be visible when the window
 * does not have the colormap focus, and minimizes flashing when the
 * colormap focus changes.
 */
static void
update_default_colormap (GtermWidget w)
{
    XColor *ip, *op;
    int j, n;
    Pixel v;

    XColor colors[MAX_SZCMAP];
    Pixel pixels[MAX_SZCMAP];
    char allocated[MAX_SZCMAP];
    int overflow, req, need, first, nelem, i;
    unsigned long plane_masks[1];
    Colormap defcmap;


    if (!w || !XtIsRealized ((Widget)w))
	return;
    if (w->gterm.useDefaultCM || !w->gterm.haveColormap)
	return;
    if (w->gterm.useGlobalCmap)				/* 24-bit 	*/
	return;

    if (DBG_TRACE)
	fprintf (stderr, "update_def_colormap:  ENTER\n");

    first = SZ_STATIC_CMAP;
    nelem = w->gterm.ncolors;

    defcmap = DefaultColormapOfScreen (w->gterm.screen);
    /* need = min (MAX_SZCMAP, first + nelem - SZ_STATIC_CMAP); */
    need = MAX_SZCMAP;

    /* Get the colormap cells. */
    for (req=need, n=0;  req > 0 && n < need;  )
	if (XAllocColorCells (w->gterm.display, defcmap, False,
		plane_masks, 0, &pixels[n], req)) {
	    n += req;
	} else
	    req /= 2;

    /* Perform the color matching.  This is awkward as the pixel value
     * assignments may be different in the two colormaps.  We have to look
     * up each pixel before attempting to assign a color, or XStoreColors
     * below will result in a server error.
     */
    memset (allocated, 0, sizeof(allocated));
    overflow = 0;

    for (i=0;  i < n;  i++) {
	v = pixels[i];
	if (v < MAX_SZCMAP)
	    allocated[v] = 1;
	else {
	    overflow++;
	    break;
	}
    }

    ip = &w->gterm.color[first];
    op = colors;
    if (overflow) {
	for (i=0;  i < nelem;  i++, ip++) {
	    for (j=0, v = ip->pixel;  j < n;  j++) {
		if (pixels[j] == v) {
		    *op++ = *ip;
		    break;
		}
	    }
	}
    } else {
	for (j=0;  j < nelem;  j++, ip++) {
	    if (allocated[ip->pixel]) {
		allocated[ip->pixel] = 0;
		*op++ = *ip;
	    }
	}
    }

    if (op > colors)
	XStoreColors (w->gterm.display, defcmap, colors, op - colors);

    if (!w->gterm.useGlobalCmap)
        XFreeColors (w->gterm.display, defcmap, pixels, n, 0);

done:
    if (DBG_TRACE)
	fprintf (stderr, "update_def_colormap:  LEAVING\n");
}




/* Global Colormap routines.
 */
static int SetGlobalCmap(GtermWidget w)
{
  static int init=0;

  if( !init ){
      strcpy (global_cmapname, XtNcmapName);
      global_ncolors = 0;
      global_mincolors = 0;
      init = 1;
  }
  if( !w->gterm.useGlobalCmap ){
      w->gterm.cmap = (Pixel *)XtCalloc(MAX_SZCMAP, sizeof(Pixel));
      w->gterm.color = (XColor *)XtCalloc(MAX_SZCMAP, sizeof(XColor));

      return(0);

  } else{
      w->gterm.cmap  = global_cmap;
      w->gterm.color = global_color;

      return(global_ncolors);
  }
}


static int ParseGlobalCmap(GtermWidget w)
{
  char *s;
  char *t;
  char *cmapname;
  int colors;
  int usedefault=0;

  /* process a directive such as "default[n:m,name]", where
   *  n = min number of colors that must be allocated or else
   *  use a private map called "name". m is the max colors to
   *  allocate (same as maxColors).  Either or both can be omitted
   */
  cmapname = w->gterm.cmapName;
  if( !strncmp (cmapname, "default", 7) ){
    usedefault = 1;
    if( (s=strchr(cmapname,'[')) != NULL ){
      /* skip open bracket */
      s++;
      /* get min number of colors */
      global_mincolors = strtol(s, &t, 10);
      /* for n:m syntax, get max colors */
      if( *t == ':' ){
        s = ++t;
        colors = strtol(s, &t, 10);
        if( colors > 0 )
          w->gterm.maxColors = colors;
      }
      /* look for default name */
      if( *t == ',' ){
        t++;
      }
      s = t;
      /* this is the new name of the cmap -- but it can't be "default"! */
      if( (strncmp(s, "default", 7)) && (*s != ']') ){
        strcpy(global_cmapname, s);
        /* null out closing bracket */
        if( (s = strchr(global_cmapname, ']')) != NULL )
          *s = '\0';
      }
      /* now make sure we can grab the min number of colors,
         or else set up to use a private color map */
      colors = GetMaxCmapColors(w);
      if( colors < global_mincolors ){
        usedefault = 0;
        w->gterm.haveColormap = 0;
        strcpy(w->gterm.cmapName, global_cmapname);
      }
      else{
        w->gterm.maxColors = min(w->gterm.maxColors, colors);
      }
    }
  }
  return(usedefault);
}

/*
 *
 * GetMaxCmapColors -- try to determine how many colors we can alloc in the
 * default colormap. We do this now in order set maxColors to this number,
 * to avoid the situation where a larger colormap is used that repeats
 * the actually-allocated colormap -- very ugly ...
 *
 */
static int GetMaxCmapColors(GtermWidget w)
{
  int n;
  unsigned long plane_masks[1];
  int req;
  int first, nelem, maxelem;
  Pixel cmap[MAX_SZCMAP];
  Display *dpy;
  Colormap colormap;
  Visual *visual;
  int screen;


  dpy      = XtDisplay(w);
  screen   = XDefaultScreen(dpy);
  visual   = XDefaultVisual(dpy, screen);
  colormap = XDefaultColormap(dpy, screen);

  /* Make sure we have the right sort of visual.
  */
  if (visual->class == TrueColor)
    return (global_ncolors);

  if ((visual->class != PseudoColor) && (visual->class != GrayScale))
    return (0);

  /* Get current colormap specs.
  */
  GtQueryColormap (w, 0, &first, &nelem, &maxelem);

  /* Try to alloc the max size colormap.
  */
  if (maxelem > 0) {
    req = min(MAX_SZCMAP, maxelem);
    for (n=0;  req > 0 && n < maxelem;  ){
      if (XAllocColorCells (dpy, colormap,
            False, plane_masks, 0,  &cmap[n], req)) {
        n += req;
      } else
        req /= 2;
    }

    /* just wondering ... don't really need this.
    */
    if (! w->gterm.useGlobalCmap)
	XFreeColors (dpy, colormap, cmap, n, 0);

  } else
    n = 0;

  return (n);
}


static int
GetGlobalColors(void)
{
  return (global_ncolors);
}


static void
SetGlobalColors(int n)
{
  global_ncolors = n;
}


/*
 * This routine will search the STATIC colors of w->core.colormap to find an
 * exact match for the color name. If no match is found, the foreground is
 * returned.
 */

static Pixel
ColorNameToPixel (GtermWidget w, String str)
{
    int i;
    XColor color;
    XColor cmap[SZ_STATIC_CMAP];
    char   *ip, *op, name[32];
    unsigned long r, g, b;
    unsigned long val = 0;



    bzero (cmap, (SZ_STATIC_CMAP * sizeof(XColor)));
    if (!str)
	return (0);

    if (w->gterm.w_depth > ColormapDepth && w->gterm.useGlobalCmap) {
	bzero (name, 32);
	for (ip=str, op=name; ip && *ip; ) 
	    *op++ = (char) tolower ((int)*ip++);


        for (i=0; i < (SZ_STATIC_CMAP + SZ_OVERLAY_CMAP); i++) {
	    if (strcmp (name, static_colors[i].name) == 0) {
		r = ((uchar)static_colors[i].r & 0xff) << 16;
		g = ((uchar)static_colors[i].g & 0xff) << 8;
		b = ((uchar)static_colors[i].b & 0xff);
		val = static_colors[i].value;
		break;
	    }
	}
	return (val);

    } else {
        /* Find what colors are available... */
        for (i=0; i < SZ_STATIC_CMAP; i++) {
            memset (&cmap[i], 0, sizeof(XColor));
            cmap[i].pixel = i;
            cmap[i].flags = DoRed | DoGreen | DoBlue;
        }

        XQueryColors(w->gterm.display, w->core.colormap, cmap, SZ_STATIC_CMAP);

        /* ...and find a match */
        if (XParseColor(w->gterm.display, w->core.colormap, str, &color)) {
            for (i=0; i<SZ_STATIC_CMAP; i++) {
                if ((color.red   == cmap[i].red) &&
                    (color.green == cmap[i].green) &&
                    (color.blue  == cmap[i].blue)) {
                        return i;
                }
            }
        }

    }

    return 1;
}

