/* 
 * 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-1995 Sun Microsystems, Inc.
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * SCCS: @(#) tkVisual.c 1.19 97/04/25 16:52:17
 */

#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 interp->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. */
    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;
    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) {
	interp->result = "couldn't find an appropriate visual";
	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 interp->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. */
    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;
	}
    } 
}
