/*
 * ptkCanvGroup.c --
 *
 *	This file implements grid items for canvas
 *	widgets.
 *
 * Copyright (c) 1991-1994 The Regents of the University of California.
 * Copyright (c) 1994-1996 Sun Microsystems, Inc.
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * RCS: @(#) $Id: tkGroup.c,v 1.2 1998/09/14 18:23:16 stanton Exp $
 */

#include "tkPort.h"
#include "tk.h"
#include "tkInt.h"
#include "tkCanvases.h"

/*
 * The structure below defines the record for each rectangle/oval item.
 */

typedef struct GroupItem  {
    Tk_Item header;		/* Generic stuff that's the same for all
				 * types.  MUST BE FIRST IN STRUCTURE. */
    double posn[2];		/* Nonminal position of the group */
    Tcl_Interp *interp;		/* For error reporting */
    Tk_Canvas canvas;		/* Needed to find items by id when configured */
    int numMembers;		/* Number of items in the group */
    int numSlots;		/* Space in the array */
    Tk_Item **members;		/* array of items */
} GroupItem;

/*
 * Information used for parsing configuration specs:
 */

static int		MembersParseProc _ANSI_ARGS_((ClientData clientData,
			    Tcl_Interp *interp, Tk_Window tkwin,
			    Tcl_Obj * value, char *recordPtr, int offset));
static Tcl_Obj *		MembersPrintProc _ANSI_ARGS_((ClientData clientData,
			    Tk_Window tkwin, char *recordPtr, int offset,
			    Tcl_FreeProc **freeProcPtr));


static Tk_CustomOption stateOption = {
    TkStateParseProc,
    TkStatePrintProc, (ClientData) 3
};
static Tk_CustomOption tagsOption = {
    Tk_CanvasTagsParseProc,
    Tk_CanvasTagsPrintProc, (ClientData) NULL
};

static Tk_CustomOption membersOption = {
    MembersParseProc,
    MembersPrintProc, (ClientData) NULL
};


static Tk_ConfigSpec configSpecs[] = {
    {TK_CONFIG_CUSTOM, "-members",          NULL,          NULL,
	"1.0", Tk_Offset(GroupItem, members),
	TK_CONFIG_DONT_SET_DEFAULT, &membersOption},
    {TK_CONFIG_CUSTOM, "-state",          NULL,          NULL,
	         NULL, Tk_Offset(Tk_Item, state),TK_CONFIG_NULL_OK,
	&stateOption},
    {TK_CONFIG_CUSTOM, "-tags",          NULL,          NULL,
	         NULL, 0, TK_CONFIG_NULL_OK, &tagsOption},
    {TK_CONFIG_CALLBACK, "-updatecommand",          NULL,          NULL,
	         NULL, Tk_Offset(Tk_Item, updateCmd), TK_CONFIG_NULL_OK},
    {TK_CONFIG_END,          NULL,          NULL,          NULL,
	         NULL, 0, 0}
};

/*
 * Prototypes for procedures defined in this file:
 */

static void		ComputeGroupBbox _ANSI_ARGS_((Tk_Canvas canvas,
			    GroupItem *groupPtr));
static int		ConfigureGroup _ANSI_ARGS_((Tcl_Interp *interp,
			    Tk_Canvas canvas, Tk_Item *itemPtr, int argc,
			    Tcl_Obj *CONST *args, int flags));
static int		CreateGroup _ANSI_ARGS_((Tcl_Interp *interp,
			    Tk_Canvas canvas, struct Tk_Item *itemPtr,
			    int argc, Tcl_Obj *CONST *args));
static void		DeleteGroup _ANSI_ARGS_((Tk_Canvas canvas,
			    Tk_Item *itemPtr, Display *display));
static void		DisplayGroup _ANSI_ARGS_((Tk_Canvas canvas,
			    Tk_Item *itemPtr, Display *display, Drawable dst,
			    int x, int y, int width, int height));
static int		GroupCoords _ANSI_ARGS_((Tcl_Interp *interp,
			    Tk_Canvas canvas, Tk_Item *itemPtr, int argc,
			    Tcl_Obj *CONST *args));
static int		GroupToPostscript _ANSI_ARGS_((Tcl_Interp *interp,
			    Tk_Canvas canvas, Tk_Item *itemPtr, int prepass));
static int		GroupToArea _ANSI_ARGS_((Tk_Canvas canvas,
			    Tk_Item *itemPtr, double *areaPtr));
static double		GroupToPoint _ANSI_ARGS_((Tk_Canvas canvas,
			    Tk_Item *itemPtr, double *pointPtr));
static void		ScaleGroup _ANSI_ARGS_((Tk_Canvas canvas,
			    Tk_Item *itemPtr, double originX, double originY,
			    double scaleX, double scaleY));
static void		TranslateGroup _ANSI_ARGS_((Tk_Canvas canvas,
			    Tk_Item *itemPtr, double deltaX, double deltaY));
static int		GroupIndex _ANSI_ARGS_((Tcl_Interp *interp,
			    Tk_Canvas canvas, Tk_Item *itemPtr, Tcl_Obj *indexString,
			    int *indexPtr));
static int		GroupInsert _ANSI_ARGS_((Tk_Canvas canvas,
			    Tk_Item *itemPtr, int beforeThis, Tcl_Obj *string));
static void		GroupInsertProc _ANSI_ARGS_((Tk_Canvas canvas,
			    Tk_Item *itemPtr, int beforeThis, Tcl_Obj *string));
static void		GroupDChars _ANSI_ARGS_((Tk_Canvas canvas,
			    Tk_Item *itemPtr, int first, int last));

/*
 * The structures below defines the rectangle and oval item types
 * by means of procedures that can be invoked by generic item code.
 */

Tk_ItemType ptkCanvGroupType = {
    "group",				/* name */
    sizeof(GroupItem),			/* itemSize */
    CreateGroup,			/* createProc */
    configSpecs,			/* configSpecs */
    ConfigureGroup,			/* configureProc */
    GroupCoords,			/* coordProc */
    DeleteGroup,			/* deleteProc */
    DisplayGroup,			/* displayProc */
    TK_ITEM_ALWAYS_REDRAW|TK_CONFIG_OBJS,/* flags */
    GroupToPoint,			/* pointProc */
    GroupToArea,			/* areaProc */
    GroupToPostscript,			/* postscriptProc */
    ScaleGroup,				/* scaleProc */
    TranslateGroup,			/* translateProc */
    GroupIndex,				/* indexProc */
    (Tk_ItemCursorProc *) NULL,		/* icursorProc */  /* Abuse to set active? */
    (Tk_ItemSelectionProc *) NULL,	/* selectionProc */
    GroupInsertProc,			/* insertProc */
    GroupDChars,			/* dTextProc */
    (Tk_ItemType *) NULL,		/* nextPtr */
    (Tk_ItemBboxProc *) ComputeGroupBbox,/* bboxProc */
    Tk_Offset(Tk_VisitorType, visitGroup), /* acceptProc */
    NULL,	/* getCoordPtr */
    NULL	/* setCoordPtr */
};


static void
ShowMembers(char *f,GroupItem *groupPtr)
{
 int i;
 LangDebug("%s gid=%d %d [",f,groupPtr->header.id, groupPtr->numMembers);
 if (groupPtr->numMembers > groupPtr->numSlots)
  abort();
 for (i=0; i < groupPtr->numMembers; i++)
  {
   if (groupPtr->members[i])
    {
     LangDebug(" %d",groupPtr->members[i]->id);
    }
   else
    {
     LangDebug(" NULL",groupPtr->members[i]->id);
    }
  }
 LangDebug("]\n");
}



/*
 *--------------------------------------------------------------
 *
 * CreateGroup --
 *
 *	This procedure is invoked to create a new rectangle
 *	or oval item in a canvas.
 *
 * Results:
 *	A standard Tcl return value.  If an error occurred in
 *	creating the item, then an error message is left in
 *	Tcl_GetResult(interp);  in this case itemPtr is left uninitialized,
 *	so it can be safely freed by the caller.
 *
 * Side effects:
 *	A new rectangle or oval item is created.
 *
 *--------------------------------------------------------------
 */

static int
CreateGroup(interp, canvas, itemPtr, argc, args)
    Tcl_Interp *interp;			/* For error reporting. */
    Tk_Canvas canvas;			/* Canvas to hold new item. */
    Tk_Item *itemPtr;			/* Record to hold new item;  header
					 * has been initialized by caller. */
    int argc;				/* Number of arguments in args. */
    Tcl_Obj *CONST *args;		/* Arguments describing group. */
{
    GroupItem *groupPtr = (GroupItem *) itemPtr;
    int i;

    if (argc==1) {
	i = 1;
    } else {
	char *arg = Tcl_GetStringFromObj(args[1], NULL);
	if ((argc>1) && (arg[0] == '-')
		&& (arg[1] >= 'a') && (arg[1] <= 'z')) {
	    i = 1;
	} else {
	    i = 2;
	}
    }

    if (argc < i) {
	Tcl_AppendResult(interp, "wrong # args: should be \"",
		Tk_PathName(Tk_CanvasTkwin(canvas)), " create ",
		itemPtr->typePtr->name, " x1 y1 ?options?\"",
		         NULL);
	return TCL_ERROR;
    }

    /*
     * Carry out initialization that is needed in order to clean
     * up after errors during the the remainder of this procedure.
     */

    groupPtr->canvas     = canvas;
    groupPtr->interp     = interp;
    groupPtr->members    = NULL;
    groupPtr->numSlots   = 0;
    groupPtr->numMembers = 0;

    /*
     * Process the arguments to fill in the item record.
     */

    if ((GroupCoords(interp, canvas, itemPtr, i, args) != TCL_OK)) {
	goto error;
    }
    if (ConfigureGroup(interp, canvas, itemPtr, argc-i, args+i, 0)
	    == TCL_OK) {
	return TCL_OK;
    }

    error:
    DeleteGroup(canvas, itemPtr, Tk_Display(Tk_CanvasTkwin(canvas)));
    return TCL_ERROR;
}

/*
 *--------------------------------------------------------------
 *
 * GroupCoords --
 *
 *	This procedure is invoked to process the "coords" widget
 *	command on rectangles and ovals.  See the user documentation
 *	for details on what it does.
 *
 * Results:
 *	Returns TCL_OK or TCL_ERROR, and sets Tcl_GetResult(interp).
 *
 * Side effects:
 *	The coordinates for the given item may be changed.
 *
 *--------------------------------------------------------------
 */

static int
GroupCoords(interp, canvas, itemPtr, argc, args)
    Tcl_Interp *interp;			/* Used for error reporting. */
    Tk_Canvas canvas;			/* Canvas containing item. */
    Tk_Item *itemPtr;			/* Item whose coordinates are to be
					 * read or modified. */
    int argc;				/* Number of coordinates supplied in
					 * args. */
    Tcl_Obj *CONST *args;		/* Array of coordinates: x1, y1,
					 * x2, y2, ... */
{
    GroupItem *groupPtr = (GroupItem *) itemPtr;
    char c0[TCL_DOUBLE_SPACE];

    if (argc == 0) {
	Tcl_Obj *obj = Tcl_NewObj();
	Tcl_Obj *subobj = Tcl_NewDoubleObj(groupPtr->posn[0]);
	Tcl_ListObjAppendElement(interp, obj, subobj);
	subobj = Tcl_NewDoubleObj(groupPtr->posn[1]);
	Tcl_ListObjAppendElement(interp, obj, subobj);
	Tcl_SetObjResult(interp, obj);
    } else if ((argc == 1)||(argc == 2)) {
	double newX;
	double newY;
 	if (argc==1) {
	    if (Tcl_ListObjGetElements(interp, args[0], &argc, (Tcl_Obj ***)&args) != TCL_OK) {
		return TCL_ERROR;
	    } else if (argc != 2) {
		sprintf(c0,"%d",argc);
		Tcl_AppendResult(interp, "wrong # coordinates: expected 2, got ",
		c0,          NULL);
		return TCL_ERROR;
	    }
	}
	if ((Tk_CanvasGetCoordFromObj(interp, canvas, args[0],
 		    &newX) != TCL_OK)
		|| (Tk_CanvasGetCoordFromObj(interp, canvas, args[1],
		    &newY) != TCL_OK)) {
	    return TCL_ERROR;
	}
	TranslateGroup(canvas, itemPtr, newX - groupPtr->posn[0], newY - groupPtr->posn[1]);
    } else {
	sprintf(c0,"%d",argc);
	Tcl_AppendResult(interp, "wrong # coordinates: expected 0 or 4, got ",
	c0,          NULL);
	return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 *--------------------------------------------------------------
 *
 * ConfigureGroup --
 *
 *	This procedure is invoked to configure various aspects
 *	of a rectangle or oval item, such as its border and
 *	background colors.
 *
 * Results:
 *	A standard Tcl result code.  If an error occurs, then
 *	an error message is left in Tcl_GetResult(interp).
 *
 * Side effects:
 *	Configuration information, such as colors and stipple
 *	patterns, may be set for itemPtr.
 *
 *--------------------------------------------------------------
 */

static int
ConfigureGroup(interp, canvas, itemPtr, argc, args, flags)
    Tcl_Interp *interp;		/* Used for error reporting. */
    Tk_Canvas canvas;		/* Canvas containing itemPtr. */
    Tk_Item *itemPtr;		/* Rectangle item to reconfigure. */
    int argc;			/* Number of elements in args.  */
    Tcl_Obj *CONST *args;	/* Arguments describing things to configure. */
    int flags;			/* Flags to pass to Tk_ConfigureWidget. */
{
    TkCanvas *canvasPtr = (TkCanvas *) canvas;
    GroupItem *groupPtr = (GroupItem *) itemPtr;
    Tk_Item *saveGroup  = canvasPtr->activeGroup;
    Tk_State state = Tk_GetItemState(canvas, itemPtr);
    int i;
    Tk_Window tkwin = Tk_CanvasTkwin(canvas);

    if (Tk_ConfigureWidget(interp, tkwin, configSpecs, argc, args,
	    (char *) groupPtr, flags|TK_CONFIG_OBJS) != TCL_OK) {
	return TCL_ERROR;
    }

    /*
     * A few of the options require additional processing, such as
     * graphics contexts.
     */

    itemPtr->redraw_flags &= ~TK_ITEM_STATE_DEPENDANT;

    ComputeGroupBbox(canvas, groupPtr);

    return TCL_OK;
}

/*
 *--------------------------------------------------------------
 *
 * DeleteGroup --
 *
 *	This procedure is called to clean up the data structure
 *	associated with a rectangle or oval item.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Resources associated with itemPtr are released.
 *
 *--------------------------------------------------------------
 */

static void
DeleteGroup(canvas, itemPtr, display)
    Tk_Canvas canvas;			/* Info about overall widget. */
    Tk_Item *itemPtr;			/* Item that is being deleted. */
    Display *display;			/* Display containing window for
					 * canvas. */
{
    TkCanvas *canvasPtr = (TkCanvas *) canvas;
    GroupItem *groupPtr = (GroupItem *) itemPtr;
    Tk_Item *saveGroup  = canvasPtr->activeGroup;
    Tk_State state = Tk_GetItemState(canvas, itemPtr);
    int i;

    canvasPtr->activeGroup = itemPtr;
    for (i=groupPtr->numMembers-1; i >= 0; i--) {
	Tk_Item *subitemPtr = groupPtr->members[i];
	TkGroupRemoveItem(subitemPtr);
	
#ifdef DELETE_GROUP_DELETES_MEMBERS
	if (subitemPtr != NULL) {
	    (*subitemPtr->typePtr->deleteProc)(canvas, subitemPtr, display);
	}
#endif
    }
    canvasPtr->activeGroup = saveGroup;
    if (groupPtr->members) {
	ckfree((char *) groupPtr->members);
    }
}


void
TkGroupRemoveItem(itemPtr)
Tk_Item *itemPtr;
{
    GroupItem *groupPtr = (GroupItem *) (itemPtr->group);
    if (groupPtr != NULL) {
	int i;
	for (i=groupPtr->numMembers-1; i >= 0; i--) {
	    if (groupPtr->members[i] == itemPtr) {
		int j;
		for (j=i+1; j < groupPtr->numMembers; j++) {
		    groupPtr->members[j-1] = groupPtr->members[j];
		}
		itemPtr->redraw_flags |= FORCE_REDRAW;
		groupPtr->numMembers--;
		itemPtr->group = NULL;
		return;
	    }
	}
    }
  itemPtr->group = NULL;
  LangDebug("Cannot find %d in %d\n",itemPtr->id, groupPtr->header.id);
}


/*
 *--------------------------------------------------------------
 *
 * ComputeGroupBbox --
 *
 *	This procedure is invoked to compute the bounding box of
 *	all the pixels that may be drawn as part of a rectangle
 *	or oval.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The fields x1, y1, x2, and y2 are updated in the header
 *	for itemPtr.
 *
 *--------------------------------------------------------------
 */

	/* ARGSUSED */
static void
ComputeGroupBbox(canvas, groupPtr)
    Tk_Canvas canvas;			/* Canvas that contains item. */
    GroupItem *groupPtr;		/* Item whose bbox is to be
					 * recomputed. */
{
    TkCanvas *canvasPtr = (TkCanvas *) canvas;
    Tk_Item *saveGroup  = canvasPtr->activeGroup;
    Tk_State state = Tk_GetItemState(canvas, &groupPtr->header);
    int seen = 0;
    int i;

    canvasPtr->activeGroup = &groupPtr->header;
    for (i=0; i < groupPtr->numMembers; i++) {
	Tk_Item *subitemPtr = groupPtr->members[i];
	if (subitemPtr != NULL) {
	    if (Tk_GetItemState(canvas, subitemPtr) == TK_STATE_HIDDEN) {
		continue;
	    }
	    if (seen++ == 0) {
		groupPtr->header.x1 = subitemPtr->x1;
		groupPtr->header.y1 = subitemPtr->y1;
		groupPtr->header.x2 = subitemPtr->x2;
		groupPtr->header.y2 = subitemPtr->y2;
	    } else {
		if (subitemPtr->x1 < groupPtr->header.x1) {
		     groupPtr->header.x1 = subitemPtr->x1;
		}
		if (subitemPtr->y1 < groupPtr->header.y1) {
		     groupPtr->header.y1 = subitemPtr->y1;
		}
		if (subitemPtr->x2 > groupPtr->header.x2) {
		     groupPtr->header.x2 = subitemPtr->x2;
		}
		if (subitemPtr->y2 > groupPtr->header.y2) {
		     groupPtr->header.y2 = subitemPtr->y2;
		}
	    }
	}
    }
    canvasPtr->activeGroup = saveGroup;

    /* If all items were hidden then have a "null" bbox */

    if (seen == 0) {
	groupPtr->header.x1 = groupPtr->posn[0];
	groupPtr->header.y1 = groupPtr->posn[1];
	groupPtr->header.x2 = groupPtr->header.x1;
	groupPtr->header.y2 = groupPtr->header.y1;
    }
}

/*
 *--------------------------------------------------------------
 *
 * DisplayGroup --
 *
 *	This procedure is invoked to draw a rectangle or oval
 *	item in a given drawable.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	ItemPtr is drawn in drawable using the transformation
 *	information in canvas.
 *
 *--------------------------------------------------------------
 */

static void
DisplayGroup(canvas, itemPtr, display, drawable, x, y, width, height)
    Tk_Canvas canvas;			/* Canvas that contains item. */
    Tk_Item *itemPtr;			/* Item to be displayed. */
    Display *display;			/* Display on which to draw item. */
    Drawable drawable;			/* Pixmap or window in which to draw
					 * item. */
    int x, y, width, height;		/* Describes region of canvas that
					 * must be redisplayed (not used). */
{
    TkCanvas *canvasPtr = (TkCanvas *) canvas;
    GroupItem *groupPtr = (GroupItem *) itemPtr;
    Tk_Item *saveGroup  = canvasPtr->activeGroup;
    Tk_State state = Tk_GetItemState(canvas, itemPtr);
    int i;
    if (state == TK_STATE_HIDDEN) {
	return;
    }
    canvasPtr->activeGroup = itemPtr;
    for (i=0; i < groupPtr->numMembers; i++) {
	Tk_Item *subitemPtr = groupPtr->members[i];
	if (subitemPtr != NULL) {
	    if (Tk_GetItemState(canvas, subitemPtr) == TK_STATE_HIDDEN) {
		continue;
	    }
	    if (drawable != None ||
		(subitemPtr->typePtr->alwaysRedraw & 1)) {
		if (subitemPtr->updateCmd) {
		    if (canvasPtr->updateCmds == NULL) {
			canvasPtr->updateCmds = Tcl_NewListObj(0,NULL);
		    }
		    Tcl_IncrRefCount(subitemPtr->updateCmd);
		    Tcl_ListObjAppendElement(canvasPtr->interp,canvasPtr->updateCmds,
				subitemPtr->updateCmd);
		}
		(*subitemPtr->typePtr->displayProc)(canvas, subitemPtr, display,
		    drawable, x, y, width, height);
	    }
	}
    }
    canvasPtr->activeGroup = saveGroup;
}

/*
 *--------------------------------------------------------------
 *
 * GroupToPoint --
 *
 *	Computes the distance from a given point to a given
 *	group, in canvas units.
 *
 * Results:
 *	The return value is 0 if the point whose x and y coordinates
 *	are coordPtr[0] and coordPtr[1] is inside the group.  If the
 *	point isn't inside the rectangle then the return value is the
 *	distance from the point to the group.
 *
 * Side effects:
 *	None.
 *
 *--------------------------------------------------------------
 */

	/* ARGSUSED */
static double
GroupToPoint(canvas, itemPtr, pointPtr)
    Tk_Canvas canvas;		/* Canvas containing item. */
    Tk_Item *itemPtr;		/* Item to check against point. */
    double *pointPtr;		/* Pointer to x and y coordinates. */
{
    TkCanvas *canvasPtr = (TkCanvas *) canvas;
    GroupItem *groupPtr = (GroupItem *) itemPtr;
    Tk_Item *saveGroup  = canvasPtr->activeGroup;
    Tk_State state = Tk_GetItemState(canvas, itemPtr);
    int i;

    double best = 1.0e36;

    if (state == TK_STATE_HIDDEN) {
	return best;
    }

    /* If the group is active it is invisible to picking */
    if (state == TK_STATE_ACTIVE) {
	return best;
    }

    canvasPtr->activeGroup = itemPtr;
    for (i=0; i < groupPtr->numMembers; i++) {
	Tk_Item *subitemPtr = groupPtr->members[i];
	if (subitemPtr != NULL) {
	    double try = (*subitemPtr->typePtr->pointProc)(canvas, subitemPtr, pointPtr);
	    if (try < best) {
		best = try;
		if (best == 0.0) {
		   break;
		}
	    }
	}
    }
    canvasPtr->activeGroup = saveGroup;
    return best;
}


/*
 *--------------------------------------------------------------
 *
 * GroupToArea --
 *
 *	This procedure is called to determine whether an item
 *	lies entirely inside, entirely outside, or overlapping
 *	a given rectangle.
 *
 * Results:
 *	-1 is returned if the item is entirely outside the area
 *	given by rectPtr, 0 if it overlaps, and 1 if it is entirely
 *	inside the given area.
 *
 * Side effects:
 *	None.
 *
 *--------------------------------------------------------------
 */

	/* ARGSUSED */
static int
GroupToArea(canvas, itemPtr, areaPtr)
    Tk_Canvas canvas;		/* Canvas containing item. */
    Tk_Item *itemPtr;		/* Item to check against rectangle. */
    double *areaPtr;		/* Pointer to array of four coordinates
				 * (x1, y1, x2, y2) describing rectangular
				 * area.  */
{
    TkCanvas *canvasPtr = (TkCanvas *) canvas;
    GroupItem *groupPtr = (GroupItem *) itemPtr;
    Tk_Item *saveGroup  = canvasPtr->activeGroup;
    Tk_State state = Tk_GetItemState(canvas, itemPtr);
    int i;
#define ALL_OUTSIDE 1
#define ALL_INSIDE  2
    int seen    = ALL_INSIDE|ALL_OUTSIDE;

    if (state == TK_STATE_HIDDEN) {
	return -1;
    }

    /* If the group is active it is invisible to picking */
    if (state == TK_STATE_ACTIVE) {
	return -1;
    }

    canvasPtr->activeGroup = itemPtr;
    for (i=0; i < groupPtr->numMembers; i++) {
	Tk_Item *subitemPtr = groupPtr->members[i];
	if (subitemPtr != NULL) {
	    int inner =  (*subitemPtr->typePtr->areaProc)(canvas, subitemPtr, areaPtr);
	    if (inner < 0)   /* outside */
		seen &= ~ALL_INSIDE;  /* clear the inside option */
	    if (inner == 0)  /* overlap */
		seen = 0;
	    if (inner > 0)   /* inside */
		seen &= ~ALL_OUTSIDE;  /* clear the outside option */
	    if (seen == 0)
		break;
	}
    }
    canvasPtr->activeGroup = saveGroup;

    switch (seen) {
      case 0 :
       return 0;
      case ALL_INSIDE  :
       return 1;
      default:
      case ALL_OUTSIDE :
       return -1;
    }
}


/*
 *--------------------------------------------------------------
 *
 * ScaleGroup --
 *
 *	This procedure is invoked to rescale a rectangle or oval
 *	item.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The rectangle or oval referred to by itemPtr is rescaled
 *	so that the following transformation is applied to all
 *	point coordinates:
 *		x' = originX + scaleX*(x-originX)
 *		y' = originY + scaleY*(y-originY)
 *
 *--------------------------------------------------------------
 */

static void
ScaleGroup(canvas, itemPtr, originX, originY, scaleX, scaleY)
    Tk_Canvas canvas;			/* Canvas containing rectangle. */
    Tk_Item *itemPtr;			/* Rectangle to be scaled. */
    double originX, originY;		/* Origin about which to scale rect. */
    double scaleX;			/* Amount to scale in X direction. */
    double scaleY;			/* Amount to scale in Y direction. */
{
    TkCanvas *canvasPtr = (TkCanvas *) canvas;
    GroupItem *groupPtr = (GroupItem *) itemPtr;
    Tk_Item *saveGroup  = canvasPtr->activeGroup;
    Tk_State state = Tk_GetItemState(canvas, itemPtr);
    int i;

    groupPtr->posn[0] = originX + scaleX*(groupPtr->posn[0] - originX);
    groupPtr->posn[1] = originY + scaleY*(groupPtr->posn[1] - originY);

    canvasPtr->activeGroup = itemPtr;
    for (i=0; i < groupPtr->numMembers; i++) {
	Tk_Item *subitemPtr = groupPtr->members[i];
	if (subitemPtr != NULL) {
	    (*subitemPtr->typePtr->scaleProc)(canvas, subitemPtr, originX, originY, scaleX, scaleY);
	}
    }
    canvasPtr->activeGroup = saveGroup;

    ComputeGroupBbox(canvas, groupPtr);
}

/*
 *--------------------------------------------------------------
 *
 * TranslateGroup --
 *
 *	This procedure is called to move a rectangle or oval by a
 *	given amount.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The position of the rectangle or oval is offset by
 *	(xDelta, yDelta), and the bounding box is updated in the
 *	generic part of the item structure.
 *
 *--------------------------------------------------------------
 */

static void
TranslateGroup(canvas, itemPtr, deltaX, deltaY)
    Tk_Canvas canvas;			/* Canvas containing item. */
    Tk_Item *itemPtr;			/* Item that is being moved. */
    double deltaX, deltaY;		/* Amount by which item is to be
					 * moved. */
{
    TkCanvas *canvasPtr = (TkCanvas *) canvas;
    GroupItem *groupPtr = (GroupItem *) itemPtr;
    Tk_Item *saveGroup  = canvasPtr->activeGroup;
    int i;
    groupPtr->posn[0] += deltaX;
    groupPtr->posn[1] += deltaY;
    canvasPtr->activeGroup = itemPtr;
    for (i=0; i < groupPtr->numMembers; i++) {
	Tk_Item *subitemPtr = groupPtr->members[i];
	if (subitemPtr != NULL) {
	    (*subitemPtr->typePtr->translateProc)(canvas, subitemPtr, deltaX, deltaY);
	}
    }
    canvasPtr->activeGroup = saveGroup;
    ComputeGroupBbox(canvas, groupPtr);
}

/*
 *--------------------------------------------------------------
 *
 * GroupToPostscript --
 *
 *	This procedure is called to generate Postscript for
 *	rectangle and oval items.
 *
 * Results:
 *	The return value is a standard Tcl result.  If an error
 *	occurs in generating Postscript then an error message is
 *	left in Tcl_GetResult(interp), replacing whatever used to be there.
 *	If no error occurs, then Postscript for the rectangle is
 *	appended to the result.
 *
 * Side effects:
 *	None.
 *
 *--------------------------------------------------------------
 */

static int
GroupToPostscript(interp, canvas, itemPtr, prepass)
    Tcl_Interp *interp;			/* Interpreter for error reporting. */
    Tk_Canvas canvas;			/* Information about overall canvas. */
    Tk_Item *itemPtr;			/* Item for which Postscript is
					 * wanted. */
    int prepass;			/* 1 means this is a prepass to
					 * collect font information;  0 means
					 * final Postscript is being created. */
{
    TkCanvas *canvasPtr = (TkCanvas *) canvas;
    GroupItem *groupPtr = (GroupItem *) itemPtr;
    Tk_Item *saveGroup  = canvasPtr->activeGroup;
    Tk_State state = Tk_GetItemState(canvas, itemPtr);
    int code = TCL_OK;
    int i;

    if (state == TK_STATE_HIDDEN) {
	return code;
    }

    canvasPtr->activeGroup = itemPtr;
    for (i=0; i < groupPtr->numMembers; i++) {
	Tk_Item *subitemPtr = groupPtr->members[i];
	if (subitemPtr != NULL) {
	    if (Tk_GetItemState(canvas, subitemPtr) == TK_STATE_HIDDEN) {
		continue;
	    }
	    code = (*subitemPtr->typePtr->postscriptProc)(interp, canvas,
			subitemPtr, prepass);
	    if (code != TCL_OK) {
		break;
	    }
	}
    }
    canvasPtr->activeGroup = saveGroup;
    return code;
}

static int
GroupIndex(interp, canvas, itemPtr, obj, indexPtr)
Tcl_Interp *interp;
Tk_Canvas canvas;
Tk_Item *itemPtr;
Tcl_Obj *obj;
int *indexPtr;
{
    TkCanvas *canvasPtr = (TkCanvas *) canvas;
    GroupItem *groupPtr = (GroupItem *) itemPtr;
    Tk_Item *saveGroup  = canvasPtr->activeGroup;
    Tk_State state = Tk_GetItemState(canvas, itemPtr);
    int i;
    int length;
    int id;
    char *string;
    double point[2];
    double bestDist;
    char *end, *p;
    Tcl_Obj **objv;

    *indexPtr = -1;

    if (Tcl_ListObjGetElements(interp, obj, &i, &objv) == TCL_OK && i == 2
	&& Tk_CanvasGetCoordFromObj(interp, canvas, objv[0], &point[0]) == TCL_OK
	&& Tk_CanvasGetCoordFromObj(interp, canvas, objv[1], &point[1]) == TCL_OK) {
	goto doxy;
    }

    string = Tcl_GetStringFromObj(obj, &length);
    if (string[0] == 'e') {
	if (strncmp(string, "end", length) == 0) {
	    *indexPtr = groupPtr->numMembers;
	} else {
	    badIndex:

	    /*
	     * Some of the paths here leave messages in interp->result,
	     * so we have to clear it out before storing our own message.
	     */

	    Tcl_SetResult(interp, (char *) NULL, TCL_STATIC);
	    Tcl_AppendResult(interp, "bad index \"", string, "\"",
		    (char *) NULL);
	    return TCL_ERROR;
	}
    } else if (string[0] == '@') {
	p = string+1;
	point[0] = strtod(p, &end);
	if ((end == p) || (*end != ',')) {
	    goto badIndex;
	}
	p = end+1;
	point[1] = strtod(p, &end);
	if ((end == p) || (*end != 0)) {
	    goto badIndex;
	}
     doxy:
	bestDist = 1.0e36;
	*indexPtr = 0;
	canvasPtr->activeGroup = itemPtr;
	for(i=0; i < groupPtr->numMembers; i++) {
	    Tk_Item *subitemPtr = groupPtr->members[i];
	    double dist = (*subitemPtr->typePtr->pointProc)(canvas, subitemPtr, point);
	    if (dist < bestDist) {
		bestDist  = dist;
		*indexPtr = i;
	    }
	}
	canvasPtr->activeGroup = saveGroup;
    } else {
	if (Tcl_GetIntFromObj(interp, obj, &id) == TCL_OK) {
	    for(i=0; i < groupPtr->numMembers; i++) {
		Tk_Item *subitemPtr = groupPtr->members[i];
		if (subitemPtr != NULL && subitemPtr->id == id) {
		    *indexPtr = i;
		    return TCL_OK;
		}
	    }
	    goto badIndex;
	} else {
	    return TCL_ERROR;
	}
    }
    return TCL_OK;
}

static int
GroupInsert(canvas, itemPtr, beforeThis, string)
Tk_Canvas canvas;
Tk_Item *itemPtr;
int beforeThis;
Tcl_Obj *string;
{
    TkCanvas *canvasPtr = (TkCanvas *) canvas;
    GroupItem *groupPtr = (GroupItem *) itemPtr;
    Tk_Item *saveGroup  = canvasPtr->activeGroup;
    Tk_State state = Tk_GetItemState(canvas, itemPtr);
    Tcl_Obj **objv;
    int argc;
    int i;
    int id;
    if (Tcl_ListObjGetElements(groupPtr->interp,string,&argc,&objv) == TCL_OK) {
	int count = 0;
	for (i=0; i < argc; i++) {
	    if (Tcl_GetIntFromObj(groupPtr->interp,objv[i],&id) == TCL_OK) {
		Tcl_HashEntry *entryPtr = Tcl_FindHashEntry(&canvasPtr->idTable, (char *) id);
		if (entryPtr != NULL) {
		    Tk_Item *subitemPtr = (Tk_Item *) Tcl_GetHashValue(entryPtr);
		    if (subitemPtr == NULL
			|| subitemPtr == itemPtr
			|| subitemPtr->group == itemPtr) {
			continue;
		    }
		    if (subitemPtr->group != NULL) {
			TkGroupRemoveItem(subitemPtr);
		    }
		    count++;
		}
	    } else {
		return TCL_ERROR;
	    }
	}
	i = count + groupPtr->numMembers;
	if (i > groupPtr->numSlots) {
	    if (groupPtr->members == NULL) {
		groupPtr->members = (Tk_Item **)ckalloc(i*sizeof(Tk_Item *));
	    } else {
		groupPtr->members = (Tk_Item **)ckrealloc((char *)groupPtr->members,
					i*sizeof(Tk_Item *));
	    }
	    if (groupPtr->members != NULL) {
		groupPtr->numSlots = i;
	    } else {
		groupPtr->numMembers = 0;
		groupPtr->numSlots   = 0;
		Tcl_SetResult(groupPtr->interp,"Out of memory",TCL_STATIC);
		return TCL_ERROR;
	    }
	}
	/* Move tail up */
	for (i=groupPtr->numMembers-1; i >= beforeThis; i--) {
		groupPtr->members[i+count] = groupPtr->members[i];
	}
	/* Fill in slots */
	groupPtr->numMembers += count;
	for (i=0; i < argc; i++) {
	    groupPtr->members[beforeThis] = NULL;
	    if (Tcl_GetIntFromObj(groupPtr->interp,objv[i],&id) == TCL_OK) {
		Tcl_HashEntry *entryPtr = Tcl_FindHashEntry(&canvasPtr->idTable, (char *) id);
		if (entryPtr != NULL) {
		    Tk_Item *subitemPtr = (Tk_Item *) Tcl_GetHashValue(entryPtr);
		    if (subitemPtr == NULL
			|| subitemPtr == itemPtr
			|| subitemPtr->group == itemPtr) {
			continue;
		    }
		    subitemPtr->group = itemPtr;
		    subitemPtr->redraw_flags |= FORCE_REDRAW;
		    groupPtr->members[beforeThis] = subitemPtr;
		    beforeThis++;
		    count--;
		}
	    }
	}
	if (count != 0) {
	   abort();
	}
	ComputeGroupBbox(groupPtr->canvas, groupPtr);
	return TCL_OK;
    } else {
	return TCL_ERROR;
    }
}

/* Just like above but with void return to go in the function table */
static void
GroupInsertProc(canvas, itemPtr, beforeThis, string)
Tk_Canvas canvas;
Tk_Item *itemPtr;
int beforeThis;
Tcl_Obj *string;
{
 GroupInsert(canvas, itemPtr, beforeThis, string);
}


static void
GroupDChars(canvas, itemPtr, first, last)
Tk_Canvas canvas;
Tk_Item *itemPtr;
int first;
int last;
{
    TkCanvas *canvasPtr = (TkCanvas *) canvas;
    GroupItem *groupPtr = (GroupItem *) itemPtr;
    Tk_Item *saveGroup  = canvasPtr->activeGroup;
    Tk_State state = Tk_GetItemState(canvas, itemPtr);
    int i;

    if (first < 0) {
	first = 0;
    }
    if (last >=  groupPtr->numMembers) {
	last = groupPtr->numMembers-1;
    }
    if (first > last) {
	return;
    }
    for (i=last; i >= first; i--) {
	TkGroupRemoveItem(groupPtr->members[i]);
    }
    ComputeGroupBbox(groupPtr->canvas, groupPtr);
}

static int
MembersParseProc(clientData,interp,tkwin,value,recordPtr,offset)
ClientData clientData;
Tcl_Interp *interp;
Tk_Window tkwin;
Tcl_Obj * value;
char *recordPtr;
int offset;
{
    Tk_Item *itemPtr    = (Tk_Item *) recordPtr;
    GroupItem *groupPtr = (GroupItem *) itemPtr;
    int code = TCL_OK;
    Tk_CanvasEventuallyRedraw(groupPtr->canvas, itemPtr->x1, itemPtr->y1, itemPtr->x2, itemPtr->y2);
    GroupDChars(groupPtr->canvas, itemPtr, 0, groupPtr->numMembers-1);
    code = GroupInsert(groupPtr->canvas, itemPtr, 0, value);
    Tk_CanvasEventuallyRedraw(groupPtr->canvas, itemPtr->x1, itemPtr->y1, itemPtr->x2, itemPtr->y2);
    return code;
}

static Tcl_Obj *
MembersPrintProc(clientData,tkwin,recordPtr,offset,freeProcPtr)
ClientData clientData;
Tk_Window tkwin;
char *recordPtr;
int offset;
Tcl_FreeProc **freeProcPtr;
{
    GroupItem *groupPtr = (GroupItem *) recordPtr;
    Tcl_Obj *result = Tcl_NewListObj(0,NULL);
    int i;
    for (i=0; i < groupPtr->numMembers; i++) {
	Tk_Item *subitemPtr = groupPtr->members[i];
	if (subitemPtr != NULL) {
	    Tcl_ListObjAppendElement(groupPtr->interp,result,
			Tcl_NewIntObj(subitemPtr->id));
	}
    }
    return result;
}