/*
 * Item.c -- Implementation of items.
 *
 * Authors		: Patrick Lecoanet.
 * Creation date	: 
 *
 * $Id: Item.c,v 1.82 2004/03/24 15:06:44 lecoanet Exp $
 */

/*
 *  Copyright (c) 1993 - 2000 CENA, Patrick Lecoanet --
 *
 * This code is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This code is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this code; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */


#include "Types.h"
#include "glu.h"
#include "Field.h"
#include "Item.h"
#include "Group.h"
#include "WidgetInfo.h"
#include "Geo.h"
#include "Draw.h"
#include "MapInfo.h"
#include "Image.h"
#include "Color.h"
#include "tkZinc.h"
#ifdef OM
#include "OverlapMan.h"
#endif

#include <limits.h>		/* For INT_MAX */
#include <stdarg.h>
#include <stdio.h>
#include <string.h>


static const char rcsid[] = "$Id: Item.c,v 1.82 2004/03/24 15:06:44 lecoanet Exp $";
static const char compile_id[]="$Compile: " __FILE__ " " __DATE__ " " __TIME__ " $";


static	ZnList	item_classes = NULL;
static	ZnList	item_stack = NULL;


/*
 * This array must be kept in sync with the
 * corresponding defines in Item.h.
 */
static char *attribute_type_strings[] = {
  "",
  "boolean",
  "bitmap",
  "bitmaplist",
  "string",
  "font",
  "edgelist",
  "relief",
  "dimension",
  "priority",
  "alignment",
  "autoalignment",
  "lineend",
  "labelformat",
  "linestyle",
  "lineshape",
  "item",
  "angle",
  "integer",
  "unsignedint",
  "point",
  "anchor",
  "taglist",
  "mapinfo",
  "image",
  "leaderanchors",
  "joinstyle",
  "capstyle",
  "gradient",
  "gradientlist",
  "window",
  "alpha",
  "fillrule",
  "short",
  "unsignedshort"
  "char"
  "unsignedchar"
};


/*
 **********************************************************************************
 *
 * Forward functions
 * 
 **********************************************************************************
 */
static void Invalidate(ZnItem item, int reason);
static void AttributeToObj(ZnWInfo *wi, void *record, ZnAttrConfig *desc,
			   char *buffer, Tcl_Obj *result);



/*
 **********************************************************************************
 *
 * ZnUpdateItemImage --
 *
 **********************************************************************************
 */
void
ZnUpdateItemImage(void *client_data)
{
  ZnItem item = (ZnItem) client_data;

  /*printf("Invalidation of item %ld\n", item->id);*/
  Invalidate(item, ZN_CLFC_FLAG | ZN_COORDS_FLAG);  
}


/*
 **********************************************************************************
 *
 * InitAttrDesc --
 *
 **********************************************************************************
 */
static void
InitAttrDesc(ZnAttrConfig	*attr_desc)
{
  if (!attr_desc) {
    return;
  }

  while (attr_desc->type != ZN_CONFIG_END) {
    attr_desc->uid = Tk_GetUid(attr_desc->name);
    attr_desc++;
  }
}


/*
 **********************************************************************************
 *
 * AttributesInfo --
 *
 **********************************************************************************
 */
int
ZnAttributesInfo(ZnWInfo	*wi,
		 void		*record,
		 ZnAttrConfig	*desc,
		 int		argc,
		 Tcl_Obj *CONST	args[])
{
  Tk_Uid	attr_uid = NULL;
  Tcl_Obj	*l, *entries[5];
  char		buffer[256];
  
  if (argc == 1) {
    attr_uid = Tk_GetUid(Tcl_GetString(args[0]));
    
    while (True) {
      if (desc->type == ZN_CONFIG_END) {
	Tcl_AppendResult(wi->interp, "unknown attribute \"",
			 Tcl_GetString(args[0]), "\"", NULL);
	return TCL_ERROR;
      }
      else if (attr_uid == desc->uid) {
	break;
      }
      else {
	desc++;
      }
    }
    entries[0] = Tcl_NewStringObj(desc->name, -1);
    entries[1] = Tcl_NewStringObj(attribute_type_strings[desc->type], -1);
    entries[2] = Tcl_NewBooleanObj(desc->read_only ? 1 : 0);
    entries[3] = Tcl_NewStringObj("", -1);
    entries[4] = Tcl_NewStringObj("", -1);
    AttributeToObj(wi, record, desc, buffer, entries[4]);
    Tcl_SetObjResult(wi->interp, Tcl_NewListObj(5, entries));
  }
  else {
    l = Tcl_GetObjResult(wi->interp);
    while (desc->type != ZN_CONFIG_END) {
      entries[0] = Tcl_NewStringObj(desc->name, -1);
      entries[1] = Tcl_NewStringObj(attribute_type_strings[desc->type], -1);
      entries[2] = Tcl_NewBooleanObj(desc->read_only ? 1 : 0);
      entries[3] = Tcl_NewStringObj("", -1);
      entries[4] = Tcl_NewStringObj("", -1);
      AttributeToObj(wi, record, desc, buffer, entries[4]);
      Tcl_ListObjAppendElement(wi->interp, l, Tcl_NewListObj(5, entries));
      desc++;
    }
  }
  
  return TCL_OK;
}


/*
 **********************************************************************************
 *
 * ZnConfigureAttributes --
 *
 **********************************************************************************
 */
int
ZnConfigureAttributes(ZnWInfo		*wi,
		      ZnItem		item,
		      void		*record,
		      ZnAttrConfig	*attr_desc,
		      int		argc,
		      Tcl_Obj *CONST	args[],
		      int		*flags)
{
  int		i;
  Tk_Uid	attr_uid;
  ZnAttrConfig	*desc;
  ZnPtr		valp;
  char		*str;
  
  for (i = 0; i < argc; i += 2) {
    attr_uid = Tk_GetUid(Tcl_GetString(args[i]));

    desc = attr_desc;
    while (True) {
      if (desc->type == ZN_CONFIG_END) {
	Tcl_AppendResult(wi->interp, "unknown attribute \"",
			 Tcl_GetString(args[i]), "\"", NULL);
        return TCL_ERROR;
      }
      else if (attr_uid == desc->uid) {
	if (desc->read_only) {
	  Tcl_AppendResult(wi->interp, "attribute \"",
			   Tcl_GetString(args[i]), "\" can only be read", NULL);
	  return TCL_ERROR;
	}
	valp = ((char *) record) + desc->offset;
	/*printf("record <0x%X>, valp <0x%X>, offset %d\n", record, valp, desc->offset);*/
	switch (desc->type) {
	case ZN_CONFIG_GRADIENT:
	  {
	    ZnGradient *g;
	    Tk_Uid new_name = Tk_GetUid(Tcl_GetString(args[i+1]));
	    char   *name = NULL;
	    if (*((ZnGradient **) valp)) {
	      name = ZnNameOfGradient(*((ZnGradient **) valp));
	    }
	    if (name != new_name) {
	      g = ZnGetGradient(wi->interp, wi->win, new_name);
	      if (!g) {
		Tcl_AppendResult(wi->interp,
				 " gradient expected for attribute \"",
				 Tcl_GetString(args[i]), "\"", NULL);
		return TCL_ERROR;
	      }
	      if (*((ZnGradient **) valp)) {
		ZnFreeGradient(*((ZnGradient **) valp));
	      }
	      *((ZnGradient **) valp) = g;
	      *flags |= desc->flags;
	    }
	    break;
	  }
	case ZN_CONFIG_GRADIENT_LIST:
	  {
	    ZnList	 new_grad_list = NULL;
	    ZnGradient	 **grads;
	    unsigned int num_grads, j, k;
	    Tcl_Obj	 **elems;
	    
	    if (Tcl_ListObjGetElements(wi->interp, args[i+1],
				       &num_grads, &elems) == TCL_ERROR) {
	      Tcl_AppendResult(wi->interp,
			       " gradient list expected for attribute \"",
			       Tcl_GetString(args[i]), "\"", NULL);
	      return TCL_ERROR;
	    }
	    if (num_grads) {
	      new_grad_list = ZnListNew(num_grads, sizeof(ZnGradient *));
	      ZnListAssertSize(new_grad_list, num_grads);
	      grads = ZnListArray(new_grad_list);
	      for (j = 0; j < num_grads; j++) {
		str = Tcl_GetString(elems[j]);
		if (!*str) {
		  if (j == 0) {
		    goto grads_err;
		  }
		  grads[j] = grads[j-1];
		}
		else {
		  grads[j] = ZnGetGradient(wi->interp, wi->win, str);
		}
		if (!grads[j]) {
		grads_err:
		  Tcl_AppendResult(wi->interp, " invalid gradient \"", str,
				   "\" in gradient list", NULL);
		  for (k = 0; k < j; k++) {
		    ZnFreeGradient(grads[k]);
		  }
		  ZnListFree(new_grad_list);
		  return TCL_ERROR;
		}
	      }
	    }
	    if (*((ZnList *) valp)) {
	      num_grads = ZnListSize(*((ZnList *) valp));
	      grads = ZnListArray(*((ZnList *) valp));
	      for (j = 0; j < num_grads; j++) {
		if (grads[j]) {
		  ZnFreeGradient(grads[j]);
		}
	      }
	      ZnListFree(*((ZnList *) valp));
	      *((ZnList *) valp) = new_grad_list;
	      *flags |= desc->flags;
	    }
	    else {
	      if (new_grad_list) {
		*((ZnList *) valp) = new_grad_list;
		*flags |= desc->flags;
	      }
	    }
	    break;
	  }
	case ZN_CONFIG_BOOL:
	  {
	    int	b;
	    if (Tcl_GetBooleanFromObj(wi->interp, args[i+1], &b) != TCL_OK) {
	      Tcl_AppendResult(wi->interp, " boolean expected for attribute \"",
			       Tcl_GetString(args[i]), "\"", NULL);
	      return TCL_ERROR;
	    }
	    if (b ^ (ISSET(*((unsigned short *) valp), desc->bool_bit) != 0)) {
	      ASSIGN(*((unsigned short *) valp), desc->bool_bit, b);
	      *flags |= desc->flags;
	    }
	    break;
	  }
	case ZN_CONFIG_IMAGE:
	case ZN_CONFIG_BITMAP:
	  {
	    ZnImage image = ZnUnspecifiedImage;
	    ZnBool  is_bmap = True;
	    char    *name = "";

	    if (*((ZnImage *) valp) != ZnUnspecifiedImage) {
	      name = ZnNameOfImage(*((ZnImage *) valp));
	    }
	    str = Tcl_GetString(args[i+1]);
	    if (strcmp(name, str) != 0) {
	      if (strlen(str) != 0) {
		if (desc->type == ZN_CONFIG_IMAGE) {
		  image = ZnGetImage(wi, str, ZnUpdateItemImage, record);
		  if (image == ZnUnspecifiedImage) {
		    Tcl_AppendResult(wi->interp, " image expected for attribute \"",
				     Tcl_GetString(args[i]), "\"", NULL);
		    return TCL_ERROR;
		  }
		}
		else {
		  image = ZnGetImage(wi, str, NULL, NULL);
		  if ((image == ZnUnspecifiedImage) ||
		      (!(is_bmap = ZnImageIsBitmap(image)))) {
		    if (!is_bmap) {
		      ZnFreeImage(image, NULL, NULL);
		    }
		    Tcl_AppendResult(wi->interp, " bitmap expected for attribute \"",
				     Tcl_GetString(args[i]), "\"", NULL);
		    return TCL_ERROR;
		  }
		}
	      }
	      if (*((ZnImage *) valp) != ZnUnspecifiedImage) {
		ZnFreeImage(*((ZnImage *) valp), ZnUpdateItemImage, record);
	      }
	      *((ZnImage *) valp) = image;
	      *flags |= desc->flags;
	    }
	    break;
	  }
	case ZN_CONFIG_BITMAP_LIST:
	  {
	    ZnList	 new_pat_list = NULL;
	    ZnImage	 *pats;
	    unsigned int num_pats, j, k;
	    Tcl_Obj	 **elems;
	    ZnBool	 is_bmap = True;

	    if (Tcl_ListObjGetElements(wi->interp, args[i+1],
				       &num_pats, &elems) == TCL_ERROR) {
	      Tcl_AppendResult(wi->interp,
			       " pattern list expected for attribute \"",
			       Tcl_GetString(args[i]), "\"", NULL);
	      return TCL_ERROR;
	    }
	    if (num_pats) {
	      new_pat_list = ZnListNew(num_pats, sizeof(Pixmap));
	      ZnListAssertSize(new_pat_list, num_pats);
	      pats = ZnListArray(new_pat_list);
	      for (j = 0; j < num_pats; j++) {
		str = Tcl_GetString(elems[j]);
		if (strlen(str) != 0) {
		  pats[j] = ZnGetImage(wi, str, NULL, NULL);
		  if ((pats[j] == ZnUnspecifiedImage) ||
		      !(is_bmap = ZnImageIsBitmap(pats[j]))) {
		    if (!is_bmap) {
		      ZnFreeImage(pats[j], NULL, NULL);
		    }
		    for (k = 0; k < j; k++) {
		      ZnFreeImage(pats[k], NULL, NULL);
		    }
		    ZnListFree(new_pat_list);
		    Tcl_AppendResult(wi->interp, " unknown pattern \"", str,
				     "\" in pattern list", NULL);
		    return TCL_ERROR;
		  }
		}
		else {
		  pats[j] = ZnUnspecifiedImage;
		}
	      }
	    }
	    if (*((ZnList *) valp)) {
	      num_pats = ZnListSize(*((ZnList *) valp));
	      pats = ZnListArray(*((ZnList *) valp));
	      for (j = 0; j < num_pats; j++) {
		if (pats[j] != ZnUnspecifiedImage) {
		  ZnFreeImage(pats[j], NULL, NULL);
		}
	      }
	      ZnListFree(*((ZnList *) valp));
	      *((ZnList *) valp) = new_pat_list;
	      *flags |= desc->flags;
	    }
	    else {
	      if (new_pat_list) {
		*((ZnList *) valp) = new_pat_list;
		*flags |= desc->flags;
	      }
	    }
	    break;
	  }
	case ZN_CONFIG_TAG_LIST:
	  {
	    int		num_tags, j;
	    Tcl_Obj	**elems;
	    
	    if (Tcl_ListObjGetElements(wi->interp, args[i+1],
				       &num_tags, &elems) == TCL_ERROR) {
	      Tcl_AppendResult(wi->interp,
			       " tag list expected for attribute \"",
			       Tcl_GetString(args[i]), "\"", NULL);
	      return TCL_ERROR;
	    }
	    if (*((ZnList *) valp)) {
	      ZnITEM.FreeTags(item);
	      *flags |= desc->flags;
	    }
	    if (num_tags) {
	      for (j = 0; j < num_tags; j++) {
		ZnITEM.AddTag(item, Tk_GetUid(Tcl_GetString(elems[j])));
	      }
	      *flags |= desc->flags;
	    }
	    break;
	  }
	case ZN_CONFIG_STRING:
	case ZN_CONFIG_MAP_INFO:
	  {
	    char *text = NULL;
	    str = Tcl_GetString(args[i+1]);
	    if (!*((char **) valp) || strcmp(str, *((char **) valp)) != 0) {
	      if (strlen(str)) {
		text = (char *) ZnMalloc(strlen(str)+1);
		strcpy(text, str);
	      }
	      if (*((char **) valp)) {
		ZnFree(*((char **) valp));
	      }
	      *((char **) valp) = text;
	      *flags |= desc->flags;
	    }
	    break;
	  }
	case ZN_CONFIG_FONT:
	  {
	    Tk_Font font;
	    Tk_Uid  name = "";
	    if (*((Tk_Font *) valp)) {
	      name = Tk_NameOfFont(*((Tk_Font *) valp));
	    }
	    str = Tcl_GetString(args[i+1]);
	    if (strcmp(name, str) != 0) {
	      font = Tk_GetFont(wi->interp, wi->win, str);
	      if (!font) {
		Tcl_AppendResult(wi->interp, " font expected for attribute \"",
				 Tcl_GetString(args[i]), "\"", NULL);
		return TCL_ERROR;
	      }
	      if (*((Tk_Font *) valp)) {
		Tk_FreeFont(*((Tk_Font *) valp));
	      }
	      *((Tk_Font *) valp) = font;
	      *flags |= desc->flags;
	    }
	    break;
	  }
	case ZN_CONFIG_EDGE_LIST:
	  {
	    ZnBorder border;

	    if (ZnGetBorder(wi, args[i+1], &border) == TCL_ERROR) {
	      Tcl_AppendResult(wi->interp, " edge list expected for attribute \"",
			       Tcl_GetString(args[i]), "\"", NULL);
	      return TCL_ERROR;
	    }
	    if (border != *((ZnBorder *) valp)) {
	      *((ZnBorder *) valp) = border;
	      *flags |= desc->flags;
	    }
	    break;
	  }
	case ZN_CONFIG_LINE_SHAPE:
	  {
	    ZnLineShape line_shape;

	    if (ZnGetLineShape(wi, Tcl_GetString(args[i+1]), &line_shape) == TCL_ERROR) {
	      Tcl_AppendResult(wi->interp, " line shape expected for attribute \"",
			       Tcl_GetString(args[i]), "\"", NULL);
	      return TCL_ERROR;
	    }
	    if (line_shape != *((ZnLineShape *) valp)) {
	      *((ZnLineShape *) valp) = line_shape;
	      *flags |= desc->flags;
	    }
	    break;
	  }
	case ZN_CONFIG_LINE_STYLE:
	  {
	    ZnLineStyle    line_style;

	    if (ZnGetLineStyle(wi, Tcl_GetString(args[i+1]), &line_style) == TCL_ERROR) {
	      Tcl_AppendResult(wi->interp, " line style expected for attribute \"",
			       Tcl_GetString(args[i]), "\"", NULL);
	      return TCL_ERROR;
	    }
	    if (line_style != *((ZnLineStyle *) valp)) {
	      *((ZnLineStyle *) valp) = line_style;
	      *flags |= desc->flags;
	    }
	    break;
	  }
	case ZN_CONFIG_LINE_END:
	  {
	    ZnLineEnd line_end = NULL;
	    str = Tcl_GetString(args[i+1]);
	    if (strlen(str) != 0) {
	      line_end = ZnLineEndCreate(wi->interp, str);
	      if (line_end == NULL) {
		return TCL_ERROR;
	      }
	    }
	    if (*((ZnLineEnd *) valp) != NULL) {
	      ZnLineEndDelete(*((ZnLineEnd *) valp));
	      *((ZnLineEnd *) valp) = line_end;
	      *flags |= desc->flags;
	    }
	    else {
	      if (line_end != NULL) {
		*((ZnLineEnd *) valp) = line_end;
		*flags |= desc->flags;
	      }
	    }
	    break;
	  }
	case ZN_CONFIG_RELIEF:
	  {
	    ZnReliefStyle relief;
	    if (ZnGetRelief(wi, Tcl_GetString(args[i+1]), &relief) == TCL_ERROR) {
	      Tcl_AppendResult(wi->interp, " relief expected for attribute \"",
			       Tcl_GetString(args[i]), "\"", NULL);
	      return TCL_ERROR;
	    }
	    if (relief != *((ZnReliefStyle *) valp)) {
	      /*printf("valp <0x%X>, flags <0x%X>, relief %d\n", valp, flags, relief);*/
	      *((ZnReliefStyle *) valp) = relief;
	      *flags |= desc->flags;
	    }
	    break;
	  }
	case ZN_CONFIG_JOIN_STYLE:
	  {
	    int	join;
	    if (Tk_GetJoinStyle(wi->interp, Tcl_GetString(args[i+1]), &join) == TCL_ERROR) {
	      Tcl_AppendResult(wi->interp, " join expected for attribute \"",
			       Tcl_GetString(args[i]), "\"", NULL);
	      return TCL_ERROR;
	    }
	    if (join != *((int *) valp)) {
	      *((int *) valp) = join;
	      *flags |= desc->flags;
	    }
	    break;
	  }
	case ZN_CONFIG_CAP_STYLE:
	  {
	    int	cap;
	    if (Tk_GetCapStyle(wi->interp, Tcl_GetString(args[i+1]), &cap) == TCL_ERROR) {
	      Tcl_AppendResult(wi->interp, " cap expected for attribute \"",
			       Tcl_GetString(args[i]), "\"", NULL);
	      return TCL_ERROR;
	    }
	    if (cap != *((int *) valp)) {
	      *((int *) valp) = cap;
	      *flags |= desc->flags;
	    }
	    break;
	  }
	case ZN_CONFIG_POINT:
	  {
	    ZnPoint	point;
	    int		largc;
	    Tcl_Obj	**largv;
	    double	d;

	    if ((Tcl_ListObjGetElements(wi->interp, args[i+1],
					&largc, &largv) == TCL_ERROR) ||
		(largc != 2)) {
	    point_error:
	      Tcl_AppendResult(wi->interp, " position expected for attribute \"",
			       Tcl_GetString(args[i]), "\"", NULL);
	      return TCL_ERROR;
	    }
	    if (Tcl_GetDoubleFromObj(wi->interp, largv[0], &d) == TCL_ERROR) {
	      goto point_error;
	    }
	    point.x = d;
	    if (Tcl_GetDoubleFromObj(wi->interp, largv[1], &d) == TCL_ERROR) {
	      goto point_error;
	    }
	    point.y = d;
	    if ((point.x != ((ZnPoint *) valp)->x) ||
		(point.y != ((ZnPoint *) valp)->y)) {
	      *((ZnPoint *) valp) = point;
	      *flags |= desc->flags;
	    }
	    break;
	  }
	case ZN_CONFIG_DIM:
	  {
	    ZnDim dim;
	    if (Tcl_GetDoubleFromObj(wi->interp, args[i+1], &dim) == TCL_ERROR) {
	      Tcl_AppendResult(wi->interp, " dimension expected for attribute \"",
			       Tcl_GetString(args[i+1]), "\"", NULL);
	      return TCL_ERROR;
	    }
	    if (dim != *((ZnDim *) valp)) {
	      *((ZnDim *) valp) = dim;
	      *flags |= desc->flags;
	    }
	    break;
	  }
	case ZN_CONFIG_PRI:
	  {
	    int	pri;
	    if (Tcl_GetIntFromObj(wi->interp, args[i+1], &pri) == TCL_ERROR) {
	      return TCL_ERROR;
	    }
	    if (pri < 0) {
	      Tcl_AppendResult(wi->interp, " priority must be a positive integer \"",
			       Tcl_GetString(args[i+1]), "\"", NULL);
	      return TCL_ERROR;
	    }
	    if (pri != *((unsigned short *) valp)) {
	      *((unsigned short *) valp) = pri;
	      ZnITEM.UpdateItemPriority(item, ZN_NO_ITEM, True);
	      *flags |= desc->flags;
	    }
	    break;
	  }
	case ZN_CONFIG_ITEM:
	  /*
	   * Can be an item id or a tag. In this last case
	   * consider only the first item (unspecified order)
	   * associated with the tag.
	   */
	  {
	    ZnItem	item2;
	    int		result;
	    ZnTagSearch	*search_var = NULL;

	    if (strlen(Tcl_GetString(args[i+1])) == 0) {
	      item2 = ZN_NO_ITEM;
	    }
	    else {
	      result = ZnItemWithTagOrId(wi, args[i+1], &item2, &search_var);
	      ZnTagSearchDestroy(search_var);
	      if ((result == TCL_ERROR) || (item2 == ZN_NO_ITEM)) {
		Tcl_AppendResult(wi->interp, " unknown item \"",
				 Tcl_GetString(args[i+1]), "\"", NULL);
		return TCL_ERROR;
	      }
	    }
	    if (item2 != *((ZnItem *) valp)) {
	      *((ZnItem *) valp) = item2;
	      *flags |= desc->flags;
	    }
	  }
	  break;
	case ZN_CONFIG_WINDOW:
	  {
	    Tk_Window	win, ancestor, parent;
	    str = Tcl_GetString(args[i+1]);
	    if (strlen(str) == 0) {
	      win = NULL;
	    }
	    else {
	      win = Tk_NameToWindow(wi->interp, str, wi->win);
	      if (win == NULL) {
		return TCL_ERROR;
	      }
	      else {
		/*
		 * Make sure that the zinc widget is either the parent of the
		 * window associated with the item or a descendant of that
		 * parent.  Also, don't allow a toplevel window or the widget
		 * itself to be managed.
		 */
		parent = Tk_Parent(win);
		for (ancestor = wi->win; ; ancestor = Tk_Parent(ancestor)) {
		  if (ancestor == parent) {
		    break;
		  }
		  if (((Tk_FakeWin *) (ancestor))->flags & TK_TOP_LEVEL) {
		  badWindow:
		    Tcl_AppendResult(wi->interp, "can't use ",
				     Tk_PathName(win),
				     " in a window item of this zinc widget",
				     (char *) NULL);
		    win = NULL;
		    return TCL_ERROR;
		  }
		}
		if (((Tk_FakeWin *) (win))->flags & TK_TOP_LEVEL) {
		  goto badWindow;
		}
		if (win == wi->win) {
		  goto badWindow;
		}
		if (win != *((Tk_Window *) valp)) {
		  *((Tk_Window *) valp) = win;
		  *flags |= desc->flags;
		}
	      }
	    }
	  }
	  break;
	case ZN_CONFIG_CHAR:
	case ZN_CONFIG_UCHAR:
	case ZN_CONFIG_ALPHA:
	  {
	    int integer;
	    if (Tcl_GetIntFromObj(wi->interp, args[i+1], &integer) == TCL_ERROR) {
	      return TCL_ERROR;
	    }
	    switch (desc->type) {
	    case ZN_CONFIG_UCHAR:
	      if (integer < 0) {
		integer = 0;
	      }
	    case ZN_CONFIG_ALPHA:
	      if (integer < 0) {
		integer = 0;
	      }
	      if (integer > 100) {
		integer = 100;
	      }
	      break;
	    }
	    if (integer != *((char *) valp)) {
	      *((char *) valp) = integer;
	      *flags |= desc->flags;
	    }
	    break;
	  }
	case ZN_CONFIG_SHORT:
	case ZN_CONFIG_USHORT:
	  {
	    int integer;
	    if (Tcl_GetIntFromObj(wi->interp, args[i+1], &integer) == TCL_ERROR) {
	      return TCL_ERROR;
	    }
	    if (desc->type == ZN_CONFIG_SHORT) {
	      if (integer < SHRT_MIN) {
		integer = SHRT_MIN;
	      }
	      else if (integer > SHRT_MAX) {
		integer = SHRT_MAX;
	      }
	      if (integer != *((short *) valp)) {
		*((short *) valp) = integer;
		*flags |= desc->flags;
	      }
	    }
	    else {
	      if (integer < 0) {
		integer = 0;
	      }
	      else if (integer > USHRT_MAX) {
		integer = USHRT_MAX;
	      }
	      if (integer != *((unsigned short *) valp)) {
		*((unsigned short *) valp) = integer;
		*flags |= desc->flags;
	      }
	    }
	    break;
	  }
	case ZN_CONFIG_INT:
	case ZN_CONFIG_UINT:
	case ZN_CONFIG_ANGLE:
	  {
	    int integer;
	    if (Tcl_GetIntFromObj(wi->interp, args[i+1], &integer) == TCL_ERROR) {
	      return TCL_ERROR;
	    }
	    switch (desc->type) {
	    case ZN_CONFIG_ANGLE:
	      if ((integer > 360) || (integer < -360)) {
		integer = integer % 360;
	      }
	      break;
	    case ZN_CONFIG_UINT:
	      if (integer < 0) {
		integer = 0;
	      }
	      break;
	    }
	    if (integer != *((int *) valp)) {
	      *((int *) valp) = integer;
	      *flags |= desc->flags;
	    }
	    break;
	  }
	case ZN_CONFIG_FILL_RULE:
	  {
	    ZnFillRule fill_rule;

	    if (ZnGetFillRule(wi, Tcl_GetString(args[i+1]), &fill_rule) == TCL_ERROR) {
	      Tcl_AppendResult(wi->interp, " fill rule expected for attribute \"",
			       Tcl_GetString(args[i]), "\"", NULL);
	      return TCL_ERROR;
	    }
	    if (fill_rule != *((ZnFillRule *) valp)) {
	      *((ZnFillRule *) valp) = fill_rule;
	      *flags |= desc->flags;
	    }
	    break;
	  }
	case ZN_CONFIG_ALIGNMENT:
	  {
	    Tk_Justify justify;
	    if (Tk_GetJustify(wi->interp, Tcl_GetString(args[i+1]), &justify) == TCL_ERROR) {
	      Tcl_AppendResult(wi->interp, " justify expected for attribute \"",
			       Tcl_GetString(args[i]), "\"", NULL);
	      return TCL_ERROR;
	    }
	    if (justify != *((Tk_Justify *) valp)) {
	      *((Tk_Justify *) valp) = justify;
	      *flags |= desc->flags;
	    }
	    break;
	  }
	case ZN_CONFIG_ANCHOR:
	  {
	    Tk_Anchor	anchor;
	    if (Tk_GetAnchor(wi->interp, Tcl_GetString(args[i+1]), &anchor) == TCL_ERROR) {
	      Tcl_AppendResult(wi->interp, " anchor expected for attribute \"",
			       Tcl_GetString(args[i]), "\"", NULL);
	      return TCL_ERROR;
	    }
	    if (anchor != *((Tk_Anchor *) valp)) {
	      *((Tk_Anchor *) valp) = anchor;
	      *flags |= desc->flags;
	    }
	    break;
	  }
	case ZN_CONFIG_LABEL_FORMAT:
	  {
	    ZnLabelFormat frmt = NULL;

	    str = Tcl_GetString(args[i+1]);
	    while (*str && (*str == ' ')) {
	      str++;
	    }
	    if (strlen(str) != 0) {
	      frmt = ZnLFCreate(wi->interp, str,
				ZnFIELD.NumFields(item->class->GetFieldSet(item)));
	      if (frmt == NULL) {
		return TCL_ERROR;
	      }
	    }

	    if (*((ZnLabelFormat *) valp) != NULL) {
	      ZnLFDelete(*((ZnLabelFormat *) valp));
	      *((ZnLabelFormat *) valp) = frmt;
	      *flags |= desc->flags;
	    }
	    else {
	      if (frmt != NULL) {
		*((ZnLabelFormat *) valp) = frmt;
		*flags |= desc->flags;
	      }
	    }
	    break;
	  }

	case ZN_CONFIG_AUTO_ALIGNMENT:
	  {
	    ZnAutoAlign aa;

	    if (ZnGetAutoAlign(wi, Tcl_GetString(args[i+1]), &aa) == TCL_ERROR) {
	      Tcl_AppendResult(wi->interp, " auto alignment expected for attribute \"",
			       Tcl_GetString(args[i]), "\"", NULL);
	      return TCL_ERROR;
	    }
	    if ((aa.automatic != ((ZnAutoAlign *) valp)->automatic) ||
		(aa.align[0] != ((ZnAutoAlign *) valp)->align[0]) ||
		(aa.align[1] != ((ZnAutoAlign *) valp)->align[1]) ||
		(aa.align[2] != ((ZnAutoAlign *) valp)->align[2])) {
	      *((ZnAutoAlign *) valp) = aa;
	      *flags |= desc->flags;
	    }
	    break;
	  }

	case ZN_CONFIG_LEADER_ANCHORS:
	  {
	    ZnLeaderAnchors lanch = NULL;
	    if (ZnGetLeaderAnchors(wi, Tcl_GetString(args[i+1]), &lanch) == TCL_ERROR) {
	      Tcl_AppendResult(wi->interp, " leader anchors expected for attribute \"",
			       Tcl_GetString(args[i]), "\"", NULL);
	      return TCL_ERROR;
	    }
	    if (*((ZnLeaderAnchors *) valp) != NULL) {
	      ZnFree(*((ZnLeaderAnchors *) valp));
	      *((ZnLeaderAnchors *) valp) = lanch;
	      *flags |= desc->flags;
	    }
	    else {
	      if (lanch != NULL) {
		*((ZnLeaderAnchors *) valp) = lanch;
		*flags |= desc->flags;
	      }
	    }
	    break;
	  }
	}
	break;
      }
      else {
	desc++;
      }
    }
  }
  return TCL_OK;
}


/*
 **********************************************************************************
 *
 * AttributeToObj --
 *
 *	Returns the obj representation of the attribute pointed
 *	by 'valp'. The attribute type is given by 'type'. The function
 *	never fail. The buffer parameter should be able to
 *	contain 256 characters at least.
 *
 **********************************************************************************
 */
static void
AttributeToObj(ZnWInfo		*wi,
	       void		*record,
	       ZnAttrConfig	*desc,
	       char		*buffer,
	       Tcl_Obj		*result)
{
  char		*valp = ((char *) record) + desc->offset;
  char		*str = "";
  Tcl_Obj	*o;
  unsigned int	i;

  switch (desc->type) {
  case ZN_CONFIG_GRADIENT:
    if (*((ZnGradient **) valp)) {
      str = ZnNameOfGradient(*((ZnGradient **) valp));
    }
    break;
  case ZN_CONFIG_GRADIENT_LIST:
    {
      unsigned int num_grads;
      ZnGradient   **grads;

      if (*((ZnList *) valp)) {
	grads = ZnListArray(*((ZnList *) valp));
	num_grads = ZnListSize(*((ZnList *) valp));
	
	for (i = 0; i < num_grads; i++) {
	  o = Tcl_NewStringObj(ZnNameOfGradient(grads[i]), -1);
	  Tcl_ListObjAppendElement(wi->interp, result, o);
	}
	return;
      }
    }
    break;
  case ZN_CONFIG_BOOL:
    Tcl_SetBooleanObj(result, ISSET(*((unsigned short *) valp), desc->bool_bit)?1:0);
    return;
  case ZN_CONFIG_IMAGE:
  case ZN_CONFIG_BITMAP:
    if (*((ZnImage *) valp)) {
      str = ZnNameOfImage(*((ZnImage *) valp));
    }
    break;
  case ZN_CONFIG_BITMAP_LIST:
    {
      unsigned int num_pats=0;
      ZnImage	   *pats;

      if (*((ZnList *) valp)) {
	pats = (ZnImage *) ZnListArray(*((ZnList *) valp));
	num_pats = ZnListSize(*((ZnList *) valp));

	for (i = 0; i < num_pats; i++) {
	  if (pats[i] != ZnUnspecifiedImage) {
	    o = Tcl_NewStringObj(ZnNameOfImage(pats[i]), -1);
	  }
	  else {
	    o = Tcl_NewStringObj("", -1);
	  }
	  Tcl_ListObjAppendElement(wi->interp, result, o);
	}
	return;
      }
      break;
    }
  case ZN_CONFIG_TAG_LIST:
    {
      unsigned int num_tags=0;
      Tk_Uid	   *tags;

      if (*((ZnList *) valp)) {
	tags = (Tk_Uid *) ZnListArray(*((ZnList *) valp));
	num_tags = ZnListSize(*((ZnList *) valp));
	for (i = 0; i < num_tags; i++) {
	  Tcl_ListObjAppendElement(wi->interp, result,
				   Tcl_NewStringObj(tags[i], -1));
	}
	return;
      }
      break;
    }
  case ZN_CONFIG_STRING:
  case ZN_CONFIG_MAP_INFO:
    if (*((char **) valp)) {
      str = *((char **) valp);
    }
    break;
  case ZN_CONFIG_FONT:
    if (*((Tk_Font *) valp)) {
      str = (char *) Tk_NameOfFont(*((Tk_Font *) valp));
    }
    break;
  case ZN_CONFIG_EDGE_LIST:
    str = buffer;
    ZnNameOfBorder(*((ZnBorder *) valp), buffer);
    break;
  case ZN_CONFIG_LINE_SHAPE:
    str = ZnNameOfLineShape(*((ZnLineShape *) valp));
    break;
  case ZN_CONFIG_FILL_RULE:
    str = ZnNameOfFillRule(*((ZnFillRule *) valp));
    break;
  case ZN_CONFIG_LINE_STYLE:
    str = ZnNameOfLineStyle(*((ZnLineStyle *) valp));
    break;
  case ZN_CONFIG_LINE_END:
    if (*((ZnLineEnd *) valp)) {
      str = ZnLineEndGetString(*((ZnLineEnd *) valp));
    }
    break;
  case ZN_CONFIG_RELIEF:
    str = ZnNameOfRelief(*((ZnReliefStyle *) valp));
    break;
  case ZN_CONFIG_JOIN_STYLE:
    str = (char *) Tk_NameOfJoinStyle(*((int *) valp));
    break;
  case ZN_CONFIG_CAP_STYLE:
    str = (char *) Tk_NameOfCapStyle(*((int *) valp));
    break;
  case ZN_CONFIG_POINT:
    Tcl_ListObjAppendElement(wi->interp, result, Tcl_NewDoubleObj(((ZnPoint *) valp)->x));
    Tcl_ListObjAppendElement(wi->interp, result, Tcl_NewDoubleObj(((ZnPoint *) valp)->y));
    return;
  case ZN_CONFIG_ITEM:
    if (*((ZnItem *) valp) != ZN_NO_ITEM) {
      Tcl_SetLongObj(result, (int) (*((ZnItem *) valp))->id);
      return;
    }
    break;
  case ZN_CONFIG_WINDOW:
    if (*((Tk_Window *) valp) != NULL) {
      str = Tk_PathName(*((Tk_Window *) valp));
    }
    break;
  case ZN_CONFIG_CHAR:
    Tcl_SetIntObj(result, *((char *) valp));
    return;    
  case ZN_CONFIG_UCHAR:
  case ZN_CONFIG_ALPHA:
    Tcl_SetIntObj(result, *((unsigned char *) valp));
    return;    
  case ZN_CONFIG_USHORT:
  case ZN_CONFIG_PRI:
    Tcl_SetIntObj(result, *((unsigned short *) valp));
    return;
  case ZN_CONFIG_SHORT:
    Tcl_SetIntObj(result, *((short *) valp));
    return;
  case ZN_CONFIG_UINT:
    Tcl_SetIntObj(result, *((unsigned int *) valp));
    return;
  case ZN_CONFIG_INT:
  case ZN_CONFIG_ANGLE:
    Tcl_SetIntObj(result, *((int *) valp));
    return;
  case ZN_CONFIG_DIM:
    Tcl_SetDoubleObj(result, *((ZnDim *) valp));
    return;
  case ZN_CONFIG_ALIGNMENT:
    str = (char *) Tk_NameOfJustify(*((Tk_Justify *) valp));
    break;
  case ZN_CONFIG_ANCHOR:
    str = (char *) Tk_NameOfAnchor(*((Tk_Anchor *) valp));
    break;
  case ZN_CONFIG_LABEL_FORMAT:
    if (*((ZnLabelFormat *) valp)) {
      str = ZnLFGetString(*((ZnLabelFormat *) valp));
    }
    break;
  case ZN_CONFIG_AUTO_ALIGNMENT:
    str = buffer;
    ZnNameOfAutoAlign((ZnAutoAlign *) valp, buffer);
    break;
  case ZN_CONFIG_LEADER_ANCHORS:
    str = buffer;
    ZnNameOfLeaderAnchors(*((ZnLeaderAnchors *) valp), buffer);
    break;
  }
  Tcl_SetStringObj(result, str, -1);
}


/*
 **********************************************************************************
 *
 * ZnQueryAttribute --
 *
 **********************************************************************************
 */
int
ZnQueryAttribute(ZnWInfo	*wi,
		 void		*record,
		 ZnAttrConfig	*desc,
		 Tcl_Obj	*attr_name)
{
  Tk_Uid	attr_uid = Tk_GetUid(Tcl_GetString(attr_name));
  Tcl_Obj	*result = NULL;
  char		buffer[256];
  
  while (True) {
    if (desc->type == ZN_CONFIG_END) {
      Tcl_AppendResult(wi->interp, "unknown attribute \"", attr_uid, "\"", NULL);
      return TCL_ERROR;
    }
    else if (attr_uid == desc->uid) {
      result = Tcl_GetObjResult(wi->interp);
      AttributeToObj(wi, record, desc, buffer, result);
      break;
    }
    else {
      desc++;
    }
  }
  return TCL_OK;
}


/*
 **********************************************************************************
 *
 * ZnItemClassList --
 *
 **********************************************************************************
 */
ZnList
ZnItemClassList()
{
  return item_classes;
}

/*
 **********************************************************************************
 *
 * ZnLookupItemClass --
 *
 **********************************************************************************
 */
ZnItemClass
ZnLookupItemClass(char	*class_name)
{
  ZnItemClass	*class;
  int		i, num_classes;

  class = (ZnItemClass *) ZnListArray(item_classes);
  num_classes = ZnListSize(item_classes);
  for (i = 0; i < num_classes; i++) {
    if (strcmp((class[i])->name, class_name) == 0) {
      return class[i];
    }
  }
  
  return NULL;
}

/*
 **********************************************************************************
 *
 * ZnAddItemClass --
 *
 **********************************************************************************
 */
void
ZnAddItemClass(ZnItemClass	class)
{
  if (!ZnLookupItemClass(class->name)) {
    ZnListAdd(item_classes, &class, ZnListTail);
    InitAttrDesc(class->attr_desc);
  }
}

/*
 **********************************************************************************
 *
 * ZnItemInit --
 *	Initialize classes static state.
 *
 **********************************************************************************
 */
void
ZnItemInit()
{
  /* First check if static part already inited */
  if (item_classes == NULL) {
    item_classes = ZnListNew(16, sizeof(ZnItemClass));
    ZnAddItemClass(ZnTrack);
    ZnAddItemClass(ZnWayPoint);
    ZnAddItemClass(ZnMap);
    ZnAddItemClass(ZnReticle);
    ZnAddItemClass(ZnTabular);
    ZnAddItemClass(ZnRectangle);
    ZnAddItemClass(ZnArc);
    ZnAddItemClass(ZnCurve);
    ZnAddItemClass(ZnTriangles);
    ZnAddItemClass(ZnGroup);
    ZnAddItemClass(ZnIcon);
    ZnAddItemClass(ZnText);
    ZnAddItemClass(ZnWindow);
    InitAttrDesc(ZnFIELD.attr_desc);
  }
}


/*
 **********************************************************************************
 *
 * UpdateItemDependency -- Method
 *	Update the group dependency list following a change in the
 *	connection of an item.
 *
 **********************************************************************************
 */
static void
UpdateItemDependency(ZnItem	item,
		     ZnItem	old_connection)
{
  if (old_connection == ZN_NO_ITEM) {
    /* Add a connection */
    ZnInsertDependentItem(item);
  }
  else if (item->connected_item == ZN_NO_ITEM) {
    /* Remove a connection */
    ZnExtractDependentItem(item);
  }
  else {
    /* Move at end to ensure that it will be updated after
     * the (new) item it depends upon.
     */
    ZnExtractDependentItem(item);
    ZnInsertDependentItem(item);
  }
}


/*
 **********************************************************************************
 *
 * ExtractItem --
 *	Extract an item from its context, includes updating graphic
 *	state flags.
 *
 **********************************************************************************
 */
static void
ExtractItem(ZnItem	item)
{
  ZnWInfo	*wi = item->wi;
  ZnItem	group = item->parent;

  /* damage bounding boxes */
  if (ISSET(item->flags, ZN_VISIBLE_BIT)) {
    ZnDamage(wi, &item->item_bounding_box);
  }
  
  /*
   * Tell that we need to repick
   */
  if (item->class != ZnGroup) {
    SET(wi->flags, ZN_INTERNAL_NEED_REPICK);
  }

  if (group != ZN_NO_ITEM) {
    /* Remove me from dependency list. */
    ZnExtractDependentItem(item);
    
    /* Disconnect all dependents on me. */
    ZnDisconnectDependentItems(item);
    /*
     * Remove me as a clip item.
     */
    ZnGroupRemoveClip(group, item);
    /*
     * Remove me from item list.
     */
    ZnGroupExtractItem(item);
  }
}


/*
 **********************************************************************************
 *
 * InsertItem -- Method
 *
 *	Insert an item in the display list according to its priority.
 *	It is inserted in front of items of lower or same priority. If
 *	mark_item is not ZN_NO_ITEM the insertion is done relative
 *	to this item, before it if 'before' is True, after it otherwise.
 *	mark_item must be in the group 'group'.
 *
 **********************************************************************************
 */
static void
InsertItem(ZnItem	item,
	   ZnItem	grp,
	   ZnItem	mark_item,
	   ZnBool	before)
{
  if (!grp) {
    grp = item->wi->top_group;
  }
  item->parent = grp;
  
  if (mark_item && (mark_item->parent != grp)) {
    mark_item = ZN_NO_ITEM;
  }
  
  ZnGroupInsertItem(grp, item, mark_item, before);
}


/*
 **********************************************************************************
 *
 * UpdateItemPriority -- Method
 *	Reorder a group's item list following a change in an
 *	item priority or a call to lower/raise.
 *
 **********************************************************************************
 */
static void
UpdateItemPriority(ZnItem	item,
		   ZnItem	mark_item,
		   ZnBool	before)
{
  ZnItem	parent = item->parent;
  
  ZnGroupExtractItem(item);
  InsertItem(item, parent, mark_item, before);
  Invalidate(item, ZN_DRAW_FLAG);
  SET(item->wi->flags, ZN_INTERNAL_NEED_REPICK);
}


/*
 **********************************************************************************
 *
 * SetId,
 * FreeId -- Method
 *	Get a fresh object id from the widget and enter the new
 *	object with this id in the object hash table. The id is
 *	incremented. FreeId on the other hand suppress the item
 *	from the hash table and set its object id to zero.
 *
 **********************************************************************************
 */
static void
SetId(ZnItem	item)
{
  ZnWInfo	*wi = item->wi;
  Tcl_HashEntry	*entry;
  int		dummy;

  item->id = wi->obj_id;
  wi->obj_id++;
  entry = Tcl_CreateHashEntry(wi->id_table, (char *) item->id, &dummy);
  Tcl_SetHashValue(entry, item);  
}

static void
FreeId(ZnItem	item)
{
  Tcl_HashEntry	*entry;
  
  if (item->id) {
    entry = Tcl_FindHashEntry(item->wi->id_table, (char *) item->id);
    if (entry) {
      Tcl_DeleteHashEntry(entry);
      item->id = 0;
    }
  }
}

/*
 **********************************************************************************
 *
 * AddTag -- Method
 *	Add a tag to the item. If the tag is already on the list it
 *	is not added. As a side effect the tag/item pair is added to
 *	the tag table of the widget.
 *	'tag' must be a Tk_Uid.
 *
 **********************************************************************************
 */
static void
AddTag(ZnItem	item,
       Tk_Uid	tag)
{
  int	num, i;
  char	**ptr;
  
  /*
   * No tags yet.
   */
  if (!item->tags) {
    item->tags = ZnListNew(1, sizeof(char *));
  }
  else {
    /*
     * If the tag is already there, that's done.
     */
    ptr = (char **) ZnListArray(item->tags);
    num = ZnListSize(item->tags);
    for (i = 0; i < num; i++) {
      if (ptr[i] == tag) {
	return;
      }
    }
  }
  /*
   * Add it.
   */
  ZnListAdd(item->tags, (void *) &tag, ZnListTail);
}

/*
 **********************************************************************************
 *
 * RemoveTag -- Method
 *
 **********************************************************************************
 */
static void
RemoveTag(ZnItem	item,
	  Tk_Uid	tag)
{
  unsigned int	indx, num;
  char		**ptr;
  
  if (!item->tags) {
    return;
  }
  /*
   * look up the tag in the list.
   */
  ptr = (char **) ZnListArray(item->tags);
  num = ZnListSize(item->tags);
  for (indx = 0; indx < num; indx++) {
    if (ptr[indx] == tag) {
      /* The tag list is not freed when empty to avoid
       * overhead when using tags intensively. */
      ZnListDelete(item->tags, indx);
      return;
    }
  }
}

/*
 **********************************************************************************
 *
 * FreeTags -- Method
 *
 **********************************************************************************
 */
static void
FreeTags(ZnItem	item)
{
  if (!item->tags) {
    return;
  }
  ZnListFree(item->tags);
  item->tags = NULL;
}


/*
 **********************************************************************************
 *
 * HasTag -- Method
 *
 **********************************************************************************
 */
static ZnBool
HasTag(ZnItem	item,
       Tk_Uid	tag)
{
  int		num;
  Tk_Uid	*tags;

  if (!item->tags || !ZnListSize(item->tags)) {
    return False;
  }
  else {
    num = ZnListSize(item->tags);
    tags = ZnListArray(item->tags);
    for (tags = ZnListArray(item->tags); num > 0; tags++, num--) {
      if (*tags == tag) {
	return True;
      }
    }
  }
  return False;
}


/*
 **********************************************************************************
 *
 * ZnCreateItem --
 *
 *	InsertItem and ConfigureItem must be called after CreateItem
 *	to finalize the setup of a new item. This is so even if
 *	there are no attributes to be changed after creation.
 *	ConfigureItem must be called in this case with the 'init'
 *	parameter set to True.
 *
 **********************************************************************************
 */
ZnItem
ZnCreateItem(ZnWInfo	    *wi,
	     ZnItemClass   item_class,
	     int	    *argc,
	     Tcl_Obj *CONST *args[])
{
  ZnItem	item;

  item = ZnMalloc(item_class->size);
  
  /* Initialize common state */
  item->class = item_class;
  item->wi = wi;
  item->parent = NULL;
  item->previous = ZN_NO_ITEM;
  item->next = ZN_NO_ITEM;
  CLEAR(item->flags, ZN_UPDATE_DEPENDENT_BIT);
  item->inv_flags = 0;
  item->transfo = NULL;
  item->parent = NULL;
  item->connected_item = ZN_NO_ITEM;
#ifdef GL
#ifdef GL_LIST
  item->gl_list = 0;
#endif
#endif
  ZnResetBBox(&item->item_bounding_box);

  /* Init item specific attributes */
  if (item_class->Init(item, argc, args) == TCL_ERROR) {
    ZnFree(item);
    return ZN_NO_ITEM;
  }

  SetId(item);
  item->tags = NULL;
  
  SET(wi->flags, ZN_INTERNAL_NEED_REPICK);
  wi->num_items++;

  return (item);
}


/*
 **********************************************************************************
 *
 * CloneItem -- Method
 *	Can't clone the top level group.
 *
 **********************************************************************************
 */
static ZnItem
CloneItem(ZnItem	model)
{
  ZnWInfo	*wi = model->wi;
  ZnItem 	item;
  Tk_Uid	*tags;
  unsigned int	num_tags;
  int		i;
  
  if (!model->parent) {
    return ZN_NO_ITEM;
  }
  
  item = ZnMalloc(model->class->size);
  memcpy(item, model, model->class->size);

  item->previous = ZN_NO_ITEM;
  item->next = ZN_NO_ITEM;
  item->connected_item = ZN_NO_ITEM;
  CLEAR(item->flags, ZN_UPDATE_DEPENDENT_BIT);
  item->inv_flags = 0;
  SetId(item);

  if (model->tags) {
    item->tags = NULL;
    tags = (Tk_Uid *) ZnListArray(model->tags);
    num_tags = ZnListSize(model->tags);
    for (i = num_tags-1; i >= 0; i--, tags++) {
      AddTag(item, *tags);
    }
  }

  if (item->transfo) {
    item->transfo = ZnTransfoDuplicate(item->transfo);
  }
  
  /* Call item's clone to duplicate non shared resources */
  item->class->Clone(item);

  SET(wi->flags, ZN_INTERNAL_NEED_REPICK);
  wi->num_items++;

  Invalidate(item, ZN_COORDS_FLAG);

  return item;
}


/*
 **********************************************************************************
 *
 * ConfigureItem -- Method
 *
 **********************************************************************************
 */
static int
ConfigureItem(ZnItem		item,
	      int		field,
	      int		argc,
	      Tcl_Obj	*CONST	argv[],
	      ZnBool		init)
{
  ZnWInfo	*wi = item->wi;
  int		flags;
  ZnBool	previous_visible = init ? False : ISSET(item->flags, ZN_VISIBLE_BIT);

  flags = 0;
  ASSIGN(flags, ZN_COORDS_FLAG, init);
  if (argv) {
    if (field < 0){
      if (item->class->Configure(item, argc, argv, &flags) == TCL_ERROR) {
	return TCL_ERROR;
      }
      if (item->class->GetFieldSet && ISSET(flags, ZN_CLFC_FLAG)) {
	ZnFIELD.ClearFieldCache(item->class->GetFieldSet(item), -1);
      }
    }
    else if (item->class->GetFieldSet) {
      if (ZnFIELD.ConfigureField(item->class->GetFieldSet(item),
				 field, argc, argv, &flags) == TCL_ERROR) {
	return TCL_ERROR;
      }
    }
    else {
      return TCL_ERROR;
    }
  }

  if (previous_visible && ISCLEAR(item->flags, ZN_VISIBLE_BIT)) {
    /*
     * Special case when the item has its visibility
     * just turned out.
     */
    ZnDamage(wi, &item->item_bounding_box);
  }
  
  Invalidate(item, flags);
  
  return TCL_OK;
}


/*
 **********************************************************************************
 *
 * QueryItem -- Method
 *
 **********************************************************************************
 */
static int
QueryItem(ZnItem		item,
	  int			field,
	  int			argc,
	  Tcl_Obj *CONST	argv[])
{
  if (field < 0) {
    return item->class->Query(item, argc, argv);
  }
  else if (item->class->GetFieldSet) {
    return ZnFIELD.QueryField(item->class->GetFieldSet(item),
			      field, argc, argv);
  }
  
  return TCL_ERROR;
}


/*
 **********************************************************************************
 *
 * ComposeTransform --
 *	Compose a transform transfo with current_t to new_t.
 *
 **********************************************************************************
 */
static void
ComposeTransform(ZnTransfo	*transfo,
		 ZnPoint	*pos,
		 ZnTransfo	*current_t,
		 ZnTransfo	*new_t,
		 ZnBool		compose_scale,
		 ZnBool		compose_rot)
{
  ZnBool	full;
  ZnTransfo	t, t2;

  full = compose_scale && compose_rot;
  
  if (!transfo && !pos && full) {
    *new_t = *current_t;
    return;
  }
  if (full) {
    /*
     * Full concatenation.
     */
    /*ZnPrintTransfo(transfo);*/
    if (pos) {
      if (!transfo) {
	ZnTransfoSetIdentity(&t);
      }
      else {
	t = *transfo;
      }
      ZnTranslate(&t, pos->x, pos->y, False);
      ZnTransfoCompose(new_t, &t, current_t);
    }
    else {
      ZnTransfoCompose(new_t, transfo, current_t);
    }
  }
  else {
    ZnPoint	scale, trans, local_scale, local_trans, p;
    ZnReal	local_rot, rot;
    
    ZnTransfoSetIdentity(new_t);
    ZnTransfoDecompose(transfo, &local_scale, &local_trans, &local_rot, NULL);
    ZnScale(new_t, local_scale.x, local_scale.y);
    ZnRotateRad(new_t, local_rot);

    ZnTransfoDecompose(current_t, &scale, &trans, &rot, NULL);

    if (pos) {
      ZnTransfoSetIdentity(&t);
      ZnTranslate(&t, pos->x, pos->y, False);
      ZnTransfoCompose(&t2, &t, current_t);
      ZnTransformPoint(&t2, &local_trans, &p);
    }
    else {
      ZnTransformPoint(current_t, &local_trans, &p);
    }

    if (compose_scale) {
      ZnScale(new_t, scale.x, scale.y);
    }
    if (compose_rot) {
      ZnRotateRad(new_t, rot);
    }
    ZnTranslate(new_t, p.x, p.y, False);
  }
}


/*
 **********************************************************************************
 *
 * GetItemTransform -- Method
 *	Compute the current transform for an item.
 *
 **********************************************************************************
 */
static void
GetItemTransform(ZnItem		item,
		 ZnTransfo	*t)
{
  ZnItem	*items;
  int		i;
  ZnTransfo	t_tmp, *t1, *t2, *swap;
  ZnPoint	*pos;

  if (item_stack == NULL) {
    item_stack = ZnListNew(16, sizeof(ZnItem));
  }
  else {
    ZnListEmpty(item_stack);
  }
  
  while (item != ZN_NO_ITEM) {
    ZnListAdd(item_stack, &item, ZnListTail);
    item = item->parent;
  }
  
  ZnTransfoSetIdentity(t);
  t1 = t;
  t2 = &t_tmp;
  items = (ZnItem *) ZnListArray(item_stack);
  for (i = ZnListSize(item_stack)-1; i >= 0; i--) {
    pos = NULL;
    if (items[i]->class->pos_offset >= 0) {
      pos = (ZnPoint *) (((char *) items[i]) + items[i]->class->pos_offset);
      if (pos->x == 0 && pos->y == 0) {
	pos = NULL;
      }
    }
    ComposeTransform(items[i]->transfo, pos, t1, t2,
		     ISSET(items[i]->flags, ZN_COMPOSE_SCALE_BIT),
		     ISSET(items[i]->flags, ZN_COMPOSE_ROTATION_BIT));
    swap = t2;
    t2 = t1;
    t1 = swap;
  }
  if (t1 != t) {
    *t = *t1;
  }
}



/*
 **********************************************************************************
 *
 * ZnResetTransformStack
 * ZnInitTransformStack
 * ZnFreeTransformStack
 * ZnCurrentTransform
 * ZnPushTransform
 * ZnPopTransform --
 *
 **********************************************************************************
 */
void
ZnResetTransformStack(ZnWInfo	*wi)
{
  ZnListAssertSize(wi->transfo_stack, 1);
  wi->current_transfo = (ZnTransfo *) ZnListAt(wi->transfo_stack, 0);
  ZnTransfoSetIdentity(wi->current_transfo);
}

void
ZnInitTransformStack(ZnWInfo	*wi)
{
  wi->transfo_stack = ZnListNew(8, sizeof(ZnTransfo));
  ZnResetTransformStack(wi);
}

void
ZnFreeTransformStack(ZnWInfo	*wi)
{
  ZnListFree(wi->transfo_stack);
}

void
ZnPushTransform(ZnWInfo		*wi,
		ZnTransfo	*transfo,
		ZnPoint		*pos,
		ZnBool		compose_scale,
		ZnBool		compose_rot)
{
  ZnTransfo	*next_t;
  unsigned int	num_t;

  /*
   * Push the current transform and concatenate
   * the new transform taking into account the
   * combination flags.
   */
  num_t = ZnListSize(wi->transfo_stack);
  ZnListAssertSize(wi->transfo_stack, num_t+1);
  next_t = (ZnTransfo *) ZnListAt(wi->transfo_stack, num_t);
  ComposeTransform(transfo, pos, wi->current_transfo, next_t,
		   compose_scale, compose_rot);
  wi->current_transfo = next_t;
}

void
ZnPopTransform(ZnWInfo	*wi)
{
  /*
   * Restore the previous transform.
   */
  ZnListDelete(wi->transfo_stack, ZnListTail);
  wi->current_transfo = (ZnTransfo *) ZnListAt(wi->transfo_stack, ZnListTail);
}


/*
 **********************************************************************************
 *
 * ZnResetClipStack
 * ZnInitClipStack
 * ZnFreeClipStack
 * ZnCurrentClip
 * ZnPushClip
 * ZnPopClip --
 *
 **********************************************************************************
 */
/*
 * Describe the clipping at a given node
 * of the item hierarchy.
 */
typedef struct _ClipState {
  ZnBool	simple;		/* The clip is an aligned rectangle.	*/
  TkRegion	region;		/* The region used to draw and to	*/
				/* probe for picking.			*/
  ZnBBox	clip_box;	/* The bounding box of the clip area.	*/
} ClipState;

void
ZnResetClipStack(ZnWInfo	*wi)
{
  int		i;
  ClipState	*clips = (ClipState *) ZnListArray(wi->clip_stack);
  
  /*
   * Should not happen, clip stack should be
   * empty when this function is called.
   */
  for (i = ZnListSize(wi->clip_stack)-1; i >= 0; i--) {
    TkDestroyRegion(clips[i].region);
  }
  ZnListEmpty(wi->clip_stack);
  wi->current_clip = NULL;
}

void
ZnInitClipStack(ZnWInfo	*wi)
{
  wi->clip_stack = ZnListNew(8, sizeof(ClipState));
  ZnResetClipStack(wi);  
}

void
ZnFreeClipStack(ZnWInfo	*wi)
{
  ZnListFree(wi->clip_stack);
}

ZnBool
ZnCurrentClip(ZnWInfo	*wi,
	      TkRegion	*reg,
	      ZnBBox	**clip_box,
	      ZnBool	*simple)
{
  if (wi->current_clip) {
    if (reg) {
      *reg = wi->current_clip->region;
    }
    if (clip_box) {
      *clip_box = &wi->current_clip->clip_box;
    }
    if (simple) {
      *simple = wi->current_clip->simple;
    }
    return True;
  }
  
  return False;
}

/*
 * If simple is True poly is a pointer to an
 * array of two points. In the other case it
 * is a regular pointer to a multi contour poly.
 */
void
ZnPushClip(ZnWInfo	*wi,
	   ZnTriStrip	*tristrip,
	   ZnBool	simple,
	   ZnBool	set_gc)
{
  unsigned int	i, j, num_clips;
  unsigned int	num_pts, max_num_pts;
  ZnPoint	*p;
  ClipState	*previous_clip=NULL;
  TkRegion	reg, reg_op, reg_to;
  XRectangle	rect;
  XPoint	xpts[3];
  
  if (tristrip->num_strips == 0) {
    return;
  }
  max_num_pts = tristrip->strips[0].num_points;
  for (j = 0; j < tristrip->num_strips; j++) {
    num_pts = tristrip->strips[j].num_points;
    if (num_pts > max_num_pts) {
      num_pts = max_num_pts;
    }
  }
  if ((simple && (max_num_pts < 2)) ||
      (!simple && (max_num_pts < 3))) {
    return;
  }
  
  num_clips = ZnListSize(wi->clip_stack);
  /*  printf("PushClip: num clips %d\n", num_clips);fflush(stdout);*/
  if (num_clips != 0) {
    previous_clip = (ClipState *) ZnListAt(wi->clip_stack, ZnListTail);
  }
  ZnListAssertSize(wi->clip_stack, num_clips+1);
  wi->current_clip = (ClipState *) ZnListAt(wi->clip_stack, ZnListTail);
  wi->current_clip->simple = simple;

  /*
   * Compute the local region.
   */
  if (simple) {
    rect.x = (short) tristrip->strips[0].points[0].x;
    rect.y = (short) tristrip->strips[0].points[0].y;
    rect.width = ((unsigned short) (tristrip->strips[0].points[1].x -
				    tristrip->strips[0].points[0].x));
    rect.height = ((unsigned short) (tristrip->strips[0].points[1].y -
				     tristrip->strips[0].points[0].y));
    reg = TkCreateRegion();
    TkUnionRectWithRegion(&rect, reg, reg);
    /*printf("Adding a simple clip: %d, %d, %d, %d\n",
      rect.x, rect.y, rect.width, rect.height);*/
  }
  else {
    reg = TkCreateRegion();
    for (j = 0; j < tristrip->num_strips; j++) {
      num_pts = tristrip->strips[j].num_points;
      p = tristrip->strips[j].points;
      if (tristrip->strips[j].fan) {
	xpts[0].x = ZnNearestInt(p->x);
	xpts[0].y = ZnNearestInt(p->y);
	p++;
	xpts[1].x = ZnNearestInt(p->x);
	xpts[1].y = ZnNearestInt(p->y);
	p++;
	for (i = 2; i < num_pts; i++, p++) {
	  xpts[2].x = ZnNearestInt(p->x);
	  xpts[2].y = ZnNearestInt(p->y);
	  reg_op = (TkRegion) ZnPolygonRegion(xpts, 3, EvenOddRule);
	  reg_to = TkCreateRegion();
	  ZnUnionRegion(reg, reg_op, reg_to);
	  TkDestroyRegion(reg);
	  TkDestroyRegion(reg_op);
	  reg = reg_to;
	  xpts[1] = xpts[2];
	}
      }
      else {
	xpts[0].x = (short) p->x;
	xpts[0].y = (short) p->y;
	p++;
	xpts[1].x = (short) p->x;
	xpts[1].y = (short) p->y;
	p++;
	for (i = 2 ; i < num_pts; i++, p++) {
	  xpts[2].x = (short) p->x;
	  xpts[2].y = (short) p->y;	
	  reg_op = (TkRegion) ZnPolygonRegion(xpts, 3, EvenOddRule);
	  reg_to = TkCreateRegion();
	  ZnUnionRegion(reg, reg_op, reg_to);
	  TkDestroyRegion(reg);
	  TkDestroyRegion(reg_op);
	  reg = reg_to;
	  xpts[0] = xpts[1];
	  xpts[1] = xpts[2];
	}
      }
    }
  }
  
  /*
   * Combine with previous region if any.
   */
  if (previous_clip) {
    wi->current_clip->region = TkCreateRegion();
    TkIntersectRegion(reg, previous_clip->region, wi->current_clip->region);
    TkDestroyRegion(reg);
    /*printf("Merging with previous clip\n");*/
  }
  else {
    wi->current_clip->region = reg;
  }
  TkClipBox(wi->current_clip->region, &rect);
  wi->current_clip->clip_box.orig.x = rect.x;
  wi->current_clip->clip_box.orig.y = rect.y;
  wi->current_clip->clip_box.corner.x = rect.x + rect.width;
  wi->current_clip->clip_box.corner.y = rect.y + rect.height;

  /*
   * Set the clipping in the GC. 
   */
  if (set_gc) {
    if (wi->render) {
#ifdef GL
      glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
      glEnable(GL_STENCIL_TEST);
      glStencilFunc(GL_EQUAL, (GLint) num_clips, 0xFF);      
      glStencilOp(GL_KEEP, GL_INCR, GL_INCR);
      glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
      if (simple) {
	/*	printf("Clip box is : %d, %d, %d, %d, num_clips : %d\n",
		rect.x, rect.y, rect.width, rect.height, num_clips);*/
	glBegin(GL_QUADS);
	glVertex2d(wi->current_clip->clip_box.orig.x, wi->current_clip->clip_box.orig.y);
	glVertex2d(wi->current_clip->clip_box.orig.x, wi->current_clip->clip_box.corner.y);
	glVertex2d(wi->current_clip->clip_box.corner.x, wi->current_clip->clip_box.corner.y);
	glVertex2d(wi->current_clip->clip_box.corner.x, wi->current_clip->clip_box.orig.y);
	glEnd();
      }
      else {
	for (j = 0; j < tristrip->num_strips; j++) {
	  num_pts = tristrip->strips[j].num_points;
	  p = tristrip->strips[j].points;
	  if (tristrip->strips[j].fan) {
	    glBegin(GL_TRIANGLE_FAN);
	  }
	  else {
	    glBegin(GL_TRIANGLE_STRIP);
	  }
	  for (i = 0; i < num_pts; i++, p++) {
	    glVertex2d(p->x, p->y);
	  }
	  glEnd();
	}
      }
      glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
      glStencilFunc(GL_EQUAL, (GLint) (num_clips+1), 0xFF);
      glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);	
#endif
    }
    else {
      TkSetRegion(wi->dpy, wi->gc, wi->current_clip->region);
    }
  }
}

void
ZnPopClip(ZnWInfo	*wi,
	  ZnBool	set_gc)
{
  int	num_clips;
  
  if (wi->current_clip == NULL) {
    return;
  }
  
  TkDestroyRegion(wi->current_clip->region);
  ZnListDelete(wi->clip_stack, ZnListTail);
  num_clips = ZnListSize(wi->clip_stack);
  
  if (num_clips != 0) {
    wi->current_clip = (ClipState *) ZnListAt(wi->clip_stack, ZnListTail);
  }
  else {
    wi->current_clip = NULL;
  }

  /*
   * Set the clipping in the GC.
   */
  if (set_gc) {
    if (num_clips != 0) {
      if (wi->render) {
#ifdef GL
	glStencilFunc(GL_EQUAL, (GLint) (num_clips+1), 0xFF);
	glStencilOp(GL_KEEP, GL_DECR, GL_DECR);
	glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
#if 0
	if (wi->current_clip->simple) {
#endif
	  glBegin(GL_QUADS);
	  glVertex2d(wi->current_clip->clip_box.orig.x, wi->current_clip->clip_box.orig.y);
	  glVertex2d(wi->current_clip->clip_box.orig.x, wi->current_clip->clip_box.corner.y);
	  glVertex2d(wi->current_clip->clip_box.corner.x, wi->current_clip->clip_box.corner.y);
	  glVertex2d(wi->current_clip->clip_box.corner.x, wi->current_clip->clip_box.orig.y);
	  glEnd();
#if 0
	}
	else {
	}
#endif
	glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
	glStencilFunc(GL_EQUAL, (GLint) num_clips, 0xFF);    
	glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);	
#endif
      }
      else {
	TkSetRegion(wi->dpy, wi->gc, wi->current_clip->region);
      }
    }
    else {
      /*printf("resetting clip mask\n");*/
      if (wi->render) {
#ifdef GL
	glClear(GL_STENCIL_BUFFER_BIT);
	glDisable(GL_STENCIL_TEST);
#endif
      }
      else {
	XSetClipMask(wi->dpy, wi->gc, None);
      }
    }
  }
  /*printf("PopClip: num clips %d\n", ZnListSize(wi->clip_stack));fflush(stdout);*/
}


/*
 **********************************************************************************
 *
 * Invalidate -- Method
 *
 **********************************************************************************
 */
static void
Invalidate(ZnItem	item,
	   int		reason)
{
  /* 
   * Why this test has to be so an abrupt shortcut ?
   * It precludes addition of meaningful reasons
   * by subsequent invalidations .
   *
     if (ISSET(item->inv_flags, ZN_TRANSFO_FLAG)) {
    return;
    }*/

  if (ISSET(reason, ZN_COORDS_FLAG) ||
      ISSET(reason, ZN_TRANSFO_FLAG)) {
    ZnItem parent = item->parent;
    while ((parent != NULL) &&
	   ISCLEAR(parent->inv_flags, ZN_COORDS_FLAG) &&
	   ISCLEAR(parent->inv_flags, ZN_TRANSFO_FLAG)) {
      SET(parent->inv_flags, ZN_COORDS_FLAG);
      /*printf("invalidate coords for parent %d\n", parent->id);*/
      parent = parent->parent;
    }
    /*
     * There is no need to set the DRAW flag to force the invalidation
     * of the current bounding box. This will be done by ComputeCoordinates
     * in Group.
     */
    item->inv_flags |= reason;
    /*printf("invalidate %s for item %d, flags %s\n",
	   ISSET(reason, ZN_TRANSFO_FLAG)?"TRANSFO":"COORDS", item->id,
	   ISSET(item->inv_flags, ZN_TRANSFO_FLAG)?"TRANSFO":"COORDS");*/
    ZnNeedRedisplay(item->wi);
  }
  else if (ISSET(reason, ZN_DRAW_FLAG)) {
    if (ISSET(item->flags, ZN_VISIBLE_BIT)) {
      /*printf("invalidate graphics for item %d\n", item->id);*/
      ZnDamage(item->wi, &item->item_bounding_box);
#ifdef GL
#ifdef GL_LIST
      /*
       * Remove the item display list so that it will be recreated
       * to reflect the changes.
       */
      if (item->gl_list) {
	glDeleteLists(item->gl_list, 1);
	item->gl_list = 0;
      }
#endif
#endif
    }
  }
}

	   
/*
 **********************************************************************************
 *
 * InvalidateItems -- Method
 *	Invalidate the geometric state of all items belonging
 *	to a given class. The search for items starts at group
 *	and proceed depth first.
 *
 **********************************************************************************
 */
static void
InvalidateItems(ZnItem		group,
		ZnItemClass	item_class)
{
  ZnItem	item;

  if (group->class != ZnGroup) {
    return;
  }
  item = ZnGroupHead(group);
  while (item != ZN_NO_ITEM) {
    if (item->class == item_class) {
      Invalidate(item, ZN_COORDS_FLAG);
    }
    else if (item->class == ZnGroup) {
      InvalidateItems(item, item_class);
    }
    item = item->next;
  }
}


/*
 **********************************************************************************
 *
 * ResetTransfo
 * SetTransfo
 * TranslateItem
 * ScaleItem
 * SkewItem
 * RotateItem -- Methods
 *	Set of functions that deal with item transform. They take care
 *	of all details including managing NULL transforms and invalidating
 *	the item hierarchy.
 *
 **********************************************************************************
 */
static void
ResetTransfo(ZnItem	item)
{
  if (item->transfo) {
    ZnFree(item->transfo);
    item->transfo = NULL;
  }
  Invalidate(item, ZN_TRANSFO_FLAG);
}


static void
SetTransfo(ZnItem	item,
	   ZnTransfo	*t)
{
  if (item->transfo) {
    ZnFree(item->transfo);
  }
  if (!t || ZnTransfoIsIdentity(t)) {
    item->transfo = NULL;
  }
  else {
    item->transfo = ZnTransfoDuplicate(t);
  }
  Invalidate(item, ZN_TRANSFO_FLAG);
}


static void
TranslateItem(ZnItem	item,
	      ZnReal	dx,
	      ZnReal	dy,
	      ZnBool	abs)
{
  if (!item->transfo) {
    item->transfo = ZnTransfoNew();
  }
  ZnTranslate(item->transfo, dx, dy, abs);
  Invalidate(item, ZN_TRANSFO_FLAG);
}


static void
ScaleItem(ZnItem	item,
	  ZnReal	sx,
	  ZnReal	sy,
	  ZnPoint	*p)
{
  if (!item->transfo) {
    item->transfo = ZnTransfoNew();
  }
  if (p) {
    ZnTranslate(item->transfo, -p->x, -p->y, False);
  }
  ZnScale(item->transfo, sx, sy);
  if (p) {
    ZnTranslate(item->transfo, p->x, p->y, False);
  }
  Invalidate(item, ZN_TRANSFO_FLAG);
}


static void
SkewItem(ZnItem	item,
	 ZnReal	x_skew,
	 ZnReal	y_skew)
{
  if (!item->transfo) {
    item->transfo = ZnTransfoNew();
  }
  ZnSkewRad(item->transfo, x_skew, y_skew);
  Invalidate(item, ZN_TRANSFO_FLAG);
}


static void
RotateItem(ZnItem	item,
	   ZnReal	angle,
	   ZnBool	deg,
	   ZnPoint	*p)
{
  if (!item->transfo) {
    item->transfo = ZnTransfoNew();
  }
  if (p) {
    ZnTranslate(item->transfo, -p->x, -p->y, False);
  }
  if (deg) {
    ZnRotateDeg(item->transfo, angle);
  }
  else {
    ZnRotateRad(item->transfo, angle);
  }
  if (p) {
    ZnTranslate(item->transfo, p->x, p->y, False);
  }

  Invalidate(item, ZN_TRANSFO_FLAG);
}


/*
 **********************************************************************************
 *
 * DestroyItem -- Method
 *
 **********************************************************************************
 */
static void
DestroyItem(ZnItem	item)
{
  ZnWInfo	*wi = item->wi;
  ZnTextInfo	*ti = &wi->text_info;

  /*
   * Extract it from its group.
   */
  ExtractItem(item);
  
  /*
   * Update state variables to prevent dangling pointers.
   */
  if (wi->current_item == item) {
    wi->current_item = ZN_NO_ITEM;
    wi->current_part = ZN_NO_PART;
  }
  if (wi->new_item == item) {
    wi->new_item = ZN_NO_ITEM;
    wi->new_part = ZN_NO_PART;
  }
  if ((wi->hot_item == item) || (wi->hot_prev) == item) {
    wi->hot_item = ZN_NO_ITEM;
  }
  if (ti->sel_item == item) {
    ti->sel_item = ZN_NO_ITEM;
    ti->sel_field = ZN_NO_PART;
  }
  if (ti->anchor_item == item) {
    ti->anchor_item = ZN_NO_ITEM;
    ti->anchor_field = ZN_NO_PART;
  }
  if (wi->focus_item == item) {
    wi->focus_item = ZN_NO_ITEM;
    wi->focus_field = ZN_NO_PART;
  }

  /*
   * Call per class removal code.
   */
  (item->class->Destroy)(item);
  /*
   * Free the transform if any.
   */
  if (item->transfo) {
    ZnFree(item->transfo);
  }
  /*
   * Remove the item from the item table and free
   * all its tags.
   */
  FreeId(item);
  FreeTags(item);
  /*
   * Free the item own memory
   */
  ZnFree(item);
  wi->num_items--;
}


/*
 **********************************************************************************
 *
 * Generic methods on items --
 *
 **********************************************************************************
 */

struct _ZnITEM ZnITEM = {
  CloneItem,
  DestroyItem,
  ConfigureItem,
  QueryItem,
  InsertItem,
  UpdateItemPriority,
  UpdateItemDependency,
  ExtractItem,
  SetId,
  FreeId,
  AddTag,
  RemoveTag,
  FreeTags,
  HasTag,
  ResetTransfo,
  SetTransfo,
  TranslateItem,
  ScaleItem,
  SkewItem,
  RotateItem,
  Invalidate,
  InvalidateItems,
  GetItemTransform
};
