/*
 * tkUnix3d.c --
 *
 *	This file contains the platform specific routines for drawing 3d
 *	borders in the Motif style.
 *
 * Copyright © 1996 Sun Microsystems, Inc.
 *
 * See the file "license.terms" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 */

#include "tkInt.h"
#include "tk3d.h"

#if !(defined(_WIN32) || defined(MAC_OSX_TK))
#include "tkUnixInt.h"
#endif

/*
 * This structure is used to keep track of the extra colors used by Unix 3D
 * borders.
 */

typedef struct {
    TkBorder info;
    GC solidGC;		/* Used to draw solid relief. */
} UnixBorder;

/*
 *----------------------------------------------------------------------
 *
 * TkpGetBorder --
 *
 *	This function allocates a new TkBorder structure.
 *
 * Results:
 *	Returns a newly allocated TkBorder.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

TkBorder *
TkpGetBorder(void)
{
    UnixBorder *borderPtr = (UnixBorder *)ckalloc(sizeof(UnixBorder));

    borderPtr->solidGC = NULL;
    return (TkBorder *) borderPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * TkpFreeBorder --
 *
 *	This function frees any colors allocated by the platform specific part
 *	of this module.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	May deallocate some colors.
 *
 *----------------------------------------------------------------------
 */

void
TkpFreeBorder(
    TkBorder *borderPtr)
{
    UnixBorder *unixBorderPtr = (UnixBorder *) borderPtr;
    Display *display = DisplayOfScreen(borderPtr->screen);

    if (unixBorderPtr->solidGC != NULL) {
	Tk_FreeGC(display, unixBorderPtr->solidGC);
    }
}
/*
 *--------------------------------------------------------------
 *
 * Tk_3DVerticalBevel --
 *
 *	This procedure draws a vertical bevel along one side of an object. The
 *	bevel is always rectangular in shape:
 *			|||
 *			|||
 *			|||
 *			|||
 *			|||
 *			|||
 *	An appropriate shadow color is chosen for the bevel based on the
 *	leftBevel and relief arguments. Normally this procedure is called
 *	first, then Tk_3DHorizontalBevel is called next to draw neat corners.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Graphics are drawn in drawable.
 *
 *--------------------------------------------------------------
 */

void
Tk_3DVerticalBevel(
    Tk_Window tkwin,		/* Window for which border was allocated. */
    Drawable drawable,		/* X window or pixmap in which to draw. */
    Tk_3DBorder border,		/* Token for border to draw. */
    int x, int y, int width, int height,
				/* Area of vertical bevel. */
    int leftBevel,		/* Non-zero means this bevel forms the left
				 * side of the object; 0 means it forms the
				 * right side. */
    int relief)			/* Kind of bevel to draw. For example,
				 * TK_RELIEF_RAISED means interior of object
				 * should appear higher than exterior. */
{
    TkBorder *borderPtr = (TkBorder *) border;
    GC left, right;
    Display *display = Tk_Display(tkwin);

    if ((borderPtr->lightGC == NULL) && (relief != TK_RELIEF_FLAT)) {
	TkpGetShadows(borderPtr, tkwin);
    }

    if (relief == TK_RELIEF_RAISED) {
	XFillRectangle(display, drawable,
		(leftBevel) ? borderPtr->lightGC : borderPtr->darkGC,
		x, y, (unsigned) width, (unsigned) height);
    } else if (relief == TK_RELIEF_SUNKEN) {
	XFillRectangle(display, drawable,
		(leftBevel) ? borderPtr->darkGC : borderPtr->lightGC,
		x, y, (unsigned) width, (unsigned) height);
    } else if (relief == TK_RELIEF_RIDGE) {
	int half;

	left = borderPtr->lightGC;
	right = borderPtr->darkGC;
    ridgeGroove:
	half = width/2;
	if (!leftBevel && (width & 1)) {
	    half++;
	}
	XFillRectangle(display, drawable, left, x, y, (unsigned) half,
		(unsigned) height);
	XFillRectangle(display, drawable, right, x+half, y,
		(unsigned) (width-half), (unsigned) height);
    } else if (relief == TK_RELIEF_GROOVE) {
	left = borderPtr->darkGC;
	right = borderPtr->lightGC;
	goto ridgeGroove;
    } else if (relief == TK_RELIEF_FLAT) {
	XFillRectangle(display, drawable, borderPtr->bgGC, x, y,
		(unsigned) width, (unsigned) height);
    } else if (relief == TK_RELIEF_SOLID) {
	UnixBorder *unixBorderPtr = (UnixBorder *) borderPtr;
	if (unixBorderPtr->solidGC == NULL) {
	    XGCValues gcValues;

	    gcValues.foreground = BlackPixelOfScreen(borderPtr->screen);
	    unixBorderPtr->solidGC = Tk_GetGC(tkwin, GCForeground, &gcValues);
	}
	XFillRectangle(display, drawable, unixBorderPtr->solidGC, x, y,
		(unsigned) width, (unsigned) height);
    }
}

/*
 *--------------------------------------------------------------
 *
 * Tk_3DHorizontalBevel --
 *
 *	This procedure draws a horizontal bevel along one side of an object.
 *	The bevel has mitered corners (depending on leftIn and rightIn
 *	arguments).
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *--------------------------------------------------------------
 */

void
Tk_3DHorizontalBevel(
    Tk_Window tkwin,		/* Window for which border was allocated. */
    Drawable drawable,		/* X window or pixmap in which to draw. */
    Tk_3DBorder border,		/* Token for border to draw. */
    int x, int y, int width, int height,
				/* Bounding box of area of bevel. Height gives
				 * width of border. */
    int leftIn, int rightIn,	/* Describes whether the left and right edges
				 * of the bevel angle in or out as they go
				 * down. For example, if "leftIn" is true, the
				 * left side of the bevel looks like this:
				 *	___________
				 *	 __________
				 *	  _________
				 *	   ________
				 */
    int topBevel,		/* Non-zero means this bevel forms the top
				 * side of the object; 0 means it forms the
				 * bottom side. */
    int relief)			/* Kind of bevel to draw. For example,
				 * TK_RELIEF_RAISED means interior of object
				 * should appear higher than exterior. */
{
    TkBorder *borderPtr = (TkBorder *) border;
    Display *display = Tk_Display(tkwin);
    int bottom, halfway, x1, x2, x1Delta, x2Delta;
    UnixBorder *unixBorderPtr = (UnixBorder *) borderPtr;
    GC topGC = NULL, bottomGC = NULL;
				/* Initializations needed only to prevent
				 * compiler warnings. */

    if ((borderPtr->lightGC == NULL) && (relief != TK_RELIEF_FLAT) &&
	    (relief != TK_RELIEF_SOLID)) {
	TkpGetShadows(borderPtr, tkwin);
    }

    /*
     * Compute a GC for the top half of the bevel and a GC for the bottom half
     * (they're the same in many cases).
     */

    switch (relief) {
    case TK_RELIEF_FLAT:
	topGC = bottomGC = borderPtr->bgGC;
	break;
    case TK_RELIEF_GROOVE:
	topGC = borderPtr->darkGC;
	bottomGC = borderPtr->lightGC;
	break;
    case TK_RELIEF_RAISED:
	topGC = bottomGC = (topBevel? borderPtr->lightGC : borderPtr->darkGC);
	break;
    case TK_RELIEF_RIDGE:
	topGC = borderPtr->lightGC;
	bottomGC = borderPtr->darkGC;
	break;
    case TK_RELIEF_SOLID:
	if (unixBorderPtr->solidGC == NULL) {
	    XGCValues gcValues;

	    gcValues.foreground = BlackPixelOfScreen(borderPtr->screen);
	    unixBorderPtr->solidGC = Tk_GetGC(tkwin, GCForeground, &gcValues);
	}
	XFillRectangle(display, drawable, unixBorderPtr->solidGC, x, y,
		(unsigned) width, (unsigned) height);
	return;
    case TK_RELIEF_SUNKEN:
	topGC = bottomGC = (topBevel? borderPtr->darkGC : borderPtr->lightGC);
	break;
    }

    /*
     * Compute various other geometry-related stuff.
     */

    x1 = x;
    if (!leftIn) {
	x1 += height;
    }
    x2 = x+width;
    if (!rightIn) {
	x2 -= height;
    }
    x1Delta = (leftIn) ? 1 : -1;
    x2Delta = (rightIn) ? -1 : 1;
    halfway = y + height/2;
    if (!topBevel && (height & 1)) {
	halfway++;
    }
    bottom = y + height;

    /*
     * Draw one line for each y-coordinate covered by the bevel.
     */

    for ( ; y < bottom; y++) {
	/*
	 * X Dimensions are 16-bit, so avoid wraparound or display errors by
	 * limiting these here.
	 */

	if (x1 < -32767) {
	    x1 = -32767;
	}
	if (x2 > 32767) {
	    x2 = 32767;
	}

	/*
	 * In some weird cases (such as large border widths for skinny
	 * rectangles) x1 can be >= x2. Don't draw the lines in these cases.
	 */

	if (x1 < x2) {
	    XFillRectangle(display, drawable,
		    (y < halfway) ? topGC : bottomGC, x1, y,
		    (unsigned) (x2-x1), (unsigned) 1);
	}
	x1 += x1Delta;
	x2 += x2Delta;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TkpGetShadows --
 *
 *	This procedure computes the shadow colors for a 3-D border and fills
 *	in the corresponding fields of the Border structure. It's called
 *	lazily, so that the colors aren't allocated until something is
 *	actually drawn with them. That way, if a border is only used for flat
 *	backgrounds the shadow colors will never be allocated.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The lightGC and darkGC fields in borderPtr get filled in, if they
 *	weren't already.
 *
 *----------------------------------------------------------------------
 */

void
TkpGetShadows(
    TkBorder *borderPtr,	/* Information about border. */
    Tk_Window tkwin)		/* Window where border will be used for
				 * drawing. */
{
    XColor lightColor, darkColor;
    int stressed, tmp1, tmp2;
    int r, g, b;
    XGCValues gcValues;

    if (borderPtr->lightGC != NULL) {
	return;
    }
    stressed = TkpCmapStressed(tkwin, borderPtr->colormap);

    /*
     * First, handle the case of a color display with lots of colors. The
     * shadow colors get computed using whichever formula results in the
     * greatest change in color:
     * 1. Lighter shadow is half-way to white, darker shadow is half way to
     *    dark.
     * 2. Lighter shadow is 40% brighter than background, darker shadow is 40%
     *    darker than background.
     */

    if (!stressed && (Tk_Depth(tkwin) >= 6)) {
	/*
	 * This is a color display with lots of colors. For the dark shadow,
	 * cut 40% from each of the background color components. But if the
	 * background is already very dark, make the dark color a little
	 * lighter than the background by increasing each color component
	 * 1/4th of the way to MAX_INTENSITY.
	 *
	 * For the light shadow, boost each component by 40% or half-way to
	 * white, whichever is greater (the first approach works better for
	 * unsaturated colors, the second for saturated ones). But if the
	 * background is already very bright, instead choose a slightly darker
	 * color for the light shadow by reducing each color component by 10%.
	 *
	 * Compute the colors using integers, not using lightColor.red etc.:
	 * these are shorts and may have problems with integer overflow.
	 */

	/*
	 * Compute the dark shadow color.
	 */

	r = (int) borderPtr->bgColorPtr->red;
	g = (int) borderPtr->bgColorPtr->green;
	b = (int) borderPtr->bgColorPtr->blue;

	if (r*0.5*r + g*1.0*g + b*0.28*b < MAX_INTENSITY*0.05*MAX_INTENSITY) {
	    darkColor.red = (MAX_INTENSITY + 3*r)/4;
	    darkColor.green = (MAX_INTENSITY + 3*g)/4;
	    darkColor.blue = (MAX_INTENSITY + 3*b)/4;
	} else {
	    darkColor.red = (60 * r)/100;
	    darkColor.green = (60 * g)/100;
	    darkColor.blue = (60 * b)/100;
	}

	/*
	 * Allocate the dark shadow color and its GC.
	 */

	borderPtr->darkColorPtr = Tk_GetColorByValue(tkwin, &darkColor);
	gcValues.foreground = borderPtr->darkColorPtr->pixel;
	borderPtr->darkGC = Tk_GetGC(tkwin, GCForeground, &gcValues);

	/*
	 * Compute the light shadow color.
	 */

	if (g > MAX_INTENSITY*0.95) {
	    lightColor.red = (90 * r)/100;
	    lightColor.green = (90 * g)/100;
	    lightColor.blue = (90 * b)/100;
	} else {
	    tmp1 = (14 * r)/10;
	    if (tmp1 > MAX_INTENSITY) {
		tmp1 = MAX_INTENSITY;
	    }
	    tmp2 = (MAX_INTENSITY + r)/2;
	    lightColor.red = (tmp1 > tmp2) ? tmp1 : tmp2;
	    tmp1 = (14 * g)/10;
	    if (tmp1 > MAX_INTENSITY) {
		tmp1 = MAX_INTENSITY;
	    }
	    tmp2 = (MAX_INTENSITY + g)/2;
	    lightColor.green = (tmp1 > tmp2) ? tmp1 : tmp2;
	    tmp1 = (14 * b)/10;
	    if (tmp1 > MAX_INTENSITY) {
		tmp1 = MAX_INTENSITY;
	    }
	    tmp2 = (MAX_INTENSITY + b)/2;
	    lightColor.blue = (tmp1 > tmp2) ? tmp1 : tmp2;
	}

	/*
	 * Allocate the light shadow color and its GC.
	 */

	borderPtr->lightColorPtr = Tk_GetColorByValue(tkwin, &lightColor);
	gcValues.foreground = borderPtr->lightColorPtr->pixel;
	borderPtr->lightGC = Tk_GetGC(tkwin, GCForeground, &gcValues);
	return;
    }

    if (borderPtr->shadow == None) {
	borderPtr->shadow = Tk_GetBitmap(NULL, tkwin,
		Tk_GetUid("gray50"));
	if (borderPtr->shadow == None) {
	    Tcl_Panic("TkpGetShadows couldn't allocate bitmap for border");
	}
    }
    if (borderPtr->visual->map_entries > 2) {
	/*
	 * This isn't a monochrome display, but the colormap either ran out of
	 * entries or didn't have very many to begin with. Generate the light
	 * shadows with a white stipple and the dark shadows with a black
	 * stipple.
	 */

	gcValues.foreground = borderPtr->bgColorPtr->pixel;
	gcValues.background = BlackPixelOfScreen(borderPtr->screen);
	gcValues.stipple = borderPtr->shadow;
	gcValues.fill_style = FillOpaqueStippled;
	borderPtr->darkGC = Tk_GetGC(tkwin,
		GCForeground|GCBackground|GCStipple|GCFillStyle, &gcValues);
	gcValues.background = WhitePixelOfScreen(borderPtr->screen);
	borderPtr->lightGC = Tk_GetGC(tkwin,
		GCForeground|GCBackground|GCStipple|GCFillStyle, &gcValues);
	return;
    }

    /*
     * This is just a measly monochrome display, hardly even worth its
     * existence on this earth. Make one shadow a 50% stipple and the other
     * the opposite of the background.
     */

    gcValues.foreground = WhitePixelOfScreen(borderPtr->screen);
    gcValues.background = BlackPixelOfScreen(borderPtr->screen);
    gcValues.stipple = borderPtr->shadow;
    gcValues.fill_style = FillOpaqueStippled;
    borderPtr->lightGC = Tk_GetGC(tkwin,
	    GCForeground|GCBackground|GCStipple|GCFillStyle, &gcValues);
    if (borderPtr->bgColorPtr->pixel
	    == WhitePixelOfScreen(borderPtr->screen)) {
	gcValues.foreground = BlackPixelOfScreen(borderPtr->screen);
	borderPtr->darkGC = Tk_GetGC(tkwin, GCForeground, &gcValues);
    } else {
	borderPtr->darkGC = borderPtr->lightGC;
	borderPtr->lightGC = Tk_GetGC(tkwin, GCForeground, &gcValues);
    }
}

/*
 * Local Variables:
 * mode: c
 * c-basic-offset: 4
 * fill-column: 78
 * End:
 */
