/* 
 * tkVisual.c --
 *
 *  This file contains library procedures for allocating and
 *  freeing visuals and colormaps.  This code is based on a
 *  prototype implementation by Paul Mackerras.
 *
 * Copyright (c) 1994 The Regents of the University of California.
 * Copyright (c) 1994-1997 Sun Microsystems, Inc.
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * RCS: @(#) Id
 */

#include "tkInt.h"
#include "tkPort.h"

/*
 * The table below maps from symbolic names for visual classes
 * to the associated X class symbols.
 */

typedef struct VisualDictionary {
    char *name;      /* Textual name of class. */
    int minLength;    /* Minimum # characters that must be
         * specified for an unambiguous match. */
    int class;      /* X symbol for class. */
} VisualDictionary;
static VisualDictionary visualNames[] = {
    {"best",    1,  0},
    {"directcolor",  2,  DirectColor},
    {"grayscale",  1,  GrayScale},
    {"greyscale",  1,  GrayScale},
    {"pseudocolor",  1,  PseudoColor},
    {"staticcolor",  7,  StaticColor},
    {"staticgray",  7,  StaticGray},
    {"staticgrey",  7,  StaticGray},
    {"truecolor",  1,  TrueColor},
    {NULL,    0,  0},
};

/*
 * One of the following structures exists for each distinct non-default
 * colormap allocated for a display by Tk_GetColormap.
 */

struct TkColormap {
    Colormap colormap;    /* X's identifier for the colormap. */
    Visual *visual;    /* Visual for which colormap was
         * allocated. */
    int refCount;    /* How many uses of the colormap are still
         * outstanding (calls to Tk_GetColormap
         * minus calls to Tk_FreeColormap). */
    int shareable;    /* 0 means this colormap was allocated by
         * a call to Tk_GetColormap with "new",
         * implying that the window wants it all
         * for itself.  1 means that the colormap
         * was allocated as a default for a particular
         * visual, so it can be shared. */
    struct TkColormap *nextPtr;  /* Next in list of colormaps for this display,
         * or NULL for end of list. */
};

/*
 *----------------------------------------------------------------------
 *
 * Tk_GetVisual --
 *
 *  Given a string identifying a particular kind of visual, this
 *  procedure returns a visual and depth that matches the specification.
 *
 * Results:
 *  The return value is normally a pointer to a visual.  If an
 *  error occurred in looking up the visual, NULL is returned and
 *  an error message is left in the interp's result.  The depth of the
 *  visual is returned to *depthPtr under normal returns.  If
 *  colormapPtr is non-NULL, then this procedure also finds a
 *  suitable colormap for use with the visual in tkwin, and it
 *  returns that colormap in *colormapPtr unless an error occurs.
 *
 * Side effects:
 *  A new colormap may be allocated.
 *
 *----------------------------------------------------------------------
 */

Visual *
Tk_GetVisual(interp, tkwin, string, depthPtr, colormapPtr)
    Tcl_Interp *interp;      /* Interpreter to use for error
           * reporting. */
    Tk_Window tkwin;      /* Window in which visual will be
           * used. */
    CONST char *string;      /* String describing visual.  See
           * manual entry for details. */
    int *depthPtr;      /* The depth of the returned visual
           * is stored here. */
    Colormap *colormapPtr;    /* If non-NULL, then a suitable
           * colormap for visual is placed here.
           * This colormap must eventually be
           * freed by calling Tk_FreeColormap. */
{
    Tk_Window tkwin2;
    XVisualInfo template, *visInfoList, *bestPtr;
    long mask;
    Visual *visual;
    int length, c, numVisuals, prio, bestPrio, i;
    CONST char *p;
    VisualDictionary *dictPtr;
    TkColormap *cmapPtr;
    TkDisplay *dispPtr = ((TkWindow *) tkwin)->dispPtr;

    /*
     * Parse string and set up a template for use in searching for
     * an appropriate visual.
     */

    c = string[0];
    if (c == '.') {
  /*
   * The string must be a window name.  If the window is on the
   * same screen as tkwin, then just use its visual.  Otherwise
   * use the information about the visual as a template for the
   * search.
   */

  tkwin2 = Tk_NameToWindow(interp, string, tkwin);
  if (tkwin2 == NULL) {
      return NULL;
  }
  visual = Tk_Visual(tkwin2);
  if (Tk_Screen(tkwin) == Tk_Screen(tkwin2)) {
      *depthPtr = Tk_Depth(tkwin2);
      if (colormapPtr != NULL) {
    /*
     * Use the colormap from the other window too (but be sure
     * to increment its reference count if it's one of the ones
     * allocated here).
     */

    *colormapPtr = Tk_Colormap(tkwin2);
    for (cmapPtr = dispPtr->cmapPtr; cmapPtr != NULL;
      cmapPtr = cmapPtr->nextPtr) {
        if (cmapPtr->colormap == *colormapPtr) {
      cmapPtr->refCount += 1;
      break;
        }
    }
      }
      return visual;
  }
  template.depth = Tk_Depth(tkwin2);
  template.class = visual->class;
  template.red_mask = visual->red_mask;
  template.green_mask = visual->green_mask;
  template.blue_mask = visual->blue_mask;
  template.colormap_size = visual->map_entries;
  template.bits_per_rgb = visual->bits_per_rgb;
  mask = VisualDepthMask|VisualClassMask|VisualRedMaskMask
    |VisualGreenMaskMask|VisualBlueMaskMask|VisualColormapSizeMask
    |VisualBitsPerRGBMask;
    } else if ((c == 0) || ((c == 'd') && (string[1] != 0)
      && (strncmp(string, "default", strlen(string)) == 0))) {
  /*
   * Use the default visual for the window's screen.
   */

  if (colormapPtr != NULL) {
      *colormapPtr = DefaultColormapOfScreen(Tk_Screen(tkwin));
  }
  *depthPtr = DefaultDepthOfScreen(Tk_Screen(tkwin));
  return DefaultVisualOfScreen(Tk_Screen(tkwin));
    } else if (isdigit(UCHAR(c))) {
  int visualId;

  /*
  * This is a visual ID.
  */

  if (Tcl_GetInt(interp, string, &visualId) == TCL_ERROR) {
      Tcl_ResetResult(interp);
      Tcl_AppendResult(interp, "bad X identifier for visual: ",
        string, "\"", (char *) NULL);
      return NULL;
  }
  template.visualid = visualId;
  mask = VisualIDMask;
    } else {
  /*
   * Parse the string into a class name (or "best") optionally
   * followed by whitespace and a depth.
   */

  for (p = string; *p != 0; p++) {
      if (isspace(UCHAR(*p)) || isdigit(UCHAR(*p))) {
    break;
      }
  }
  length = p - string;
  template.class = -1;
  for (dictPtr = visualNames; dictPtr->name != NULL; dictPtr++) {
      if ((dictPtr->name[0] == c) && (length >= dictPtr->minLength)
        && (strncmp(string, dictPtr->name,
        (size_t) length) == 0)) {
    template.class = dictPtr->class;
    break;
      }
  }
  if (template.class == -1) {
      Tcl_AppendResult(interp, "unknown or ambiguous visual name \"",
        string, "\": class must be ", (char *) NULL);
      for (dictPtr = visualNames; dictPtr->name != NULL; dictPtr++) {
    Tcl_AppendResult(interp, dictPtr->name, ", ", (char *) NULL);
      }
      Tcl_AppendResult(interp, "or default", (char *) NULL);
      return NULL;
  }
  while (isspace(UCHAR(*p))) {
      p++;
  }
  if (*p == 0) {
      template.depth = 10000;
  } else {
      if (Tcl_GetInt(interp, p, &template.depth) != TCL_OK) {
    return NULL;
      }
  }
  if (c == 'b') {
      mask = 0;
  } else {
      mask = VisualClassMask;
  }
    }

    /*
     * Find all visuals that match the template we've just created,
     * and return an error if there are none that match.
     */

    template.screen = Tk_ScreenNumber(tkwin);
    mask |= VisualScreenMask;
    visInfoList = XGetVisualInfo(Tk_Display(tkwin), mask, &template,
      &numVisuals);
    if (visInfoList == NULL) {
  Tcl_SetResult(interp, "couldn't find an appropriate visual",
    TCL_STATIC);
  return NULL;
    }

    /*
     * Search through the visuals that were returned to find the best
     * one.  The choice is based on the following criteria, in decreasing
     * order of importance:
     *
     * 1. Depth: choose a visual with exactly the desired depth,
     *    else one with more bits than requested but as few bits
     *    as possible, else one with fewer bits but as many as
     *    possible.
     * 2. Class: some visual classes are more desirable than others;
     *    pick the visual with the most desirable class.
     * 3. Default: the default visual for the screen gets preference
     *    over other visuals, all else being equal.
     */

    bestPrio = 0;
    bestPtr = NULL;
    for (i = 0; i < numVisuals; i++) {
  switch (visInfoList[i].class) {
      case DirectColor:  prio = 5; break;
      case GrayScale:  prio = 1; break;
      case PseudoColor:  prio = 7; break;
      case StaticColor:  prio = 3; break;
      case StaticGray:  prio = 1; break;
      case TrueColor:  prio = 5; break;
      default:    prio = 0; break;
  }
  if (visInfoList[i].visual
    == DefaultVisualOfScreen(Tk_Screen(tkwin))) {
      prio++;
  }
  if (bestPtr == NULL) {
      goto newBest;
  }
  if (visInfoList[i].depth < bestPtr->depth) {
      if (visInfoList[i].depth >= template.depth) {
    goto newBest;
      }
  } else if (visInfoList[i].depth > bestPtr->depth) {
      if (bestPtr->depth < template.depth) {
    goto newBest;
      }
  } else {
      if (prio > bestPrio) {
    goto newBest;
      }
  }
  continue;

  newBest:
  bestPtr = &visInfoList[i];
  bestPrio = prio;
    }
    *depthPtr = bestPtr->depth;
    visual = bestPtr->visual;
    XFree((char *) visInfoList);

    /*
     * If we need to find a colormap for this visual, do it now.
     * If the visual is the default visual for the screen, then
     * use the default colormap.  Otherwise search for an existing
     * colormap that's shareable.  If all else fails, create a new
     * colormap.
     */

    if (colormapPtr != NULL) {
  if (visual == DefaultVisualOfScreen(Tk_Screen(tkwin))) {
      *colormapPtr = DefaultColormapOfScreen(Tk_Screen(tkwin));
  } else {
      for (cmapPtr = dispPtr->cmapPtr; cmapPtr != NULL;
        cmapPtr = cmapPtr->nextPtr) {
    if (cmapPtr->shareable && (cmapPtr->visual == visual)) {
        *colormapPtr = cmapPtr->colormap;
        cmapPtr->refCount += 1;
        goto done;
    }
      }
      cmapPtr = (TkColormap *) ckalloc(sizeof(TkColormap));
      cmapPtr->colormap = XCreateColormap(Tk_Display(tkwin),
        RootWindowOfScreen(Tk_Screen(tkwin)), visual,
        AllocNone);
      cmapPtr->visual = visual;
      cmapPtr->refCount = 1;
      cmapPtr->shareable = 1;
      cmapPtr->nextPtr = dispPtr->cmapPtr;
      dispPtr->cmapPtr = cmapPtr;
      *colormapPtr = cmapPtr->colormap;
  }
    }

    done:
    return visual;
}

/*
 *----------------------------------------------------------------------
 *
 * Tk_GetColormap --
 *
 *  Given a string identifying a colormap, this procedure finds
 *  an appropriate colormap.
 *
 * Results:
 *  The return value is normally the X resource identifier for the
 *  colormap.  If an error occurs, None is returned and an error
 *  message is placed in the interp's result.
 *
 * Side effects:
 *  A reference count is incremented for the colormap, so
 *  Tk_FreeColormap must eventually be called exactly once for
 *  each call to Tk_GetColormap.
 *
 *----------------------------------------------------------------------
 */

Colormap
Tk_GetColormap(interp, tkwin, string)
    Tcl_Interp *interp;      /* Interpreter to use for error
           * reporting. */
    Tk_Window tkwin;      /* Window where colormap will be
           * used. */
    CONST char *string;      /* String that identifies colormap:
           * either "new" or the name of
           * another window. */
{
    Colormap colormap;
    TkColormap *cmapPtr;
    TkDisplay *dispPtr = ((TkWindow *) tkwin)->dispPtr;
    Tk_Window other;

    /*
     * Allocate a new colormap, if that's what is wanted.
     */

    if (strcmp(string, "new") == 0) {
  cmapPtr = (TkColormap *) ckalloc(sizeof(TkColormap));
  cmapPtr->colormap = XCreateColormap(Tk_Display(tkwin),
    RootWindowOfScreen(Tk_Screen(tkwin)), Tk_Visual(tkwin),
    AllocNone);
  cmapPtr->visual = Tk_Visual(tkwin);
  cmapPtr->refCount = 1;
  cmapPtr->shareable = 0;
  cmapPtr->nextPtr = dispPtr->cmapPtr;
  dispPtr->cmapPtr = cmapPtr;
  return cmapPtr->colormap;
    }

    /*
     * Use a colormap from an existing window.  It must have the same
     * visual as tkwin (which means, among other things, that the
     * other window must be on the same screen).
     */

    other = Tk_NameToWindow(interp, string, tkwin);
    if (other == NULL) {
  return None;
    }
    if (Tk_Screen(other) != Tk_Screen(tkwin)) {
  Tcl_AppendResult(interp, "can't use colormap for ", string,
    ": not on same screen", (char *) NULL);
  return None;
    }
    if (Tk_Visual(other) != Tk_Visual(tkwin)) {
  Tcl_AppendResult(interp, "can't use colormap for ", string,
    ": incompatible visuals", (char *) NULL);
  return None;
    }
    colormap = Tk_Colormap(other);

    /*
     * If the colormap was a special one allocated by code in this file,
     * increment its reference count.
     */

    for (cmapPtr = dispPtr->cmapPtr; cmapPtr != NULL;
      cmapPtr = cmapPtr->nextPtr) {
  if (cmapPtr->colormap == colormap) {
      cmapPtr->refCount += 1;
  }
    }
    return colormap;
}

/*
 *----------------------------------------------------------------------
 *
 * Tk_FreeColormap --
 *
 *  This procedure is called to release a colormap that was
 *  previously allocated by Tk_GetColormap.
 *
 * Results:
 *  None.
 *
 * Side effects:
 *  The colormap's reference count is decremented.  If this was the
 *  last reference to the colormap, then the colormap is freed.
 *
 *----------------------------------------------------------------------
 */

void
Tk_FreeColormap(display, colormap)
    Display *display;      /* Display for which colormap was
           * allocated. */
    Colormap colormap;      /* Colormap that is no longer needed.
           * Must have been returned by previous
           * call to Tk_GetColormap, or
           * preserved by a previous call to
           * Tk_PreserveColormap. */
{
    TkDisplay *dispPtr;
    TkColormap *cmapPtr, *prevPtr;

    /*
     * Find Tk's information about the display, then see if this
     * colormap is a non-default one (if it's a default one, there
     * won't be an entry for it in the display's list).
     */

    dispPtr = TkGetDisplay(display);
    if (dispPtr == NULL) {
  panic("unknown display passed to Tk_FreeColormap");
    }
    for (prevPtr = NULL, cmapPtr = dispPtr->cmapPtr; cmapPtr != NULL;
      prevPtr = cmapPtr, cmapPtr = cmapPtr->nextPtr) {
  if (cmapPtr->colormap == colormap) {
      cmapPtr->refCount -= 1;
      if (cmapPtr->refCount == 0) {
    XFreeColormap(display, colormap);
    if (prevPtr == NULL) {
        dispPtr->cmapPtr = cmapPtr->nextPtr;
    } else {
        prevPtr->nextPtr = cmapPtr->nextPtr;
    }
    ckfree((char *) cmapPtr);
      }
      return;
  }
    } 
}

/*
 *----------------------------------------------------------------------
 *
 * Tk_PreserveColormap --
 *
 *  This procedure is called to indicate to Tk that the specified
 *  colormap is being referenced from another location and should
 *  not be freed until all extra references are eliminated.  The
 *  colormap must have been returned by Tk_GetColormap.
 *
 * Results:
 *  None.
 *
 * Side effects:
 *  The colormap's reference count is incremented, so
 *  Tk_FreeColormap must eventually be called exactly once for
 *  each call to Tk_PreserveColormap.
 *
 *----------------------------------------------------------------------
 */

void
Tk_PreserveColormap(display, colormap)
    Display *display;      /* Display for which colormap was
           * allocated. */
    Colormap colormap;      /* Colormap that should be
           * preserved. */
{
    TkDisplay *dispPtr;
    TkColormap *cmapPtr;

    /*
     * Find Tk's information about the display, then see if this
     * colormap is a non-default one (if it's a default one, there
     * won't be an entry for it in the display's list).
     */

    dispPtr = TkGetDisplay(display);
    if (dispPtr == NULL) {
  panic("unknown display passed to Tk_PreserveColormap");
    }
    for (cmapPtr = dispPtr->cmapPtr; cmapPtr != NULL;
      cmapPtr = cmapPtr->nextPtr) {
  if (cmapPtr->colormap == colormap) {
      cmapPtr->refCount += 1;
      return;
  }
    } 
}
