/*
 * Copyright (c) 2003, Joe English
 *
 * Core widget utilities.
 */

#include "tkInt.h"
#include "ttkTheme.h"
#include "ttkWidget.h"

#ifdef MAC_OSX_TK
#define TK_NO_DOUBLE_BUFFERING 1
#endif

/*------------------------------------------------------------------------
 * +++ Internal helper routines.
 */

/* UpdateLayout --
 * 	Call the widget's get-layout hook to recompute corePtr->layout.
 * 	Returns TCL_OK if successful, returns TCL_ERROR and leaves
 * 	the layout unchanged otherwise.
 */
static int UpdateLayout(Tcl_Interp *interp, WidgetCore *corePtr)
{
    Ttk_Theme themePtr = Ttk_GetCurrentTheme(interp);
    Ttk_Layout newLayout =
    	corePtr->widgetSpec->getLayoutProc(interp, themePtr,corePtr);

    if (newLayout) {
	if (corePtr->layout) {
	    Ttk_FreeLayout(corePtr->layout);
	}
	corePtr->layout = newLayout;
	return TCL_OK;
    }
    return TCL_ERROR;
}

/* SizeChanged --
 * 	Call the widget's sizeProc to compute new requested size
 * 	and pass it to the geometry manager.
 */
static void SizeChanged(WidgetCore *corePtr)
{
    int reqWidth = 1, reqHeight = 1;

    if (corePtr->widgetSpec->sizeProc(corePtr,&reqWidth,&reqHeight)) {
	Tk_GeometryRequest(corePtr->tkwin, reqWidth, reqHeight);
    }
}

#ifndef TK_NO_DOUBLE_BUFFERING

/* BeginDrawing --
 * 	Returns a Drawable for drawing the widget contents.
 *	This is normally an off-screen Pixmap, copied to
 *	the window by EndDrawing().
 */
static Drawable BeginDrawing(Tk_Window tkwin)
{
    return Tk_GetPixmap(Tk_Display(tkwin), Tk_WindowId(tkwin),
	    Tk_Width(tkwin), Tk_Height(tkwin), Tk_Depth(tkwin));
}

/* EndDrawing --
 *	Copy the drawable contents to the screen and release resources.
 */
static void EndDrawing(Tk_Window tkwin, Drawable d)
{
    XGCValues gcValues;
    GC gc;

    gcValues.function = GXcopy;
    gcValues.graphics_exposures = False;
    gc = Tk_GetGC(tkwin, GCFunction|GCGraphicsExposures, &gcValues);

    XCopyArea(Tk_Display(tkwin), d, Tk_WindowId(tkwin), gc,
	    0, 0, (unsigned) Tk_Width(tkwin), (unsigned) Tk_Height(tkwin),
	    0, 0);

    Tk_FreePixmap(Tk_Display(tkwin), d);
    Tk_FreeGC(Tk_Display(tkwin), gc);
}
#else
/* No double-buffering: draw directly into the window. */
static Drawable BeginDrawing(Tk_Window tkwin) { return Tk_WindowId(tkwin); }
static void EndDrawing(Tk_Window tkwin, Drawable d) { }
#endif

/* DrawWidget --
 *	Redraw a widget.  Called as an idle handler.
 */
static void DrawWidget(ClientData recordPtr)
{
    WidgetCore *corePtr = recordPtr;

    corePtr->flags &= ~REDISPLAY_PENDING;
    if (Tk_IsMapped(corePtr->tkwin)) {
	Drawable d = BeginDrawing(corePtr->tkwin);
	corePtr->widgetSpec->layoutProc(recordPtr);
	corePtr->widgetSpec->displayProc(recordPtr, d);
	EndDrawing(corePtr->tkwin, d);
    }
}

/* TtkRedisplayWidget --
 * 	Schedule redisplay as an idle handler.
 */
void TtkRedisplayWidget(WidgetCore *corePtr)
{
    if (corePtr->flags & WIDGET_DESTROYED) {
	return;
    }

    if (!(corePtr->flags & REDISPLAY_PENDING)) {
	Tcl_DoWhenIdle(DrawWidget, corePtr);
	corePtr->flags |= REDISPLAY_PENDING;
    }
}

/* TtkResizeWidget --
 * 	Recompute widget size, schedule geometry propagation and redisplay.
 */
void TtkResizeWidget(WidgetCore *corePtr)
{
    if (corePtr->flags & WIDGET_DESTROYED) {
	return;
    }

    SizeChanged(corePtr);
    TtkRedisplayWidget(corePtr);
}

/* TtkWidgetChangeState --
 * 	Set / clear the specified bits in the 'state' flag,
 */
void TtkWidgetChangeState(WidgetCore *corePtr,
    unsigned int setBits, unsigned int clearBits)
{
    Ttk_State oldState = corePtr->state;
    corePtr->state = (oldState & ~clearBits) | setBits;
    if (corePtr->state ^ oldState) {
	TtkRedisplayWidget(corePtr);
    }
}

/* WidgetInstanceObjCmd --
 *	Widget instance command implementation.
 */
static int
WidgetInstanceObjCmd(
    ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
{
    WidgetCore *corePtr = clientData;
    const Ttk_Ensemble *commands = corePtr->widgetSpec->commands;
    int status;

    Tcl_Preserve(clientData);
    status = Ttk_InvokeEnsemble(commands,1, clientData,interp,objc,objv);
    Tcl_Release(clientData);

    return status;
}

/*------------------------------------------------------------------------
 * +++ Widget destruction.
 *
 * A widget can be destroyed when the application explicitly
 * destroys the window or one of its ancestors via [destroy]
 * or Tk_DestroyWindow(); when the application deletes the widget
 * instance command; when there is an error in the widget constructor;
 * or when another application calls XDestroyWindow on the window ID.
 *
 * The window receives a <DestroyNotify> event in all cases,
 * so we do the bulk of the cleanup there.  See [#2207435] for
 * further notes (esp. re: Tk_FreeConfigOptions).
 *
 * Widget code that reenters the interp should only do so
 * when the widtget is Tcl_Preserve()d, and should check
 * the WIDGET_DESTROYED flag bit upon return.
 */

/* WidgetInstanceObjCmdDeleted --
 * 	Widget instance command	deletion callback.
 */
static void
WidgetInstanceObjCmdDeleted(ClientData clientData)
{
    WidgetCore *corePtr = clientData;
    corePtr->widgetCmd = NULL;
    if (corePtr->tkwin != NULL)
	Tk_DestroyWindow(corePtr->tkwin);
}

/* FreeWidget --
 *	 Final cleanup for widget; called via Tcl_EventuallyFree().
 */
static void
FreeWidget(void *memPtr)
{
    ckfree(memPtr);
}

/* DestroyWidget --
 * 	Main widget destructor; called from <DestroyNotify> event handler.
 */
static void
DestroyWidget(WidgetCore *corePtr)
{
    corePtr->flags |= WIDGET_DESTROYED;

    corePtr->widgetSpec->cleanupProc(corePtr);

    Tk_FreeConfigOptions(
	(ClientData)corePtr, corePtr->optionTable, corePtr->tkwin);

    if (corePtr->layout) {
	Ttk_FreeLayout(corePtr->layout);
    }

    if (corePtr->flags & REDISPLAY_PENDING) {
	Tcl_CancelIdleCall(DrawWidget, corePtr);
    }

    corePtr->tkwin = NULL;
    if (corePtr->widgetCmd) {
	Tcl_Command cmd = corePtr->widgetCmd;
	corePtr->widgetCmd = 0;
	/* NB: this can reenter the interpreter via a command traces */
	Tcl_DeleteCommandFromToken(corePtr->interp, cmd);
    }
    Tcl_EventuallyFree(corePtr, (Tcl_FreeProc *) FreeWidget);
}

/*
 * CoreEventProc --
 *	Event handler for basic events.
 *	Processes Expose, Configure, FocusIn/Out, and Destroy events.
 *	Also handles <<ThemeChanged>> virtual events.
 *
 *	For Expose and Configure, simply schedule the widget for redisplay.
 *	For Destroy events, handle the cleanup process.
 *
 *	For Focus events, set/clear the focus bit in the state field.
 *	It turns out this is impossible to do correctly in a binding script,
 *	because Tk filters out focus events with detail == NotifyInferior.
 *
 *	For Deactivate/Activate pseudo-events, set/clear the background state
 *	flag.
 */

static const unsigned CoreEventMask
    = ExposureMask
    | StructureNotifyMask
    | FocusChangeMask
    | VirtualEventMask
    | ActivateMask
    | EnterWindowMask
    | LeaveWindowMask
    ;

static void CoreEventProc(ClientData clientData, XEvent *eventPtr)
{
    WidgetCore *corePtr = (WidgetCore *)clientData;

    switch (eventPtr->type)
    {
	case ConfigureNotify :
	    TtkRedisplayWidget(corePtr);
	    break;
	case Expose :
	    if (eventPtr->xexpose.count == 0) {
		TtkRedisplayWidget(corePtr);
	    }
	    break;
	case DestroyNotify :
	    Tk_DeleteEventHandler(
		corePtr->tkwin, CoreEventMask,CoreEventProc,clientData);
	    DestroyWidget(corePtr);
	    break;
	case FocusIn:
	case FocusOut:
	    /* Don't process "virtual crossing" events */
	    if (   eventPtr->xfocus.detail == NotifyInferior
		|| eventPtr->xfocus.detail == NotifyAncestor
		|| eventPtr->xfocus.detail == NotifyNonlinear)
	    {
		if (eventPtr->type == FocusIn)
		    corePtr->state |= TTK_STATE_FOCUS;
		else
		    corePtr->state &= ~TTK_STATE_FOCUS;
		TtkRedisplayWidget(corePtr);
	    }
	    break;
	case ActivateNotify:
	    corePtr->state &= ~TTK_STATE_BACKGROUND;
	    TtkRedisplayWidget(corePtr);
	    break;
	case DeactivateNotify:
	    corePtr->state |= TTK_STATE_BACKGROUND;
	    TtkRedisplayWidget(corePtr);
	    break;
	case LeaveNotify:
	    corePtr->state &= ~TTK_STATE_HOVER;
	    TtkRedisplayWidget(corePtr);
	    break;
	case EnterNotify:
	    corePtr->state |= TTK_STATE_HOVER;
	    TtkRedisplayWidget(corePtr);
	    break;
	case VirtualEvent: {
	    const char *name = ((XVirtualEvent *)eventPtr)->name;
	    if ((name != NULL) && !strcmp("ThemeChanged", name)) {
		(void)UpdateLayout(corePtr->interp, corePtr);
		SizeChanged(corePtr);
		TtkRedisplayWidget(corePtr);
	    }
	    break;
	}
	default:
	    /* can't happen... */
	    break;
    }
}

/*
 * WidgetWorldChanged --
 * 	Default Tk_ClassWorldChangedProc() for widgets.
 * 	Invoked whenever fonts or other system resources are changed;
 * 	recomputes geometry.
 */
static void WidgetWorldChanged(ClientData clientData)
{
    WidgetCore *corePtr = clientData;
    SizeChanged(corePtr);
    TtkRedisplayWidget(corePtr);
}

static Tk_ClassProcs widgetClassProcs = {
    sizeof(Tk_ClassProcs),	/* size */
    WidgetWorldChanged,	/* worldChangedProc */
    NULL,					/* createProc */
    NULL					/* modalProc */
};

/*
 * TtkWidgetConstructorObjCmd --
 *	General-purpose widget constructor command implementation.
 *	ClientData is a WidgetSpec *.
 */
int TtkWidgetConstructorObjCmd(
    ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
{
    WidgetSpec *widgetSpec = clientData;
    const char *className = widgetSpec->className;
    Tk_OptionTable optionTable =
	Tk_CreateOptionTable(interp, widgetSpec->optionSpecs);
    Tk_Window tkwin;
    void *recordPtr;
    WidgetCore *corePtr;
    Tk_SavedOptions savedOptions;
    int i;

    if (objc < 2 || objc % 2 == 1) {
	Tcl_WrongNumArgs(interp, 1, objv, "pathName ?-option value ...?");
	return TCL_ERROR;
    }

    /* Check if a -class option has been specified.
     * We have to do this before the InitOptions() call,
     * since InitOptions() is affected by the widget class.
     */
    for (i = 2; i < objc; i += 2) {
	if (!strcmp(Tcl_GetString(objv[i]), "-class")) {
	    className = Tcl_GetString(objv[i+1]);
	    break;
	}
    }

    tkwin = Tk_CreateWindowFromPath(
	interp, Tk_MainWindow(interp), Tcl_GetString(objv[1]), NULL);
    if (tkwin == NULL)
	return TCL_ERROR;

    /*
     * Allocate and initialize the widget record.
     */
    recordPtr = ckalloc(widgetSpec->recordSize);
    memset(recordPtr, 0, widgetSpec->recordSize);
    corePtr = recordPtr;

    corePtr->tkwin	= tkwin;
    corePtr->interp 	= interp;
    corePtr->widgetSpec	= widgetSpec;
    corePtr->widgetCmd	= Tcl_CreateObjCommand(interp, Tk_PathName(tkwin),
	WidgetInstanceObjCmd, recordPtr, WidgetInstanceObjCmdDeleted);
    corePtr->optionTable = optionTable;
    corePtr->layout	= NULL;
    corePtr->flags 	= 0;
    corePtr->state 	= 0;

    Tk_SetClass(tkwin, className);
    Tk_SetClassProcs(tkwin, &widgetClassProcs, recordPtr);
    Tk_SetWindowBackgroundPixmap(tkwin, ParentRelative);

    widgetSpec->initializeProc(interp, recordPtr);

    Tk_CreateEventHandler(tkwin, CoreEventMask, CoreEventProc, recordPtr);

    /*
     * Initial configuration.
     */

    Tcl_Preserve(corePtr);
    if (Tk_InitOptions(interp, recordPtr, optionTable, tkwin) != TCL_OK) {
	goto error;
    }

    if (Tk_SetOptions(interp, recordPtr, optionTable,
	    objc - 2, objv + 2, tkwin, &savedOptions, NULL) != TCL_OK) {
	Tk_RestoreSavedOptions(&savedOptions);
	goto error;
    } else {
	Tk_FreeSavedOptions(&savedOptions);
    }
    if (widgetSpec->configureProc(interp, recordPtr, ~0) != TCL_OK)
	goto error;
    if (widgetSpec->postConfigureProc(interp, recordPtr, ~0) != TCL_OK)
	goto error;

    if (WidgetDestroyed(corePtr))
	goto error;

    Tcl_Release(corePtr);

    SizeChanged(corePtr);
    Tk_MakeWindowExist(tkwin);

    Tcl_SetObjResult(interp, Tcl_NewStringObj(Tk_PathName(tkwin), -1));
    return TCL_OK;

error:
    if (WidgetDestroyed(corePtr)) {
	Tcl_SetObjResult(interp, Tcl_NewStringObj(
		"widget has been destroyed", -1));
    } else {
	Tk_DestroyWindow(tkwin);
    }
    Tcl_Release(corePtr);
    return TCL_ERROR;
}

/*------------------------------------------------------------------------
 * +++ Default implementations for widget hook procedures.
 */

/* TtkWidgetGetLayout --
 * 	Default getLayoutProc.
 *	Looks up the layout based on the -style resource (if specified),
 *	otherwise use the widget class.
 */
Ttk_Layout TtkWidgetGetLayout(
    Tcl_Interp *interp, Ttk_Theme themePtr, void *recordPtr)
{
    WidgetCore *corePtr = recordPtr;
    const char *styleName = 0;

    if (corePtr->styleObj)
    	styleName = Tcl_GetString(corePtr->styleObj);

    if (!styleName || *styleName == '\0')
    	styleName = corePtr->widgetSpec->className;

    return Ttk_CreateLayout(interp, themePtr, styleName,
	recordPtr, corePtr->optionTable, corePtr->tkwin);
}

/*
 * TtkWidgetGetOrientedLayout --
 * 	Helper routine.  Same as TtkWidgetGetLayout, but prefixes
 * 	"Horizontal." or "Vertical." to the style name, depending
 * 	on the value of the 'orient' option.
 */
Ttk_Layout TtkWidgetGetOrientedLayout(
    Tcl_Interp *interp, Ttk_Theme themePtr, void *recordPtr, Tcl_Obj *orientObj)
{
    WidgetCore *corePtr = recordPtr;
    const char *baseStyleName = 0;
    Tcl_DString styleName;
    int orient = TTK_ORIENT_HORIZONTAL;
    Ttk_Layout layout;

    Tcl_DStringInit(&styleName);

    /* Prefix:
     */
    Ttk_GetOrientFromObj(NULL, orientObj, &orient);
    if (orient == TTK_ORIENT_HORIZONTAL)
	Tcl_DStringAppend(&styleName, "Horizontal.", -1);
    else
	Tcl_DStringAppend(&styleName, "Vertical.", -1);

    /* Add base style name:
     */
    if (corePtr->styleObj)
    	baseStyleName = Tcl_GetString(corePtr->styleObj);
    if (!baseStyleName || *baseStyleName == '\0')
    	baseStyleName = corePtr->widgetSpec->className;

    Tcl_DStringAppend(&styleName, baseStyleName, -1);

    /* Create layout:
     */
    layout= Ttk_CreateLayout(interp, themePtr, Tcl_DStringValue(&styleName),
	recordPtr, corePtr->optionTable, corePtr->tkwin);

    Tcl_DStringFree(&styleName);

    return layout;
}

/* TtkNullInitialize --
 * 	Default widget initializeProc (no-op)
 */
void TtkNullInitialize(Tcl_Interp *interp, void *recordPtr)
{
}

/* TtkNullPostConfigure --
 * 	Default widget postConfigureProc (no-op)
 */
int TtkNullPostConfigure(Tcl_Interp *interp, void *clientData, int mask)
{
    return TCL_OK;
}

/* TtkCoreConfigure --
 * 	Default widget configureProc.
 * 	Handles -style option.
 */
int TtkCoreConfigure(Tcl_Interp *interp, void *clientData, int mask)
{
    WidgetCore *corePtr = clientData;
    int status = TCL_OK;

    if (mask & STYLE_CHANGED) {
	status = UpdateLayout(interp, corePtr);
    }

    return status;
}

/* TtkNullCleanup --
 * 	Default widget cleanupProc (no-op)
 */
void TtkNullCleanup(void *recordPtr)
{
    return;
}

/* TtkWidgetDoLayout --
 * 	Default widget layoutProc.
 */
void TtkWidgetDoLayout(void *clientData)
{
    WidgetCore *corePtr = clientData;
    Ttk_PlaceLayout(corePtr->layout,corePtr->state,Ttk_WinBox(corePtr->tkwin));
}

/* TtkWidgetDisplay --
 * 	Default widget displayProc.
 */
void TtkWidgetDisplay(void *recordPtr, Drawable d)
{
    WidgetCore *corePtr = recordPtr;
    Ttk_DrawLayout(corePtr->layout, corePtr->state, d);
}

/* TtkWidgetSize --
 * 	Default widget sizeProc()
 */
int TtkWidgetSize(void *recordPtr, int *widthPtr, int *heightPtr)
{
    WidgetCore *corePtr = recordPtr;
    Ttk_LayoutSize(corePtr->layout, corePtr->state, widthPtr, heightPtr);
    return 1;
}

/*------------------------------------------------------------------------
 * +++ Default implementations for widget subcommands.
 */

/* $w cget -option
 */
int TtkWidgetCgetCommand(
    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
{
    WidgetCore *corePtr = recordPtr;
    Tcl_Obj *result;

    if (objc != 3) {
	Tcl_WrongNumArgs(interp, 2, objv, "option");
	return TCL_ERROR;
    }
    result = Tk_GetOptionValue(interp, recordPtr,
		corePtr->optionTable, objv[2], corePtr->tkwin);
    if (result == NULL)
	return TCL_ERROR;
    Tcl_SetObjResult(interp, result);
    return TCL_OK;
}

/* $w configure ?-option ?value ....??
 */
int TtkWidgetConfigureCommand(
    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
{
    WidgetCore *corePtr = recordPtr;
    Tcl_Obj *result;

    if (objc == 2) {
	result = Tk_GetOptionInfo(interp, recordPtr,
		corePtr->optionTable, NULL, corePtr->tkwin);
    } else if (objc == 3) {
	result = Tk_GetOptionInfo(interp, recordPtr,
		corePtr->optionTable, objv[2], corePtr->tkwin);
    } else {
	Tk_SavedOptions savedOptions;
	int status;
	int mask = 0;

	status = Tk_SetOptions(interp, recordPtr,
		corePtr->optionTable, objc - 2, objv + 2,
		corePtr->tkwin, &savedOptions, &mask);
	if (status != TCL_OK)
	    return status;

	if (mask & READONLY_OPTION) {
	    Tcl_SetObjResult(interp, Tcl_NewStringObj(
		    "attempt to change read-only option", -1));
	    Tk_RestoreSavedOptions(&savedOptions);
	    return TCL_ERROR;
	}

	status = corePtr->widgetSpec->configureProc(interp, recordPtr, mask);
	if (status != TCL_OK) {
	    Tk_RestoreSavedOptions(&savedOptions);
	    return status;
	}
	Tk_FreeSavedOptions(&savedOptions);

	status = corePtr->widgetSpec->postConfigureProc(interp,recordPtr,mask);
	if (WidgetDestroyed(corePtr)) {
	    Tcl_SetObjResult(interp, Tcl_NewStringObj(
		    "widget has been destroyed", -1));
	    status = TCL_ERROR;
	}
	if (status != TCL_OK) {
	    return status;
	}

	if (mask & (STYLE_CHANGED | GEOMETRY_CHANGED)) {
	    SizeChanged(corePtr);
	}

	TtkRedisplayWidget(corePtr);
	result = Tcl_NewObj();
    }

    if (result == 0) {
	return TCL_ERROR;
    }
    Tcl_SetObjResult(interp, result);
    return TCL_OK;
}

/* $w state ? $stateSpec ?
 *
 * 	If $stateSpec is specified, modify the widget state accordingly,
 * 	return a new stateSpec representing the changed bits.
 *
 * 	Otherwise, return a statespec matching all the currently-set bits.
 */

int TtkWidgetStateCommand(
    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
{
    WidgetCore *corePtr = recordPtr;
    Ttk_StateSpec spec;
    int status;
    Ttk_State oldState, changed;

    if (objc == 2) {
	Tcl_SetObjResult(interp,
	    Ttk_NewStateSpecObj(corePtr->state, 0ul));
	return TCL_OK;
    }

    if (objc != 3) {
	Tcl_WrongNumArgs(interp, 2, objv, "state-spec");
	return TCL_ERROR;
    }
    status = Ttk_GetStateSpecFromObj(interp, objv[2], &spec);
    if (status != TCL_OK)
	return status;

    oldState = corePtr->state;
    corePtr->state = Ttk_ModifyState(corePtr->state, &spec);
    changed = corePtr->state ^ oldState;

    TtkRedisplayWidget(corePtr);

    Tcl_SetObjResult(interp,
	Ttk_NewStateSpecObj(oldState & changed, ~oldState & changed));
    return status;
}

/* $w instate $stateSpec ?$script?
 *
 * 	Tests if widget state matches $stateSpec.
 *	If $script is specified, execute script if state matches.
 *	Otherwise, return true/false
 */

int TtkWidgetInstateCommand(
    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
{
    WidgetCore *corePtr = recordPtr;
    Ttk_State state = corePtr->state;
    Ttk_StateSpec spec;
    int status = TCL_OK;

    if (objc < 3 || objc > 4) {
	Tcl_WrongNumArgs(interp, 2, objv, "state-spec ?script?");
	return TCL_ERROR;
    }
    status = Ttk_GetStateSpecFromObj(interp, objv[2], &spec);
    if (status != TCL_OK)
	return status;

    if (objc == 3) {
	Tcl_SetObjResult(interp,
	    Tcl_NewBooleanObj(Ttk_StateMatches(state,&spec)));
    } else if (objc == 4) {
	if (Ttk_StateMatches(state,&spec)) {
	    status = Tcl_EvalObjEx(interp, objv[3], 0);
	}
    }
    return status;
}

/* $w identify $x $y
 * $w identify element $x $y
 * 	Returns: name of element at $x, $y
 */
int TtkWidgetIdentifyCommand(
    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
{
    WidgetCore *corePtr = recordPtr;
    Ttk_Element element;
    static const char *whatTable[] = { "element", NULL };
    int x, y, what;

    if (objc < 4 || objc > 5) {
	Tcl_WrongNumArgs(interp, 2, objv, "?what? x y");
	return TCL_ERROR;
    }
    if (objc == 5) {
	/* $w identify element $x $y */
	if (Tcl_GetIndexFromObjStruct(interp, objv[2], whatTable,
		sizeof(char *), "option", 0, &what) != TCL_OK)
	{
	    return TCL_ERROR;
	}
    }

    if (   Tcl_GetIntFromObj(interp, objv[objc-2], &x) != TCL_OK
	|| Tcl_GetIntFromObj(interp, objv[objc-1], &y) != TCL_OK
    ) {
	return TCL_ERROR;
    }

    element = Ttk_IdentifyElement(corePtr->layout, x, y);
    if (element) {
	const char *elementName = Ttk_ElementName(element);
	Tcl_SetObjResult(interp,Tcl_NewStringObj(elementName,-1));
    }

    return TCL_OK;
}

/*EOF*/
