/* 
 * Motif Tools Library, Version 2.0
 * $Id: Color.c,v 1.6 1994/07/04 03:03:50 david Exp $
 * 
 * Written by David Flanagan.
 * Copyright (c) 1992, 1993, 1994 by Dovetail Systems.
 * All Rights Reserved.  See the file COPYRIGHT for details.
 * This is not free software.  See the file SHAREWARE for details.
 * There is no warranty for this software.  See NO_WARRANTY for details.
 */

#include <ctype.h>
#include <Xmt/Xmt.h>
#include <Xmt/Lexer.h>
#include <Xmt/AppResP.h>
#include <X11/IntrinsicP.h>

#ifndef X_NOT_STDC_ENV
#include <stdlib.h>
#else
extern long int strtol();
#endif

/*
 * HSL color syntax: H / S / L, where H, S, and L are integers
 * with an optional leading '+' or '-'.  Whitespace is okay.
 */
#if NeedFunctionPrototypes
static Boolean ParseHSLColor(StringConst name, XColor *c,
			     XmtAppResources *appres, Boolean is_foreground)
#else
static Boolean ParseHSLColor(name, c, appres, is_foreground)
StringConst name;
XColor *c;
XmtAppResources *appres;
Boolean is_foreground;
#endif
{
    int h, s, l;
    unsigned r, g, b;
    unsigned base_h, base_s, base_l;
    String hue, saturation, lightness, dummy;

    hue = (String)name;
    h = strtol(hue, &saturation, 0);
    if (saturation == hue) return False;

    while(isspace(*saturation)) saturation++;
    if (*saturation++ != '/') return False;
    s = strtol(saturation, &lightness, 0);
    if (lightness == saturation) return False;

    while(isspace(*lightness)) lightness++;
    if (*lightness++ != '/') return False;
    l = strtol(lightness, &dummy, 0);
    if (dummy == lightness) return False;

    while(isspace(*dummy)) dummy++;
    if (*dummy) return False;

    if (is_foreground) {
	base_h = appres->foreground_hue;
	base_s = appres->foreground_saturation;
	base_l = appres->foreground_lightness;
    }
    else {
	base_h = appres->background_hue;
	base_s = appres->background_saturation;
	base_l = appres->background_lightness;
    }

    if ((hue[0] == '+') || (hue[0] == '-'))
	h = (h + base_h) % 360;
    if ((saturation[0] == '+') || (saturation[0] == '-')) {
	s = s + base_s;
	if (s < 0) s = 0;
	if (s > 100) s = 100;
    }
    if ((lightness[0] == '+') || (lightness[0] == '-')) {
	l = l + base_l;
	if (l < 0) l = 0;
	if (l > 100) l = 100;
    }

    XmtHSLToRGB(h, s, l, &r, &g, &b);
    c->red = r;
    c->green = g;
    c->blue = b;
    c->flags = DoRed | DoGreen | DoBlue;
    return True;
}

/*
 * This function allocates the named color, using the specified
 * colormap, visual, and color table.  The widget argument is
 * used only to obtain the display, and application resoruces.
 * If the colormap, visual, or color table are unspecified, the
 * widget is used to obtain default values.
 * 
 * Return codes:
 *   0 -> successful allocation
 *   1 -> unknown or malformed color name
 *   2 -> colormap full
 */

#if NeedFunctionPrototypes
int XmtAllocColor(Widget w, Colormap cmap, Visual *visual,
		  XmtColorTable ctable, StringConst name,
		  Pixel *pixel_return)
#else
int XmtAllocColor(w, cmap, visual, ctable, name, pixel_return)
Widget w;
Colormap cmap;
Visual *visual;
XmtColorTable ctable;
StringConst name;
Pixel *pixel_return;
#endif
{
    Widget shell = XmtGetApplicationShell(w);
    Display *dpy = XtDisplay(shell);
    Colormap root_colormap = shell->core.colormap;
    XmtAppResources *appres = XmtGetApplicationResources(shell);
    Boolean is_foreground = (name[0] == '+');
    XColor c1, c2;

    /* get default values for unspecified values */
    while(!XtIsWidget(w)) w = XtParent(w);
    if (cmap == None) cmap = w->core.colormap;
    if (visual == NULL) visual = XmtGetVisual(w);
    if (ctable == NULL) ctable = appres->colortable;

    /*
     * first thing we do is look for a monochrome "fallback" color.
     * If the first character in the color name is '+', it means
     * that the color is a foreground color.  If the 1st char is '-',
     * it means that the color is a background color.  If this is a
     * monochrome visual, return the foreground or background color.
     * Otherwise, ignore this first character.
     */
    if ((name[0] == '+') || (name[0] == '-')) {
	if ((visual->map_entries == 2) && (cmap == root_colormap)) {
	    if (name[0] == '+') *pixel_return = appres->foreground;
	    else *pixel_return = appres->background;
	    return 0;
	}
	name++;
    }

    /*
     * Now parse some other color types, depending on the (possibly new)
     * first character in the color name:
     *   '#' implies a standard X hexidecimal RGB spec, parsed with
     *       XParseColor for compatibility with X11R4.
     *   '%' implies an Xmt HSL color, which can include H,S,L deltas.
     *   '$' implies an Xmt symbolic color, looked up in the color table
     * If a color does not begin with one of these three characters, then
     * it is first compared to the constants XtDefaultForeground and
     * XtDefaultBackground, for compatibility with the Xt Pixel converter.
     * Otherwise, it is a color name, or and Xcms color specification,
     * and is converted with XAllocNamedColor()
     */
    if (name[0] == '#') {
	if (XParseColor(dpy, cmap, name, &c1) == 0) return 1;
	if (XAllocColor(dpy, cmap, &c1) == 0) return 2;
	*pixel_return = c1.pixel;
	return 0;
    }
    else if (name[0] == '%') {
	if (ParseHSLColor(&name[1], &c1, appres, is_foreground) == False)
	    return 1;
	if (XAllocColor(dpy, cmap, &c1) == 0) return 2;
	*pixel_return = c1.pixel;
	return 0;
    }
    else if (name[0] == '$') {  /* lookup symbolic colors by recursing */
	String newname;
	if (!ctable) return 1;
	newname = XmtLookupColorName(ctable, &name[1]);
	if (newname == NULL) return 1;
	return XmtAllocColor(w, cmap, visual, ctable, newname, pixel_return);
    }
    else {
	if (strcmp(name, XtDefaultBackground) == 0) {
	    if (cmap == root_colormap) {
		*pixel_return = appres->background;
		return 0;
	    }
	    else return XmtAllocColor(w, cmap, visual, ctable,
				       "#ffffffffffff", pixel_return);
	}
	if (strcmp(name, XtDefaultForeground) == 0) {
	    if (cmap == root_colormap) {
		*pixel_return = appres->foreground;
		return 0;
	    }
	    else return XmtAllocColor(w, cmap, visual, ctable, "#000",
				       pixel_return);
	}
	if (XAllocNamedColor(dpy, cmap, name, &c1, &c2)) {
	    *pixel_return = c1.pixel;
	    return 0;
	}
	else {
	    if (XLookupColor(dpy, cmap, name, &c1, &c2)) return 2;
	    else return 1;
	}
    }
}

/*
 * Call XmtAllocColor, using default colormap, visual, & color table.
 */
#if NeedFunctionPrototypes
int XmtAllocWidgetColor(Widget w, StringConst name, Pixel *pixel_return)
#else
int XmtAllocWidgetColor(w, name, pixel_return)
Widget w;
StringConst name;
Pixel *pixel_return;
#endif
{
    return XmtAllocColor(w, None, NULL, NULL, name, pixel_return);
}

/*
 * This function calls XFreeColors() to free most allocated values,
 * but doesn't free the foreground and background colors in the
 * application's default colormap--XmtAllocColor() can return these
 * pixel values without actually allocating them.
 * The widget argument is used to obtain the display, and the app
 * resources needed to deterermine whether the pixel should be freed.
 * If no colormap is specified, the widget's colormap will be used.
 * The only real reason to specify separate widget and colormap arguments
 * is for the String-to-Pixel converter which needs to cache on a
 * per-colormap basis without caching on a per-widget basis.  It passes
 * the widget colormap, but always passes the application shell widget
 */
 
#if NeedFunctionPrototypes
void XmtFreeColor(Widget w, Colormap cmap, Pixel color)
#else
void XmtFreeColor(w, cmap, color)
Widget w;
Colormap cmap;
Pixel color;
#endif
{
    Widget shell = XmtGetApplicationShell(w);
    Display *dpy = XtDisplay(shell);
    Colormap root_colormap = shell->core.colormap;
    XmtAppResources *appres = XmtGetApplicationResources(shell);

    if (cmap == None) {
	while(!XtIsWidget(w)) w = XtParent(w);
	cmap = w->core.colormap;
    }

    /*
     * XmtAllocColor() sometimes doesn't actually allocate a color,
     * but instead just returns the default fg or bg color.  In this
     * case, there is nothing to free.
     */
    if ((cmap == root_colormap) &&
	((color == appres->background) || (color == appres->foreground)))
	return;

    XFreeColors(dpy, cmap, &color, 1, 0L);
}

/*
 * A simpler version
 */
#if NeedFunctionPrototypes
void XmtFreeWidgetColor(Widget w, Pixel color)
#else
void XmtFreeWidgetColor(w, color)
Widget w;
Pixel color;
#endif
{
    XmtFreeColor(w, w->core.colormap, color);
}

/*
 * Like XmtAllocColor, but stores the color into an already-allocated
 * read/write color cell.  Since read/write Visuals are not monochrome,
 * it ignores the +/- fallback syntax, and also does not handle
 * XtDefault[Fore|Back]Ground.  Returns 0 on success, or 1 if 
 * the color name was unknown or malformed.  It never returns 2,
 * like XmtAllocColor() can. Note that it takes a roundtrip
 * to the server to check color names, and then another request
 * to store the color--we need to do this to prevent X errors on
 * bad color names.  Also, note that this function takes a Visual *
 * argument that is currently unused.  This is to allow a future extension
 * that will allow different colors to be specified depending on visual
 * type (e.g. for gray scale).
 */
/* ARGSUSED */
#if NeedFunctionPrototypes
int XmtStoreColor(Widget w, Colormap cmap, Visual *visual,
		  XmtColorTable ctable, StringConst name, Pixel pixel)
#else
int XmtStoreColor(w, cmap, visual, ctable, name, pixel)
Widget w;
Colormap cmap;
Visual *visual;
XmtColorTable ctable;
StringConst name;
Pixel pixel;
#endif
{
    Display *dpy;
    XmtAppResources *appres = XmtGetApplicationResources(w);
    Boolean is_foreground = (name[0] == '+');
    XColor c1, c2;

    /* set up default values for unspecified arguments */
    while(!XtIsWidget(w)) w = XtParent(w);
    dpy = XtDisplay(w);
    if (cmap == None) cmap = w->core.colormap;
    if (visual == NULL) visual = XmtGetVisual(w);
    if (ctable == NULL) ctable = appres->colortable;

    c1.flags = DoRed | DoGreen | DoBlue;
    
    /* ignore foreground/background fallbacks */
    if ((name[0] == '-') || (name[0] == '+')) name++;

    if (name[0] == '#') {
	if (XParseColor(dpy, cmap, name, &c1) == 0) return 1;
	XStoreColor(dpy, cmap, &c1);
	return 0;
    }
    else if (name[0] == '%') {
	if (ParseHSLColor(&name[1], &c1, appres, is_foreground) == False)
	    return 1;
	XStoreColor(dpy, cmap, &c1);
	return 0;
    }
    else if (name[0] == '$') {
	String newname;
	if (!ctable) return 1;
	/*
	 * handle symbolic colors by recursing, since the XmtColorTable
	 * API doesn't include a function for storing colors in
	 * read/write cells.
	 */
	newname = XmtLookupColorName(ctable, &name[1]);
	if (newname == NULL) return 1;
	return XmtStoreColor(w, cmap, visual, ctable, newname, pixel);
    }
    else {
	if (XLookupColor(dpy, cmap, name, &c2, &c1) == 0) return 1;
	XStoreColor(dpy, cmap, &c1);
	return 0;
    }
}

#if NeedFunctionPrototypes
int XmtStoreWidgetColor(Widget w, StringConst name, Pixel pixel)
#else
int XmtStoreWidgetColor(w, name, pixel)
Widget w;
StringConst name;
Pixel pixel;
#endif
{
    return XmtStoreColor(w, None, NULL, NULL, name, pixel);
}
