/* 
 * tkTableWin.c --
 *
 *	This module implements embedded windows for table widgets.
 *	Much of this code is adapted from tkGrid.c and tkTextWind.c.
 *
 * Copyright (c) 1998-2002 Jeffrey Hobbs
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * RCS: @(#) $Id: tkTableWin.c,v 1.10 2004/02/08 03:09:47 cerney Exp $
 */

#include "tkVMacro.h"
#include "tkTable.h"

static int	StickyParseProc _ANSI_ARGS_((ClientData clientData,
			Tcl_Interp *interp, Tk_Window tkwin,
			Arg value, char *widgRec, int offset));
Arg		StickyPrintProc _ANSI_ARGS_((ClientData clientData,
			Tk_Window tkwin, char *widgRec, int offset,
			Tcl_FreeProc **freeProcPtr));

static void	EmbWinLostSlaveProc _ANSI_ARGS_((ClientData clientData,
						Tk_Window tkwin));
static void	EmbWinRequestProc _ANSI_ARGS_((ClientData clientData,
					       Tk_Window tkwin));

static void	EmbWinCleanup _ANSI_ARGS_((Table *tablePtr,
					   TableEmbWindow *ewPtr));
static int	EmbWinConfigure _ANSI_ARGS_((Table *tablePtr,
					     TableEmbWindow *ewPtr,
					     int objc, Tcl_Obj *CONST objv[]));
static void	EmbWinStructureProc _ANSI_ARGS_((ClientData clientData,
						 XEvent *eventPtr));
static void	EmbWinUnmapNow _ANSI_ARGS_((Tk_Window ewTkwin,
					    Tk_Window tkwin));

static Tk_GeomMgr tableGeomType = {
    "table",			/* name */
    EmbWinRequestProc,		/* requestProc */
    EmbWinLostSlaveProc,	/* lostSlaveProc */
};

/* windows subcommands */
static CONST char *winCmdNames[] = {
    "cget", "configure", "delete", "move", "names",          NULL
};
enum winCommand {
    WIN_CGET, WIN_CONFIGURE, WIN_DELETE, WIN_MOVE, WIN_NAMES
};

/* Flag values for "sticky"ness  The 16 combinations subsume the packer's
 * notion of anchor and fill.
 *
 * STICK_NORTH  	This window sticks to the top of its cavity.
 * STICK_EAST		This window sticks to the right edge of its cavity.
 * STICK_SOUTH		This window sticks to the bottom of its cavity.
 * STICK_WEST		This window sticks to the left edge of its cavity.
 */

#define STICK_NORTH	(1<<0)
#define STICK_EAST	(1<<1)
#define STICK_SOUTH	(1<<2)
#define STICK_WEST	(1<<3)

/*
 * The default specification for configuring embedded windows
 * Done like this to make the command line parsing easy
 */

static Tk_CustomOption stickyOption	= { StickyParseProc, StickyPrintProc,
					    (ClientData) NULL };
static Tk_CustomOption tagBdOpt		= { TableOptionBdSet, TableOptionBdGet,
					    (ClientData) BD_TABLE_WIN };

static Tk_ConfigSpec winConfigSpecs[] = {
  {TK_CONFIG_BORDER, "-background", "background", "Background", NULL,
   Tk_Offset(TableEmbWindow, bg),
   TK_CONFIG_DONT_SET_DEFAULT|TK_CONFIG_NULL_OK },
  {TK_CONFIG_SYNONYM, "-bd", "borderWidth",          NULL,          NULL, 0, 0},
  {TK_CONFIG_SYNONYM, "-bg", "background",          NULL,          NULL, 0, 0},
  {TK_CONFIG_CUSTOM, "-borderwidth", "borderWidth", "BorderWidth", "",
   0 /* no offset */,
   TK_CONFIG_DONT_SET_DEFAULT|TK_CONFIG_NULL_OK, &tagBdOpt },
  {TK_CONFIG_STRING, "-create",          NULL,          NULL,          NULL,
   Tk_Offset(TableEmbWindow, create),
   TK_CONFIG_DONT_SET_DEFAULT|TK_CONFIG_NULL_OK },
  {TK_CONFIG_PIXELS, "-padx",          NULL,          NULL,          NULL,
   Tk_Offset(TableEmbWindow, padX), TK_CONFIG_DONT_SET_DEFAULT },
  {TK_CONFIG_PIXELS, "-pady",          NULL,          NULL,          NULL,
   Tk_Offset(TableEmbWindow, padY), TK_CONFIG_DONT_SET_DEFAULT },
  {TK_CONFIG_CUSTOM, "-sticky",          NULL,          NULL,          NULL,
   Tk_Offset(TableEmbWindow, sticky), TK_CONFIG_DONT_SET_DEFAULT,
   &stickyOption},
  {TK_CONFIG_RELIEF, "-relief", "relief", "Relief", NULL,
   Tk_Offset(TableEmbWindow, relief), 0 },
  {TK_CONFIG_WINDOW, "-window",          NULL,          NULL,          NULL,
   Tk_Offset(TableEmbWindow, tkwin),
   TK_CONFIG_DONT_SET_DEFAULT|TK_CONFIG_NULL_OK },
  {TK_CONFIG_END,          NULL,          NULL,          NULL,
            NULL, 0, 0 }
};

/*
 *----------------------------------------------------------------------
 *
 * StickyPrintProc --
 *	Converts the internal boolean combination of "sticky" bits onto
 *	a TCL string element containing zero or more of n, s, e, or w.
 *
 * Results:
 *	A string is placed into the "result" pointer.
 *
 * Side effects:
 *	none.
 *
 *----------------------------------------------------------------------
 */
Arg
StickyPrintProc(clientData, tkwin, widgRec, offset, freeProcPtr)
    ClientData clientData;		/* Ignored. */
    Tk_Window tkwin;			/* Window for text widget. */
    char *widgRec;			/* Pointer to TkTextEmbWindow
					 * structure. */
    int offset;				/* Ignored. */
    Tcl_FreeProc **freeProcPtr;		/* Pointer to variable to fill in with
					 * information about how to reclaim
					 * storage for return string. */
{
    int flags = ((TableEmbWindow *) widgRec)->sticky;
    int count = 0;
    char *result = (char *) ckalloc(5*sizeof(char));

    if (flags&STICK_NORTH) result[count++] = 'n';
    if (flags&STICK_EAST)  result[count++] = 'e';
    if (flags&STICK_SOUTH) result[count++] = 's';
    if (flags&STICK_WEST)  result[count++] = 'w';

    *freeProcPtr = TCL_DYNAMIC;
    result[count] = '\0';
    return LangStringArg(result);
}

/*
 *----------------------------------------------------------------------
 *
 * StringParseProc --
 *	Converts an ascii string representing a widgets stickyness
 *	into the boolean result.
 *
 * Results:
 *	The boolean combination of the "sticky" bits is retuned.  If an
 *	error occurs, such as an invalid character, -1 is returned instead.
 *
 * Side effects:
 *	none
 *
 *----------------------------------------------------------------------
 */
static int
StickyParseProc(clientData, interp, tkwin, value, widgRec, offset)
    ClientData clientData;		/* Not used.*/
    Tcl_Interp *interp;			/* Used for reporting errors. */
    Tk_Window tkwin;			/* Window for text widget. */
    Arg  value;				/* Value of option. */
    char *widgRec;			/* Pointer to TkTextEmbWindow
					 * structure. */
    int offset;				/* Offset into item (ignored). */
{
    register TableEmbWindow *ewPtr = (TableEmbWindow *) widgRec;
    int sticky = 0;
    char c;
    char * valuestring = Tcl_GetString(value);

    while ((c = *valuestring++) != '\0') {
	switch (c) {
	case 'n': case 'N': sticky |= STICK_NORTH; break;
	case 'e': case 'E': sticky |= STICK_EAST;  break;
	case 's': case 'S': sticky |= STICK_SOUTH; break;
	case 'w': case 'W': sticky |= STICK_WEST;  break;
	case ' ': case ',': case '\t': case '\r': case '\n': break;
	default:
	    Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
				   "bad sticky value \"", --valuestring,
				   "\": must contain n, s, e or w",
				            NULL);
	    return TCL_ERROR;
	}
    }
    ewPtr->sticky = sticky;
    return TCL_OK;
}

/*
 * ckallocs space for a new embedded window structure and clears the structure
 * returns the pointer to the new structure
 */
static TableEmbWindow *
TableNewEmbWindow(Table *tablePtr)
{
    TableEmbWindow *ewPtr = (TableEmbWindow *) ckalloc(sizeof(TableEmbWindow));
    memset((VOID *) ewPtr, 0, sizeof(TableEmbWindow));

    /*
     * Set the values that aren't 0/NULL by default
     */
    ewPtr->tablePtr	= tablePtr;
    ewPtr->relief	= -1;
    ewPtr->padX		= -1;
    ewPtr->padY		= -1;

    return ewPtr;
}

/* 
 *----------------------------------------------------------------------
 *
 * EmbWinCleanup --
 *	Releases resources used by an embedded window before it is freed up.
 *
 * Results:
 *	Window will no longer be valid.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */
static void
EmbWinCleanup(Table *tablePtr, TableEmbWindow *ewPtr)
{
    Tk_FreeOptions(winConfigSpecs, (char *) ewPtr, tablePtr->display, 0);
}

/*
 *--------------------------------------------------------------
 *
 * EmbWinDisplay --
 *
 *	This procedure is invoked by TableDisplay for
 *	mapping windows into cells.
 *
 * Results:
 *	Displays or moves window on table screen.
 *
 * Side effects:
 *	None.
 *
 *--------------------------------------------------------------
 */
void
EmbWinDisplay(Table *tablePtr, Drawable window, TableEmbWindow *ewPtr,
	      TableTag *tagPtr, int x, int y, int width, int height)
{
    Tk_Window tkwin = tablePtr->tkwin;
    Tk_Window ewTkwin = ewPtr->tkwin;
    int diffx=0;	/* Cavity width - slave width. */
    int diffy=0;	/* Cavity hight - slave height. */
    int sticky = ewPtr->sticky;
    int padx, pady;

    if (ewPtr->bg)		tagPtr->bg	= ewPtr->bg;
    if (ewPtr->relief != -1)	tagPtr->relief	= ewPtr->relief;
    if (ewPtr->borders) {
	tagPtr->borderStr	= ewPtr->borderStr;
	tagPtr->borders		= ewPtr->borders;
	tagPtr->bd[0]		= ewPtr->bd[0];
	tagPtr->bd[1]		= ewPtr->bd[1];
	tagPtr->bd[2]		= ewPtr->bd[2];
	tagPtr->bd[3]		= ewPtr->bd[3];
    }

    padx = (ewPtr->padX < 0) ? tablePtr->padX : ewPtr->padX;
    pady = (ewPtr->padY < 0) ? tablePtr->padY : ewPtr->padY;

    x		+= padx;
    width	-= padx*2;
    y		+= pady;
    height	-= pady*2;

    if (width > Tk_ReqWidth(ewPtr->tkwin)) {
	diffx = width - Tk_ReqWidth(ewPtr->tkwin);
	width = Tk_ReqWidth(ewPtr->tkwin);
    }
    if (height > Tk_ReqHeight(ewPtr->tkwin)) {
	diffy = height - Tk_ReqHeight(ewPtr->tkwin);
	height = Tk_ReqHeight(ewPtr->tkwin);
    }
    if (sticky&STICK_EAST && sticky&STICK_WEST) {
	width += diffx;
    }
    if (sticky&STICK_NORTH && sticky&STICK_SOUTH) {
	height += diffy;
    }
    if (!(sticky&STICK_WEST)) {
	x += (sticky&STICK_EAST) ? diffx : diffx/2;
    }
    if (!(sticky&STICK_NORTH)) {
	y += (sticky&STICK_SOUTH) ? diffy : diffy/2;
    }

    /*
     * If we fall below a specific minimum width/height requirement,
     * we just unmap the window
     */
    if (width < 4 || height < 4) {
	if (ewPtr->displayed) {
	    EmbWinUnmapNow(ewTkwin, tkwin);
	}
	return;
    }

    if (tkwin == Tk_Parent(ewTkwin)) {
	if ((x != Tk_X(ewTkwin)) || (y != Tk_Y(ewTkwin))
	    || (width != Tk_Width(ewTkwin))
	    || (height != Tk_Height(ewTkwin))) {
	    Tk_MoveResizeWindow(ewTkwin, x, y, width, height);
	}
	Tk_MapWindow(ewTkwin);
    } else {
	Tk_MaintainGeometry(ewTkwin, tkwin, x, y, width, height);
    }
    ewPtr->displayed = 1;
}

/*
 *--------------------------------------------------------------
 *
 * EmbWinUnmapNow --
 *	Handles unmapping the window depending on parent.
 *	tkwin should be tablePtr->tkwin.
 *	ewTkwin should be ewPtr->tkwin.
 *
 * Results:
 *	Removes the window.
 *
 * Side effects:
 *	None.
 *
 *--------------------------------------------------------------
 */
static void
EmbWinUnmapNow(Tk_Window ewTkwin, Tk_Window tkwin)
{
    if (tkwin != Tk_Parent(ewTkwin)) {
	Tk_UnmaintainGeometry(ewTkwin, tkwin);
    }
    Tk_UnmapWindow(ewTkwin);
}

/*
 *--------------------------------------------------------------
 *
 * EmbWinUnmap --
 *	This procedure is invoked by TableAdjustParams for
 *	unmapping windows managed moved offscreen.
 *	rlo, ... should be in real coords.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Unmaps embedded windows.
 *
 *--------------------------------------------------------------
 */
void
EmbWinUnmap(Table *tablePtr, int rlo, int rhi, int clo, int chi)
{
    register TableEmbWindow *ewPtr;
    Tcl_HashEntry *entryPtr;
    int row, col, trow, tcol;
    char buf[INDEX_BUFSIZE];

    /*
     * Transform numbers from real to user user coords
     */
    rlo += tablePtr->rowOffset;
    rhi += tablePtr->rowOffset;
    clo += tablePtr->colOffset;
    chi += tablePtr->colOffset;
    for (row = rlo; row <= rhi; row++) {
	for (col = clo; col <= chi; col++) {
	    TableTrueCell(tablePtr, row, col, &trow, &tcol);
	    TableMakeArrayIndex(trow, tcol, buf);
	    entryPtr = Tcl_FindHashEntry(tablePtr->winTable, buf);
	    if (entryPtr != NULL) {
		ewPtr = (TableEmbWindow *) Tcl_GetHashValue(entryPtr);
		if (ewPtr->displayed) {
		    ewPtr->displayed = 0;
		    if (ewPtr->tkwin != NULL && tablePtr->tkwin != NULL) {
			EmbWinUnmapNow(ewPtr->tkwin, tablePtr->tkwin);
		    }
		}
	    }
	}
    }
}

/*
 *--------------------------------------------------------------
 *
 * EmbWinRequestProc --
 *	This procedure is invoked by Tk_GeometryRequest for
 *	windows managed by the Table.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Arranges for tkwin, and all its managed siblings, to
 *	be re-arranged at the next idle point.
 *
 *--------------------------------------------------------------
 */
static void
EmbWinRequestProc(clientData, tkwin)
    ClientData clientData;	/* Table's information about
				 * window that got new preferred
				 * geometry.  */
    Tk_Window tkwin;		/* Other Tk-related information
				 * about the window. */
{
    register TableEmbWindow *ewPtr = (TableEmbWindow *) clientData;

    /*
     * Resize depends on the sticky
     */
    if (ewPtr->displayed && ewPtr->hPtr != NULL) {
	Table *tablePtr = ewPtr->tablePtr;
	int row, col, x, y, width, height;

	TableParseArrayIndex(&row, &col,
			     Tcl_GetHashKey(tablePtr->winTable, ewPtr->hPtr));
	if (TableCellVCoords(tablePtr, row-tablePtr->rowOffset,
			     col-tablePtr->colOffset, &x, &y, &width, &height,
			     0)) {
	    TableInvalidate(tablePtr, x, y, width, height, 0);
	}
    }
}

static void
EmbWinRemove(TableEmbWindow *ewPtr)
{
    Table *tablePtr = ewPtr->tablePtr;

    if (ewPtr->tkwin != NULL) {
	Tk_DeleteEventHandler(ewPtr->tkwin, StructureNotifyMask,
			      EmbWinStructureProc, (ClientData) ewPtr);
	ewPtr->tkwin = NULL;
    }
    ewPtr->displayed = 0;
    if (tablePtr->tkwin != NULL) {
	int row, col, x, y, width, height;

	TableParseArrayIndex(&row, &col,
			     Tcl_GetHashKey(tablePtr->winTable, ewPtr->hPtr));
	/* this will cause windows removed from the table to actually
	 * cause the associated embdedded window hash data to be removed */
	Tcl_DeleteHashEntry(ewPtr->hPtr);
	if (TableCellVCoords(tablePtr, row-tablePtr->rowOffset,
			     col-tablePtr->colOffset, &x, &y, &width, &height,
			     0))
	    TableInvalidate(tablePtr, x, y, width, height, 1);
    }
    /* this will cause windows removed from the table to actually
     * cause the associated embdedded window hash data to be removed */
    EmbWinCleanup(tablePtr, ewPtr);
    ckfree((char *) ewPtr);
}

/*
 *--------------------------------------------------------------
 *
 * EmbWinLostSlaveProc --
 *	This procedure is invoked by Tk whenever some other geometry
 *	claims control over a slave that used to be managed by us.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Forgets all table-related information about the slave.
 *
 *--------------------------------------------------------------
 */

static void
EmbWinLostSlaveProc(clientData, tkwin)
    ClientData clientData;	/* Table structure for slave window that
				 * was stolen away. */
    Tk_Window tkwin;		/* Tk's handle for the slave window. */
{
    register TableEmbWindow *ewPtr = (TableEmbWindow *) clientData;

#if 0
    Tcl_CancelIdleCall(EmbWinDelayedUnmap, (ClientData) ewPtr);
#endif
    EmbWinUnmapNow(tkwin, ewPtr->tablePtr->tkwin);
    EmbWinRemove(ewPtr);
}

/*
 *--------------------------------------------------------------
 *
 * EmbWinStructureProc --
 *	This procedure is invoked by the Tk event loop whenever
 *	StructureNotify events occur for a window that's embedded
 *	in a table widget.  This procedure's only purpose is to
 *	clean up when windows are deleted.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The window is disassociated from the window segment, and
 *	the portion of the table is redisplayed.
 *
 *--------------------------------------------------------------
 */
static void
EmbWinStructureProc(clientData, eventPtr)
    ClientData clientData;	/* Pointer to record describing window item. */
    XEvent *eventPtr;		/* Describes what just happened. */
{
    register TableEmbWindow *ewPtr = (TableEmbWindow *) clientData;

    if (eventPtr->type != DestroyNotify) {
	return;
    }

    EmbWinRemove(ewPtr);
}

/*
 *--------------------------------------------------------------
 *
 * EmbWinDelete --
 *	This procedure is invoked by ... whenever
 *	an embedded window is being deleted.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The embedded window is deleted, if it exists, and any resources
 *	associated with it are released.
 *
 *--------------------------------------------------------------
 */
void
EmbWinDelete(register Table *tablePtr, TableEmbWindow *ewPtr)
{
    Tcl_HashEntry *entryPtr = ewPtr->hPtr;

    if (ewPtr->tkwin != NULL) {
	Tk_Window tkwin = ewPtr->tkwin;
	/*
	 * Delete the event handler for the window before destroying
	 * the window, so that EmbWinStructureProc doesn't get called
	 * (we'll already do everything that it would have done, and
	 * it will just get confused).
	 */

	ewPtr->tkwin = NULL;
	Tk_DeleteEventHandler(tkwin, StructureNotifyMask,
			      EmbWinStructureProc, (ClientData) ewPtr);
	Tk_DestroyWindow(tkwin);
    }
    if (tablePtr->tkwin != NULL && entryPtr != NULL) {
	int row, col, x, y, width, height;
	TableParseArrayIndex(&row, &col,
			     Tcl_GetHashKey(tablePtr->winTable, entryPtr));
	Tcl_DeleteHashEntry(entryPtr);

	if (TableCellVCoords(tablePtr, row-tablePtr->rowOffset,
			     col-tablePtr->colOffset,
			     &x, &y, &width, &height, 0))
	    TableInvalidate(tablePtr, x, y, width, height, 0);
    }
#if 0
    Tcl_CancelIdleCall(EmbWinDelayedUnmap, (ClientData) ewPtr);
#endif
    EmbWinCleanup(tablePtr, ewPtr);
    ckfree((char *) ewPtr);
}

/*
 *--------------------------------------------------------------
 *
 * EmbWinConfigure --
 *	This procedure is called to handle configuration options
 *	for an embedded window.
 *
 * Results:
 *	The return value is a standard Tcl result.  If TCL_ERROR is
 *	returned, then the interp's result contains an error message..
 *
 * Side effects:
 *	Configuration information for the embedded window changes,
 *	such as alignment, stretching, or name of the embedded
 *	window.
 *
 *--------------------------------------------------------------
 */
static int
EmbWinConfigure(tablePtr, ewPtr, objc, objv)
     Table *tablePtr;		/* Information about table widget that
				 * contains embedded window. */
     TableEmbWindow *ewPtr;	/* Embedded window to be configured. */
     int objc;			/* Number of objs in objv. */
     Tcl_Obj *CONST objv[];	/* Obj type options. */
{
    Tcl_Interp *interp = tablePtr->interp;
    Tk_Window oldWindow;
    int i, result;
    Arg *args;

    oldWindow = ewPtr->tkwin;

    /* Stringify */
    args = (Arg *) ckalloc((objc + 1) * sizeof(Arg));
    for (i = 0; i < objc; i++)
	args[i] = LangStringArg(Tcl_GetString(objv[i]));
    args[i] = NULL;
    result = Tk_ConfigureWidget(interp, tablePtr->tkwin,
				winConfigSpecs, objc, args, (char *) ewPtr,
				TK_CONFIG_ARGV_ONLY);
    ckfree((char *) args);
    if (result != TCL_OK) {
	return TCL_ERROR;
    }

    if (oldWindow != ewPtr->tkwin) {
	ewPtr->displayed = 0;
	if (oldWindow != NULL) {
	    Tk_DeleteEventHandler(oldWindow, StructureNotifyMask,
				  EmbWinStructureProc, (ClientData) ewPtr);
	    Tk_ManageGeometry(oldWindow, (Tk_GeomMgr *) NULL,
			      (ClientData) NULL);
	    EmbWinUnmapNow(oldWindow, tablePtr->tkwin);
	}
	if (ewPtr->tkwin != NULL) {
	    Tk_Window ancestor, parent;

	    /*
	     * Make sure that the table is either the parent of the
	     * embedded window or a descendant of that parent.  Also,
	     * don't allow a top-level window to be managed inside
	     * a table.
	     */

	    parent = Tk_Parent(ewPtr->tkwin);
	    for (ancestor = tablePtr->tkwin; ;
		 ancestor = Tk_Parent(ancestor)) {
		if (ancestor == parent) {
		    break;
		}
		if (Tk_IsTopLevel(ancestor)) {
		badMaster:
		    Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
					   "can't embed ",
					   Tk_PathName(ewPtr->tkwin), " in ",
					   Tk_PathName(tablePtr->tkwin),
					            NULL);
		    ewPtr->tkwin = NULL;
		    return TCL_ERROR;
		}
	    }
	    if (Tk_IsTopLevel(ewPtr->tkwin) ||
		(ewPtr->tkwin == tablePtr->tkwin)) {
		goto badMaster;
	    }

	    /*
	     * Take over geometry management for the window, plus create
	     * an event handler to find out when it is deleted.
	     */

	    Tk_ManageGeometry(ewPtr->tkwin, &tableGeomType, (ClientData)ewPtr);
	    Tk_CreateEventHandler(ewPtr->tkwin, StructureNotifyMask,
				  EmbWinStructureProc, (ClientData) ewPtr);
	}
    }
    return TCL_OK;
}

/*
 *--------------------------------------------------------------
 *
 * Table_WinMove --
 *	This procedure is invoked by ... whenever
 *	an embedded window is being moved.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	If an embedded window is in the dest cell, it is deleted.
 *
 *--------------------------------------------------------------
 */
int
Table_WinMove(register Table *tablePtr, char *CONST srcPtr,
	   char *CONST destPtr, int flags)
{
    int srow, scol, row, col, new;
    Tcl_HashEntry *entryPtr;
    TableEmbWindow *ewPtr;

    if (TableGetIndex(tablePtr, srcPtr, &srow, &scol) != TCL_OK ||
	TableGetIndex(tablePtr, destPtr, &row, &col) != TCL_OK) {
	return TCL_ERROR;
    }
    entryPtr = Tcl_FindHashEntry(tablePtr->winTable, srcPtr);
    if (entryPtr == NULL) {
	if (flags & INV_NO_ERR_MSG) {
	    return TCL_OK;
	} else {
	    Tcl_AppendStringsToObj(Tcl_GetObjResult(tablePtr->interp),
		    "no window at index \"", srcPtr, "\"",          NULL);
	    return TCL_ERROR;
	}
    }
    /* avoid moving it to the same location */
    if (srow == row && scol == col) {
	return TCL_OK;
    }
    /* get the window pointer */
    ewPtr = (TableEmbWindow *) Tcl_GetHashValue(entryPtr);
    /* and free the old hash table entry */
    Tcl_DeleteHashEntry(entryPtr);

    entryPtr = Tcl_CreateHashEntry(tablePtr->winTable, destPtr, &new);
    if (!new) {
	/* window already there - just delete it */
	TableEmbWindow *ewPtrDel;
	ewPtrDel = (TableEmbWindow *) Tcl_GetHashValue(entryPtr);
	/* This prevents the deletion of it's own entry, since we need it */
	ewPtrDel->hPtr = NULL;
	EmbWinDelete(tablePtr, ewPtrDel);
    }
    /* set the new entry's value */
    Tcl_SetHashValue(entryPtr, (ClientData) ewPtr);
    ewPtr->hPtr = entryPtr;

    if (flags & INV_FORCE) {
	int x, y, w, h;
	/* Invalidate old cell */
	if (TableCellVCoords(tablePtr, srow-tablePtr->rowOffset,
		scol-tablePtr->colOffset, &x, &y, &w, &h, 0)) {
	    TableInvalidate(tablePtr, x, y, w, h, 0);
	}
	/* Invalidate new cell */
	if (TableCellVCoords(tablePtr, row-tablePtr->rowOffset,
		col-tablePtr->colOffset, &x, &y, &w, &h, 0)) {
	    TableInvalidate(tablePtr, x, y, w, h, 0);
	}
    }
    return TCL_OK;
}

/*
 *--------------------------------------------------------------
 *
 * Table_WinDelete --
 *	This procedure is invoked by ... whenever
 *	an embedded window is being delete.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	Window info will be deleted.
 *
 *--------------------------------------------------------------
 */
int
Table_WinDelete(register Table *tablePtr, char *CONST idxPtr)
{
    Tcl_HashEntry *entryPtr;

    entryPtr = Tcl_FindHashEntry(tablePtr->winTable, idxPtr);
    if (entryPtr != NULL) {
	/* get the window pointer & clean up data associated with it */
	EmbWinDelete(tablePtr, (TableEmbWindow *) Tcl_GetHashValue(entryPtr));
    }
    return TCL_OK;
}

/*
 *--------------------------------------------------------------
 *
 * Table_WindowCmd --
 *	This procedure is invoked to process the window method
 *	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.
 *
 *--------------------------------------------------------------
 */
int
Table_WindowCmd(ClientData clientData, register Tcl_Interp *interp,
		int objc, Tcl_Obj *CONST objv[])
{
    register Table *tablePtr = (Table *)clientData;
    int result = TCL_OK, cmdIndex, row, col, x, y, width, height, i, new;
    TableEmbWindow *ewPtr;
    Tcl_HashEntry *entryPtr;
    Tcl_HashSearch search;
    char buf[INDEX_BUFSIZE], *keybuf, *winname;

    if (objc < 3) {
	Tcl_WrongNumArgs(interp, 2, objv, "option ?arg arg ...?");
	return TCL_ERROR;
    }

    /* parse the next argument */
    if (Tcl_GetIndexFromObj(interp, objv[2], winCmdNames,
			    "option", 0, &cmdIndex) != TCL_OK) {
	return TCL_ERROR;
    }
    switch ((enum winCommand) cmdIndex) {
    case WIN_CGET:
	if (objc != 5) {
	    Tcl_WrongNumArgs(interp, 3, objv, "index option");
	    return TCL_ERROR;
	}
	entryPtr = Tcl_FindHashEntry(tablePtr->winTable,
				     Tcl_GetString(objv[3]));
	if (entryPtr == NULL) {
	    Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
				   "no window at index \"",
				   Tcl_GetString(objv[3]), "\"",          NULL);
	    return TCL_ERROR;
	} else {
	    ewPtr = (TableEmbWindow *) Tcl_GetHashValue(entryPtr);
	    result = Tk_ConfigureValue(interp, tablePtr->tkwin, winConfigSpecs,
				       (char *) ewPtr,
				       Tcl_GetString(objv[4]), 0);
	}
	return result;	/* CGET */

    case WIN_CONFIGURE:
	if (objc < 4) {
	    Tcl_WrongNumArgs(interp, 3, objv, "index ?arg arg  ...?");
	    return TCL_ERROR;
	}
	if (TableGetIndexObj(tablePtr, objv[3], &row, &col) == TCL_ERROR) {
	    return TCL_ERROR;
	}
	TableMakeArrayIndex(row, col, buf);
	entryPtr = Tcl_CreateHashEntry(tablePtr->winTable, buf, &new);

	if (new) {
	    /* create the structure */
	    ewPtr = TableNewEmbWindow(tablePtr);

	    /* insert it into the table */
	    Tcl_SetHashValue(entryPtr, (ClientData) ewPtr);
	    ewPtr->hPtr = entryPtr;

	    /* configure the window structure */
	    result = EmbWinConfigure(tablePtr, ewPtr, objc-4, objv+4);
	    if (result == TCL_ERROR) {
		/* release the structure */
		EmbWinCleanup(tablePtr, ewPtr);
		ckfree((char *) ewPtr);

		/* and free the hash table entry */
		Tcl_DeleteHashEntry(entryPtr);
	    }
	} else {
	    /* window exists, do a reconfig if we have enough args */
	    /* get the window pointer from the table */
	    ewPtr = (TableEmbWindow *) Tcl_GetHashValue(entryPtr);

	    /* 5 args means that there are values to replace */
	    if (objc > 5) {
		/* and do a reconfigure */
		result = EmbWinConfigure(tablePtr, ewPtr, objc-4, objv+4);
	    }
	}
	if (result == TCL_ERROR) {
	    return TCL_ERROR;
	}

	/*
	 * If there were less than 6 args, we need
	 * to do a printout of the config, even for new windows
	 */
	if (objc < 6) {
	    result = Tk_ConfigureInfo(interp, tablePtr->tkwin, winConfigSpecs,
				      (char *) ewPtr, (objc == 5)?
				      Tcl_GetString(objv[4]) : NULL, 0);
	} else {
	    /* Otherwise we reconfigured so invalidate
	     * the table for a redraw */
	    if (TableCellVCoords(tablePtr, row-tablePtr->rowOffset,
				 col-tablePtr->colOffset,
				 &x, &y, &width, &height, 0)) {
		TableInvalidate(tablePtr, x, y, width, height, 1);
	    }
	}
	return result;	/* CONFIGURE */

    case WIN_DELETE:
	if (objc < 4) {
	    Tcl_WrongNumArgs(interp, 3, objv, "index ?index ...?");
	    return TCL_ERROR;
	}
	for (i = 3; i < objc; i++) {
	    Table_WinDelete(tablePtr, Tcl_GetString(objv[i]));
	}
	break;

    case WIN_MOVE:
	if (objc != 5) {
	    Tcl_WrongNumArgs(interp, 3, objv, "srcIndex destIndex");
	    return TCL_ERROR;
	}
	result = Table_WinMove(tablePtr, Tcl_GetString(objv[3]),
			       Tcl_GetString(objv[4]), INV_FORCE);
	break;

    case WIN_NAMES: {
	Tcl_Obj *objPtr = Tcl_NewObj();

	/* just print out the window names */
	if (objc < 3 || objc > 4) {
	    Tcl_WrongNumArgs(interp, 3, objv, "?pattern?");
	    return TCL_ERROR;
	}
	winname = (objc == 4) ? Tcl_GetString(objv[3]) : NULL;
	entryPtr = Tcl_FirstHashEntry(tablePtr->winTable, &search);
	while (entryPtr != NULL) {
	    keybuf = Tcl_GetHashKey(tablePtr->winTable, entryPtr);
	    if (objc == 3 || Tcl_StringMatch(keybuf, winname) ) {
		Tcl_ListObjAppendElement(NULL, objPtr,
					 Tcl_NewStringObj(keybuf, -1));
	    }
	    entryPtr = Tcl_NextHashEntry(&search);
	}
	Tcl_SetResult(interp, Tcl_GetString(TableCellSort(tablePtr, Tcl_GetStringFromObj(Tcl_GetObjResult(interp),NULL))), TCL_DYNAMIC);


	break;
    }
    }
    return TCL_OK;
}