/*
 * tkStyle.c --
 *
 *	This file implements the widget styles and themes support.
 *
 * Copyright (c) 1990-1993 The Regents of the University of California.
 * Copyright (c) 1994-1997 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: tkStyle.c,v 1.3 2002/08/05 04:30:40 dgp Exp $
 */

#include "tkInt.h"

/*
 * The following structure is used to cache widget option specs matching an
 * element's required options defined by Tk_ElementOptionSpecs. It also holds
 * information behind Tk_StyledElement opaque tokens.
 */

typedef struct StyledWidgetSpec {
    struct StyledElement *elementPtr;	/* Pointer to the element holding this
					 * structure. */
    Tk_OptionTable optionTable;		/* Option table for the widget class
					 * using the element. */
    CONST Tk_OptionSpec **optionsPtr;	/* Table of option spec pointers,
					 * matching the option list provided
					 * during element registration.
					 * Malloc'd. */
} StyledWidgetSpec;

/*
 * Elements are declared using static templates. But static
 * information must be completed by dynamic information only
 * accessible at runtime. For each registered element, an instance of
 * the following structure is stored in each style engine and used to
 * cache information about the widget types (identified by their
 * optionTable) that use the given element.
 */

typedef struct StyledElement {
    struct Tk_ElementSpec *specPtr;
				/* Filled with template provided during
				 * registration. NULL means no implementation
				 * is available for the current engine. */
    int nbWidgetSpecs;		/* Size of the array below. Number of distinct
				 * widget classes (actually, distinct option
				 * tables) that used the element so far. */
    StyledWidgetSpec *widgetSpecs;
				/* See above for the structure definition.
				 * Table grows dynamically as new widgets
				 * use the element. Malloc'd. */
} StyledElement;

/*
 * The following structure holds information behind Tk_StyleEngine opaque
 * tokens.
 */

typedef struct StyleEngine {
    CONST char *name;		/* Name of engine. Points to a hash key. */
    StyledElement *elements;	/* Table of widget element descriptors. Each
				 * element is indexed by a unique system-wide
				 * ID. Table grows dynamically as new elements
				 * are registered. Malloc'd*/
    struct StyleEngine *parentPtr;
				/* Parent engine. Engines may be layered to form
				 * a fallback chain, terminated by the default
				 * system engine. */
} StyleEngine;

/*
 * Styles are instances of style engines. The following structure holds
 * information behind Tk_Style opaque tokens.
 */

typedef struct Style {
    int refCount;		/* Number of active uses of this style.
				 * If this count is 0, then this Style
				 * structure is no longer valid. */
    Tcl_HashEntry *hashPtr;	/* Entry in style table for this structure,
				 * used when deleting it. */
    CONST char *name;		/* Name of style. Points to a hash key. */
    StyleEngine *enginePtr;	/* Style engine of which the style is an
				 * instance. */
    ClientData clientData;	/* Data provided during registration. */
} Style;

/*
 * Each registered element uses an instance of the following structure.
 */

typedef struct Element {
    CONST char *name;		/* Name of element. Points to a hash key. */
    int id;			/* Id of element. */
    int genericId;		/* Id of generic element. */
    int created;		/* Boolean, whether the element was created
				 * explicitly (was registered) or implicitly
				 * (by a derived element). */
} Element;

/*
 * Thread-local data.
 */

typedef struct ThreadSpecificData {
    int nbInit;			/* Number of calls to the init proc. */
    Tcl_HashTable engineTable;	/* Map a name to a style engine. Keys are
				 * strings, values are Tk_StyleEngine
				 * pointers. */
    StyleEngine *defaultEnginePtr;
				/* Default, core-defined style engine. Global
				 * fallback for all engines. */
    Tcl_HashTable styleTable;	/* Map a name to a style. Keys are strings,
				 * values are Tk_Style pointers.*/
    int nbElements;		/* Size of the below tables. */
    Tcl_HashTable elementTable;	/* Map a name to an element Id. Keys are
				 * strings, values are integer element IDs. */
    Element *elements;		/* Array of Elements. */
} ThreadSpecificData;

static Tcl_ThreadDataKey dataKey;

/*
 * Forward declarations for procedures defined later in this file:
 */

/* TODO: sort alpha. */
static int		CreateElement _ANSI_ARGS_((CONST char *name,
			    int create));
static void		DupStyleObjProc _ANSI_ARGS_((Tcl_Obj *srcObjPtr,
			    Tcl_Obj *dupObjPtr));
static void		FreeElement _ANSI_ARGS_((Element *elementPtr));
static void		FreeStyle _ANSI_ARGS_((Style *stylePtr));
static void		FreeStyledElement _ANSI_ARGS_((
			    StyledElement *elementPtr));
static void		FreeStyleEngine _ANSI_ARGS_((
			    StyleEngine *enginePtr));
static void		FreeStyleObjProc _ANSI_ARGS_((Tcl_Obj *objPtr));
static void		FreeWidgetSpec _ANSI_ARGS_((
			    StyledWidgetSpec *widgetSpecPtr));
static StyledElement *	GetStyledElement _ANSI_ARGS_((
			    StyleEngine *enginePtr, int elementId));
static StyledWidgetSpec * GetWidgetSpec _ANSI_ARGS_((StyledElement *elementPtr,
			    Tk_OptionTable optionTable));
static void		InitElement _ANSI_ARGS_((Element *elementPtr,
			    CONST char *name, int id, int genericId,
			    int created));
static void		InitStyle _ANSI_ARGS_((Style *stylePtr,
			    Tcl_HashEntry *hashPtr, CONST char *name,
			    StyleEngine *enginePtr, ClientData clientData));
static void		InitStyledElement _ANSI_ARGS_((
			    StyledElement *elementPtr));
static void		InitStyleEngine _ANSI_ARGS_((StyleEngine *enginePtr,
			    CONST char *name, StyleEngine *parentPtr));
static void		InitWidgetSpec _ANSI_ARGS_((
			    StyledWidgetSpec *widgetSpecPtr,
			    StyledElement *elementPtr,
			    Tk_OptionTable optionTable));
static int		SetStyleFromAny _ANSI_ARGS_((Tcl_Interp *interp,
			    Tcl_Obj *objPtr));

/*
 * The following structure defines the implementation of the "style" Tcl
 * object, used for drawing. The internalRep.otherValuePtr field of
 * each style object points to the Style structure for the stylefont, or
 * NULL.
 */

static Tcl_ObjType styleObjType = {
    "style",			/* name */
    FreeStyleObjProc,		/* freeIntRepProc */
    DupStyleObjProc,		/* dupIntRepProc */
    NULL,			/* updateStringProc */
    SetStyleFromAny		/* setFromAnyProc */
};

/*
 *---------------------------------------------------------------------------
 *
 * TkStylePkgInit --
 *
 *	This procedure is called when an application is created.  It
 *	initializes all the structures that are used by the style
 *	package on a per application basis.
 *
 * Results:
 *	Stores data in thread-local storage.
 *
 * Side effects:
 *	Memory allocated.
 *
 *---------------------------------------------------------------------------
 */

void
TkStylePkgInit(mainPtr)
    TkMainInfo *mainPtr;	/* The application being created. */
{
    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
            Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));

    if (tsdPtr->nbInit != 0) return;

    /*
     * Initialize tables.
     */

    Tcl_InitHashTable(&tsdPtr->engineTable, TCL_STRING_KEYS);
    Tcl_InitHashTable(&tsdPtr->styleTable, TCL_STRING_KEYS);
    Tcl_InitHashTable(&tsdPtr->elementTable, TCL_STRING_KEYS);
    tsdPtr->nbElements = 0;
    tsdPtr->elements = NULL;

    /*
     * Create the default system engine.
     */

    tsdPtr->defaultEnginePtr =
	    (StyleEngine *) Tk_RegisterStyleEngine(NULL, NULL);

    /*
     * Create the default system style.
     */

    Tk_CreateStyle(NULL, (Tk_StyleEngine) tsdPtr->defaultEnginePtr,
	    (ClientData) 0);

    tsdPtr->nbInit++;
}

/*
 *---------------------------------------------------------------------------
 *
 * TkStylePkgFree --
 *
 *	This procedure is called when an application is deleted.  It
 *	deletes all the structures that were used by the style package
 *	for this application.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Memory freed.
 *
 *---------------------------------------------------------------------------
 */

void
TkStylePkgFree(mainPtr)
    TkMainInfo *mainPtr;	/* The application being deleted. */
{
    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
            Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
    Tcl_HashSearch search;
    Tcl_HashEntry *entryPtr;
    StyleEngine *enginePtr;
    int i;

    tsdPtr->nbInit--;
    if (tsdPtr->nbInit != 0) return;

    /*
     * Free styles.
     */

    entryPtr = Tcl_FirstHashEntry(&tsdPtr->styleTable, &search);
    while (entryPtr != NULL) {
	ckfree((char *) Tcl_GetHashValue(entryPtr));
	entryPtr = Tcl_NextHashEntry(&search);
    }
    Tcl_DeleteHashTable(&tsdPtr->styleTable);

    /*
     * Free engines.
     */

    entryPtr = Tcl_FirstHashEntry(&tsdPtr->engineTable, &search);
    while (entryPtr != NULL) {
	enginePtr = (StyleEngine *) Tcl_GetHashValue(entryPtr);
	FreeStyleEngine(enginePtr);
	ckfree((char *) enginePtr);
	entryPtr = Tcl_NextHashEntry(&search);
    }
    Tcl_DeleteHashTable(&tsdPtr->engineTable);

    /*
     * Free elements.
     */

    for (i = 0; i < tsdPtr->nbElements; i++) {
	FreeElement(tsdPtr->elements+i);
    }
    Tcl_DeleteHashTable(&tsdPtr->elementTable);
    ckfree((char *) tsdPtr->elements);
}

/*
 *---------------------------------------------------------------------------
 *
 * Tk_RegisterStyleEngine --
 *
 *	This procedure is called to register a new style engine. Style engines
 *	are stored in thread-local space.
 *
 * Results:
 *	The newly allocated engine.
 *
 * Side effects:
 *	Memory allocated. Data added to thread-local table.
 *
 *---------------------------------------------------------------------------
 */

Tk_StyleEngine
Tk_RegisterStyleEngine(name, parent)
    CONST char *name;		/* Name of the engine to create. NULL or empty
				 * means the default system engine. */
    Tk_StyleEngine parent;	/* The engine's parent. NULL means the default
				 * system engine. */
{
    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
            Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
    Tcl_HashEntry *entryPtr;
    int newEntry;
    StyleEngine *enginePtr;

    /*
     * Attempt to create a new entry in the engine table.
     */

    entryPtr = Tcl_CreateHashEntry(&tsdPtr->engineTable, (name?name:""),
	    &newEntry);
    if (!newEntry) {
	/*
	 * An engine was already registered by that name.
	 */

	return NULL;
    }

    /*
     * Allocate and intitialize a new engine.
     */

    enginePtr = (StyleEngine *) ckalloc(sizeof(StyleEngine));
    InitStyleEngine(enginePtr, Tcl_GetHashKey(&tsdPtr->engineTable, entryPtr),
	    (StyleEngine *) parent);
    Tcl_SetHashValue(entryPtr, (ClientData) enginePtr);

    return (Tk_StyleEngine) enginePtr;
}

/*
 *---------------------------------------------------------------------------
 *
 * InitStyleEngine --
 *
 *	Initialize a newly allocated style engine.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Memory allocated.
 *
 *---------------------------------------------------------------------------
 */

static void
InitStyleEngine(enginePtr, name, parentPtr)
    StyleEngine *enginePtr;	/* Points to an uninitialized engine. */
    CONST char *name;		/* Name of the registered engine. NULL or empty
				 * means the default system engine. Usually
				 * points to the hash key. */
    StyleEngine *parentPtr;	/* The engine's parent. NULL means the default
				 * system engine. */
{
    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
            Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
    int elementId;

    if (name == NULL || *name == '\0') {
	/*
	 * This is the default style engine.
	 */

	enginePtr->parentPtr = NULL;

    } else if (parentPtr == NULL) {
	/*
	 * The default style engine is the parent.
	 */

	enginePtr->parentPtr = tsdPtr->defaultEnginePtr;

    } else {
	enginePtr->parentPtr = parentPtr;
    }

    /*
     * Allocate and initialize elements array.
     */

    if (tsdPtr->nbElements > 0) {
	enginePtr->elements = (StyledElement *) ckalloc(
		sizeof(StyledElement) * tsdPtr->nbElements);
	for (elementId = 0; elementId < tsdPtr->nbElements; elementId++) {
	    InitStyledElement(enginePtr->elements+elementId);
	}
    } else {
	enginePtr->elements = NULL;
    }
}

/*
 *---------------------------------------------------------------------------
 *
 * FreeStyleEngine --
 *
 *	Free an engine and its associated data.
 *
 * Results:
 *	None
 *
 * Side effects:
 *	Memory freed.
 *
 *---------------------------------------------------------------------------
 */

static void
FreeStyleEngine(enginePtr)
    StyleEngine *enginePtr;	/* The style engine to free. */
{
    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
            Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
    int elementId;

    /*
     * Free allocated elements.
     */

    for (elementId = 0; elementId < tsdPtr->nbElements; elementId++) {
	FreeStyledElement(enginePtr->elements+elementId);
    }
    if (enginePtr->elements) {
	ckfree((char *) enginePtr->elements);
    }
}

/*
 *---------------------------------------------------------------------------
 *
 * Tk_GetStyleEngine --
 *
 *	Retrieve a registered style engine by its name.
 *
 * Results:
 *	A pointer to the style engine, or NULL if none found.
 *
 * Side effects:
 *	None.
 *
 *---------------------------------------------------------------------------
 */

Tk_StyleEngine
Tk_GetStyleEngine(name)
    CONST char *name;		/* Name of the engine to retrieve. NULL or
				 * empty means the default system engine. */
{
    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
            Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
    Tcl_HashEntry *entryPtr;

    if (name == NULL) {
	return (Tk_StyleEngine) tsdPtr->defaultEnginePtr;
    }

    entryPtr = Tcl_FindHashEntry(&tsdPtr->engineTable, (name?name:""));
    if (!entryPtr) {
	return NULL;
    }

    return (Tk_StyleEngine) Tcl_GetHashValue(entryPtr);
}

/*
 *---------------------------------------------------------------------------
 *
 * InitElement --
 *
 *	Initialize a newly allocated element.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *---------------------------------------------------------------------------
 */

static void
InitElement(elementPtr, name, id, genericId, created)
    Element *elementPtr;	/* Points to an uninitialized element.*/
    CONST char *name;		/* Name of the registered element. Usually
				 * points to the hash key. */
    int id;			/* Unique element ID. */
    int genericId;		/* ID of generic element. -1 means none. */
    int created;		/* Boolean, whether the element was created
				 * explicitly (was registered) or implicitly
				 * (by a derived element). */
{
    elementPtr->name = name;
    elementPtr->id = id;
    elementPtr->genericId = genericId;
    elementPtr->created = (created?1:0);
}

/*
 *---------------------------------------------------------------------------
 *
 * FreeElement --
 *
 *	Free an element and its associated data.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Memory freed.
 *
 *---------------------------------------------------------------------------
 */

static void
FreeElement(elementPtr)
    Element *elementPtr;	/* The element to free. */
{
    /* Nothing to do. */
}

/*
 *---------------------------------------------------------------------------
 *
 * InitStyledElement --
 *
 *	Initialize a newly allocated styled element.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *---------------------------------------------------------------------------
 */

static void
InitStyledElement(elementPtr)
    StyledElement *elementPtr;	/* Points to an uninitialized element.*/
{
    memset(elementPtr, 0, sizeof(StyledElement));
}

/*
 *---------------------------------------------------------------------------
 *
 * FreeStyledElement --
 *
 *	Free a styled element and its associated data.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Memory freed.
 *
 *---------------------------------------------------------------------------
 */

static void
FreeStyledElement(elementPtr)
    StyledElement *elementPtr;	/* The styled element to free. */
{
    int i;

    /*
     * Free allocated widget specs.
     */

    for (i = 0; i < elementPtr->nbWidgetSpecs; i++) {
	FreeWidgetSpec(elementPtr->widgetSpecs+i);
    }
    ckfree((char *) elementPtr->widgetSpecs);
}

/*
 *---------------------------------------------------------------------------
 *
 * CreateElement --
 *
 *	Find an existing or create a new element.
 *
 * Results:
 *	The unique ID for the created or found element.
 *
 * Side effects:
 *	Memory allocated.
 *
 *---------------------------------------------------------------------------
 */

static int
CreateElement(name, create)
    CONST char *name;	/* Name of the element. */
    int create;		/* Boolean, whether the element is being created
			 * explicitly (being registered) or implicitly (by a
			 * derived element). */
{
    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
            Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
    Tcl_HashEntry *entryPtr, *engineEntryPtr;
    Tcl_HashSearch search;
    int newEntry;
    int elementId, genericId = -1;
    char *dot;
    StyleEngine *enginePtr;

    /*
     * Find or create the element.
     */

    entryPtr = Tcl_CreateHashEntry(&tsdPtr->elementTable, name, &newEntry);
    if (!newEntry) {
	elementId = (int) Tcl_GetHashValue(entryPtr);
	if (create) {
	    tsdPtr->elements[elementId].created = 1;
	}
	return elementId;
    }

    /*
     * The element didn't exist. If it's a derived element, find or
     * create its generic element ID.
     */

    dot = strchr(name, '.');
    if (dot) {
	genericId = CreateElement(dot+1, 0);
    }

    elementId = tsdPtr->nbElements++;
    Tcl_SetHashValue(entryPtr, (ClientData) elementId);

    /*
     * Reallocate element table.
     */

    tsdPtr->elements = (Element *) ckrealloc((char *) tsdPtr->elements,
	    sizeof(Element) * tsdPtr->nbElements);
    InitElement(tsdPtr->elements+elementId,
	    Tcl_GetHashKey(&tsdPtr->elementTable, entryPtr), elementId,
	    genericId, create);

    /*
     * Reallocate style engines' element table.
     */

    engineEntryPtr = Tcl_FirstHashEntry(&tsdPtr->engineTable, &search);
    while (engineEntryPtr != NULL) {
	enginePtr = (StyleEngine *) Tcl_GetHashValue(engineEntryPtr);

	enginePtr->elements = (StyledElement *) ckrealloc(
		(char *) enginePtr->elements,
		sizeof(StyledElement) * tsdPtr->nbElements);
	InitStyledElement(enginePtr->elements+elementId);

	engineEntryPtr = Tcl_NextHashEntry(&search);
    }

    return elementId;
}

/*
 *---------------------------------------------------------------------------
 *
 * Tk_GetElementId --
 *
 *	Find an existing element.
 *
 * Results:
 *	The unique ID for the found element, or -1 if not found.
 *
 * Side effects:
 *	Generic elements may be created.
 *
 *---------------------------------------------------------------------------
 */

int
Tk_GetElementId(name)
    CONST char *name;		/* Name of the element. */
{
    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
            Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
    Tcl_HashEntry *entryPtr;
    int genericId = -1;
    char *dot;

    /*
     * Find the element Id.
     */

    entryPtr = Tcl_FindHashEntry(&tsdPtr->elementTable, name);
    if (entryPtr) {
	return (int) Tcl_GetHashValue(entryPtr);
    }

    /*
     * Element not found. If the given name was derived, then first search for
     * the generic element. If found, create the new derived element.
     */

    dot = strchr(name, '.');
    if (!dot) {
	return -1;
    }
    genericId = Tk_GetElementId(dot+1);
    if (genericId == -1) {
	return -1;
    }
    if (!tsdPtr->elements[genericId].created) {
	/*
	 * The generic element was created implicitly and thus has no real
	 * existence.
	 */

	return -1;
    } else {
	/*
	 * The generic element was created explicitly. Create the derived
	 * element.
	 */

	return CreateElement(name, 1);
    }
}

/*
 *---------------------------------------------------------------------------
 *
 * Tk_RegisterStyledElement --
 *
 *	Register an implementation of a new or existing element for the
 *	given style engine.
 *
 * Results:
 *	The unique ID for the created or found element.
 *
 * Side effects:
 *	Elements may be created. Memory allocated.
 *
 *---------------------------------------------------------------------------
 */

int
Tk_RegisterStyledElement(engine, templatePtr)
    Tk_StyleEngine engine;		/* Style engine providing the
					 * implementation. */
    Tk_ElementSpec *templatePtr;	/* Static template information about
					 * the element. */
{
    int elementId;
    StyledElement *elementPtr;
    Tk_ElementSpec *specPtr;
    int nbOptions;
    register Tk_ElementOptionSpec *srcOptions, *dstOptions;

    if (templatePtr->version != TK_STYLE_VERSION_1) {
	/*
	 * Version mismatch. Do nothing.
	 */

	return -1;
    }

    if (engine == NULL) {
	engine = Tk_GetStyleEngine(NULL);
    }

    /*
     * Register the element, allocating storage in the various engines if
     * necessary.
     */

    elementId = CreateElement(templatePtr->name, 1);

    /*
     * Initialize the styled element.
     */

    elementPtr = ((StyleEngine *) engine)->elements+elementId;

    specPtr = (Tk_ElementSpec *) ckalloc(sizeof(Tk_ElementSpec));
    specPtr->version = templatePtr->version;
    specPtr->name = ckalloc(strlen(templatePtr->name)+1);
    strcpy(specPtr->name, templatePtr->name);
    nbOptions = 0;
    for (nbOptions = 0, srcOptions = templatePtr->options;
	 srcOptions->name != NULL;
	 nbOptions++, srcOptions++);
    specPtr->options = (Tk_ElementOptionSpec *) ckalloc(
	    sizeof(Tk_ElementOptionSpec) * (nbOptions+1));
    for (srcOptions = templatePtr->options, dstOptions = specPtr->options;
	 /* End condition within loop */;
	 srcOptions++, dstOptions++) {
	if (srcOptions->name == NULL) {
	    dstOptions->name = NULL;
	    break;
	}

	dstOptions->name = ckalloc(strlen(srcOptions->name)+1);
	strcpy(dstOptions->name, srcOptions->name);
	dstOptions->type = srcOptions->type;
    }
    specPtr->getSize = templatePtr->getSize;
    specPtr->getBox = templatePtr->getBox;
    specPtr->getBorderWidth = templatePtr->getBorderWidth;
    specPtr->draw = templatePtr->draw;

    elementPtr->specPtr = specPtr;
    elementPtr->nbWidgetSpecs = 0;
    elementPtr->widgetSpecs = NULL;

    return elementId;
}

/*
 *---------------------------------------------------------------------------
 *
 * GetStyledElement --
 *
 *	Get a registered implementation of an existing element for the
 *	given style engine.
 *
 * Results:
 *	The styled element descriptor, or NULL if not found.
 *
 * Side effects:
 *	None.
 *
 *---------------------------------------------------------------------------
 */

static StyledElement *
GetStyledElement(enginePtr, elementId)
    StyleEngine *enginePtr;	/* Style engine providing the implementation.
				 * NULL means the default system engine. */
    int elementId;		/* Unique element ID */{
    StyledElement *elementPtr;
    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
    StyleEngine *enginePtr2;

    if (enginePtr == NULL) {
	enginePtr = tsdPtr->defaultEnginePtr;
    }

    while (elementId >= 0 && elementId < tsdPtr->nbElements) {
	/*
	 * Look for an implemented element through the engine chain.
	 */

	enginePtr2 = enginePtr;
	do {
	    elementPtr = enginePtr2->elements+elementId;
	    if (elementPtr->specPtr != NULL) {
		return elementPtr;
	    }
	    enginePtr2 = enginePtr2->parentPtr;
	} while (enginePtr2 != NULL);

	/*
	 * None found, try with the generic element.
	 */

	elementId = tsdPtr->elements[elementId].genericId;
    }

    /*
     * No matching element found.
     */

    return NULL;
}

/*
 *---------------------------------------------------------------------------
 *
 * InitWidgetSpec --
 *
 *	Initialize a newly allocated widget spec.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Memory allocated.
 *
 *---------------------------------------------------------------------------
 */

static void
InitWidgetSpec(widgetSpecPtr, elementPtr, optionTable)
    StyledWidgetSpec *widgetSpecPtr;	/* Points to an uninitialized widget
					 * spec. */
    StyledElement *elementPtr;		/* Styled element descriptor. */
    Tk_OptionTable optionTable;		/* The widget's option table. */
{
    int i, nbOptions;
    Tk_ElementOptionSpec *elementOptionPtr;
    CONST Tk_OptionSpec *widgetOptionPtr;

    widgetSpecPtr->elementPtr = elementPtr;
    widgetSpecPtr->optionTable = optionTable;

    /*
     * Count the number of options.
     */

    for (nbOptions = 0, elementOptionPtr = elementPtr->specPtr->options;
	    elementOptionPtr->name != NULL;
	    nbOptions++, elementOptionPtr++) {
    }

    /*
     * Build the widget option list.
     */

    widgetSpecPtr->optionsPtr = (CONST Tk_OptionSpec **) ckalloc(
	    sizeof(Tk_OptionSpec *) * nbOptions);
    for (i = 0, elementOptionPtr = elementPtr->specPtr->options;
	    i < nbOptions;
	    i++, elementOptionPtr++) {
	widgetOptionPtr = TkGetOptionSpec(elementOptionPtr->name, optionTable);

	/*
	 * Check that the widget option type is compatible with one of the
	 * element's required types.
	 */

	if (   elementOptionPtr->type == TK_OPTION_END
	    || elementOptionPtr->type == widgetOptionPtr->type) {
	    widgetSpecPtr->optionsPtr[i] = widgetOptionPtr;
	} else {
	    widgetSpecPtr->optionsPtr[i] = NULL;
	}
    }
}

/*
 *---------------------------------------------------------------------------
 *
 * FreeWidgetSpec --
 *
 *	Free a widget spec and its associated data.
 *
 * Results:
 *	None
 *
 * Side effects:
 *	Memory freed.
 *
 *---------------------------------------------------------------------------
 */

static void
FreeWidgetSpec(widgetSpecPtr)
    StyledWidgetSpec *widgetSpecPtr;	/* The widget spec to free. */
{
    ckfree((char *) widgetSpecPtr->optionsPtr);
}

/*
 *---------------------------------------------------------------------------
 *
 * GetWidgetSpec --
 *
 *	Return a new or existing widget spec for the given element and
 *	widget type (identified by its option table).
 *
 * Results:
 *	A pointer to the matching widget spec.
 *
 * Side effects:
 *	Memory may be allocated.
 *
 *---------------------------------------------------------------------------
 */

static StyledWidgetSpec *
GetWidgetSpec(elementPtr, optionTable)
    StyledElement *elementPtr;		/* Styled element descriptor. */
    Tk_OptionTable optionTable;		/* The widget's option table. */
{
    StyledWidgetSpec *widgetSpecPtr;
    int i;

    /*
     * Try to find an existing widget spec.
     */

    for (i = 0; i < elementPtr->nbWidgetSpecs; i++) {
	widgetSpecPtr = elementPtr->widgetSpecs+i;
	if (widgetSpecPtr->optionTable == optionTable) {
	    return widgetSpecPtr;
	}
    }

    /*
     * Create and initialize a new widget spec.
     */

    i = elementPtr->nbWidgetSpecs++;
    elementPtr->widgetSpecs = (StyledWidgetSpec *) ckrealloc(
	    (char *) elementPtr->widgetSpecs,
	    sizeof(StyledWidgetSpec) * elementPtr->nbWidgetSpecs);
    widgetSpecPtr = elementPtr->widgetSpecs+i;
    InitWidgetSpec(widgetSpecPtr, elementPtr, optionTable);

    return widgetSpecPtr;
}

/*
 *---------------------------------------------------------------------------
 *
 * Tk_GetStyledElement --
 *
 *	This procedure returns a styled instance of the given element.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Cached data may be allocated or updated.
 *
 *---------------------------------------------------------------------------
 */

Tk_StyledElement
Tk_GetStyledElement(style, elementId, optionTable)
    Tk_Style style;		/* The widget style. */
    int elementId;		/* Unique element ID. */
    Tk_OptionTable optionTable;	/* Option table for the widget. */
{
    Style *stylePtr = (Style *) style;
    StyledElement *elementPtr;

    /*
     * Get an element implementation and call corresponding hook.
     */

    elementPtr = GetStyledElement((stylePtr?stylePtr->enginePtr:NULL),
	    elementId);
    if (!elementPtr) {
	return NULL;
    }

    return (Tk_StyledElement) GetWidgetSpec(elementPtr, optionTable);
}

/*
 *---------------------------------------------------------------------------
 *
 * Tk_GetElementSize --
 *
 *	This procedure computes the size of the given widget element according
 *	to its style.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Cached data may be allocated or updated.
 *
 *---------------------------------------------------------------------------
 */

void
Tk_GetElementSize(style, element, recordPtr, tkwin, width, height, inner, widthPtr,
	heightPtr)
    Tk_Style style;			/* The widget style. */
    Tk_StyledElement element;		/* The styled element, previously
					 * returned by Tk_GetStyledElement. */
    char *recordPtr;			/* The widget record. */
    Tk_Window tkwin;			/* The widget window. */
    int width, height;			/* Requested size. */
    int inner;				/* Boolean. If TRUE, compute the outer
					 * size according to the requested
					 * minimum inner size. If FALSE, compute
					 * the inner size according to the
					 * requested maximum outer size. */
    int *widthPtr, *heightPtr;		/* Returned size. */
{
    Style *stylePtr = (Style *) style;
    StyledWidgetSpec *widgetSpecPtr = (StyledWidgetSpec *) element;

    widgetSpecPtr->elementPtr->specPtr->getSize(stylePtr->clientData,
	    recordPtr, widgetSpecPtr->optionsPtr, tkwin, width, height, inner,
	    widthPtr, heightPtr);
}

/*
 *---------------------------------------------------------------------------
 *
 * Tk_GetElementBox --
 *
 *	This procedure computes the bounding or inscribed box coordinates
 *	of the given widget element according to its style and within the
 *	given limits.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Cached data may be allocated or updated.
 *
 *---------------------------------------------------------------------------
 */

void
Tk_GetElementBox(style, element, recordPtr, tkwin, x, y, width, height, inner,
	xPtr, yPtr, widthPtr, heightPtr)
    Tk_Style style;			/* The widget style. */
    Tk_StyledElement element;		/* The styled element, previously
					 * returned by Tk_GetStyledElement. */
    char *recordPtr;			/* The widget record. */
    Tk_Window tkwin;			/* The widget window. */
    int x, y;				/* Top left corner of available area. */
    int width, height;			/* Size of available area. */
    int inner;				/* Boolean. If TRUE, compute the
					 * bounding box according to the
					 * requested inscribed box size. If
					 * FALSE, compute the inscribed box
					 * according to the requested bounding
					 * box. */
    int *xPtr, *yPtr;			/* Returned top left corner. */
    int *widthPtr, *heightPtr;		/* Returned size. */
{
    Style *stylePtr = (Style *) style;
    StyledWidgetSpec *widgetSpecPtr = (StyledWidgetSpec *) element;

    widgetSpecPtr->elementPtr->specPtr->getBox(stylePtr->clientData,
	    recordPtr, widgetSpecPtr->optionsPtr, tkwin, x, y, width, height,
	    inner, xPtr, yPtr, widthPtr, heightPtr);
}

/*
 *---------------------------------------------------------------------------
 *
 * Tk_GetElementBorderWidth --
 *
 *	This procedure computes the border widthof the given widget element
 *	according to its style and within the given limits.
 *
 * Results:
 *	Border width in pixels. This value is uniform for all four sides.
 *
 * Side effects:
 *	Cached data may be allocated or updated.
 *
 *---------------------------------------------------------------------------
 */

int
Tk_GetElementBorderWidth(style, element, recordPtr, tkwin)
    Tk_Style style;			/* The widget style. */
    Tk_StyledElement element;		/* The styled element, previously
					 * returned by Tk_GetStyledElement. */
    char *recordPtr;			/* The widget record. */
    Tk_Window tkwin;			/* The widget window. */
{
    Style *stylePtr = (Style *) style;
    StyledWidgetSpec *widgetSpecPtr = (StyledWidgetSpec *) element;

    return widgetSpecPtr->elementPtr->specPtr->getBorderWidth(
	    stylePtr->clientData, recordPtr, widgetSpecPtr->optionsPtr, tkwin);
}

/*
 *---------------------------------------------------------------------------
 *
 * Tk_DrawElement --
 *
 *	This procedure draw the given widget element in a given drawable area.
 *
 * Results:
 *	None
 *
 * Side effects:
 *	Cached data may be allocated or updated.
 *
 *---------------------------------------------------------------------------
 */

void
Tk_DrawElement(style, element, recordPtr, tkwin, d, x, y, width, height, state)
    Tk_Style style;			/* The widget style. */
    Tk_StyledElement element;		/* The styled element, previously
					 * returned by Tk_GetStyledElement. */
    char *recordPtr;			/* The widget record. */
    Tk_Window tkwin;			/* The widget window. */
    Drawable d;				/* Where to draw element. */
    int x, y;				/* Top left corner of element. */
    int width, height;			/* Size of element. */
    int state;				/* Drawing state flags. */
{
    Style *stylePtr = (Style *) style;
    StyledWidgetSpec *widgetSpecPtr = (StyledWidgetSpec *) element;

    widgetSpecPtr->elementPtr->specPtr->draw(stylePtr->clientData,
	    recordPtr, widgetSpecPtr->optionsPtr, tkwin, d, x, y, width,
	    height, state);
}

/*
 *---------------------------------------------------------------------------
 *
 * Tk_CreateStyle --
 *
 *	This procedure is called to create a new style as an instance of the
 *	given engine. Styles are stored in thread-local space.
 *
 * Results:
 *	The newly allocated style.
 *
 * Side effects:
 *	Memory allocated. Data added to thread-local table. The style's
 *	refCount is incremented.
 *
 *---------------------------------------------------------------------------
 */

Tk_Style
Tk_CreateStyle(name, engine, clientData)
    CONST char *name;		/* Name of the style to create. NULL or empty
				 * means the default system style. */
    Tk_StyleEngine engine;	/* The style engine. */
    ClientData clientData;	/* Private data passed as is to engine code. */
{
    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
            Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
    Tcl_HashEntry *entryPtr;
    int newEntry;
    Style *stylePtr;

    /*
     * Attempt to create a new entry in the style table.
     */

    entryPtr = Tcl_CreateHashEntry(&tsdPtr->styleTable, (name?name:""),
	    &newEntry);
    if (!newEntry) {
	/*
	 * A style was already registered by that name.
	 */

	return NULL;
    }

    /*
     * Allocate and intitialize a new style.
     */

    stylePtr = (Style *) ckalloc(sizeof(Style));
    InitStyle(stylePtr, entryPtr, Tcl_GetHashKey(&tsdPtr->styleTable, entryPtr),
	    (engine?(StyleEngine *) engine:tsdPtr->defaultEnginePtr), clientData);
    Tcl_SetHashValue(entryPtr, (ClientData) stylePtr);
    stylePtr->refCount++;

    return (Tk_Style) stylePtr;
}

/*
 *---------------------------------------------------------------------------
 *
 * Tk_NameOfStyle --
 *
 *	Given a style, return its registered name.
 *
 * Results:
 *	The return value is the name that was passed to Tk_CreateStyle() to
 *	create the style.  The storage for the returned string is private
 *	(it points to the corresponding hash key) The caller should not modify
 *	this string.
 *
 * Side effects:
 *	None.
 *
 *---------------------------------------------------------------------------
 */

CONST char *
Tk_NameOfStyle(style)
    Tk_Style style;		/* Style whose name is desired. */
{
    Style *stylePtr = (Style *) style;

    return stylePtr->name;
}

/*
 *---------------------------------------------------------------------------
 *
 * InitStyle --
 *
 *	Initialize a newly allocated style.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *---------------------------------------------------------------------------
 */

static void
InitStyle(stylePtr, hashPtr, name, enginePtr, clientData)
    Style *stylePtr;		/* Points to an uninitialized style. */
    Tcl_HashEntry *hashPtr;	/* Hash entry for the registered style. */
    CONST char *name;		/* Name of the registered style. NULL or empty
				 * means the default system style. Usually
				 * points to the hash key. */
    StyleEngine *enginePtr;	/* The style engine. */
    ClientData clientData;	/* Private data passed as is to engine code. */
{
    stylePtr->refCount = 0;
    stylePtr->hashPtr = hashPtr;
    stylePtr->name = name;
    stylePtr->enginePtr = enginePtr;
    stylePtr->clientData = clientData;
}

/*
 *---------------------------------------------------------------------------
 *
 * FreeStyle --
 *
 *	Free a style and its associated data.
 *
 * Results:
 *	None
 *
 * Side effects:
 *	None.
 *
 *---------------------------------------------------------------------------
 */

static void
FreeStyle(stylePtr)
    Style *stylePtr;		/* The style to free. */
{
    /* Nothing to do. */
}

/*
 *---------------------------------------------------------------------------
 *
 * Tk_GetStyle --
 *
 *	Retrieve a registered style by its name.
 *
 * Results:
 *	A pointer to the style engine, or NULL if none found.  In the latter
 *	case and if the interp is not NULL, an error message is left in the
 *	interp's result.
 *
 * Side effects:
 *	None.
 *
 *---------------------------------------------------------------------------
 */

Tk_Style
Tk_GetStyle(interp, name)
    Tcl_Interp *interp;		/* Interp for error return. */
    CONST char *name;		/* Name of the style to retrieve. NULL or empty
				 * means the default system style. */
{
    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
            Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
    Tcl_HashEntry *entryPtr;
    Style *stylePtr;

    /*
     * Search for a corresponding entry in the style table.
     */

    entryPtr = Tcl_FindHashEntry(&tsdPtr->styleTable, (name?name:""));
    if (entryPtr == NULL) {
	if (interp != NULL) {
	    Tcl_AppendResult(interp, "style \"", name, "\" doesn't exist", NULL);
	}
	return (Tk_Style) NULL;
    }
    stylePtr = (Style *) Tcl_GetHashValue(entryPtr);
    stylePtr->refCount++;

    return (Tk_Style) stylePtr;
}

/*
 *---------------------------------------------------------------------------
 *
 * Tk_FreeStyle --
 *
 *	Free a style previously created by Tk_CreateStyle.
 *
 * Results:
 *	None
 *
 * Side effects:
 *	The style's refCount is decremented. If it reaches zero, the style
 *	is freed.
 *
 *---------------------------------------------------------------------------
 */

void
Tk_FreeStyle(style)
    Tk_Style style;		/* The style to free. */
{
    Style *stylePtr = (Style *) style;

    if (stylePtr == NULL) {
	return;
    }
    stylePtr->refCount--;
    if (stylePtr->refCount > 0) {
	return;
    }

    /*
     * Keep the default style alive.
     */

    if (*stylePtr->name == '\0') {
	stylePtr->refCount = 1;
	return;
    }

    Tcl_DeleteHashEntry(stylePtr->hashPtr);
    FreeStyle(stylePtr);
    ckfree((char *) stylePtr);
}

/*
 *---------------------------------------------------------------------------
 *
 * Tk_AllocStyleFromObj --
 *
 *	Map the string name of a style to a corresponding Tk_Style. The style
 *	must have already been created by Tk_CreateStyle.
 *
 * Results:
 *	The return value is a token for the style that matches objPtr, or
 *	NULL if none found.  If NULL is returned, an error message will be
 *	left in interp's result object.
 *
 * Side effects:
 * 	The style's reference count is incremented. For each call to this
 *	procedure, there should eventually be a call to Tk_FreeStyle() or
 *	Tk_FreeStyleFromObj() so that the database is cleaned up when styles
 *	aren't in use anymore.
 *
 *---------------------------------------------------------------------------
 */

Tk_Style
Tk_AllocStyleFromObj(interp, objPtr)
    Tcl_Interp *interp;		/* Interp for error return. */
    Tcl_Obj *objPtr;		/* Object containing name of the style to
				 * retrieve. */
{
    Style *stylePtr;

    if (TclObjGetType(objPtr) != &styleObjType) {
	SetStyleFromAny(interp, objPtr);
	stylePtr = (Style *) TclObjInternal(objPtr)->otherValuePtr;
    } else {
	stylePtr = (Style *) TclObjInternal(objPtr)->otherValuePtr;
	stylePtr->refCount++;
    }

    return (Tk_Style) stylePtr;
}

/*
 *----------------------------------------------------------------------
 *
 * Tk_GetStyleFromObj --
 *
 *	Find the style that corresponds to a given object.  The style must
 *	have already been created by Tk_CreateStyle.
 *
 * Results:
 *	The return value is a token for the style that matches objPtr, or
 *	NULL if none found.
 *
 * Side effects:
 *	If the object is not already a style ref, the conversion will free
 *	any old internal representation.
 *
 *----------------------------------------------------------------------
 */

Tk_Style
Tk_GetStyleFromObj(objPtr)
    Tcl_Obj *objPtr;		/* The object from which to get the style. */
{
    if (TclObjGetType(objPtr) != &styleObjType) {
	SetStyleFromAny((Tcl_Interp *) NULL, objPtr);
    }

    return (Tk_Style) TclObjInternal(objPtr)->otherValuePtr;
}

/*
 *---------------------------------------------------------------------------
 *
 * Tk_FreeStyleFromObj --
 *
 *	Called to release a style inside a Tcl_Obj *.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	If the object is a style ref, the conversion will free its
 *	internal representation.
 *
 *---------------------------------------------------------------------------
 */

void
Tk_FreeStyleFromObj(objPtr)
    Tcl_Obj *objPtr;		/* The Tcl_Obj * to be freed. */
{
    if (TclObjGetType(objPtr) == &styleObjType) {
	FreeStyleObjProc(objPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * SetStyleFromAny --
 *
 *	Convert the internal representation of a Tcl object to the
 *	style internal form.
 *
 * Results:
 *	Always returns TCL_OK.  If an error occurs is returned (e.g. the
 *	style doesn't exist), an error message will be left in interp's
 *	result.
 *
 * Side effects:
 *	The object is left with its typePtr pointing to styleObjType.
 *	The reference count is incremented (in Tk_GetStyle()).
 *
 *----------------------------------------------------------------------
 */

static int
SetStyleFromAny(interp, objPtr)
    Tcl_Interp *interp;		/* Used for error reporting if not NULL. */
    Tcl_Obj *objPtr;		/* The object to convert. */
{
    Tcl_ObjType *typePtr;
    char *name;

    /*
     * Free the old internalRep before setting the new one.
     */

    name = Tcl_GetString(objPtr);
    typePtr = TclObjGetType(objPtr);
    if ((typePtr != NULL) && (typePtr->freeIntRepProc != NULL)) {
	(*typePtr->freeIntRepProc)(objPtr);
    }

    TclObjSetType(objPtr,&styleObjType);
    TclObjInternal(objPtr)->otherValuePtr = (VOID *) Tk_GetStyle(interp, name);

    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * FreeStyleObjProc --
 *
 *	This proc is called to release an object reference to a style.
 *	Called when the object's internal rep is released.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The reference count is decremented (in Tk_FreeStyle()).
 *
 *---------------------------------------------------------------------------
 */

static void
FreeStyleObjProc(objPtr)
    Tcl_Obj *objPtr;		/* The object we are releasing. */
{
    Style *stylePtr = (Style *) TclObjInternal(objPtr)->otherValuePtr;

    if (stylePtr != NULL) {
	Tk_FreeStyle((Tk_Style) stylePtr);
	TclObjInternal(objPtr)->otherValuePtr = NULL;
    }
}

/*
 *---------------------------------------------------------------------------
 *
 * DupStyleObjProc --
 *
 *	When a cached style object is duplicated, this is called to
 *	update the internal reps.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The style's refCount is incremented and the internal rep of the copy
 *	is set to point to it.
 *
 *---------------------------------------------------------------------------
 */

static void
DupStyleObjProc(srcObjPtr, dupObjPtr)
    Tcl_Obj *srcObjPtr;		/* The object we are copying from. */
    Tcl_Obj *dupObjPtr;		/* The object we are copying to. */
{
    Style *stylePtr = (Style *) TclObjInternal(srcObjPtr)->otherValuePtr;

    TclObjSetType(dupObjPtr,TclObjGetType(srcObjPtr));
    TclObjInternal(dupObjPtr)->otherValuePtr = (VOID *) stylePtr;

    if (stylePtr != NULL) {
	stylePtr->refCount++;
    }
}
