
/*
 * bltContainer.c --
 *
 *	This module implements a container widget for the BLT toolkit.
 *
 * Copyright 1998 Lucent Technologies, Inc.
 *
 * Permission to use, copy, modify, and distribute this software and
 * its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice appear in all
 * copies and that both that the copyright notice and warranty
 * disclaimer appear in supporting documentation, and that the names
 * of Lucent Technologies or any of their entities not be used in
 * advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission.
 *
 * Lucent Technologies disclaims all warranties with regard to this
 * software, including all implied warranties of merchantability and
 * fitness.  In no event shall Lucent Technologies be liable for any
 * special, indirect or consequential damages or any damages
 * whatsoever resulting from loss of use, data or profits, whether in
 * an action of contract, negligence or other tortuous action, arising
 * out of or in connection with the use or performance of this
 * software.
 *
 *	Container widget created by George A. Howlett
 */

#include "bltInt.h"

#ifndef NO_CONTAINER

#include "bltList.h"
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#ifndef WIN32
#include <X11/Xproto.h>
#endif

#define XDEBUG

#define SEARCH_TRIES	100	/* Maximum number of attempts to check for
				 * a given window before failing. */
#define SEARCH_INTERVAL 20	/* Number milliseconds to wait after each 
				 * attempt to find a window. */

#define SEARCH_TKWIN	(1<<0)	/* Search via Tk window pathname. */
#define SEARCH_XID	(1<<1)	/* Search via an XID 0x0000000. */
#define SEARCH_CMD	(1<<2)	/* Search via a command-line arguments. */
#define SEARCH_NAME	(1<<3)	/* Search via the application name. */
#define SEARCH_ALL	(SEARCH_TKWIN | SEARCH_XID | SEARCH_CMD | SEARCH_NAME)

#define CONTAINER_REDRAW		(1<<1)
#define CONTAINER_MAPPED		(1<<2)
#define CONTAINER_FOCUS			(1<<4)
#define CONTAINER_INIT			(1<<5)
#define CONTAINER_GEOMETRY		(1<<6)
#define CONTAINER_MOVE			(1<<7)

#define DEF_CONTAINER_BG_MONO		STD_MONO_NORMAL_BG
#define DEF_CONTAINER_BG_COLOR		STD_COLOR_NORMAL_BG
#define DEF_CONTAINER_BORDER_WIDTH	STD_BORDERWIDTH
#define DEF_CONTAINER_COMMAND		(char *)NULL
#define DEF_CONTAINER_CURSOR		(char *)NULL
#define DEF_CONTAINER_HEIGHT		"0"
#define DEF_CONTAINER_HIGHLIGHT_BG_COLOR STD_COLOR_NORMAL_BG
#define DEF_CONTAINER_HIGHLIGHT_BG_MONO	STD_MONO_NORMAL_BG
#define DEF_CONTAINER_HIGHLIGHT_COLOR	RGB_BLACK
#define DEF_CONTAINER_HIGHLIGHT_WIDTH	"2"
#define DEF_CONTAINER_RELIEF		"sunken"
#define DEF_CONTAINER_TAKE_FOCUS	"0"
#define DEF_CONTAINER_TIMEOUT		"20"
#define DEF_CONTAINER_WIDTH		"0"
#define DEF_CONTAINER_WINDOW		(char *)NULL

#if (TK_MAJOR_VERSION == 4)
#define TK_REPARENTED			0x2000
#endif

typedef struct SearchInfoRec SearchInfo;
typedef void (SearchProc) _ANSI_ARGS_((Display *display, Window window, 
       SearchInfo *infoPtr));

struct SearchInfoRec {
    SearchProc *searchProc;
    char *pattern;		/* Search pattern. */

    Window window;		/* XID of last window that matches criteria. */
    int nMatches;		/* Number of windows that match the pattern. */
    int saveNames;		/* Indicates to save the names of the window
				 * XIDs that match the search criteria. */
    Tcl_DString dString;	/* Will contain the strings of the window XIDs
				 * matching the search criteria. */
};

typedef struct {
    Tk_Window tkwin;		/* Window that embodies the widget.
                                 * NULL means that the window has been
                                 * destroyed but the data structures
                                 * haven't yet been cleaned up.*/

    Display *display;		/* Display containing widget; needed,
                                 * among other things, to release
                                 * resources after tkwin has already
                                 * gone away. */

    Tcl_Interp *interp;		/* Interpreter associated with widget. */

    Tcl_Command cmdToken;	/* Token for widget's command. */

    unsigned int flags;		/* For bit-field definitions, see above. */

    int inset;			/* Total width of borders; traversal
				 * highlight and 3-D border. Indicates the
				 * offset from outside edges to leave room
				 * for borders. */

    Tk_Cursor cursor;		/* X Cursor */

    Tk_3DBorder border;		/* 3D border surrounding the embedded
				 * window. */
    int borderWidth;		/* Width of 3D border. */
    int relief;			/* 3D border relief. */

    /*
     * Focus highlight ring
     */
    int highlightWidth;		/* Width in pixels of highlight to draw
				 * around widget when it has the focus.
				 * <= 0 means don't draw a highlight. */
    XColor *highlightBgColor;	/* Color for drawing traversal highlight
				 * area when highlight is off. */
    XColor *highlightColor;	/* Color for drawing traversal highlight. */

    GC highlightGC;		/* GC for focus highlight. */

    char *takeFocus;		/* Says whether to select this widget during
				 * tab traveral operations.  This value isn't
				 * used in C code, but for the widget's Tcl
				 * bindings. */

    int reqWidth, reqHeight;	/* Requested dimensions of the container
				 * window. */

    Window reqWindow;		/* XID of last window that matches criteria. */

    Window window;		/* X window Id of embedded window contained
				 * by the widget.  If None, no window is
				 * embedded. */
    Tk_Window embedTkWin;	/* Tk_Window that embodies window.
                                 * NULL means that the window has been
                                 * destroyed but the data structures
                                 * haven't yet been cleaned up.*/
    int x, y, width, height;

    int timeout;
} Container;


static int StringToXID _ANSI_ARGS_((ClientData clientData,
	Tcl_Interp *interp, Tk_Window tkwin, char *string, char *widgRec,
	int offset));
static char *XIDToString _ANSI_ARGS_((ClientData clientData,
	Tk_Window tkwin, char *widgRec, int offset,
	Tcl_FreeProc **freeProcPtrPtr));

static Tk_CustomOption XIDOption =
{
    StringToXID, XIDToString, (ClientData)(SEARCH_TKWIN | SEARCH_XID),
};

static Tk_CustomOption XIDNameOption =
{
    StringToXID, XIDToString, (ClientData)SEARCH_NAME,
};

static Tk_CustomOption XIDCmdOption =
{
    StringToXID, XIDToString, (ClientData)SEARCH_CMD,
};

extern Tk_CustomOption bltDistanceOption;
extern Tk_CustomOption bltPositiveCountOption;

static Tk_ConfigSpec configSpecs[] =
{
    {TK_CONFIG_BORDER, "-background", "background", "Background",
	DEF_CONTAINER_BG_MONO, Tk_Offset(Container, border),
	TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_BORDER, "-background", "background", "Background",
	DEF_CONTAINER_BG_COLOR, Tk_Offset(Container, border),
	TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *)NULL, (char *)NULL, 0, 0},
    {TK_CONFIG_SYNONYM, "-bg", "background", (char *)NULL, (char *)NULL, 0, 0},
    {TK_CONFIG_CUSTOM, "-command", "command", "Command",
	DEF_CONTAINER_WINDOW, Tk_Offset(Container, reqWindow),
	TK_CONFIG_DONT_SET_DEFAULT, &XIDCmdOption},
    {TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor",
	DEF_CONTAINER_CURSOR, Tk_Offset(Container, cursor), TK_CONFIG_NULL_OK},
    {TK_CONFIG_CUSTOM, "-borderwidth", "borderWidth", "BorderWidth",
	DEF_CONTAINER_BORDER_WIDTH, Tk_Offset(Container, borderWidth),
	TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_CUSTOM, "-height", "height", "Height",
	DEF_CONTAINER_HEIGHT, Tk_Offset(Container, reqHeight),
	TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_COLOR, "-highlightbackground", "highlightBackground",
	"HighlightBackground", DEF_CONTAINER_HIGHLIGHT_BG_COLOR, 
	Tk_Offset(Container, highlightBgColor), TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_COLOR, "-highlightbackground", "highlightBackground",
	"HighlightBackground", DEF_CONTAINER_HIGHLIGHT_BG_MONO, 
	Tk_Offset(Container, highlightBgColor), TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_COLOR, "-highlightcolor", "highlightColor", "HighlightColor",
	DEF_CONTAINER_HIGHLIGHT_COLOR, 
	Tk_Offset(Container, highlightColor), 0},
    {TK_CONFIG_PIXELS, "-highlightthickness", "highlightThickness",
	"HighlightThickness",
	DEF_CONTAINER_HIGHLIGHT_WIDTH, Tk_Offset(Container, highlightWidth),
	TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_CUSTOM, "-name", "name", "Name",
	DEF_CONTAINER_WINDOW, Tk_Offset(Container, reqWindow),
	TK_CONFIG_DONT_SET_DEFAULT, &XIDNameOption},
    {TK_CONFIG_RELIEF, "-relief", "relief", "Relief",
	DEF_CONTAINER_RELIEF, Tk_Offset(Container, relief), 0},
    {TK_CONFIG_STRING, "-takefocus", "takeFocus", "TakeFocus",
	DEF_CONTAINER_TAKE_FOCUS, Tk_Offset(Container, takeFocus),
	TK_CONFIG_NULL_OK},
    {TK_CONFIG_CUSTOM, "-timeout", "timeout", "Timeout",
	DEF_CONTAINER_TIMEOUT, Tk_Offset(Container, timeout),
	TK_CONFIG_DONT_SET_DEFAULT, &bltPositiveCountOption},
    {TK_CONFIG_CUSTOM, "-width", "width", "Width",
	DEF_CONTAINER_WIDTH, Tk_Offset(Container, reqWidth),
	TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_CUSTOM, "-window", "window", "Window",
	DEF_CONTAINER_WINDOW, Tk_Offset(Container, reqWindow),
	TK_CONFIG_DONT_SET_DEFAULT, &XIDOption},
    {TK_CONFIG_END, (char *)NULL, (char *)NULL, (char *)NULL,
	(char *)NULL, 0, 0}
};

/* Forward Declarations */
static void DestroyContainer _ANSI_ARGS_((DestroyData dataPtr));
static void ContainerEventProc _ANSI_ARGS_((ClientData clientdata,
	XEvent *eventPtr));
static void DisplayContainer _ANSI_ARGS_((ClientData clientData));
static void ContainerInstCmdDeleteProc _ANSI_ARGS_((ClientData clientdata));
static int ContainerInstCmd _ANSI_ARGS_((ClientData clientdata,
	Tcl_Interp *interp, int argc, char **argv));
static void EventuallyRedraw _ANSI_ARGS_((Container *conPtr));

#ifdef notdef
static Window CreateEmbeddedWindowProc _ANSI_ARGS_((Tk_Window tkwin,
	Window parent, ClientData instanceData));

typedef Window (Tk_ClassCreateProc) _ANSI_ARGS_((Tk_Window tkwin,
	Window parent, ClientData instanceData));
typedef void (Tk_ClassGeometryProc) _ANSI_ARGS_((ClientData instanceData));
typedef void (Tk_ClassModalProc) _ANSI_ARGS_((Tk_Window tkwin,
	XEvent *eventPtr));

/*
 * Widget class procedures used to implement platform specific widget
 * behavior.
 */
typedef struct {
    Tk_ClassCreateProc *createProc;
				/* Procedure to invoke when the
                                   platform-dependent window needs to be
                                   created. */
    Tk_ClassGeometryProc *geometryProc;
				/* Procedure to invoke when the geometry of a
				   window needs to be recalculated as a result
				   of some change in the system. */
    Tk_ClassModalProc *modalProc;
				/* Procedure to invoke after all bindings on a
				   widget have been triggered in order to
				   handle a modal loop. */
} Tk_ClassProcs;

static Tk_ClassProcs containerClassProcs = { 
    CreateEmbeddedWindowProc,	/* createProc. */
    NULL,			/* geometryProc. */
    NULL			/* modalProc. */
};
#endif

/*
 *----------------------------------------------------------------------
 *
 * NameOfId --
 *
 *	Returns a string representing the given XID.
 *
 * Results:
 *	A static string containing either the hexidecimal number or
 *	the pathname of a Tk window.
 *
 *----------------------------------------------------------------------
 */
static char *
NameOfId(display, id)
    Display *display;		/* Display containing both the container widget
				 * and the embedded window. */
    Window id;			/* XID of the embedded window. */
{
    if (id != None) {
	Tk_Window tkwin;
	static char string[20];

	/* See first if it's a window that Tk knows about. */
	tkwin = Tk_IdToWindow(display, id); 
	if (tkwin != NULL) {
	    return Tk_PathName(tkwin); 
	}
	sprintf(string, "0x%x", (unsigned int)id);
	return string;
    }
    return "";			/* Return empty string if XID is None. */
}

/*
 *----------------------------------------------------------------------
 *
 * NameSearch --
 *
 *	Traverses the entire window hierarchy, searching for windows 
 *	matching the name field in the SearchInfo structure. This 
 *	routine is recursively called for each successive level in 
 *	the window hierarchy.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	The SearchInfo structure will track the number of windows that 
 *	match the given criteria.
 *	
 *----------------------------------------------------------------------
 */
static void
NameSearch(display, window, infoPtr)
    Display *display;
    Window window;
    SearchInfo *infoPtr;
{
    Window *childArr, dummy;
    unsigned int nChildren;
    register int i;
    char *wmName;

    if (XFetchName(display, window, &wmName)) {
	/* Examine the name of the window. */
	if (Tcl_StringMatch(wmName, infoPtr->pattern)) {
	    if (infoPtr->saveNames) { /* Record names of matching windows. */
		Tcl_DStringAppendElement(&(infoPtr->dString), 
			 NameOfId(display, window));
		Tcl_DStringAppendElement(&(infoPtr->dString), wmName);
	    }
	    infoPtr->window = window;
	    infoPtr->nMatches++;
	}
	XFree(wmName);
    }
    if (XQueryTree(display, window, &dummy, &dummy, &childArr, &nChildren)) {
	for (i = 0; i < nChildren; i++) {
	    NameSearch(display, childArr[i], infoPtr);
	}
	XFree ((char *)childArr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * CmdSearch --
 *
 *	Traverses the entire window hierarchy, searching for windows 
 *	matching the command-line specified in the SearchInfo structure.  
 *	This routine is recursively called for each successive level
 *	in the window hierarchy.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	The SearchInfo structure will track the number of windows that 
 *	match the given command-line.
 *	
 *----------------------------------------------------------------------
 */
static void
CmdSearch(display, window, infoPtr)
    Display *display;
    Window window;
    SearchInfo *infoPtr;
{
    Window *childArr, dummy;
    unsigned int nChildren;
    int cmdArgc;
    char **cmdArgv;

    if (XGetCommand(display, window, &cmdArgv, &cmdArgc)) {
	char *string;

	string = Tcl_Merge(cmdArgc, cmdArgv);
	XFreeStringList(cmdArgv);
	if (Tcl_StringMatch(string, infoPtr->pattern)) {
	    if (infoPtr->saveNames) { /* Record names of matching windows. */
		Tcl_DStringAppendElement(&(infoPtr->dString), 
					 NameOfId(display, window));
		Tcl_DStringAppendElement(&(infoPtr->dString), string);
	    }
	    infoPtr->window = window;
	    infoPtr->nMatches++;
	}
	free(string);
    }
    if (XQueryTree(display, window, &dummy, &dummy, &childArr, &nChildren)) {
	register int i;

	for (i = 0; i < nChildren; i++) {
	    CmdSearch(display, childArr[i], infoPtr);
	}
	XFree ((char *)childArr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TimeoutProc --
 *
 *	Procedure called when the timer event elapses.  Used to wait
 *	between attempts checking for the designated window.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	Sets a flag, indicating the timeout occurred.
 *
 *----------------------------------------------------------------------
 */
static void
TimeoutProc(clientData)
    ClientData clientData;
{
    int *expirePtr = (int *)clientData;
    *expirePtr = TRUE;
}

/*
 *----------------------------------------------------------------------
 *
 * TestAndWaitForWindow --
 *
 *	Searches possibly multiple times, for windows matching the
 *	criteria given, using the search proc also given.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	Sets a flag, indicating the timeout occurred.
 *
 *----------------------------------------------------------------------
 */
static void
TestAndWaitForWindow(conPtr, infoPtr)
    Container *conPtr;		/* Container widget record. */
    SearchInfo *infoPtr;	/* Search criteria. */
{
    Window root;
    Tcl_TimerToken timerToken;
    int expire;
    int i;

    /* Get the root window to start the search.  */
    root = RootWindow(conPtr->display, Tk_ScreenNumber(conPtr->tkwin));
    timerToken = NULL;
    for (i = 0; i < SEARCH_TRIES; i++) {
	infoPtr->nMatches = 0;
	(*infoPtr->searchProc)(conPtr->display, root, infoPtr);
	if (infoPtr->nMatches > 0) {
	    if (timerToken != NULL) {
		Tcl_DeleteTimerHandler(timerToken);
	    }
	    return;
	}
	expire = FALSE;
	/*   
	 * If the application associated with the embedded window
	 * was just started (via "exec" or "bgexec"), the window may
	 * not exist yet.  We have to wait a little bit for the program
	 * to start up.  Create a timer event break us out of an wait 
	 * loop.  We'll wait for a given interval for the embedded window
	 * to appear.
	 */
	timerToken = Tcl_CreateTimerHandler(conPtr->timeout, TimeoutProc, 
		   (ClientData)&expire);
	while (!expire) {
	    /* Should file events be allowed? */
	    Tcl_DoOneEvent(TCL_TIMER_EVENTS | TCL_WINDOW_EVENTS | TCL_FILE_EVENTS);
	}
    }	
}

/*
 *----------------------------------------------------------------------
 *
 * StringToXID --
 *
 *	Converts a string into an X window Id.
 *
 * Results:
 *	If the string is successfully converted, TCL_OK is returned.
 *	Otherwise, TCL_ERROR is returned and an error message is left
 *	in interpreter's result field.
 *
 *---------------------------------------------------------------------- */
/*ARGSUSED*/
static int
StringToXID(clientData, interp, parent, string, widgRec, offset)
    ClientData clientData;	/* Not used. */
    Tcl_Interp *interp;		/* Interpreter to send results back to */
    Tk_Window parent;		/* Parent window */
    char *string;		/* String representation. */
    char *widgRec;		/* Widget record */
    int offset;			/* Offset to field in structure */
{
    unsigned int flags = (int)clientData;
    Container *conPtr = (Container *)widgRec;
    Window *winPtr = (Window *) (widgRec + offset);

    if ((flags & SEARCH_TKWIN) && (string[0] == '.')) {
	Tk_Window tkwin;

	tkwin = Tk_NameToWindow(interp, string, Tk_MainWindow(interp));
	if (tkwin == NULL) {
	    return TCL_ERROR;
	}
	*winPtr = Blt_GetRealWindowId(tkwin);
    } else if ((flags & SEARCH_XID) && (string[0] == '0') && 
	       (string[1] == 'x')) {
	int newId;

	if (Tcl_GetInt(interp, string, &newId) != TCL_OK) {
	    return TCL_ERROR;
	}
	*winPtr = newId;
    } else if ((string == NULL) || (string[0] == '\0')) {
	*winPtr = None;
    } else {
	SearchInfo info;

	memset(&info, 0, sizeof(info));
	if (flags & (SEARCH_NAME | SEARCH_CMD)) {
	    info.pattern = string;
	    if (flags & SEARCH_NAME) {
		info.searchProc = NameSearch;
	    } else if (flags & SEARCH_CMD) {
		info.searchProc = CmdSearch;
	    }
	    TestAndWaitForWindow(conPtr, &info);
	    if (info.nMatches > 1) {
		Tcl_AppendResult(interp, "more than one window matches \"", 
			 string, "\"", (char *)NULL);
		return TCL_ERROR;
	    }
	}
	if (info.nMatches == 0) {
	    Tcl_AppendResult(interp, "can't find window from pattern \"", 
			     string, "\"", (char *)NULL);
	    return TCL_ERROR;
	}
	*winPtr = info.window;
    }
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * XIDToString --
 *
 *	Converts the Tk window back to its string representation (i.e.
 *	its name).
 *
 * Results:
 *	The name of the window is returned.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static char *
XIDToString(clientData, parent, widgRec, offset, freeProcPtr)
    ClientData clientData;	/* Not used. */
    Tk_Window parent;		/* Not used. */
    char *widgRec;		/* Widget record */
    int offset;			/* Offset of field in record */
    Tcl_FreeProc **freeProcPtr;	/* Memory deallocation scheme to use */
{
    Container *conPtr = (Container *) widgRec;
    Window window = *(Window *) (widgRec + offset);

    return NameOfId(conPtr->display, window);
}

/*
 *----------------------------------------------------------------------
 *
 * XGeometryErrorProc --
 *
 *	Flags errors generated from XGetGeometry calls to the X server.
 *
 * Results:
 *	Always returns 0.
 *
 * Side Effects:
 *	Sets a flag, indicating an error occurred.
 *
 *----------------------------------------------------------------------
 */
/* ARGSUSED */
static int
XGeometryErrorProc(clientData, errEventPtr)
    ClientData clientData;
    XErrorEvent *errEventPtr;
{
    int *errorPtr = (int *)clientData;

    *errorPtr = TCL_ERROR;
    return 0;
}


#ifdef notdef
/*
 *----------------------------------------------------------------------
 *
 * CreateEmbeddedWindowProc --
 *
 *	This function creates a new Button control, subclasses
 *	the instance, and generates a new Window object.
 *
 * Results:
 *	Returns the newly allocated Window object, or None on failure.
 *
 * Side effects:
 *	Causes a new Button control to come into existence.
 *
 *----------------------------------------------------------------------
 */
static Window
CreateEmbeddedWindowProc(tkwin, parentWin, instanceData)
    Tk_Window tkwin;		/* Token for window. */
    Window parentWin;		/* Parent of new window. */
    ClientData instanceData;	/* Button instance data. */
{
    Container *conPtr = (Container *)instanceData;

#ifdef notdef
    if (Blt_ReparentWindow(conPtr->display, conPtr->window,
	   parentWin, conPtr->inset, conPtr->inset) != TCL_OK) {
	Tcl_AppendResult(conPtr->interp, "can't reparent window", (char *)NULL);
	return TCL_ERROR;
    }
#endif
    fprintf(stderr, "CreateEmbeddedWindowProc: p=%x,e=%x\n", parentWin, 
	    conPtr->window);
    return conPtr->window;
}
#endif

/*
 *----------------------------------------------------------------------
 *
 * GetContainerGeometry --
 *
 *	Computes the requested geometry of the container using the 
 *	size of embedded window as a reference.  
 *
 * Results:
 *	A standard Tcl result. 
 *
 * Side Effects:
 *	Sets a flag, indicating an error occurred.
 *
 *----------------------------------------------------------------------
 */
static int
GetContainerGeometry(interp, conPtr)
    Tcl_Interp *interp;
    Container *conPtr;
{
    int x, y, width, height, borderWidth, depth;
    Window root;
    Tk_ErrorHandler handler;
    int result;
    int any = -1;
    XWindowAttributes winAttrs;
    
    handler = Tk_CreateErrorHandler(conPtr->display, any, X_GetGeometry, any, 
	XGeometryErrorProc, (ClientData)&result);
    /*  */
    result = XGetGeometry(conPtr->display, conPtr->window, &root, &x, &y, 
	(unsigned int *)&width, (unsigned int *)&height,
	(unsigned int *)&borderWidth, (unsigned int *)&depth);
    Tk_DeleteErrorHandler(handler);
    XSync(conPtr->display, False);
    if (result == 0) {
	Tcl_AppendResult(interp, "can't get window geometry of \"", 
	 NameOfId(conPtr->display, conPtr->window), "\"",
	(char *)NULL);
	return TCL_ERROR;
    }
#ifdef notdef
    fprintf(stderr, "XGetGeometry: w=%d,h=%d\n", width, height);
#endif
    XSetWindowBorderWidth(conPtr->display, conPtr->window, 0);
    if (XGetWindowAttributes(conPtr->display, conPtr->window, &winAttrs)) {
	width = winAttrs.width;
	height = winAttrs.height;
#ifdef notdef
	fprintf(stderr, "XGetWindowAttributes: w=%d,h=%d\n", width, height);
#endif
    }
    if (conPtr->reqWidth > 0) {
	width = conPtr->reqWidth;
    } 
    if (conPtr->reqHeight > 0) {
	height = conPtr->reqHeight;
    } 
    conPtr->x = x;
    conPtr->y = y;
    conPtr->width = width;
    conPtr->height = height;
#ifdef notdef
    fprintf(stderr, "final: w=%d,h=%d\n", conPtr->width, conPtr->height);
#endif
    /* Add the designated inset to the requested dimensions. */
    width += 2 * conPtr->inset; 
    height += 2 * conPtr->inset;
    /* Set the requested width and height on behalf of the embedded window. */
    if ((Tk_ReqWidth(conPtr->tkwin) != width) ||
	(Tk_ReqHeight(conPtr->tkwin) != height)) {
	Tk_GeometryRequest(conPtr->tkwin, width, height);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * EventuallyRedraw --
 *
 *	Queues a request to redraw the widget at the next idle point.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Information gets redisplayed.  Right now we don't do selective
 *	redisplays:  the whole window will be redrawn.
 *
 *----------------------------------------------------------------------
 */
static void
EventuallyRedraw(conPtr)
    Container *conPtr;
{
    if ((conPtr->tkwin != NULL) && !(conPtr->flags & CONTAINER_REDRAW)) {
	conPtr->flags |= CONTAINER_REDRAW;
	Tcl_DoWhenIdle(DisplayContainer, (ClientData)conPtr);
    }
}

/*
 * --------------------------------------------------------------
 *
 * EmbeddedEventProc --
 *
 * 	This procedure is invoked by the Tk dispatcher for various
 * 	events on the encapsulated window.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	When the window gets deleted, internal structures get
 *	cleaned up.  When it gets resized or exposed, it's redisplayed.
 *
 * --------------------------------------------------------------
 */
static int
EmbeddedEventProc(clientData, eventPtr)
    ClientData clientData;	/* Information about the tab window. */
    XEvent *eventPtr;		/* Information about event. */
{
    Container *conPtr = (Container *) clientData;

    if ((conPtr->window == None) ||
	(eventPtr->xany.window != conPtr->window)) {
	return 0;
    }
    if ((eventPtr->type == DestroyNotify) ||
	(eventPtr->type == ReparentNotify)) {
	conPtr->window = None;
	EventuallyRedraw(conPtr);
    }
    return 1;
}

/*
 * --------------------------------------------------------------
 *
 * ContainerEventProc --
 *
 * 	This procedure is invoked by the Tk dispatcher for various
 * 	events on container widgets.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	When the window gets deleted, internal structures get
 *	cleaned up.  When it gets exposed, it is redisplayed.
 *
 * --------------------------------------------------------------
 */
static void
ContainerEventProc(clientData, eventPtr)
    ClientData clientData;	/* Information about window. */
    XEvent *eventPtr;		/* Information about event. */
{
    Container *conPtr = (Container *) clientData;

    switch (eventPtr->type) {
    case Expose:
	if (eventPtr->xexpose.count == 0) {
	    EventuallyRedraw(conPtr);
	}
	break;

    case FocusIn:
    case FocusOut:
	if (eventPtr->xfocus.detail != NotifyInferior) {
	    if (eventPtr->type == FocusIn) {
		conPtr->flags |= CONTAINER_FOCUS;
	    } else {
		conPtr->flags &= ~CONTAINER_FOCUS;
	    }
	    EventuallyRedraw(conPtr);
	}
	break;

    case ConfigureNotify:
	EventuallyRedraw(conPtr);
	break;

    case DestroyNotify:
	if (conPtr->tkwin != NULL) {
	    char *cmdName;

	    cmdName = Tcl_GetCommandName(conPtr->interp, conPtr->cmdToken);
#ifdef ITCL_NAMESPACES
	    Itk_SetWidgetCommand(conPtr->tkwin, (Tcl_Command) NULL);
#endif /* ITCL_NAMESPACES */
	    Tcl_DeleteCommand(conPtr->interp, cmdName);
	    conPtr->tkwin = NULL;
	}
	if (conPtr->flags & CONTAINER_REDRAW) {
	    Tcl_CancelIdleCall(DisplayContainer, (ClientData)conPtr);
	}
	Tcl_EventuallyFree((ClientData)conPtr, DestroyContainer);
	break;
    }
}

/*
 * --------------------------------------------------------------
 *
 * ToplevelEventProc --
 *
 * 	This procedure is invoked by the Tk dispatcher for various
 * 	events on the toplevel wrapper window.  We're only concern
 *	with Configure events.  We want to fool the embedded window
 *	into thinking that it's been moved.
 *
 * Results:
 *	None.
 *
 * --------------------------------------------------------------
 */
static int
ToplevelEventProc(clientData, eventPtr)
    ClientData clientData;	/* Information about the tab window. */
    XEvent *eventPtr;		/* Information about event. */
{
    Container *conPtr = (Container *) clientData;
    Window window;

    if ((conPtr->window == None) || (conPtr->tkwin == NULL) ||
	(eventPtr->type != ConfigureNotify)) {
	return 0;
    }
    window = Blt_GetRealWindowId(Blt_Toplevel(conPtr->tkwin));
    if (eventPtr->xany.window == window) {
	conPtr->flags |= CONTAINER_MOVE;
	EventuallyRedraw(conPtr);
    }
    return 0;
}

/*
 * ----------------------------------------------------------------------
 *
 * DestroyContainer --
 *
 * 	This procedure is invoked by Tcl_EventuallyFree or Tcl_Release
 * 	to clean up the internal structure of the widget at a safe
 * 	time (when no-one is using it anymore).
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	Everything associated with the widget is freed up.
 *
 * ----------------------------------------------------------------------
 */
static void
DestroyContainer(dataPtr)
    DestroyData dataPtr;	/* Pointer to the widget record. */
{
    Container *conPtr = (Container *) dataPtr;

    if (conPtr->highlightGC != NULL) {
	Tk_FreeGC(conPtr->display, conPtr->highlightGC);
    }
    if (conPtr->flags & CONTAINER_INIT) {
	Tk_DeleteGenericHandler(EmbeddedEventProc, (ClientData)conPtr);
    }
    Tk_DeleteGenericHandler(ToplevelEventProc, (ClientData)conPtr);
    Tk_FreeOptions(configSpecs, (char *)conPtr, conPtr->display, 0);
    free((char *)conPtr);
}

/*
 * ----------------------------------------------------------------------
 *
 * ConfigureContainer --
 *
 * 	This procedure is called to process an argv/argc list, plus
 *	the Tk option database, in order to configure (or reconfigure)
 *	the widget.
 *
 * Results:
 *	The return value is a standard Tcl result.  If TCL_ERROR is
 * 	returned, then interp->result contains an error message.
 *
 * Side Effects:
 *	Configuration information, such as text string, colors, font,
 *	etc. get set for conPtr; old resources get freed, if there
 *	were any.  The widget is redisplayed.
 *
 * ----------------------------------------------------------------------
 */
static int
ConfigureContainer(interp, conPtr, argc, argv, flags)
    Tcl_Interp *interp;		/* Interpreter to report errors. */
    Container *conPtr;		/* Information about widget; may or
			         * may not already have values for
			         * some fields. */
    int argc;
    char **argv;
    int flags;
{
    XGCValues gcValues;
    unsigned long gcMask;
    GC newGC;

    if (Tk_ConfigureWidget(interp, conPtr->tkwin, configSpecs, argc, argv,
	    (char *)conPtr, flags) != TCL_OK) {
	return TCL_ERROR;
    }
    if ((conPtr->reqHeight > 0) && (conPtr->reqWidth > 0)) {
	Tk_GeometryRequest(conPtr->tkwin, conPtr->reqWidth,
	    conPtr->reqHeight);
    }
    conPtr->inset = conPtr->borderWidth + conPtr->highlightWidth;
    if (Blt_ConfigModified(configSpecs, "-window", "-name", "-command", 
			   (char *)NULL)) {
#ifdef notdef
	if (conPtr->window != None) {
	    XUnmapWindow(conPtr->display, conPtr->window);
	}
#endif
	conPtr->flags &= ~CONTAINER_MAPPED;
	conPtr->window = conPtr->reqWindow;
	if (conPtr->window != None) {
#ifdef notdef
#if (TK_MAJOR_VERSION > 4)
	    Tk_Window tkwin;
	    Tk_FakeWin *fakePtr;

	    tkwin = Tk_CreateWindow(interp, conPtr->tkwin, 
		NameOfId(conPtr->display, conPtr->window), (char *)NULL);
	    if (tkwin == NULL) {
		return TCL_ERROR;
	    }
	    Tk_SetClass(tkwin, "Embedded");
	    conPtr->embedTkWin = tkwin;
	    TkSetClassProcs(tkwin, &containerClassProcs, (ClientData)conPtr); 
	    fakePtr = (Tk_FakeWin *) tkwin;
	    fakePtr->flags |= TK_MAPPED;
	    Tk_MakeWindowExist(conPtr->embedTkWin);
#endif
#endif
	    if (Tk_WindowId(conPtr->tkwin) == None) {
		Tk_MakeWindowExist(conPtr->tkwin);
	    }
	    if (GetContainerGeometry(interp, conPtr) != TCL_OK) {
		return TCL_ERROR;
	    }
	    if (Blt_ReparentWindow(conPtr->display, conPtr->window,
		    Tk_WindowId(conPtr->tkwin), conPtr->inset,
		    conPtr->inset) != TCL_OK) {
		Tcl_AppendResult(interp, "can't reparent window", (char *)NULL);
		return TCL_ERROR;
	    }
	    XSelectInput(conPtr->display, conPtr->window, StructureNotifyMask);
	    if ((conPtr->flags & CONTAINER_INIT) == 0) {
		Tk_CreateGenericHandler(EmbeddedEventProc, (ClientData)conPtr);
		conPtr->flags |= CONTAINER_INIT;
	    }
	    Tk_CreateGenericHandler(ToplevelEventProc, (ClientData)conPtr);
	}
    }
    /*
     * GC for focus highlight.
     */
    gcMask = GCForeground;
    gcValues.foreground = conPtr->highlightColor->pixel;
    newGC = Tk_GetGC(conPtr->tkwin, gcMask, &gcValues);
    if (conPtr->highlightGC != NULL) {
	Tk_FreeGC(conPtr->display, conPtr->highlightGC);
    }
    conPtr->highlightGC = newGC;

    EventuallyRedraw(conPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ContainerInstCmdDeleteProc --
 *
 *	This procedure can be called if the window was destroyed
 *	(tkwin will be NULL) and the command was deleted
 *	automatically.  In this case, we need to do nothing.
 *
 *	Otherwise this routine was called because the command was
 *	deleted.  Then we need to clean-up and destroy the widget.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	The widget is destroyed.
 *
 *----------------------------------------------------------------------
 */
static void
ContainerInstCmdDeleteProc(clientData)
    ClientData clientData;	/* Pointer to widget record for widget. */
{
    Container *conPtr = (Container *) clientData;

    if (conPtr->tkwin != NULL) {
	Tk_Window tkwin;

	tkwin = conPtr->tkwin;
	conPtr->tkwin = NULL;
	Tk_DestroyWindow(tkwin);
#ifdef ITCL_NAMESPACES
	Itk_SetWidgetCommand(tkwin, (Tcl_Command) NULL);
#endif /* ITCL_NAMESPACES */
    }
}

/*
 * ------------------------------------------------------------------------
 *
 * ContainerCmd --
 *
 * 	This procedure is invoked to process the Tcl command that
 * 	corresponds to a widget managed by this module. See the user
 * 	documentation for details on what it does.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side Effects:
 *	See the user documentation.
 *
 * -----------------------------------------------------------------------
 */
/* ARGSUSED */
static int
ContainerCmd(clientData, interp, argc, argv)
    ClientData clientData;	/* Main window associated with interpreter. */
    Tcl_Interp *interp;		/* Current interpreter. */
    int argc;			/* Number of arguments. */
    char **argv;		/* Argument strings. */
{
    Container *conPtr;
    Tk_Window tkwin;
    unsigned int mask;

    if (argc < 2) {
	Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
	    " pathName ?option value?...\"", (char *)NULL);
	return TCL_ERROR;
    }
    tkwin = Tk_MainWindow(interp);
    tkwin = Tk_CreateWindowFromPath(interp, tkwin, argv[1], (char *)NULL);
    if (tkwin == NULL) {
	return TCL_ERROR;
    }
    conPtr = (Container *) calloc(1, sizeof(Container));
    assert(conPtr);
    conPtr->tkwin = tkwin;
    conPtr->display = Tk_Display(tkwin);
    conPtr->interp = interp;
    conPtr->flags = 0;
    conPtr->timeout = SEARCH_INTERVAL;
    conPtr->borderWidth = conPtr->highlightWidth = 2;
    conPtr->relief = TK_RELIEF_SUNKEN;
    Tk_SetClass(tkwin, "Container");
#if (TK_MAJOR_VERSION > 4)
    Blt_SetWindowInstanceData(tkwin, (ClientData)conPtr);
#endif
    if (ConfigureContainer(interp, conPtr, argc - 2, argv + 2, 0) != TCL_OK) {
	Tk_DestroyWindow(conPtr->tkwin);
	return TCL_ERROR;
    }
    mask = (StructureNotifyMask | ExposureMask | FocusChangeMask);
    Tk_CreateEventHandler(tkwin, mask, ContainerEventProc, (ClientData)conPtr);
    conPtr->cmdToken = Tcl_CreateCommand(interp, argv[1], ContainerInstCmd,
	(ClientData)conPtr, ContainerInstCmdDeleteProc);
#ifdef ITCL_NAMESPACES
    Itk_SetWidgetCommand(conPtr->tkwin, conPtr->cmdToken);
#endif
    Tk_MakeWindowExist(tkwin);
    Tcl_SetResult(interp, Tk_PathName(conPtr->tkwin), TCL_STATIC);
    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------
 *
 * DisplayContainer --
 *
 * 	This procedure is invoked to display the widget.
 *
 * Results:
 *	None.
 *
 * Side effects:
 * 	The widget is redisplayed.
 *
 * ----------------------------------------------------------------------
 */
static void
DisplayContainer(clientData)
    ClientData clientData;	/* Information about widget. */
{
    Container *conPtr = (Container *) clientData;
    Drawable drawable;
    int width, height;

    conPtr->flags &= ~CONTAINER_REDRAW;
    if (conPtr->tkwin == NULL) {
	return;			/* Window has been destroyed. */
    }
    if (!Tk_IsMapped(conPtr->tkwin)) {
	return;
    }
    drawable = Tk_WindowId(conPtr->tkwin);
    if (conPtr->window != None) {
	if (conPtr->flags & CONTAINER_MOVE) {
	    XMoveWindow(conPtr->display, conPtr->window,
			conPtr->inset + 1, conPtr->inset + 1);
	    XMoveWindow(conPtr->display, conPtr->window,
			conPtr->inset, conPtr->inset);
	    conPtr->flags &= ~CONTAINER_MOVE;
	}
	width = Tk_Width(conPtr->tkwin) - (2 * conPtr->inset);
	height = Tk_Height(conPtr->tkwin) - (2 * conPtr->inset);
	if ((conPtr->x != conPtr->inset) || (conPtr->y != conPtr->inset) ||
	    (conPtr->width != width) || (conPtr->height != height)) {
	    XMoveResizeWindow(conPtr->display, conPtr->window,
		conPtr->inset, conPtr->inset, width, height);
	    conPtr->width = width, conPtr->height = height;
	    conPtr->x = conPtr->y = conPtr->inset;
	}
	if (!(conPtr->flags & CONTAINER_MAPPED)) {
	    XMapWindow(conPtr->display, conPtr->window);
	    conPtr->flags |= CONTAINER_MAPPED;
	}
	if (conPtr->borderWidth > 0) {
	    Tk_Draw3DRectangle(conPtr->tkwin, drawable, conPtr->border,
		conPtr->highlightWidth, conPtr->highlightWidth,
		Tk_Width(conPtr->tkwin) - 2 * conPtr->highlightWidth,
		Tk_Height(conPtr->tkwin) - 2 * conPtr->highlightWidth,
		conPtr->borderWidth, conPtr->relief);
	}
    } else {
	Tk_Fill3DRectangle(conPtr->tkwin, drawable, conPtr->border,
	    conPtr->highlightWidth, conPtr->highlightWidth,
	    Tk_Width(conPtr->tkwin) - 2 * conPtr->highlightWidth,
	    Tk_Height(conPtr->tkwin) - 2 * conPtr->highlightWidth,
	    conPtr->borderWidth, conPtr->relief);
    }

    /* Draw focus highlight ring. */
    if (conPtr->highlightWidth > 0) {
	XColor *color;
	GC gc;

	color = (conPtr->flags & CONTAINER_FOCUS)
	    ? conPtr->highlightColor : conPtr->highlightBgColor;
	gc = Tk_GCForColor(color, drawable);
	Tk_DrawFocusHighlight(conPtr->tkwin, gc, conPtr->highlightWidth,
	    drawable);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * SendOp --
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
SendOp(conPtr, interp, argc, argv)
    Container *conPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{

    if (conPtr->window != None) {
	XEvent event;
	char *p;
	KeySym symbol;
	int xid;
	Window window;

	if (Tcl_GetInt(interp, argv[2], &xid) != TCL_OK) {
	    return TCL_ERROR;
	}
	window = (Window)xid;
	event.xkey.type = KeyPress;
	event.xkey.serial = 0;
	event.xkey.display = conPtr->display;
	event.xkey.window = event.xkey.subwindow = window;
	event.xkey.time = CurrentTime;
	event.xkey.x = event.xkey.x = 100;
	event.xkey.root = 
	    RootWindow(conPtr->display, Tk_ScreenNumber(conPtr->tkwin));	
	event.xkey.x_root = Tk_X(conPtr->tkwin) + conPtr->inset;
	event.xkey.x_root = Tk_Y(conPtr->tkwin) + conPtr->inset;
	event.xkey.state = 0;
	event.xkey.same_screen = TRUE;
	
	for (p = argv[3]; *p != '\0'; p++) {
	    if (*p == '\r') {
		symbol = XStringToKeysym("Return");
	    } else if (*p == ' ') {
		symbol = XStringToKeysym("space");
	    } else {
		char save;

		save = *(p+1);
		*(p+1) = '\0';
		symbol = XStringToKeysym(p);
		*(p+1) = save;
	    }
	    event.xkey.keycode = XKeysymToKeycode(conPtr->display, symbol);
	    event.xkey.type = KeyPress;
	    if (!XSendEvent(conPtr->display, window, False, KeyPress, &event)) {
		fprintf(stderr, "send press event failed\n");
	    }
	    event.xkey.type = KeyRelease;
	    if (!XSendEvent(conPtr->display, window, False, KeyRelease, &event)) {
		fprintf(stderr, "send release event failed\n");
	    }
	}
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * FindOp --
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
FindOp(conPtr, interp, argc, argv)
    Container *conPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    Window root;
    SearchInfo info;

    memset(&info, 0, sizeof(info));
    info.pattern = argv[3];
    Tcl_DStringInit(&(info.dString));
    info.saveNames = TRUE;	/* Indicates to record all matching XIDs. */
    if (strcmp(argv[2], "-name") == 0) {
	info.searchProc = NameSearch;
    } else if (strcmp(argv[2], "-command") == 0) {
	info.searchProc = CmdSearch;
    } else {
	Tcl_AppendResult(interp, "missing \"-name\" or \"-command\" switch",
			 (char *)NULL);
	return TCL_ERROR;
    }
    root = RootWindow(conPtr->display, Tk_ScreenNumber(conPtr->tkwin));
    (*info.searchProc)(conPtr->display, root, &info);
    Tcl_DStringResult(interp, &info.dString);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * CgetOp --
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
CgetOp(conPtr, interp, argc, argv)
    Container *conPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    return Tk_ConfigureValue(interp, conPtr->tkwin, configSpecs,
	(char *)conPtr, argv[2], 0);
}

/*
 *----------------------------------------------------------------------
 *
 * ConfigureOp --
 *
 * 	This procedure is called to process an argv/argc list, plus
 *	the Tk option database, in order to configure (or reconfigure)
 *	the widget.
 *
 * Results:
 *	A standard Tcl result.  If TCL_ERROR is returned, then
 *	interp->result contains an error message.
 *
 * Side Effects:
 *	Configuration information, such as text string, colors, font,
 *	etc. get set for conPtr; old resources get freed, if there
 *	were any.  The widget is redisplayed.
 *
 *----------------------------------------------------------------------
 */
static int
ConfigureOp(conPtr, interp, argc, argv)
    Container *conPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    if (argc == 2) {
	return Tk_ConfigureInfo(interp, conPtr->tkwin, configSpecs,
	    (char *)conPtr, (char *)NULL, 0);
    } else if (argc == 3) {
	return Tk_ConfigureInfo(interp, conPtr->tkwin, configSpecs,
	    (char *)conPtr, argv[2], 0);
    }
    if (ConfigureContainer(interp, conPtr, argc - 2, argv + 2,
	    TK_CONFIG_ARGV_ONLY) != TCL_OK) {
	return TCL_ERROR;
    }
    EventuallyRedraw(conPtr);
    return TCL_OK;
}

/*
 * --------------------------------------------------------------
 *
 * ContainerCmd --
 *
 * 	This procedure is invoked to process the "container" command.
 *	See the user documentation for details on what it does.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 * --------------------------------------------------------------
 */
static Blt_OpSpec operSpecs[] =
{
    {"cget", 2, (Blt_OpProc)CgetOp, 3, 3, "option",},
    {"configure", 2, (Blt_OpProc)ConfigureOp, 2, 0, "?option value?...",},
    {"find", 1, (Blt_OpProc)FindOp, 3, 4, "?-command|-name? pattern",},
    {"send", 1, (Blt_OpProc)SendOp, 4, 4, "window string",},
};

static int numSpecs = sizeof(operSpecs) / sizeof(Blt_OpSpec);

static int
ContainerInstCmd(clientData, interp, argc, argv)
    ClientData clientData;	/* Information about the widget. */
    Tcl_Interp *interp;		/* Interpreter to report errors back to. */
    int argc;			/* Number of arguments. */
    char **argv;		/* Vector of argument strings. */
{
    Blt_OpProc opProc;
    Container *conPtr = (Container *) clientData;
    int result;

    opProc = Blt_GetOperation(interp, numSpecs, operSpecs,
	BLT_OPER_ARG1, argc, argv, 0);
    if (opProc == NULL) {
	return TCL_ERROR;
    }
    Tcl_Preserve((ClientData)conPtr);
    result = (*opProc) (conPtr, interp, argc, argv);
    Tcl_Release((ClientData)conPtr);
    return result;
}

int
Blt_ContainerInit(interp)
    Tcl_Interp *interp;
{
    static Blt_CmdSpec cmdSpec =
    {
	"container", ContainerCmd,
    };
    if (Blt_InitCmd(interp, "blt", &cmdSpec) == NULL) {
	return TCL_ERROR;
    }
    return TCL_OK;
}

#endif /* NO_CONTAINER */
