/*
 * $Id: ttkClassicTheme.c,v 1.3 2006/11/07 03:45:27 jenglish Exp $
 *
 * Copyright (c) 2004, Joe English
 *
 * Ttk widget set: classic theme.
 *
 * Implements the "classic" Motif-like Tk look.
 *
 */

#include <tk.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include "ttkTheme.h"

#define DEFAULT_BORDERWIDTH "2"
#define DEFAULT_ARROW_SIZE "15"

/*----------------------------------------------------------------------
 * +++ Highlight element implementation.
 * 	Draw a solid highlight border to indicate focus.
 */

typedef struct {
    Tcl_Obj	*highlightColorObj;
    Tcl_Obj	*highlightThicknessObj;
} HighlightElement;

static Ttk_ElementOptionSpec HighlightElementOptions[] = {
    { "-highlightcolor",TK_OPTION_COLOR,
	Tk_Offset(HighlightElement,highlightColorObj), DEFAULT_BACKGROUND },
    { "-highlightthickness",TK_OPTION_PIXELS,
	Tk_Offset(HighlightElement,highlightThicknessObj), "0" },
    {NULL}
};

static void
HighlightElementSize(
    void *clientData, void *elementRecord,
    Tk_Window tkwin, int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr)
{
    HighlightElement *hl = elementRecord;
    int highlightThickness = 0;

    Tcl_GetIntFromObj(NULL,hl->highlightThicknessObj,&highlightThickness);
    *paddingPtr = Ttk_UniformPadding((short)highlightThickness);
}

static void
HighlightElementDraw(void *clientData, void *elementRecord,
    Tk_Window tkwin, Drawable d, Ttk_Box b, unsigned int state)
{
    HighlightElement *hl = elementRecord;
    int highlightThickness = 0;
    XColor *highlightColor = Tk_GetColorFromObj(tkwin, hl->highlightColorObj);

    Tcl_GetIntFromObj(NULL,hl->highlightThicknessObj,&highlightThickness);
    if (highlightColor && highlightThickness > 0) {
	GC gc = Tk_GCForColor(highlightColor, d);
	Tk_DrawFocusHighlight(tkwin, gc, highlightThickness, d);
    }
}

static Ttk_ElementSpec HighlightElementSpec =
{
    TK_STYLE_VERSION_2,
    sizeof(HighlightElement),
    HighlightElementOptions,
    HighlightElementSize,
    HighlightElementDraw
};

/*------------------------------------------------------------------------
 * +++ Button Border element:
 * 
 * The Motif-style button border on X11 consists of (from outside-in):
 *
 * + focus indicator (controlled by -highlightcolor and -highlightthickness),
 * + default ring (if -default active; blank if -default normal)
 * + shaded border (controlled by -background, -borderwidth, and -relief)
 */

typedef struct {
    Tcl_Obj	*borderObj;
    Tcl_Obj	*borderWidthObj;
    Tcl_Obj	*reliefObj;
    Tcl_Obj	*defaultStateObj;
} ButtonBorderElement;

static Ttk_ElementOptionSpec ButtonBorderElementOptions[] =
{
    { "-background", TK_OPTION_BORDER, 
	Tk_Offset(ButtonBorderElement,borderObj), DEFAULT_BACKGROUND },
    { "-borderwidth", TK_OPTION_PIXELS, 
	Tk_Offset(ButtonBorderElement,borderWidthObj), DEFAULT_BORDERWIDTH },
    { "-relief", TK_OPTION_RELIEF, 
	Tk_Offset(ButtonBorderElement,reliefObj), "flat" },
    { "-default", TK_OPTION_ANY, 
	Tk_Offset(ButtonBorderElement,defaultStateObj), "disabled" },
    {NULL}
};

static void
ButtonBorderElementSize(
    void *clientData, void *elementRecord, Tk_Window tkwin,
    int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr)
{
    ButtonBorderElement *bd = elementRecord;
    int defaultState = TTK_BUTTON_DEFAULT_DISABLED;
    int borderWidth = 0;

    Tcl_GetIntFromObj(NULL, bd->borderWidthObj, &borderWidth);
    Ttk_GetButtonDefaultStateFromObj(NULL, bd->defaultStateObj, &defaultState);

    if (defaultState != TTK_BUTTON_DEFAULT_DISABLED) {
	borderWidth += 5;
    }
    *paddingPtr = Ttk_UniformPadding((short)borderWidth);
}

/*
 * (@@@ Note: ButtonBorderElement still still still buggy:
 * padding for default ring is drawn in the wrong color 
 * when the button is active.)
 */
static void
ButtonBorderElementDraw(
    void *clientData, void *elementRecord, Tk_Window tkwin,
    Drawable d, Ttk_Box b, unsigned int state)
{
    ButtonBorderElement *bd = elementRecord;
    Tk_3DBorder border = NULL;
    int borderWidth = 1, relief = TK_RELIEF_FLAT;
    int defaultState = TTK_BUTTON_DEFAULT_DISABLED;
    int inset = 0;

    /*
     * Get option values.
     */
    border = Tk_Get3DBorderFromObj(tkwin, bd->borderObj);
    Tcl_GetIntFromObj(NULL, bd->borderWidthObj, &borderWidth);
    Tk_GetReliefFromObj(NULL, bd->reliefObj, &relief);
    Ttk_GetButtonDefaultStateFromObj(NULL, bd->defaultStateObj, &defaultState);

    /*
     * Default ring:
     */
    switch (defaultState)
    {
	case TTK_BUTTON_DEFAULT_DISABLED :
	    break;
	case TTK_BUTTON_DEFAULT_NORMAL :
	    inset += 5;
	    break;
	case TTK_BUTTON_DEFAULT_ACTIVE :
            Tk_Draw3DRectangle(tkwin, d, border,
		b.x+inset, b.y+inset, b.width - 2*inset, b.height - 2*inset,
		2, TK_RELIEF_FLAT);
            inset += 2;
            Tk_Draw3DRectangle(tkwin, d, border,
		b.x+inset, b.y+inset, b.width - 2*inset, b.height - 2*inset,
		1, TK_RELIEF_SUNKEN);
	    ++inset;
            Tk_Draw3DRectangle(tkwin, d, border,
		b.x+inset, b.y+inset, b.width - 2*inset, b.height - 2*inset,
		2, TK_RELIEF_FLAT);
	    inset += 2;
	    break;
    }

    /*
     * 3-D border:
     */
    if (border && borderWidth > 0) {
	Tk_Draw3DRectangle(tkwin, d, border,
	    b.x+inset, b.y+inset, b.width - 2*inset, b.height - 2*inset,
	    borderWidth,relief);
    }
}

static Ttk_ElementSpec ButtonBorderElementSpec =
{
    TK_STYLE_VERSION_2,
    sizeof(ButtonBorderElement),
    ButtonBorderElementOptions,
    ButtonBorderElementSize,
    ButtonBorderElementDraw
};

/*----------------------------------------------------------------------
 * +++ Arrow element(s).
 *
 * Draws a 3-D shaded triangle.
 * clientData is an enum ArrowDirection pointer.
 */

static int ArrowElements[] = { ARROW_UP, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT };
typedef struct
{
    Tcl_Obj *sizeObj;
    Tcl_Obj *borderObj;
    Tcl_Obj *borderWidthObj;
    Tcl_Obj *reliefObj;
} ArrowElement;

static Ttk_ElementOptionSpec ArrowElementOptions[] =
{
    { "-arrowsize", TK_OPTION_PIXELS, Tk_Offset(ArrowElement,sizeObj),
	DEFAULT_ARROW_SIZE },
    { "-background", TK_OPTION_BORDER, Tk_Offset(ArrowElement,borderObj),
    	DEFAULT_BACKGROUND },
    { "-borderwidth", TK_OPTION_PIXELS, Tk_Offset(ArrowElement,borderWidthObj),
    	DEFAULT_BORDERWIDTH },
    { "-relief", TK_OPTION_RELIEF, Tk_Offset(ArrowElement,reliefObj),"raised" },
    { NULL }
};

static void ArrowElementSize(
    void *clientData, void *elementRecord, Tk_Window tkwin,
    int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr)
{
    ArrowElement *arrow = elementRecord;
    int size = 12;

    Tk_GetPixelsFromObj(NULL, tkwin, arrow->sizeObj, &size);
    *widthPtr = *heightPtr = size;
}

static void ArrowElementDraw(
    void *clientData, void *elementRecord, Tk_Window tkwin,
    Drawable d, Ttk_Box b, unsigned int state)
{
    int direction = *(int *)clientData;
    ArrowElement *arrow = elementRecord;
    Tk_3DBorder border;
    int borderWidth;
    int relief = TK_RELIEF_RAISED;
    int size;
    XPoint points[3];

    Tk_GetPixelsFromObj(NULL, tkwin, arrow->borderWidthObj, &borderWidth);
    border = Tk_Get3DBorderFromObj(tkwin, arrow->borderObj);
    Tk_GetReliefFromObj(NULL, arrow->reliefObj, &relief);

    size = b.width < b.height ? b.width : b.height;

    /*
     * @@@ There are off-by-one pixel errors in the way these are drawn;
     * @@@ need to take a look at Tk_Fill3DPolygon and X11 to find the
     * @@@ exact rules.
     */
    switch (direction)
    {
	case ARROW_UP:
	    points[2].x = b.x; 		points[2].y = b.y + size;
	    points[1].x = b.x + size/2;	points[1].y = b.y;
	    points[0].x = b.x + size;	points[0].y = b.y + size;
	    break;
	case ARROW_DOWN:
	    points[0].x = b.x; 		points[0].y = b.y;
	    points[1].x = b.x + size/2;	points[1].y = b.y + size;
	    points[2].x = b.x + size;	points[2].y = b.y;
	    break;
	case ARROW_LEFT:
	    points[0].x = b.x; 		points[0].y = b.y + size / 2;
	    points[1].x = b.x + size;	points[1].y = b.y + size;
	    points[2].x = b.x + size;	points[2].y = b.y;
	    break;
	case ARROW_RIGHT:
	    points[0].x = b.x + size;	points[0].y = b.y + size / 2;
	    points[1].x = b.x;		points[1].y = b.y;
	    points[2].x = b.x;		points[2].y = b.y + size;
	    break;
    }

    Tk_Fill3DPolygon(tkwin, d, border, points, 3, borderWidth, relief);
}

static Ttk_ElementSpec ArrowElementSpec =
{
    TK_STYLE_VERSION_2,
    sizeof(ArrowElement),
    ArrowElementOptions,
    ArrowElementSize,
    ArrowElementDraw
};


/*------------------------------------------------------------------------
 * +++ Sash element (for ttk::panedwindow)
 *
 * NOTES: 
 *
 * panedwindows with -orient horizontal use vertical sashes, and vice versa.
 *
 * Interpretation of -sashrelief 'groove' and 'ridge' are
 * swapped wrt. the core panedwindow, which (I think) has them backwards.
 *
 * Default -sashrelief is sunken; the core panedwindow has default 
 * -sashrelief raised, but that looks wrong to me.
 */

static Ttk_Orient SashClientData[] = {
    TTK_ORIENT_HORIZONTAL, TTK_ORIENT_VERTICAL 
};

typedef struct {
    Tcl_Obj *borderObj; 	/* background color */
    Tcl_Obj *sashReliefObj;	/* sash relief */
    Tcl_Obj *sashThicknessObj;	/* overall thickness of sash */
    Tcl_Obj *sashPadObj;	/* padding on either side of handle */
    Tcl_Obj *handleSizeObj;	/* handle width and height */
    Tcl_Obj *handlePadObj;	/* handle's distance from edge */
} SashElement;

static Ttk_ElementOptionSpec SashOptions[] = {
    { "-background", TK_OPTION_BORDER, 
	Tk_Offset(SashElement,borderObj), DEFAULT_BACKGROUND },
    { "-sashrelief", TK_OPTION_RELIEF, 
	Tk_Offset(SashElement,sashReliefObj), "sunken" },
    { "-sashthickness", TK_OPTION_PIXELS,
	Tk_Offset(SashElement,sashThicknessObj), "6" },
    { "-sashpad", TK_OPTION_PIXELS, 
	Tk_Offset(SashElement,sashPadObj), "2" },
    { "-handlesize", TK_OPTION_PIXELS,
	Tk_Offset(SashElement,handleSizeObj), "8" },
    { "-handlepad", TK_OPTION_PIXELS,
	Tk_Offset(SashElement,handlePadObj), "8" },
    {0,0,0}
};

static void SashElementSize(
    void *clientData, void *elementRecord, Tk_Window tkwin,
    int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr)
{
    SashElement *sash = elementRecord;
    int sashPad = 2, sashThickness = 6, handleSize = 8;
    int horizontal = *((Ttk_Orient*)clientData) == TTK_ORIENT_HORIZONTAL;

    Tk_GetPixelsFromObj(NULL, tkwin, sash->sashThicknessObj, &sashThickness);
    Tk_GetPixelsFromObj(NULL, tkwin, sash->handleSizeObj, &handleSize);
    Tk_GetPixelsFromObj(NULL, tkwin, sash->sashPadObj, &sashPad);

    if (sashThickness < handleSize + 2*sashPad)
	sashThickness = handleSize + 2*sashPad;

    if (horizontal)
	*heightPtr = sashThickness;
    else
	*widthPtr = sashThickness;
}

static void SashElementDraw(
    void *clientData, void *elementRecord, Tk_Window tkwin,
    Drawable d, Ttk_Box b, Ttk_State state)
{
    SashElement *sash = elementRecord;
    Tk_3DBorder border = Tk_Get3DBorderFromObj(tkwin, sash->borderObj);
    GC gc1,gc2;
    int relief = TK_RELIEF_RAISED;
    int handleSize = 8, handlePad = 8;
    int horizontal = *((Ttk_Orient*)clientData) == TTK_ORIENT_HORIZONTAL;
    Ttk_Box hb;

    Tk_GetPixelsFromObj(NULL, tkwin, sash->handleSizeObj, &handleSize);
    Tk_GetPixelsFromObj(NULL, tkwin, sash->handlePadObj, &handlePad);
    Tk_GetReliefFromObj(NULL, sash->sashReliefObj, &relief);

    switch (relief) {
	case TK_RELIEF_RAISED: case TK_RELIEF_RIDGE:
	    gc1 = Tk_3DBorderGC(tkwin, border, TK_3D_LIGHT_GC);
	    gc2 = Tk_3DBorderGC(tkwin, border, TK_3D_DARK_GC);
	    break;
	case TK_RELIEF_SUNKEN: case TK_RELIEF_GROOVE:
	    gc1 = Tk_3DBorderGC(tkwin, border, TK_3D_DARK_GC);
	    gc2 = Tk_3DBorderGC(tkwin, border, TK_3D_LIGHT_GC);
	    break;
	case TK_RELIEF_SOLID: 
	    gc1 = gc2 = Tk_3DBorderGC(tkwin, border, TK_3D_DARK_GC);
	    break;
	case TK_RELIEF_FLAT: 
	default:
	    gc1 = gc2 = Tk_3DBorderGC(tkwin, border, TK_3D_FLAT_GC);
	    break;
    }

    /* Draw sash line:
     */
    if (horizontal) {
	int y = b.y + b.height/2 - 1;
	XDrawLine(Tk_Display(tkwin), d, gc1, b.x, y, b.x+b.width, y); ++y;
	XDrawLine(Tk_Display(tkwin), d, gc2, b.x, y, b.x+b.width, y);
    } else {
	int x = b.x + b.width/2 - 1;
	XDrawLine(Tk_Display(tkwin), d, gc1, x, b.y, x, b.y+b.height); ++x;
	XDrawLine(Tk_Display(tkwin), d, gc2, x, b.y, x, b.y+b.height);
    }

    /* Draw handle:
     */
    if (handleSize >= 0) {
	if (horizontal) {
	    hb = Ttk_StickBox(b, handleSize, handleSize, TTK_STICK_W);
	    hb.x += handlePad;
	} else {
	    hb = Ttk_StickBox(b, handleSize, handleSize, TTK_STICK_N);
	    hb.y += handlePad;
	}
	Tk_Fill3DRectangle(tkwin, d, border, 
	    hb.x, hb.y, hb.width, hb.height, 1, TK_RELIEF_RAISED);
    }
}

static Ttk_ElementSpec SashElementSpec = {
    TK_STYLE_VERSION_2,
    sizeof(SashElement),
    SashOptions,
    SashElementSize,
    SashElementDraw
};

/*------------------------------------------------------------------------
 * +++ Widget layouts.
 */

TTK_BEGIN_LAYOUT(ButtonLayout)
    TTK_GROUP("Button.highlight", TTK_FILL_BOTH,
        TTK_GROUP("Button.border", TTK_FILL_BOTH|TTK_BORDER,
	    TTK_GROUP("Button.padding", TTK_FILL_BOTH,
	        TTK_NODE("Button.label", TTK_FILL_BOTH))))
TTK_END_LAYOUT

TTK_BEGIN_LAYOUT(CheckbuttonLayout)
    TTK_GROUP("Checkbutton.highlight", TTK_FILL_BOTH,
        TTK_GROUP("Checkbutton.border", TTK_FILL_BOTH,
	    TTK_GROUP("Checkbutton.padding", TTK_FILL_BOTH,
	        TTK_NODE("Checkbutton.indicator", TTK_PACK_LEFT)
	        TTK_NODE("Checkbutton.label", TTK_PACK_LEFT|TTK_FILL_BOTH))))
TTK_END_LAYOUT

TTK_BEGIN_LAYOUT(RadiobuttonLayout)
    TTK_GROUP("Radiobutton.highlight", TTK_FILL_BOTH,
        TTK_GROUP("Radiobutton.border", TTK_FILL_BOTH,
	    TTK_GROUP("Radiobutton.padding", TTK_FILL_BOTH,
	        TTK_NODE("Radiobutton.indicator", TTK_PACK_LEFT)
	        TTK_NODE("Radiobutton.label", TTK_PACK_LEFT|TTK_FILL_BOTH))))
TTK_END_LAYOUT

TTK_BEGIN_LAYOUT(MenubuttonLayout)
    TTK_GROUP("Menubutton.highlight", TTK_FILL_BOTH,
        TTK_GROUP("Menubutton.border", TTK_FILL_BOTH,
	    TTK_NODE("Menubutton.indicator", TTK_PACK_RIGHT)
	    TTK_GROUP("Menubutton.padding", TTK_PACK_LEFT|TTK_EXPAND|TTK_FILL_X,
	        TTK_NODE("Menubutton.label", 0))))
TTK_END_LAYOUT

/* "classic" entry, includes highlight border */
TTK_BEGIN_LAYOUT(EntryLayout)
    TTK_GROUP("Entry.highlight", TTK_FILL_BOTH,
        TTK_GROUP("Entry.field", TTK_FILL_BOTH|TTK_BORDER,
	    TTK_GROUP("Entry.padding", TTK_FILL_BOTH,
	        TTK_NODE("Entry.textarea", TTK_FILL_BOTH))))
TTK_END_LAYOUT

/* Notebook tabs -- omit focus ring */
TTK_BEGIN_LAYOUT(TabLayout)
    TTK_GROUP("Notebook.tab", TTK_FILL_BOTH,
	TTK_GROUP("Notebook.padding", TTK_FILL_BOTH,
	    TTK_NODE("Notebook.label", TTK_FILL_BOTH)))
TTK_END_LAYOUT

/* POSSIBLY: include Scale layouts w/focus border
 */

/*------------------------------------------------------------------------
 * TtkClassicTheme_Init --
 * 	Install classic theme.
 */

MODULE_SCOPE int TtkClassicTheme_Init(Tcl_Interp *interp)
{
    Ttk_Theme theme =  Ttk_CreateTheme(interp, "classic", NULL);

    if (!theme) {
	return TCL_ERROR;
    }

    /*
     * Register elements:
     */
    Ttk_RegisterElement(interp, theme, "highlight",
	    &HighlightElementSpec, NULL);

    Ttk_RegisterElement(interp, theme, "Button.border",
	    &ButtonBorderElementSpec, NULL);

    Ttk_RegisterElement(interp, theme, "uparrow",
	    &ArrowElementSpec, &ArrowElements[0]);
    Ttk_RegisterElement(interp, theme, "downarrow",
	    &ArrowElementSpec, &ArrowElements[1]);
    Ttk_RegisterElement(interp, theme, "leftarrow",
	    &ArrowElementSpec, &ArrowElements[2]);
    Ttk_RegisterElement(interp, theme, "rightarrow",
	    &ArrowElementSpec, &ArrowElements[3]);
    Ttk_RegisterElement(interp, theme, "arrow",
	    &ArrowElementSpec, &ArrowElements[0]);

    Ttk_RegisterElement(interp, theme, "hsash", 
	    &SashElementSpec, &SashClientData[0]);
    Ttk_RegisterElement(interp, theme, "vsash",
	    &SashElementSpec, &SashClientData[1]);

    /*
     * Register layouts:
     */
    Ttk_RegisterLayout(theme, "TButton", ButtonLayout);
    Ttk_RegisterLayout(theme, "TCheckbutton", CheckbuttonLayout);
    Ttk_RegisterLayout(theme, "TRadiobutton", RadiobuttonLayout);
    Ttk_RegisterLayout(theme, "TMenubutton", MenubuttonLayout);
    Ttk_RegisterLayout(theme, "TEntry", EntryLayout);
    Ttk_RegisterLayout(theme, "TNotebook.Tab", TabLayout);

    Tcl_PkgProvide(interp, "ttk::theme::classic", TTK_VERSION);

    return TCL_OK;
}

/*EOF*/
