/* winTheme.c - Copyright © 2004 Pat Thoyts <patthoyts@users.sf.net>
 */

#ifdef _MSC_VER
#define WIN32_LEAN_AND_MEAN
#endif

#include "tkWinInt.h"

#ifndef DFCS_HOT	/* Windows 98/Me, Windows 2000/XP only */
#define DFCS_HOT 0
#endif

#include "ttk/ttkThemeInt.h"

/*
 * BoxToRect --
 *	Helper routine.  Converts a Ttk_Box to a Win32 RECT.
 */
static RECT BoxToRect(Ttk_Box b)
{
    RECT rc;
    rc.top = b.y;
    rc.left = b.x;
    rc.bottom = b.y + b.height;
    rc.right = b.x + b.width;
    return rc;
}

/*
 * ReliefToEdge --
 *	Convert a Tk "relief" value into an Windows "edge" value.
 *	NB: Caller must check for RELIEF_FLAT and RELIEF_SOLID,
 *	which must be handled specially.
 *
 *	Passing the BF_FLAT flag to DrawEdge() yields something similar
 *	to TK_RELIEF_SOLID. TK_RELIEF_FLAT can be implemented by not
 *	drawing anything.
 */
static unsigned int ReliefToEdge(int relief)
{
    switch (relief) {
	case TK_RELIEF_RAISED: return EDGE_RAISED;
	case TK_RELIEF_SUNKEN: return EDGE_SUNKEN;
	case TK_RELIEF_RIDGE:  return EDGE_BUMP;
	case TK_RELIEF_GROOVE: return EDGE_ETCHED;
	case TK_RELIEF_SOLID:  return BDR_RAISEDOUTER;
	default:
	case TK_RELIEF_FLAT:   return BDR_RAISEDOUTER;
    }
}

/*------------------------------------------------------------------------
 * +++ State tables for FrameControlElements.
 */

static const Ttk_StateTable checkbutton_statemap[] = { /* see also SF#1865898 */
    { DFCS_BUTTON3STATE|DFCS_CHECKED|DFCS_INACTIVE,
	TTK_STATE_ALTERNATE|TTK_STATE_DISABLED, 0 },
    { DFCS_BUTTON3STATE|DFCS_CHECKED|DFCS_PUSHED,
	TTK_STATE_ALTERNATE|TTK_STATE_PRESSED, 0 },
    { DFCS_BUTTON3STATE|DFCS_CHECKED|DFCS_HOT,
	TTK_STATE_ALTERNATE|TTK_STATE_ACTIVE, 0 },
    { DFCS_BUTTON3STATE|DFCS_CHECKED,
	TTK_STATE_ALTERNATE, 0 },

    { DFCS_CHECKED|DFCS_INACTIVE, TTK_STATE_SELECTED|TTK_STATE_DISABLED, 0 },
    { DFCS_CHECKED|DFCS_PUSHED,   TTK_STATE_SELECTED|TTK_STATE_PRESSED, 0 },
    { DFCS_CHECKED|DFCS_HOT,      TTK_STATE_SELECTED|TTK_STATE_ACTIVE, 0 },
    { DFCS_CHECKED,	          TTK_STATE_SELECTED, 0 },

    { DFCS_INACTIVE, TTK_STATE_DISABLED, 0 },
    { DFCS_PUSHED,   TTK_STATE_PRESSED, 0 },
    { DFCS_HOT,      TTK_STATE_ACTIVE, 0 },
    { 0, 0, 0 },
};

static const Ttk_StateTable pushbutton_statemap[] = {
    { DFCS_INACTIVE,	  TTK_STATE_DISABLED, 0 },
    { DFCS_PUSHED,	  TTK_STATE_PRESSED, 0 },
    { DFCS_HOT,		  TTK_STATE_ACTIVE, 0 },
    { 0, 0, 0 }
};

static const Ttk_StateTable arrow_statemap[] = {
    { DFCS_INACTIVE,            TTK_STATE_DISABLED, 0 },
    { DFCS_PUSHED | DFCS_FLAT,  TTK_STATE_PRESSED,  0 },
    { 0, 0, 0 }
};

/*------------------------------------------------------------------------
 * +++ FrameControlElement --
 *	General-purpose element for things drawn with DrawFrameControl
 */
typedef struct {
    const char *name;		/* element name */
    int classId;		/* class id for DrawFrameControl */
    int partId;			/* part id for DrawFrameControl  */
    unsigned cxId;		/* system metric ids for width/height... */
    unsigned cyId;		/* ... or size if FIXEDSIZE bit set */
    const Ttk_StateTable *stateMap;	/* map Tk states to Win32 flags */
    Ttk_Padding margins;	/* additional placement padding */
} FrameControlElementData;

#define BASE_DIM    16
#define _FIXEDSIZE  0x80000000UL
#define _HALFMETRIC 0x40000000UL
#define FIXEDSIZE(id) (id|_FIXEDSIZE)
#define HALFMETRIC(id) (id|_HALFMETRIC)
#define GETMETRIC(m) \
    ((m) & _FIXEDSIZE ? (int)((m) & ~_FIXEDSIZE) : GetSystemMetrics((m)&0xFFFFFFF))

static FrameControlElementData FrameControlElements[] = {
    { "Checkbutton.indicator",
	DFC_BUTTON, DFCS_BUTTONCHECK, FIXEDSIZE(BASE_DIM), FIXEDSIZE(BASE_DIM),
	checkbutton_statemap, {0,0,4,0} },
    { "Radiobutton.indicator",
	DFC_BUTTON, DFCS_BUTTONRADIO, FIXEDSIZE(BASE_DIM), FIXEDSIZE(BASE_DIM),
	checkbutton_statemap, {0,0,4,0} },
    { "uparrow",
	DFC_SCROLL, DFCS_SCROLLUP, SM_CXVSCROLL, SM_CYVSCROLL,
	arrow_statemap, {0,0,0,0} },
    { "downarrow",
	DFC_SCROLL, DFCS_SCROLLDOWN, SM_CXVSCROLL, SM_CYVSCROLL,
	arrow_statemap, {0,0,0,0} },
    { "leftarrow",
	DFC_SCROLL, DFCS_SCROLLLEFT, SM_CXHSCROLL, SM_CYHSCROLL,
	arrow_statemap, {0,0,0,0} },
    { "rightarrow",
	DFC_SCROLL, DFCS_SCROLLRIGHT, SM_CXHSCROLL, SM_CYHSCROLL,
	arrow_statemap, {0,0,0,0} },
    { "sizegrip",
	DFC_SCROLL, DFCS_SCROLLSIZEGRIP, SM_CXVSCROLL, SM_CYHSCROLL,
	arrow_statemap, {0,0,0,0} },
    { "Spinbox.uparrow",
	DFC_SCROLL, DFCS_SCROLLUP, SM_CXVSCROLL, HALFMETRIC(SM_CYVSCROLL),
	arrow_statemap, {0,0,0,0} },
    { "Spinbox.downarrow",
	DFC_SCROLL, DFCS_SCROLLDOWN, SM_CXVSCROLL, HALFMETRIC(SM_CYVSCROLL),
	arrow_statemap, {0,0,0,0} },

    { 0,0,0,0,0,0, {0,0,0,0} }
};

/* ---------------------------------------------------------------------- */

static void FrameControlElementSize(
    void *clientData,
    TCL_UNUSED(void *), /* elementRecord */
    Tk_Window tkwin,
    int *widthPtr,
    int *heightPtr,
    TCL_UNUSED(Ttk_Padding *))
{
    FrameControlElementData *p = (FrameControlElementData *)clientData;
    int cx = GETMETRIC(p->cxId);
    int cy = GETMETRIC(p->cyId);

    if ((p->cxId & _FIXEDSIZE) && cx == BASE_DIM) {
	double scalingLevel = TkScalingLevel(tkwin);
	cx *= scalingLevel;
	cy *= scalingLevel;

	/*
	 * Update the corresponding element of the array FrameControlElements
	 */
	p->cxId = FIXEDSIZE(cx);
	p->cyId = FIXEDSIZE(cy);
    }

    if (p->cxId & _HALFMETRIC) cx /= 2;
    if (p->cyId & _HALFMETRIC) cy /= 2;

    *widthPtr = cx + Ttk_PaddingWidth(p->margins);
    *heightPtr = cy + Ttk_PaddingHeight(p->margins);
}

static void FrameControlElementDraw(
    void *clientData,
    TCL_UNUSED(void *), /* elementRecord */
    Tk_Window tkwin,
    Drawable d,
    Ttk_Box b,
    Ttk_State state)
{
    FrameControlElementData *elementData = (FrameControlElementData *)clientData;
    RECT rc = BoxToRect(Ttk_PadBox(b, elementData->margins));
    TkWinDCState dcState;
    HDC hdc = TkWinGetDrawableDC(Tk_Display(tkwin), d, &dcState);

    DrawFrameControl(hdc, &rc,
	elementData->classId,
	elementData->partId|Ttk_StateTableLookup(elementData->stateMap, state));
    TkWinReleaseDrawableDC(d, hdc, &dcState);
}

static const Ttk_ElementSpec FrameControlElementSpec = {
    TK_STYLE_VERSION_2,
    sizeof(NullElement),
    TtkNullElementOptions,
    FrameControlElementSize,
    FrameControlElementDraw
};

/*----------------------------------------------------------------------
 * +++ Border element implementation.
 */

typedef struct {
    Tcl_Obj	*reliefObj;
} BorderElement;

static const Ttk_ElementOptionSpec BorderElementOptions[] = {
    { "-relief",TK_OPTION_RELIEF, offsetof(BorderElement,reliefObj), "flat" },
    {NULL, TK_OPTION_BOOLEAN, 0, NULL}
};

static void BorderElementSize(
    TCL_UNUSED(void *), /* clientData */
    TCL_UNUSED(void *), /* elementRecord */
    TCL_UNUSED(Tk_Window),
    TCL_UNUSED(int *), /* widthPtr */
    TCL_UNUSED(int *), /* heightPtr */
    Ttk_Padding *paddingPtr)
{
    paddingPtr->left = paddingPtr->right = GetSystemMetrics(SM_CXEDGE);
    paddingPtr->top = paddingPtr->bottom = GetSystemMetrics(SM_CYEDGE);
}

static void BorderElementDraw(
    TCL_UNUSED(void *), /* clientData */
    void *elementRecord,
    Tk_Window tkwin,
    Drawable d,
    Ttk_Box b,
    TCL_UNUSED(Ttk_State))
{
    BorderElement *border = (BorderElement *)elementRecord;
    RECT rc = BoxToRect(b);
    int relief = TK_RELIEF_FLAT;
    TkWinDCState dcState;
    HDC hdc;

    Tk_GetReliefFromObj(NULL, border->reliefObj, &relief);

    if (relief != TK_RELIEF_FLAT) {
	UINT xFlags = (relief == TK_RELIEF_SOLID) ? BF_FLAT : 0;
	hdc = TkWinGetDrawableDC(Tk_Display(tkwin), d, &dcState);
	DrawEdge(hdc, &rc, ReliefToEdge(relief), BF_RECT | xFlags);
	TkWinReleaseDrawableDC(d, hdc, &dcState);
    }
}

static const Ttk_ElementSpec BorderElementSpec = {
    TK_STYLE_VERSION_2,
    sizeof(BorderElement),
    BorderElementOptions,
    BorderElementSize,
    BorderElementDraw
};

/*
 * Entry field borders:
 * Sunken border; also fill with window color.
 */

typedef struct {
    Tcl_Obj	*backgroundObj;
} FieldElement;

static const Ttk_ElementOptionSpec FieldElementOptions[] = {
    { "-fieldbackground", TK_OPTION_BORDER,
	offsetof(FieldElement,backgroundObj), "white" },
    { NULL, TK_OPTION_BOOLEAN, 0, NULL }
};

static void FieldElementSize(
    TCL_UNUSED(void *), /* clientData */
    TCL_UNUSED(void *), /* elementRecord */
    TCL_UNUSED(Tk_Window),
    TCL_UNUSED(int *), /* widthPtr */
    TCL_UNUSED(int *), /* heightPtr */
    Ttk_Padding *paddingPtr)
{
    paddingPtr->left = paddingPtr->right = GetSystemMetrics(SM_CXEDGE);
    paddingPtr->top = paddingPtr->bottom = GetSystemMetrics(SM_CYEDGE);
}

static void FieldElementDraw(
    TCL_UNUSED(void *), /* clientData */
    void *elementRecord,
    Tk_Window tkwin,
    Drawable d,
    Ttk_Box b,
    TCL_UNUSED(Ttk_State))
{
    FieldElement *field = (FieldElement *)elementRecord;
    Tk_3DBorder bg = Tk_Get3DBorderFromObj(tkwin, field->backgroundObj);
    RECT rc = BoxToRect(b);
    TkWinDCState dcState;
    HDC hdc;

    Tk_Fill3DRectangle(
	tkwin, d, bg, b.x, b.y, b.width, b.height, 0, TK_RELIEF_FLAT);

    hdc = TkWinGetDrawableDC(Tk_Display(tkwin), d, &dcState);
    DrawEdge(hdc, &rc, EDGE_SUNKEN, BF_RECT);
    TkWinReleaseDrawableDC(d, hdc, &dcState);
}

static const Ttk_ElementSpec FieldElementSpec = {
    TK_STYLE_VERSION_2,
    sizeof(FieldElement),
    FieldElementOptions,
    FieldElementSize,
    FieldElementDraw
};

/*------------------------------------------------------------------------
 * +++ Button borders.
 *	Drawn with DrawFrameControl instead of DrawEdge;
 *	Also draw default indicator and focus ring.
 */
typedef struct {
    Tcl_Obj	*reliefObj;
    Tcl_Obj	*highlightColorObj;
    Tcl_Obj	*defaultStateObj;
} ButtonBorderElement;

static const Ttk_ElementOptionSpec ButtonBorderElementOptions[] = {
    { "-relief",TK_OPTION_RELIEF,
	offsetof(ButtonBorderElement,reliefObj), "flat" },
    { "-highlightcolor",TK_OPTION_COLOR,
	offsetof(ButtonBorderElement,highlightColorObj), "black" },
    { "-default", TK_OPTION_ANY,
	offsetof(ButtonBorderElement,defaultStateObj), "disabled" },
    {NULL, TK_OPTION_BOOLEAN, 0, NULL}
};

static void ButtonBorderElementSize(
    TCL_UNUSED(void *), /* clientData */
    void *elementRecord,
    TCL_UNUSED(Tk_Window),
    TCL_UNUSED(int *), /* widthPtr */
    TCL_UNUSED(int *), /* heightPtr */
    Ttk_Padding *paddingPtr)
{
    ButtonBorderElement *bd = (ButtonBorderElement *)elementRecord;
    int relief = TK_RELIEF_RAISED;
    Ttk_ButtonDefaultState defaultState = TTK_BUTTON_DEFAULT_DISABLED;
    short int cx, cy;

    Tk_GetReliefFromObj(NULL, bd->reliefObj, &relief);
    Ttk_GetButtonDefaultStateFromObj(NULL, bd->defaultStateObj, &defaultState);
    cx = GetSystemMetrics(SM_CXEDGE);
    cy = GetSystemMetrics(SM_CYEDGE);

    /* Space for default indicator:
     */
    if (defaultState != TTK_BUTTON_DEFAULT_DISABLED) {
	++cx; ++cy;
    }

    /* Space for focus ring:
     */
    cx += 2;
    cy += 2;

    *paddingPtr = Ttk_MakePadding(cx,cy,cx,cy);
}

static void ButtonBorderElementDraw(
    TCL_UNUSED(void *), /* clientData */
    void *elementRecord,
    Tk_Window tkwin,
    Drawable d,
    Ttk_Box b,
    Ttk_State state)
{
    ButtonBorderElement *bd = (ButtonBorderElement *)elementRecord;
    int relief = TK_RELIEF_FLAT;
    Ttk_ButtonDefaultState defaultState = TTK_BUTTON_DEFAULT_DISABLED;
    TkWinDCState dcState;
    HDC hdc;
    RECT rc;

    Tk_GetReliefFromObj(NULL, bd->reliefObj, &relief);
    Ttk_GetButtonDefaultStateFromObj(NULL, bd->defaultStateObj, &defaultState);

    if (defaultState == TTK_BUTTON_DEFAULT_ACTIVE) {
	XColor *highlightColor =
	    Tk_GetColorFromObj(tkwin, bd->highlightColorObj);
	GC gc = Tk_GCForColor(highlightColor, d);
	XDrawRectangle(Tk_Display(tkwin), d, gc, b.x,b.y,b.width-1,b.height-1);
    }
    if (defaultState != TTK_BUTTON_DEFAULT_DISABLED) {
	++b.x; ++b.y; b.width -= 2; b.height -= 2;
    }

    hdc = TkWinGetDrawableDC(Tk_Display(tkwin), d, &dcState);

    rc = BoxToRect(b);
    DrawFrameControl(hdc, &rc,
	DFC_BUTTON,	/* classId */
	DFCS_BUTTONPUSH | Ttk_StateTableLookup(pushbutton_statemap, state));

    TkWinReleaseDrawableDC(d, hdc, &dcState);

    /* Draw focus ring:
     */
    if (state & TTK_STATE_FOCUS) {
	short int borderWidth = 3;	/* @@@ Use GetSystemMetrics?*/
	b = Ttk_PadBox(b, Ttk_UniformPadding(borderWidth));
	TkWinDrawDottedRect(Tk_Display(tkwin), d, -1, b.x, b.y,
	    b.width, b.height);
    }
}

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

/*------------------------------------------------------------------------
 * +++ Focus element.
 *	Draw dotted focus rectangle.
 */

static void FocusElementSize(
    TCL_UNUSED(void *), /* clientData */
    TCL_UNUSED(void *), /* elementRecord */
    TCL_UNUSED(Tk_Window),
    TCL_UNUSED(int *), /* widthPtr */
    TCL_UNUSED(int *), /* heightPtr */
    Ttk_Padding *paddingPtr)
{
    *paddingPtr = Ttk_UniformPadding(1);
}

static void FocusElementDraw(
    TCL_UNUSED(void *), /* clientData */
    TCL_UNUSED(void *), /* elementRecord */
    Tk_Window tkwin,
    Drawable d,
    Ttk_Box b,
    Ttk_State state)
{
    if (state & TTK_STATE_FOCUS) {
	TkWinDrawDottedRect(Tk_Display(tkwin), d, -1, b.x, b.y,
	    b.width, b.height);
    }
}

static const Ttk_ElementSpec FocusElementSpec = {
    TK_STYLE_VERSION_2,
    sizeof(NullElement),
    TtkNullElementOptions,
    FocusElementSize,
    FocusElementDraw
};

/* FillFocusElement --
 *	Draws a focus ring filled with the selection color
 */

typedef struct {
    Tcl_Obj *fillColorObj;
} FillFocusElement;

static const Ttk_ElementOptionSpec FillFocusElementOptions[] = {
    { "-focusfill", TK_OPTION_COLOR,
	offsetof(FillFocusElement,fillColorObj), "white" },
    {NULL, TK_OPTION_BOOLEAN, 0, NULL}
};

	/* @@@ FIX THIS */
static void FillFocusElementDraw(
    TCL_UNUSED(void *), /* clientData */
    void *elementRecord,
    Tk_Window tkwin,
    Drawable d,
    Ttk_Box b,
    Ttk_State state)
{
    if (state & TTK_STATE_FOCUS) {
	FillFocusElement *focus = (FillFocusElement *)elementRecord;
	XColor *fillColor = Tk_GetColorFromObj(tkwin, focus->fillColorObj);
	GC gc = Tk_GCForColor(fillColor, d);
	XFillRectangle(Tk_Display(tkwin), d, gc, b.x, b.y, b.width, b.height);

	TkWinDrawDottedRect(Tk_Display(tkwin), d, -1, b.x, b.y,
	    b.width, b.height);
    }
}

/*
 * ComboboxFocusElement --
 *	Read-only comboboxes have a filled focus ring, editable ones do not.
 */
static void ComboboxFocusElementDraw(
    void *clientData, void *elementRecord, Tk_Window tkwin,
    Drawable d, Ttk_Box b, Ttk_State state)
{
    if (state & TTK_STATE_READONLY) {
	FillFocusElementDraw(clientData, elementRecord, tkwin, d, b, state);
    }
}

static const Ttk_ElementSpec ComboboxFocusElementSpec = {
    TK_STYLE_VERSION_2,
    sizeof(FillFocusElement),
    FillFocusElementOptions,
    FocusElementSize,
    ComboboxFocusElementDraw
};

/*----------------------------------------------------------------------
 * +++ Scrollbar trough element.
 *
 * The native windows scrollbar is drawn using a pattern brush giving a
 * stippled appearance when the trough might otherwise be invisible.
 * We can deal with this here.
 */

typedef struct {	/* clientData for Trough element */
    HBRUSH     PatternBrush;
    HBITMAP    PatternBitmap;
} TroughClientData;

static const WORD Pattern[] = {
    0x5555, 0xaaaa, 0x5555, 0xaaaa, 0x5555, 0xaaaa, 0x5555, 0xaaaa
};

static void TroughClientDataDeleteProc(void *clientData)
{
    TroughClientData *cd = (TroughClientData *)clientData;
    DeleteObject(cd->PatternBrush);
    DeleteObject(cd->PatternBitmap);
    ckfree(clientData);
}

static TroughClientData *TroughClientDataInit(Tcl_Interp *interp)
{
    TroughClientData *cd = (TroughClientData *)ckalloc(sizeof(*cd));
    cd->PatternBitmap = CreateBitmap(8, 8, 1, 1, Pattern);
    cd->PatternBrush  = CreatePatternBrush(cd->PatternBitmap);
    Ttk_RegisterCleanup(interp, cd, TroughClientDataDeleteProc);
    return cd;
}

static void TroughElementDraw(
    void *clientData,
    TCL_UNUSED(void *), /* elementRecord */
    Tk_Window tkwin,
    Drawable d,
    Ttk_Box b,
    TCL_UNUSED(Ttk_State))
{
    TroughClientData *cd = (TroughClientData *)clientData;
    TkWinDCState dcState;
    HDC hdc = TkWinGetDrawableDC(Tk_Display(tkwin), d, &dcState);
    HBRUSH hbr;
    COLORREF bk, oldbk, oldtxt;

    hbr = (HBRUSH)SelectObject(hdc, GetSysColorBrush(COLOR_SCROLLBAR));
    bk = GetSysColor(COLOR_3DHIGHLIGHT);
    oldtxt = SetTextColor(hdc, GetSysColor(COLOR_3DFACE));
    oldbk = SetBkColor(hdc, bk);

    /* WAS: if (bk (COLOR_3DHIGHLIGHT) == GetSysColor(COLOR_WINDOW)) ... */
    if (GetSysColor(COLOR_SCROLLBAR) == GetSysColor(COLOR_BTNFACE)) {
	/* Draw using the pattern brush */
	SelectObject(hdc, cd->PatternBrush);
    }

    PatBlt(hdc, b.x, b.y, b.width, b.height, PATCOPY);
    SetBkColor(hdc, oldbk);
    SetTextColor(hdc, oldtxt);
    SelectObject(hdc, hbr);
    TkWinReleaseDrawableDC(d, hdc, &dcState);
}

static const Ttk_ElementSpec TroughElementSpec = {
    TK_STYLE_VERSION_2,
    sizeof(NullElement),
    TtkNullElementOptions,
    TtkNullElementSize,
    TroughElementDraw
};

/*------------------------------------------------------------------------
 * +++ Thumb element.
 */

typedef struct {
    Tcl_Obj *orientObj;
} ThumbElement;

static const Ttk_ElementOptionSpec ThumbElementOptions[] = {
    { "-orient", TK_OPTION_ANY, offsetof(ThumbElement,orientObj),"horizontal"},
    { NULL, TK_OPTION_BOOLEAN, 0, NULL }
};

static void ThumbElementSize(
    TCL_UNUSED(void *), /* clientData */
    void *elementRecord,
    TCL_UNUSED(Tk_Window),
    int *widthPtr,
    int *heightPtr,
    TCL_UNUSED(Ttk_Padding *))
{
    ThumbElement *thumbPtr = (ThumbElement *)elementRecord;
    Ttk_Orient orient;

    Ttk_GetOrientFromObj(NULL, thumbPtr->orientObj, &orient);
    if (orient == TTK_ORIENT_HORIZONTAL) {
	*widthPtr = GetSystemMetrics(SM_CXHTHUMB);
	*heightPtr = GetSystemMetrics(SM_CYHSCROLL);
    } else {
	*widthPtr = GetSystemMetrics(SM_CXVSCROLL);
	*heightPtr = GetSystemMetrics(SM_CYVTHUMB);
    }
}

static void ThumbElementDraw(
    TCL_UNUSED(void *), /* clientData */
    TCL_UNUSED(void *), /* elementRecord */
    Tk_Window tkwin,
    Drawable d,
    Ttk_Box b,
    Ttk_State state)
{
    RECT rc = BoxToRect(b);
    TkWinDCState dcState;
    HDC hdc;

    /* Windows doesn't show a thumb when the scrollbar is disabled */
    if (state & TTK_STATE_DISABLED) {
	return;
    }

    hdc = TkWinGetDrawableDC(Tk_Display(tkwin), d, &dcState);
    DrawEdge(hdc, &rc, EDGE_RAISED, BF_RECT | BF_MIDDLE);
    TkWinReleaseDrawableDC(d, hdc, &dcState);
}

static const Ttk_ElementSpec ThumbElementSpec = {
    TK_STYLE_VERSION_2,
    sizeof(ThumbElement),
    ThumbElementOptions,
    ThumbElementSize,
    ThumbElementDraw
};

/* ----------------------------------------------------------------------
 * The slider element is the shaped thumb used in the slider widget.
 * Windows likes to call this a trackbar.
 */

typedef struct {
    Tcl_Obj *orientObj;  /* orientation of the slider widget */
} SliderElement;

static const Ttk_ElementOptionSpec SliderElementOptions[] = {
    { "-orient", TK_OPTION_ANY, offsetof(SliderElement,orientObj),
      "horizontal" },
      { NULL, TK_OPTION_BOOLEAN, 0, NULL }
};

static void SliderElementSize(
    TCL_UNUSED(void *), /* clientData */
    void *elementRecord,
    TCL_UNUSED(Tk_Window),
    int *widthPtr,
    int *heightPtr,
    TCL_UNUSED(Ttk_Padding *))
{
    SliderElement *slider = (SliderElement *)elementRecord;
    Ttk_Orient orient;

    Ttk_GetOrientFromObj(NULL, slider->orientObj, &orient);
    if (orient == TTK_ORIENT_HORIZONTAL) {
	*widthPtr = (GetSystemMetrics(SM_CXHTHUMB) / 2) | 1;
	*heightPtr = GetSystemMetrics(SM_CYHSCROLL);
    } else {
	*widthPtr = GetSystemMetrics(SM_CXVSCROLL);
	*heightPtr = (GetSystemMetrics(SM_CYVTHUMB) / 2) | 1;
    }
}

static void SliderElementDraw(
    TCL_UNUSED(void *), /* clientData */
    TCL_UNUSED(void *), /* elementRecord */
    Tk_Window tkwin,
    Drawable d,
    Ttk_Box b,
    TCL_UNUSED(Ttk_State))
{
    RECT rc = BoxToRect(b);
    TkWinDCState dcState;
    HDC hdc;

    hdc = TkWinGetDrawableDC(Tk_Display(tkwin), d, &dcState);
    DrawEdge(hdc, &rc, EDGE_RAISED, BF_RECT | BF_MIDDLE);
    TkWinReleaseDrawableDC(d, hdc, &dcState);
}

static const Ttk_ElementSpec SliderElementSpec = {
    TK_STYLE_VERSION_2,
    sizeof(SliderElement),
    SliderElementOptions,
    SliderElementSize,
    SliderElementDraw
};

/*------------------------------------------------------------------------
 * +++ Notebook elements.
 */

typedef struct {
    Tcl_Obj *borderWidthObj;
    Tcl_Obj *backgroundObj;
} TabElement;

static const Ttk_ElementOptionSpec TabElementOptions[] = {
    { "-borderwidth", TK_OPTION_PIXELS,
	offsetof(TabElement,borderWidthObj), "1" },
    { "-background", TK_OPTION_BORDER,
	offsetof(TabElement,backgroundObj), DEFAULT_BACKGROUND },
    {0,TK_OPTION_BOOLEAN,0,0}
};

static void TabElementSize(
    TCL_UNUSED(void *), /* clientData */
    void *elementRecord,
    Tk_Window tkwin,
    TCL_UNUSED(int *), /* widthPtr */
    TCL_UNUSED(int *), /* heightPtr */
    Ttk_Padding *paddingPtr)
{
    TabElement *tab = (TabElement *)elementRecord;
    int borderWidth = 1;
    Ttk_PositionSpec nbTabPlcStickBit = TTK_STICK_S;
    TkMainInfo *mainInfoPtr = ((TkWindow *) tkwin)->mainPtr;

    Tk_GetPixelsFromObj(0, tkwin, tab->borderWidthObj, &borderWidth);
    *paddingPtr = Ttk_UniformPadding((short)borderWidth);

    if (mainInfoPtr != NULL) {
	nbTabPlcStickBit =
	    (Ttk_PositionSpec) (mainInfoPtr->nbTabPlacement & 0x0f);
    }

    switch (nbTabPlcStickBit) {
	default:
	case TTK_STICK_S:
	    paddingPtr->bottom = 0;
	    break;
	case TTK_STICK_N:
	    paddingPtr->top = 0;
	    break;
	case TTK_STICK_E:
	    paddingPtr->right = 0;
	    break;
	case TTK_STICK_W:
	    paddingPtr->left = 0;
	    break;
    }
}

static void TabElementDraw(
    TCL_UNUSED(void *), /* clientData */
    void *elementRecord,
    Tk_Window tkwin,
    Drawable d,
    Ttk_Box b,
    Ttk_State state)
{
    Ttk_PositionSpec nbTabPosStickBit = TTK_STICK_W;
    Ttk_PositionSpec nbTabPlcStickBit = TTK_STICK_S;
    TkMainInfo *mainInfoPtr = ((TkWindow *) tkwin)->mainPtr;
    TabElement *tab = (TabElement *)elementRecord;
    Tk_3DBorder border = Tk_Get3DBorderFromObj(tkwin, tab->backgroundObj);
    XPoint pts[6];
    double scalingLevel = TkScalingLevel(tkwin);
    int cut = round(2 * scalingLevel);
    Display *disp = Tk_Display(tkwin);
    int once = 1, borderWidth = 1;

    if (mainInfoPtr != NULL) {
	nbTabPosStickBit =
	    (Ttk_PositionSpec) (mainInfoPtr->nbTabPosition & 0x0f);
	nbTabPlcStickBit =
	    (Ttk_PositionSpec) (mainInfoPtr->nbTabPlacement & 0x0f);
    }

    if (state & TTK_STATE_SELECTED) {
	/*
	 * Draw slightly outside of the allocated parcel,
	 * to overwrite the client area border.
	 */
	switch (nbTabPlcStickBit) {
	    default:
	    case TTK_STICK_S:
		b.height += 2;
		break;
	    case TTK_STICK_N:
		b.height += 2; b.y -= 2;
		break;
	    case TTK_STICK_E:
		b.width += 2;
		break;
	    case TTK_STICK_W:
		b.width += 2; b.x -= 2;
		break;
	}
    }

    switch (nbTabPlcStickBit) {
	default:
	case TTK_STICK_S:
	    pts[0].x = b.x;  pts[0].y = b.y + b.height-1;
	    pts[1].x = b.x;  pts[1].y = b.y + cut;
	    pts[2].x = b.x + cut;  pts[2].y = b.y;
	    pts[3].x = b.x + b.width-1 - cut;  pts[3].y = b.y;
	    pts[4].x = b.x + b.width-1;  pts[4].y = b.y + cut;
	    pts[5].x = b.x + b.width-1;  pts[5].y = b.y + b.height;
	    break;
	case TTK_STICK_N:
	    pts[0].x = b.x;  pts[0].y = b.y;
	    pts[1].x = b.x;  pts[1].y = b.y + b.height-1 - cut;
	    pts[2].x = b.x + cut;  pts[2].y = b.y + b.height-1;
	    pts[3].x = b.x + b.width-1 - cut;  pts[3].y = b.y + b.height-1;
	    pts[4].x = b.x + b.width-1;  pts[4].y = b.y + b.height-1 - cut;
	    pts[5].x = b.x + b.width-1;  pts[5].y = b.y-1;
	    break;
	case TTK_STICK_E:
	    pts[0].x = b.x + b.width-1;  pts[0].y = b.y;
	    pts[1].x = b.x + cut;  pts[1].y = b.y;
	    pts[2].x = b.x;  pts[2].y = b.y + cut;
	    pts[3].x = b.x;  pts[3].y = b.y + b.height-1 - cut;
	    pts[4].x = b.x + cut;  pts[4].y = b.y + b.height-1;
	    pts[5].x = b.x + b.width;  pts[5].y = b.y + b.height-1;
	    break;
	case TTK_STICK_W:
	    pts[0].x = b.x;  pts[0].y = b.y;
	    pts[1].x = b.x + b.width-1 - cut;  pts[1].y = b.y;
	    pts[2].x = b.x + b.width-1;  pts[2].y = b.y + cut;
	    pts[3].x = b.x + b.width-1;  pts[3].y = b.y + b.height-1 - cut;
	    pts[4].x = b.x + b.width-1 - cut;  pts[4].y = b.y + b.height-1;
	    pts[5].x = b.x-1;  pts[5].y = b.y + b.height-1;
	    break;
    }

    XFillPolygon(disp, d, Tk_3DBorderGC(tkwin, border, TK_3D_FLAT_GC),
	    pts, 6, Convex, CoordModeOrigin);

    Tk_GetPixelsFromObj(NULL, tkwin, tab->borderWidthObj, &borderWidth);
    while (borderWidth--) {
	int n;

	switch (nbTabPlcStickBit) {
	    default:
	    case TTK_STICK_S:
	    case TTK_STICK_E:
		n = 3;
		break;
	    case TTK_STICK_N:
	    case TTK_STICK_W:
		n = 5;
		break;
	}
	if ((state & (TTK_STATE_LAST | TTK_STATE_SELECTED)) ==
		(TTK_STATE_LAST | TTK_STATE_SELECTED) &&
		(nbTabPosStickBit == TTK_STICK_E ||
		 nbTabPosStickBit == TTK_STICK_S)) {
	    --n;
	}

	switch (nbTabPlcStickBit) {
	    default:
	    case TTK_STICK_S:
	    case TTK_STICK_E:
		XDrawLines(disp, d,
			Tk_3DBorderGC(tkwin, border, TK_3D_LIGHT_GC),
			pts, 4, CoordModeOrigin);
		XDrawLines(disp, d,
			Tk_3DBorderGC(tkwin, border, TK_3D_DARK_GC),
			pts+3, n, CoordModeOrigin);
		if (n == 2 && once--) {
		    HDC hdc;
		    TkWinDCState dcState;
		    RECT rc;

		    hdc = TkWinGetDrawableDC(Tk_Display(tkwin), d, &dcState);
		    if (nbTabPlcStickBit == TTK_STICK_S) {
			rc.left = pts[0].x;
			rc.top = pts[4].y;
			rc.right = pts[4].x + 1;
			rc.bottom = pts[0].y+ 1;
		    } else {
			rc.left = pts[4].x;
			rc.top = pts[0].y;
			rc.right = pts[0].x + 1;
			rc.bottom = pts[4].y + 1;
		    }
		    DrawEdge(hdc, &rc, EDGE_RAISED,
			    (nbTabPlcStickBit == TTK_STICK_E) ?
			    BF_BOTTOM : BF_RIGHT);
		    TkWinReleaseDrawableDC(d, hdc, &dcState);
		}
		break;
	    case TTK_STICK_N:
	    case TTK_STICK_W:
		XDrawLines(disp, d,
			Tk_3DBorderGC(tkwin, border, TK_3D_LIGHT_GC),
			pts, 2, CoordModeOrigin);
		XDrawLines(disp, d,
			Tk_3DBorderGC(tkwin, border, TK_3D_DARK_GC),
			pts+1, n, CoordModeOrigin);
		if (n == 4 && once--) {
		    HDC hdc;
		    TkWinDCState dcState;
		    RECT rc;

		    hdc = TkWinGetDrawableDC(Tk_Display(tkwin), d, &dcState);
		    rc.left = pts[0].x;
		    rc.top = pts[0].y;
		    rc.right = pts[4].x + 1;
		    rc.bottom = pts[4].y + 1;
		    DrawEdge(hdc, &rc, EDGE_RAISED,
			    (nbTabPlcStickBit == TTK_STICK_W) ?
			    BF_BOTTOM : BF_RIGHT);
		    TkWinReleaseDrawableDC(d, hdc, &dcState);
		}
		break;
	}

	switch (nbTabPlcStickBit) {
	    default:
	    case TTK_STICK_S:
		++pts[0].x;  ++pts[1].x;  ++pts[2].y;
		++pts[3].y;  --pts[4].x;  --pts[5].x;
		break;
	    case TTK_STICK_N:
		++pts[0].x;  ++pts[1].x;  --pts[2].y;
		--pts[3].y;  --pts[4].x;  --pts[5].x;
		break;
	    case TTK_STICK_E:
		++pts[0].y;  ++pts[1].y;  ++pts[2].x;
		++pts[3].x;  --pts[4].y;  --pts[5].y;
		break;
	    case TTK_STICK_W:
		++pts[0].y;  ++pts[1].y;  --pts[2].x;
		--pts[3].x;  --pts[4].y;  --pts[5].y;
		break;
	}
    }
}

static const Ttk_ElementSpec TabElementSpec = {
    TK_STYLE_VERSION_2,
    sizeof(TabElement),
    TabElementOptions,
    TabElementSize,
    TabElementDraw
};

static void ClientElementSize(
    TCL_UNUSED(void *), /* clientData */
    TCL_UNUSED(void *), /* elementRecord */
    TCL_UNUSED(Tk_Window),
    TCL_UNUSED(int *), /* widthPtr */
    TCL_UNUSED(int *), /* heightPtr */
    Ttk_Padding *paddingPtr)
{
    paddingPtr->left = paddingPtr->right = GetSystemMetrics(SM_CXEDGE);
    paddingPtr->top = paddingPtr->bottom = GetSystemMetrics(SM_CYEDGE);
}

static void ClientElementDraw(
    TCL_UNUSED(void *), /* clientData */
    TCL_UNUSED(void *), /* elementRecord */
    Tk_Window tkwin,
    Drawable d,
    Ttk_Box b,
    TCL_UNUSED(Ttk_State))
{
    RECT rc = BoxToRect(b);
    TkWinDCState dcState;
    HDC hdc = TkWinGetDrawableDC(Tk_Display(tkwin), d, &dcState);

    DrawEdge(hdc, &rc, EDGE_RAISED, BF_RECT | BF_SOFT);
    TkWinReleaseDrawableDC(d, hdc, &dcState);
}

static const Ttk_ElementSpec ClientElementSpec = {
    TK_STYLE_VERSION_2,
    sizeof(NullElement),
    TtkNullElementOptions,
    ClientElementSize,
    ClientElementDraw
};

/*------------------------------------------------------------------------
 * +++ Layouts.
 */

TTK_BEGIN_LAYOUT_TABLE(LayoutTable)

TTK_LAYOUT("TButton",
    TTK_GROUP("Button.border", TTK_FILL_BOTH,
	TTK_GROUP("Button.padding", TTK_FILL_BOTH,
	    TTK_NODE("Button.label", TTK_FILL_BOTH))))

TTK_LAYOUT("TCombobox",
    TTK_GROUP("Combobox.field", TTK_FILL_BOTH,
	TTK_NODE("Combobox.downarrow", TTK_PACK_RIGHT|TTK_FILL_Y)
	TTK_GROUP("Combobox.padding", TTK_FILL_BOTH,
	    TTK_GROUP("Combobox.focus", TTK_FILL_BOTH,
		TTK_NODE("Combobox.textarea", TTK_FILL_BOTH)))))

TTK_END_LAYOUT_TABLE

/* ---------------------------------------------------------------------- */

MODULE_SCOPE int
TtkWinTheme_Init(
    Tcl_Interp *interp,
    TCL_UNUSED(HWND))
{
    Ttk_Theme themePtr, parentPtr;
    const FrameControlElementData *fce = FrameControlElements;

    parentPtr = Ttk_GetTheme(interp, "alt");
    themePtr = Ttk_CreateTheme(interp, "winnative", parentPtr);
    if (!themePtr) {
	return TCL_ERROR;
    }

    Ttk_RegisterElement(NULL, themePtr, "border", &BorderElementSpec, NULL);
    Ttk_RegisterElement(NULL, themePtr, "Button.border",
	&ButtonBorderElementSpec, NULL);
    Ttk_RegisterElement(NULL, themePtr, "field", &FieldElementSpec, NULL);
    Ttk_RegisterElement(NULL, themePtr, "focus", &FocusElementSpec, NULL);
    Ttk_RegisterElement(NULL, themePtr, "Combobox.focus",
	&ComboboxFocusElementSpec, NULL);
    Ttk_RegisterElement(NULL, themePtr, "thumb", &ThumbElementSpec, NULL);
    Ttk_RegisterElement(NULL, themePtr, "slider", &SliderElementSpec, NULL);
    Ttk_RegisterElement(NULL, themePtr, "Scrollbar.trough", &TroughElementSpec,
	TroughClientDataInit(interp));

    Ttk_RegisterElement(NULL, themePtr, "tab", &TabElementSpec, NULL);
    Ttk_RegisterElement(NULL, themePtr, "client", &ClientElementSpec, NULL);

    for (fce = FrameControlElements; fce->name != 0; ++fce) {
	Ttk_RegisterElement(NULL, themePtr, fce->name,
		&FrameControlElementSpec, (void *)fce);
    }

    Ttk_RegisterLayouts(themePtr, LayoutTable);

    Tcl_PkgProvide(interp, "ttk::theme::winnative", TTK_VERSION);
    return TCL_OK;
}

