/* 
 * tkWin3d.c --
 *
 *	This file contains the platform specific routines for
 *	drawing 3d borders in the Windows 95 style.
 *
 * Copyright (c) 1996 by 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: tkWin3d.c,v 1.5 2000/04/14 01:36:35 ericm Exp $
 */

#include "tkWinInt.h"
#include "tk3d.h"

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

typedef struct {
    TkBorder info;
    XColor *light2ColorPtr; /* System3dLight */
    XColor *dark2ColorPtr;  /* System3dDarkShadow */
} WinBorder;


/*
 *----------------------------------------------------------------------
 *
 * TkpGetBorder --
 *
 *	This function allocates a new TkBorder structure.
 *
 * Results:
 *	Returns a newly allocated TkBorder.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

TkBorder *
TkpGetBorder()
{
    WinBorder *borderPtr = (WinBorder *) ckalloc(sizeof(WinBorder));
    borderPtr->light2ColorPtr = NULL;
    borderPtr->dark2ColorPtr = 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(borderPtr)
    TkBorder *borderPtr;
{
    WinBorder *winBorderPtr = (WinBorder *) borderPtr;
    if (winBorderPtr->light2ColorPtr) {
	Tk_FreeColor(winBorderPtr->light2ColorPtr);
    }
    if (winBorderPtr->dark2ColorPtr) {
	Tk_FreeColor(winBorderPtr->dark2ColorPtr);
    }
}

/*
 *--------------------------------------------------------------
 *
 * 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(tkwin, drawable, border, x, y, width, height,
	leftBevel, relief)
    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, y, width, 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;
    int left, right;
    Display *display = Tk_Display(tkwin);
    TkWinDCState state;
    HDC dc = TkWinGetDrawableDC(display, drawable, &state);
    int half;

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

    switch (relief) {
	case TK_RELIEF_RAISED:
	    left = (leftBevel)
		? borderPtr->lightGC->foreground
		: borderPtr->darkGC->foreground;
	    right = (leftBevel)
		? ((WinBorder *)borderPtr)->light2ColorPtr->pixel
		: ((WinBorder *)borderPtr)->dark2ColorPtr->pixel;
	    break;
	case TK_RELIEF_SUNKEN:
	    left = (leftBevel)
		? ((WinBorder *)borderPtr)->dark2ColorPtr->pixel
		: ((WinBorder *)borderPtr)->light2ColorPtr->pixel;
	    right = (leftBevel)
		? borderPtr->darkGC->foreground
		: borderPtr->lightGC->foreground;
	    break;
	case TK_RELIEF_RIDGE:
	    left = borderPtr->lightGC->foreground;
	    right = borderPtr->darkGC->foreground;
	    break;
	case TK_RELIEF_GROOVE:
	    left = borderPtr->darkGC->foreground;
	    right = borderPtr->lightGC->foreground;
	    break;
	case TK_RELIEF_FLAT:
	    left = right = borderPtr->bgGC->foreground;
	    break;
	case TK_RELIEF_SOLID:
	    left = right = RGB(0,0,0);
	    break;
    }
    half = width/2;
    if (leftBevel && (width & 1)) {
	half++;
    }
    TkWinFillRect(dc, x, y, half, height, left);
    TkWinFillRect(dc, x+half, y, width-half, height, right);
    TkWinReleaseDrawableDC(drawable, dc, &state);
}

/*
 *--------------------------------------------------------------
 *
 * 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(tkwin, drawable, border, x, y, width, height,
	leftIn, rightIn, topBevel, relief)
    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, y, width, height;	/* Bounding box of area of bevel.  Height
				 * gives width of border. */
    int leftIn, 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;
    TkWinDCState state;
    HDC dc = TkWinGetDrawableDC(display, drawable, &state);
    int topColor, bottomColor;

    if ((borderPtr->lightGC == None) && (relief != TK_RELIEF_FLAT)) {
	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_RAISED:
	    topColor = (topBevel)
		? borderPtr->lightGC->foreground
		: borderPtr->darkGC->foreground;
	    bottomColor = (topBevel)
		? ((WinBorder *)borderPtr)->light2ColorPtr->pixel
		: ((WinBorder *)borderPtr)->dark2ColorPtr->pixel;
	    break;
	case TK_RELIEF_SUNKEN:
	    topColor = (topBevel)
		? ((WinBorder *)borderPtr)->dark2ColorPtr->pixel
		: ((WinBorder *)borderPtr)->light2ColorPtr->pixel;
	    bottomColor = (topBevel)
		? borderPtr->darkGC->foreground
		: borderPtr->lightGC->foreground;
	    break;
	case TK_RELIEF_RIDGE:
	    topColor = borderPtr->lightGC->foreground;
	    bottomColor = borderPtr->darkGC->foreground;
	    break;
	case TK_RELIEF_GROOVE:
	    topColor = borderPtr->darkGC->foreground;
	    bottomColor = borderPtr->lightGC->foreground;
	    break;
	case TK_RELIEF_FLAT:
	    topColor = bottomColor = borderPtr->bgGC->foreground;
	    break;
	case TK_RELIEF_SOLID:
	    topColor = bottomColor = RGB(0,0,0);
    }

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

    if (leftIn) {
	x1 = x+1;
    } else {
	x1 = x+height-1;
    }
    x2 = x+width;
    if (rightIn) {
	x2--;
    } else {
	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++) {
	/*
	 * 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) {
	    TkWinFillRect(dc, x1, y, x2-x1, 1,
		(y < halfway) ? topColor : bottomColor);
	}
	x1 += x1Delta;
	x2 += x2Delta;
    }
    TkWinReleaseDrawableDC(drawable, dc, &state);
}

/*
 *----------------------------------------------------------------------
 *
 * 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(borderPtr, tkwin)
    TkBorder *borderPtr;	/* Information about border. */
    Tk_Window tkwin;		/* Window where border will be used for
				 * drawing. */
{
    XColor lightColor, darkColor;
    int tmp1, tmp2;
    int r, g, b;
    XGCValues gcValues;

    if (borderPtr->lightGC != None) {
	return;
    }

    /*
     * Handle the special case of the default system colors.
     */

    if ((TkWinIndexOfColor(borderPtr->bgColorPtr) == COLOR_3DFACE)
	|| (TkWinIndexOfColor(borderPtr->bgColorPtr) == COLOR_WINDOW)) {
	borderPtr->darkColorPtr = Tk_GetColor(NULL, tkwin,
	    Tk_GetUid("SystemButtonShadow"));
	gcValues.foreground = borderPtr->darkColorPtr->pixel;
	borderPtr->darkGC = Tk_GetGC(tkwin, GCForeground, &gcValues);
	borderPtr->lightColorPtr = Tk_GetColor(NULL, tkwin,
	    Tk_GetUid("SystemButtonHighlight"));
	gcValues.foreground = borderPtr->lightColorPtr->pixel;
	borderPtr->lightGC = Tk_GetGC(tkwin, GCForeground, &gcValues);
	((WinBorder*)borderPtr)->dark2ColorPtr = Tk_GetColor(NULL, tkwin,
		Tk_GetUid("System3dDarkShadow"));
	((WinBorder*)borderPtr)->light2ColorPtr = Tk_GetColor(NULL, tkwin,
		Tk_GetUid("System3dLight"));
	return;
    } else {
	darkColor.red = 0;
	darkColor.green = 0;
	darkColor.blue = 0;
	((WinBorder*)borderPtr)->dark2ColorPtr = Tk_GetColorByValue(tkwin,
	    &darkColor);
	lightColor = *(borderPtr->bgColorPtr);
	((WinBorder*)borderPtr)->light2ColorPtr = Tk_GetColorByValue(tkwin, 
	    &lightColor);
    }
    
    /*
     * 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 (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((Tcl_Interp *) NULL, tkwin,
		Tk_GetUid("gray50"));
	if (borderPtr->shadow == None) {
	    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.foreground = WhitePixelOfScreen(borderPtr->screen);
	gcValues.background = borderPtr->bgColorPtr->pixel;
	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);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TkWinGetBorderPixels --
 *
 *	This routine returns the 5 COLORREFs used to draw a given
 *	3d border.  
 *
 * Results:
 *	Returns the colors in the specified array.
 *
 * Side effects:
 *	May cause the remaining colors to be allocated.
 *
 *----------------------------------------------------------------------
 */

COLORREF
TkWinGetBorderPixels(tkwin, border, which)
    Tk_Window tkwin;
    Tk_3DBorder border;
    int which;			/* One of TK_3D_FLAT_GC, TK_3D_LIGHT_GC,
				 * TK_3D_DARK_GC, TK_3D_LIGHT2, TK_3D_DARK2 */
{
    WinBorder *borderPtr = (WinBorder *) border;
    
    if (borderPtr->info.lightGC == None) {
	TkpGetShadows(&borderPtr->info, tkwin);
    }
    switch (which) {
	case TK_3D_FLAT_GC:
	    return borderPtr->info.bgColorPtr->pixel;
	case TK_3D_LIGHT_GC:
	    if (borderPtr->info.lightColorPtr == NULL) {
		return WhitePixelOfScreen(borderPtr->info.screen);
	    }
	    return borderPtr->info.lightColorPtr->pixel;
	case TK_3D_DARK_GC:
	    if (borderPtr->info.darkColorPtr == NULL) {
		return BlackPixelOfScreen(borderPtr->info.screen);
	    }
	    return borderPtr->info.darkColorPtr->pixel;
	case TK_3D_LIGHT2:
	    return borderPtr->light2ColorPtr->pixel;
	case TK_3D_DARK2:
	    return borderPtr->dark2ColorPtr->pixel;
    }
    return 0;
}
