The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
/*
 * Item.c -- Implementation of items.
 *
 * Authors              : Patrick Lecoanet.
 * Creation date        : 
 *
 * $Id: Item.c,v 1.93 2005/11/25 15:40:28 lecoanet Exp $
 */

/*
 *  Copyright (c) 1993 - 2005 CENA, Patrick Lecoanet --
 *
 * See the file "Copyright" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 */


#include "Types.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 ATC
#include "OverlapMan.h"
#endif

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


static const char rcsid[] = "$Id: Item.c,v 1.93 2005/11/25 15:40:28 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"
};


#ifndef PTK
static int SetAttrFromAny _ANSI_ARGS_((Tcl_Interp *interp, Tcl_Obj *obj));

/*
 * The structure below defines an object type that is used to cache the
 * result of looking up an attribute name.  If an object has this type, then
 * its internalPtr1 field points to the attr_desc table in which it was looked up,
 * and the internalPtr2 field points to the entry that matched.
 */

Tcl_ObjType ZnAttrObjType = {
    "attribute",                        /* name */
    (Tcl_FreeInternalRepProc *) NULL,   /* freeIntRepProc */
    (Tcl_DupInternalRepProc *) NULL,    /* dupIntRepProc */
    (Tcl_UpdateStringProc *) NULL,      /* updateStringProc */
    SetAttrFromAny                      /* setFromAnyProc */
};
#endif


/*
 **********************************************************************************
 *
 * Forward functions
 * 
 **********************************************************************************
 */
static void Invalidate(ZnItem item, int reason);
static Tcl_Obj *AttributeToObj(Tcl_Interp *interp, void *record, ZnAttrConfig *desc);



/*
 **********************************************************************************
 *
 * 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++;
  }
}

/*
 *----------------------------------------------------------------------
 *
 * SetAttrFromAny --
 *
 *      This procedure is called to convert a Tcl object to an attribute
 *      descriptor. This is only possible if given a attr_desc table, so
 *      this method always returns an error.
 *
 *----------------------------------------------------------------------
 */
#ifndef PTK
static int
SetAttrFromAny(Tcl_Interp       *interp,
               Tcl_Obj          *obj)
{
  Tcl_AppendToObj(Tcl_GetObjResult(interp),
                  "can't convert value to attribute except via GetAttrDesc",
                  -1);
  return TCL_ERROR;
}
#endif


/*
 **********************************************************************************
 *
 * GetAttrDesc --
 *
 **********************************************************************************
 */
static ZnAttrConfig *
GetAttrDesc(Tcl_Interp          *interp,
            Tcl_Obj             *arg,
            ZnAttrConfig        *desc_table)
{
  Tk_Uid        attr_uid;
  ZnAttrConfig  *desc;

#ifndef PTK
  if (arg->typePtr == &ZnAttrObjType) {
    if (arg->internalRep.twoPtrValue.ptr1 == (void *) desc_table) {
      return (ZnAttrConfig *) arg->internalRep.twoPtrValue.ptr2;
    }
  }
#endif

  /*
   * Answer not cached, look it up.
   */
  attr_uid = Tk_GetUid(Tcl_GetString(arg));
  desc = desc_table;

  while (True) {
    if (desc->type == ZN_CONFIG_END) {
      Tcl_AppendResult(interp, "unknown attribute \"", attr_uid, "\"", NULL);
      return NULL;
    }
    else if (attr_uid == desc->uid) {
#ifndef PTK
      if ((arg->typePtr != NULL) && (arg->typePtr->freeIntRepProc != NULL)) {
        arg->typePtr->freeIntRepProc(arg);
      }
      arg->internalRep.twoPtrValue.ptr1 = (void *) desc_table;
      arg->internalRep.twoPtrValue.ptr2 = (void *) desc;
      arg->typePtr = &ZnAttrObjType;
#endif
      return desc;
    }
    else {
      desc++;
    }
  }
}


/*
 **********************************************************************************
 *
 * AttributesInfo --
 *
 **********************************************************************************
 */
int
ZnAttributesInfo(Tcl_Interp     *interp,
                 void           *record,
                 ZnAttrConfig   *desc_table,
                 int            argc,
                 Tcl_Obj *CONST args[])
{
  Tcl_Obj       *l, *entries[5];
  
  if (argc == 1) {
    ZnAttrConfig *desc = GetAttrDesc(interp, args[0], desc_table);
    if (!desc) {
      return TCL_ERROR;
    }
    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] = AttributeToObj(interp, record, desc);
    l = Tcl_NewListObj(5, entries);
    Tcl_SetObjResult(interp, l);
  }
  else {
    l = Tcl_NewObj();
    while (desc_table->type != ZN_CONFIG_END) {
      entries[0] = Tcl_NewStringObj(desc_table->name, -1);
      entries[1] = Tcl_NewStringObj(attribute_type_strings[desc_table->type], -1);
      entries[2] = Tcl_NewBooleanObj(desc_table->read_only ? 1 : 0);
      entries[3] = Tcl_NewStringObj("", -1);
      entries[4] = AttributeToObj(interp, record, desc_table);
      Tcl_ListObjAppendElement(interp, l, Tcl_NewListObj(5, entries));
      desc_table++;
    }
    Tcl_SetObjResult(interp, l);
  }
  
  return TCL_OK;
}


/*
 **********************************************************************************
 *
 * ZnConfigureAttributes --
 *
 **********************************************************************************
 */
int
ZnConfigureAttributes(ZnWInfo           *wi,
                      ZnItem            item,
                      void              *record,
                      ZnAttrConfig      *desc_table,
                      int               argc,
                      Tcl_Obj *CONST    args[],
                      int               *flags)
{
  int           i;
  ZnAttrConfig  *desc;
  ZnPtr         valp;
  char          *str;
  
  for (i = 0; i < argc; i += 2) {
    desc = GetAttrDesc(wi->interp, args[i], desc_table);
    if (!desc) {
      return TCL_ERROR;
    }
    else 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_ANGLE:
      {
        double angle;
        int    int_angle;
        if (Tcl_GetDoubleFromObj(wi->interp, args[i+1], &angle) == TCL_ERROR) {
          return TCL_ERROR;
        }
        int_angle = (int) angle;
        int_angle = int_angle % 360;
        if (int_angle != *((int *) valp)) {
          *((int *) valp) = int_angle;
          *flags |= desc->flags;
        }
        break;
      }
    case ZN_CONFIG_DIM:
      {
        double dim;
        if (Tcl_GetDoubleFromObj(wi->interp, args[i+1], &dim) == TCL_ERROR) {
          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:
      {
        int integer;
        if (Tcl_GetIntFromObj(wi->interp, args[i+1], &integer) == TCL_ERROR) {
          return TCL_ERROR;
        }
        if ((desc->type == ZN_CONFIG_UINT) &&  (integer < 0)) {
            integer = 0;
        }
        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;
      }
    }
  }

  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.
 *
 **********************************************************************************
 */
static Tcl_Obj *
AttributeToObj(Tcl_Interp       *interp,
               void             *record,
               ZnAttrConfig     *desc)
{
  char          *valp = ((char *) record) + desc->offset;
  char          *str = "";
  Tcl_Obj       *o, *obj;
  unsigned int  i;
  char          buffer[256];

  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));
        
        obj = Tcl_NewObj();
        for (i = 0; i < num_grads; i++) {
          o = Tcl_NewStringObj(ZnNameOfGradient(grads[i]), -1);
          Tcl_ListObjAppendElement(interp, obj, o);
        }
        return obj;
      }
    }
    break;
  case ZN_CONFIG_BOOL:
    return Tcl_NewBooleanObj(ISSET(*((unsigned short *) valp), desc->bool_bit)?1:0);
  case ZN_CONFIG_IMAGE:
    if (*((ZnImage *) valp)) {
      str = ZnNameOfImage(*((ZnImage *) valp));
#if PTK
      // Just return the perl image object, it is far more
      // useful than the mere string name.
      return LangObjectObj(interp, str);
#endif
    }
    break;
  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));

        obj = Tcl_NewObj();
        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(interp, obj, o);
        }
        return obj;
      }
      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));
        obj = Tcl_NewObj();
        for (i = 0; i < num_tags; i++) {
          Tcl_ListObjAppendElement(interp, obj,
                                   Tcl_NewStringObj(tags[i], -1));
        }
        return obj;
      }
      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:
    obj = Tcl_NewObj();
    Tcl_ListObjAppendElement(interp, obj, Tcl_NewDoubleObj(((ZnPoint *) valp)->x));
    Tcl_ListObjAppendElement(interp, obj, Tcl_NewDoubleObj(((ZnPoint *) valp)->y));
    return obj;
  case ZN_CONFIG_ITEM:
    if (*((ZnItem *) valp) != ZN_NO_ITEM) {
      return Tcl_NewLongObj((int) (*((ZnItem *) valp))->id);
    }
    break;
  case ZN_CONFIG_WINDOW:
    if (*((Tk_Window *) valp) != NULL) {
      str = Tk_PathName(*((Tk_Window *) valp));
    }
    break;
  case ZN_CONFIG_CHAR:
    return Tcl_NewIntObj(*((char *) valp));
  case ZN_CONFIG_UCHAR:
  case ZN_CONFIG_ALPHA:
    return Tcl_NewIntObj(*((unsigned char *) valp));
  case ZN_CONFIG_USHORT:
  case ZN_CONFIG_PRI:
    return Tcl_NewIntObj(*((unsigned short *) valp));
  case ZN_CONFIG_SHORT:
     return Tcl_NewIntObj(*((short *) valp));
  case ZN_CONFIG_UINT:
    return Tcl_NewIntObj(*((unsigned int *) valp));
  case ZN_CONFIG_INT:
    return Tcl_NewIntObj(*((int *) valp));
  case ZN_CONFIG_ANGLE:
    return Tcl_NewDoubleObj(*((int *) valp));
  case ZN_CONFIG_DIM:
    return Tcl_NewDoubleObj(*((ZnDim *) valp));
  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;
  }
  return Tcl_NewStringObj(str, -1);
}


/*
 **********************************************************************************
 *
 * ZnQueryAttribute --
 *
 **********************************************************************************
 */
int
ZnQueryAttribute(Tcl_Interp     *interp,
                 void           *record,
                 ZnAttrConfig   *desc_table,
                 Tcl_Obj        *attr_name)
{
  ZnAttrConfig  *desc = GetAttrDesc(interp, attr_name, desc_table);
  
  if (!desc) {
    return TCL_ERROR;
  }
  Tcl_SetObjResult(interp, AttributeToObj(interp, record, 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));
#ifdef ATC
    ZnAddItemClass(ZnTrack);
    ZnAddItemClass(ZnWayPoint);
    ZnAddItemClass(ZnMap);
    ZnAddItemClass(ZnReticle);
#endif
    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
};