/*
 * tkZinc.c -- Zinc widget for the Tk Toolkit. Main module.
 *
 * Authors		: Patrick Lecoanet.
 * Creation date	: Mon Feb  1 12:13:24 1999
 *
 * $Id: tkZinc.c,v 1.94 2004/03/26 16:24:39 lecoanet Exp $
 */

/*
 *  Copyright (c) 1993 - 1999 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.
 *
 */

/*
 * Some functions and code excerpts in this file are from tkCanvas.c
 * and thus copyrighted:
 *
 * Copyright (c) 1991-1994 The Regents of the University of California.
 * Copyright (c) 1994-1997 Sun Microsystems, Inc.
 * Copyright (c) 1998-1999 by Scriptics Corporation.
 *
 */

static const char rcs_id[]="$Id: tkZinc.c,v 1.94 2004/03/26 16:24:39 lecoanet Exp $";
static const char compile_id[]="$Compile: " __FILE__ " " __DATE__ " " __TIME__ " $";
static const char * const zinc_version = "zinc-version-" VERSION;


#include "Types.h"
#include "Geo.h"
#include "Item.h"
#include "Group.h"
#include "WidgetInfo.h"
#include "tkZinc.h"
#include "MapInfo.h"
#ifdef OM
#include "OverlapMan.h"
#endif
#include "Track.h"
#include "Transfo.h"
#include "Image.h"
#include "Draw.h"
#include "Color.h"
#ifndef _WIN32
#include "perfos.h"
#endif
#include "glu.h"

#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <X11/Xatom.h>


typedef struct _TagSearchExpr {
  struct _TagSearchExpr *next; /* for linked lists of expressions - used in bindings */
  Tk_Uid		uid;	/* the uid of the whole expression */
  Tk_Uid		*uids;	/* expresion compiled to an array of uids */
  int			allocated; /* available space for array of uids */
  int			length;	/* length of expression */
  int			index;	/* current position in expression evaluation */
  int			match;	/* this expression matches event's item's tags*/
} TagSearchExpr;


#define SYMBOL_WIDTH 8
#define SYMBOL_HEIGHT 8
static unsigned char SYMBOLS_BITS[][SYMBOL_WIDTH*SYMBOL_HEIGHT/8] = {
  { 0x18, 0x18, 0x24, 0x24, 0x5a, 0x5a, 0x81, 0xff },
  { 0xff, 0x81, 0x99, 0xbd, 0xbd, 0x99, 0x81, 0xff },
  { 0x18, 0x24, 0x42, 0x99, 0x99, 0x42, 0x24, 0x18 },
  { 0x18, 0x3c, 0x5a, 0xff, 0xff, 0x5a, 0x3c, 0x18 },
  { 0x18, 0x24, 0x42, 0x81, 0x81, 0x42, 0x24, 0x18 },
  { 0x3c, 0x42, 0x81, 0x81, 0x81, 0x81, 0x42, 0x3c },
  { 0x18, 0x18, 0x24, 0x24, 0x42, 0x42, 0x81, 0xff },
  { 0xff, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xff },
  { 0x18, 0x18, 0x3c, 0x3c, 0x66, 0x66, 0xff, 0xff },
  { 0xff, 0xff, 0xe7, 0xc3, 0xc3, 0xe7, 0xff, 0xff },
  { 0x18, 0x3c, 0x7e, 0xe7, 0xe7, 0x7e, 0x3c, 0x18 },
  { 0x18, 0x3c, 0x66, 0xc3, 0xc3, 0x66, 0x3c, 0x18 },
  { 0x18, 0x3c, 0x7e, 0xff, 0xff, 0x7e, 0x3c, 0x18 },
  { 0x3c, 0x7e, 0xff, 0xff, 0xff, 0xff, 0x7e, 0x3c },
  { 0x18, 0x18, 0x3c, 0x3c, 0x7e, 0x7e, 0xff, 0xff },
  { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff },
  { 0x18, 0x7e, 0x7e, 0xff, 0xff, 0x7e, 0x7e, 0x18 },
  { 0x18, 0x66, 0x42, 0x81, 0x81, 0x42, 0x66, 0x18 },
  { 0x00, 0x00, 0x18, 0x3c, 0x3c, 0x18, 0x00, 0x00 },
  { 0x00, 0x18, 0x3c, 0x7e, 0x7e, 0x3c, 0x18, 0x00 },
  { 0x18, 0x3c, 0x7e, 0xff, 0xff, 0x7e, 0x3c, 0x18 },
  { 0x81, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x81 },
};

static unsigned char dither4x4[4][4] = {
  { 0, 8, 2, 10 },
  { 12, 4, 14, 6 },
  { 3, 11, 1, 9 },
  { 15, 7, 13, 5 }
};

static unsigned char bitmaps[ZN_NUM_ALPHA_STEPS][32][4];

static	Tk_Uid	all_uid;
static	Tk_Uid	current_uid;
static	Tk_Uid	and_uid;
static	Tk_Uid	or_uid;
static	Tk_Uid	xor_uid;
static	Tk_Uid	paren_uid;
static	Tk_Uid	end_paren_uid;
static	Tk_Uid	neg_paren_uid;
static	Tk_Uid	tag_val_uid;
static	Tk_Uid	neg_tag_val_uid;
static	Tk_Uid	dot_uid;
static	Tk_Uid	star_uid;

#ifdef GL
static ZnGLContextEntry *gl_contexts = NULL;
#endif


static  void PickCurrentItem _ANSI_ARGS_((ZnWInfo *wi, XEvent *event));
static	int ZnReliefParse _ANSI_ARGS_((ClientData client_data, Tcl_Interp *interp,
				       Tk_Window tkwin, Tcl_Obj *ovalue,
				       char *widget_rec, int offset));
static	Tcl_Obj *ZnReliefPrint _ANSI_ARGS_((ClientData client_data, Tk_Window tkwin,
					    char *widget_rec, int offset,
					    Tcl_FreeProc **free_proc));
static	int ZnGradientParse _ANSI_ARGS_((ClientData client_data, Tcl_Interp *interp,
					 Tk_Window tkwin, Tcl_Obj *ovalue,
					 char *widget_rec, int offset));
static	Tcl_Obj *ZnGradientPrint _ANSI_ARGS_((ClientData client_data, Tk_Window tkwin,
					      char *widget_rec, int offset,
					      Tcl_FreeProc **free_proc));
static	int ZnImageParse _ANSI_ARGS_((ClientData client_data, Tcl_Interp *interp,
				      Tk_Window tkwin, Tcl_Obj *ovalue,
				      char *widget_rec, int offset));
static	int ZnBitmapParse _ANSI_ARGS_((ClientData client_data, Tcl_Interp *interp,
				       Tk_Window tkwin, Tcl_Obj *ovalue,
				       char *widget_rec, int offset));
static	Tcl_Obj *ZnImagePrint _ANSI_ARGS_((ClientData client_data, Tk_Window tkwin,
					   char *widget_rec, int offset,
					   Tcl_FreeProc **free_proc));
static	Tk_CustomOption reliefOption = {
  (Tk_OptionParseProc *) ZnReliefParse,
  (Tk_OptionPrintProc *) ZnReliefPrint,
  NULL
};
static	Tk_CustomOption gradientOption = {
  (Tk_OptionParseProc *) ZnGradientParse,
  (Tk_OptionPrintProc *) ZnGradientPrint,
  NULL
};
static	Tk_CustomOption imageOption = {
  (Tk_OptionParseProc *) ZnImageParse,
  (Tk_OptionPrintProc *) ZnImagePrint,
  NULL
};
static	Tk_CustomOption bitmapOption = {
  (Tk_OptionParseProc *) ZnBitmapParse,
  (Tk_OptionPrintProc *) ZnImagePrint,
  NULL
};

/*
 * Information used for argv parsing.
 */
static Tk_ConfigSpec config_specs[] = {
  {TK_CONFIG_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
   "2", Tk_Offset(ZnWInfo, border_width), 0, NULL},
  {TK_CONFIG_CUSTOM, "-backcolor", "backColor", "BackColor",
   "#c3c3c3", Tk_Offset(ZnWInfo, back_color), 0, &gradientOption},
  {TK_CONFIG_BOOLEAN, "-confine", "confine", "Confine",
   "1", Tk_Offset(ZnWInfo, confine), 0, NULL},
  {TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor",
   "", Tk_Offset(ZnWInfo, cursor), TK_CONFIG_NULL_OK, NULL},
  {TK_CONFIG_FONT, "-font", "font", "Font",
   "-adobe-helvetica-bold-r-normal--*-120-*-*-*-*-*-*",
   Tk_Offset(ZnWInfo, font), 0, NULL},
  {TK_CONFIG_CUSTOM, "-forecolor", "foreColor", "Foreground",
   "Black", Tk_Offset(ZnWInfo, fore_color), 0, &gradientOption},
  {TK_CONFIG_BOOLEAN, "-fullreshape", "fullReshape", "FullReshape",
   "1", Tk_Offset(ZnWInfo, full_reshape), 0, NULL},
  {TK_CONFIG_PIXELS, "-height", "height", "Height",
   "7c", Tk_Offset(ZnWInfo, opt_height), 0, NULL},
  {TK_CONFIG_CUSTOM, "-highlightbackground", "highlightBackground", "HighlightBackground",
   "#c3c3c3", Tk_Offset(ZnWInfo, highlight_bg_color), 0, &gradientOption},
  {TK_CONFIG_CUSTOM, "-highlightcolor", "highlightColor", "HighlightColor",
   "Black", Tk_Offset(ZnWInfo, highlight_color), 0, &gradientOption},
  {TK_CONFIG_PIXELS, "-highlightthickness", "highlightThickness", "HighlightThickness",
   "2", Tk_Offset(ZnWInfo, highlight_width), 0, NULL},
  {TK_CONFIG_CUSTOM, "-insertbackground", "insertBackground", "Foreground",
   "Black", Tk_Offset(ZnWInfo, text_info.insert_color), 0, &gradientOption},
  {TK_CONFIG_INT, "-insertofftime", "insertOffTime", "OffTime",
   "300", Tk_Offset(ZnWInfo, insert_off_time), 0, NULL},
  {TK_CONFIG_INT, "-insertontime", "insertOnTime", "OnTime",
   "600", Tk_Offset(ZnWInfo, insert_on_time), 0, NULL},
  {TK_CONFIG_PIXELS, "-insertwidth", "insertWidth", "InsertWidth",
   "2", Tk_Offset(ZnWInfo, text_info.insert_width), 0, NULL},
  {TK_CONFIG_CUSTOM, "-mapdistancesymbol", "mapDistanceSymbol", "MapDistanceSymbol",
   "AtcSymbol19", Tk_Offset(ZnWInfo, map_distance_symbol),
   TK_CONFIG_NULL_OK, &bitmapOption},
  {TK_CONFIG_FONT, "-maptextfont", "mapTextFont", "MapTextFont",
   "-adobe-helvetica-bold-r-normal--*-120-*-*-*-*-*-*",
   Tk_Offset(ZnWInfo, map_text_font), 0, NULL},
  {TK_CONFIG_INT, "-overlapmanager", "overlapManager", "OverlapManager", "1",
   Tk_Offset(ZnWInfo, om_group_id), 0, NULL},
  {TK_CONFIG_INT, "-pickaperture", "pickAperture", "PickAperture",
   "1", Tk_Offset(ZnWInfo, pick_aperture), 0, NULL},
  {TK_CONFIG_CUSTOM, "-relief", "relief", "Relief",
   "flat", Tk_Offset(ZnWInfo, relief), 0, &reliefOption},
  {TK_CONFIG_INT, "-render", "render", "Render",
   "0", Tk_Offset(ZnWInfo, render), 0, NULL},
  {TK_CONFIG_BOOLEAN, "-reshape", "reshape", "Reshape",
   "1", Tk_Offset(ZnWInfo, reshape), 0, NULL},  
#ifdef PTK
  {TK_CONFIG_LANGARG, "-scrollregion", "scrollRegion", "ScrollRegion",
   "", Tk_Offset(ZnWInfo, region), TK_CONFIG_NULL_OK, NULL},
#else
  {TK_CONFIG_STRING, "-scrollregion", "scrollRegion", "ScrollRegion",
   "", Tk_Offset(ZnWInfo, region), TK_CONFIG_NULL_OK, NULL},
#endif
  {TK_CONFIG_CUSTOM, "-selectbackground", "selectBackground", "Foreground",
   "#a0a0a0", Tk_Offset(ZnWInfo, text_info.sel_color), 0, &gradientOption},
  {TK_CONFIG_DOUBLE, "-speedvectorlength", "speedVectorLength",
   "SpeedVectorLength", "3", Tk_Offset(ZnWInfo, speed_vector_length), 0, NULL},
  {TK_CONFIG_STRING, "-takefocus", "takeFocus", "TakeFocus",
   NULL, Tk_Offset(ZnWInfo, take_focus), TK_CONFIG_NULL_OK, NULL},
  {TK_CONFIG_CUSTOM, "-tile", "tile", "Tile",
   "", Tk_Offset(ZnWInfo, tile), 0, &imageOption},
  {TK_CONFIG_INT, "-trackvisiblehistorysize", "trackVisibleHistorySize", "TrackVisibleHistorySize",
   "6", Tk_Offset(ZnWInfo, track_visible_history_size), 0, NULL},
  {TK_CONFIG_INT, "-trackmanagedhistorysize", "trackManagedHistorySize",
   "TrackManagedHistorySize", "6", Tk_Offset(ZnWInfo, track_managed_history_size), 0, NULL},
  {TK_CONFIG_CUSTOM, "-tracksymbol", "trackSymbol", "TrackSymbol",
   "AtcSymbol15", Tk_Offset(ZnWInfo, track_symbol), TK_CONFIG_NULL_OK, &bitmapOption},
  {TK_CONFIG_PIXELS, "-width", "width", "Width",
   "10c", Tk_Offset(ZnWInfo, opt_width), 0, NULL},
#ifdef PTK
  {TK_CONFIG_CALLBACK, "-xscrollcommand", "xScrollCommand", "ScrollCommand",
   "", Tk_Offset(ZnWInfo, x_scroll_cmd), TK_CONFIG_NULL_OK, NULL},
#else
  {TK_CONFIG_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand",
   "", Tk_Offset(ZnWInfo, x_scroll_cmd), TK_CONFIG_NULL_OK, NULL},
#endif
  {TK_CONFIG_PIXELS, "-xscrollincrement", "xScrollIncrement", "ScrollIncrement",
   "0", Tk_Offset(ZnWInfo, x_scroll_incr), 0, NULL},
#ifdef PTK
  {TK_CONFIG_CALLBACK, "-yscrollcommand", "yScrollCommand", "ScrollCommand",
   "", Tk_Offset(ZnWInfo, y_scroll_cmd), TK_CONFIG_NULL_OK, NULL},
#else
  {TK_CONFIG_STRING, "-yscrollcommand", "yScrollCommand", "ScrollCommand",
   "", Tk_Offset(ZnWInfo, y_scroll_cmd), TK_CONFIG_NULL_OK, NULL},
#endif
  {TK_CONFIG_PIXELS, "-yscrollincrement", "yScrollIncrement",  "ScrollIncrement",
   "0", Tk_Offset(ZnWInfo, y_scroll_incr), 0, NULL},
  /*
   * Debug options.
   */
  {TK_CONFIG_BOOLEAN, "-drawbboxes", "drawBBoxes",
   "DrawBBoxes", "0", Tk_Offset(ZnWInfo, draw_bboxes), 0, NULL},
  {TK_CONFIG_CUSTOM, "-bboxcolor", "bboxColor", "BBoxColor",
   "Pink", Tk_Offset(ZnWInfo, bbox_color), 0, &gradientOption},
  {TK_CONFIG_INT, "-lightangle", "lightAngle", "LightAngle",
   "120", Tk_Offset(ZnWInfo, light_angle), 0, NULL},
  {TK_CONFIG_BOOLEAN, "-followpointer", "followPointer",
   "FollowPointer", "1", Tk_Offset(ZnWInfo, follow_pointer), 0, NULL},

  {TK_CONFIG_END, NULL, NULL, NULL, NULL, 0, 0, NULL}
};

/*
 * These defines must be kept in sync with the config_specs array.
 */
#define BORDER_WIDTH_SPEC		0
#define BACK_COLOR_SPEC			1
#define CONFINE_SPEC			2
#define CURSOR_SPEC			3
#define FONT_SPEC			4
#define FORE_COLOR_SPEC			5
#define FULL_RESHAPE_SPEC		6
#define HEIGHT_SPEC			7
#define HIGHLIGHT_BACK_COLOR_SPEC	8
#define HIGHLIGHT_COLOR_SPEC		9
#define HIGHLIGHT_THICKNESS_SPEC	10
#define INSERT_COLOR_SPEC		11
#define INSERT_OFF_TIME_SPEC		12
#define INSERT_ON_TIME_SPEC		13
#define INSERT_WIDTH_SPEC		14
#define MAP_DISTANCE_SYMBOL_SPEC	15
#define MAP_TEXT_FONT_SPEC		16
#define OVERLAP_MANAGER_SPEC		17
#define PICK_APERTURE_SPEC		18
#define RELIEF_SPEC			19
#define RENDER_SPEC			20
#define RESHAPE_SPEC			21
#define SCROLL_REGION_SPEC		22
#define SELECT_COLOR_SPEC		23
#define SPEED_VECTOR_LENGTH_SPEC	24
#define TAKE_FOCUS_SPEC			25
#define TILE_SPEC			26
#define VISIBLE_HISTORY_SIZE_SPEC	27
#define MANAGED_HISTORY_SIZE_SPEC	28
#define TRACK_SYMBOL_SPEC		29
#define WIDTH_SPEC			30
#define X_SCROLL_CMD_SPEC		31
#define X_SCROLL_INCREMENT_SPEC		32
#define Y_SCROLL_CMD_SPEC		33
#define Y_SCROLL_INCREMENT_SPEC		34
#define BBOXES_SPEC			35
#define BBOXES_COLOR_SPEC		36
#define LIGHT_ANGLE_SPEC		37
#define FOLLOW_POINTER_SPEC		38


static void	CmdDeleted _ANSI_ARGS_((ClientData client_data));
static void	Event _ANSI_ARGS_((ClientData client_data, XEvent *eventPtr));
static void	Bind _ANSI_ARGS_((ClientData client_data, XEvent *eventPtr));
static int	FetchSelection _ANSI_ARGS_((ClientData clientData, int offset,
					    char *buffer, int maxBytes));
static void	SelectTo _ANSI_ARGS_((ZnItem item, int field, int index));
static int	WidgetObjCmd _ANSI_ARGS_((ClientData client_data,
					  Tcl_Interp *, int argc, Tcl_Obj *CONST args[]));
static int	Configure _ANSI_ARGS_((Tcl_Interp *interp, ZnWInfo *wi,
				       int argc, Tcl_Obj *CONST args[], int flags));
static void	Redisplay _ANSI_ARGS_((ClientData client_data));
static void	Destroy _ANSI_ARGS_((char *mem_ptr));
static void	InitZinc _ANSI_ARGS_((Tcl_Interp *interp));
static void	Focus _ANSI_ARGS_((ZnWInfo *wi, ZnBool got_focus));
static void	Update _ANSI_ARGS_((ZnWInfo	*wi));
static void	Repair _ANSI_ARGS_((ZnWInfo	*wi));



/*
 *----------------------------------------------------------------------
 *
 * ZnReliefParse
 * ZnReliefPrint --
 *	Converter for the -relief option.
 *
 *----------------------------------------------------------------------
 */
static int
ZnReliefParse(ClientData	client_data __unused,
	      Tcl_Interp	*interp __unused,
	      Tk_Window		tkwin __unused,
	      Tcl_Obj		*ovalue,
	      char		*widget_rec,
	      int		offset)
{
  ZnReliefStyle *relief_ptr = (ZnReliefStyle *) (widget_rec + offset);
  ZnReliefStyle relief;
#ifdef PTK
  char	      *value = Tcl_GetString(ovalue);
#else
  char	      *value = (char *) ovalue;
#endif
  int	      result = TCL_OK;

  if (value != NULL) {
    result = ZnGetRelief((ZnWInfo *) widget_rec, value, &relief);
    if (result == TCL_OK) {
      *relief_ptr = relief;
    }
  }
  return result;
}

static Tcl_Obj *
ZnReliefPrint(ClientData	client_data __unused,
	      Tk_Window		tkwin __unused,
	      char		*widget_rec,
	      int		offset,
	      Tcl_FreeProc	**free_proc __unused)
{
  ZnReliefStyle relief = *(ZnReliefStyle *) (widget_rec + offset);
#ifdef PTK
  return Tcl_NewStringObj(ZnNameOfRelief(relief), -1);
#else
  return (Tcl_Obj *) ZnNameOfRelief(relief);
#endif
}


/*
 *----------------------------------------------------------------------
 *
 * ZnGradientParse
 * ZnGradientPrint --
 *	Converter for the -*color* options.
 *
 *----------------------------------------------------------------------
 */
static int
ZnGradientParse(ClientData	client_data __unused,
		Tcl_Interp	*interp,
		Tk_Window	tkwin,
		Tcl_Obj		*ovalue,
		char		*widget_rec,
		int		offset)
{
  ZnGradient	**grad_ptr = (ZnGradient **) (widget_rec + offset);
  ZnGradient	*grad, *prev_grad;
#ifdef PTK
  char		*value = Tcl_GetString(ovalue);
#else
  char		*value = (char *) ovalue;
#endif

  prev_grad = *grad_ptr;
  if ((value != NULL) && (*value != '\0')) {
    grad = ZnGetGradient(interp, tkwin, value);
    if (grad == NULL) {
      return TCL_ERROR;
    }
    if (prev_grad != NULL) {
      ZnFreeGradient(prev_grad);
    }
    *grad_ptr = grad;
  }
  return TCL_OK;
}

static Tcl_Obj *
ZnGradientPrint(ClientData	client_data __unused,
		Tk_Window	tkwin __unused,
		char		*widget_rec,
		int		offset,
		Tcl_FreeProc	**free_proc __unused)
{
  ZnGradient *gradient = *(ZnGradient **) (widget_rec + offset);
#ifdef PTK
  return Tcl_NewStringObj(ZnNameOfGradient(gradient), -1);
#else
  return (Tcl_Obj *) ZnNameOfGradient(gradient);
#endif
}


/*
 *----------------------------------------------------------------------
 *
 * ZnBitmapParse
 * ZnImageParse
 * ZnImagePrint --
 *	Converter for the -*image* options.
 *
 *----------------------------------------------------------------------
 */
static int
ZnBitmapParse(ClientData	client_data __unused,
	      Tcl_Interp	*interp __unused,
	      Tk_Window		tkwin __unused,
	      Tcl_Obj		*ovalue,
	      char		*widget_rec,
	      int		offset)
{
  ZnImage	*image_ptr = (ZnImage *) (widget_rec + offset);
  ZnImage	image, prev_image;
#ifdef PTK
  char		*value = Tcl_GetString(ovalue);
#else
  char		*value = (char *) ovalue;
#endif
  ZnWInfo	*wi = (ZnWInfo*) widget_rec;
  ZnBool	is_bmap = True;

  prev_image = *image_ptr;
  if ((value != NULL) && (*value != '\0')) {
    image = ZnGetImage(wi, value, NULL, NULL);
    if ((image == ZnUnspecifiedImage) ||
	! (is_bmap = ZnImageIsBitmap(image))) {
      if (!is_bmap) {
	ZnFreeImage(image, NULL, NULL);
      }
      return TCL_ERROR;
    }
    if (prev_image != NULL) {
      ZnFreeImage(prev_image, NULL, NULL);
    }
    *image_ptr = image;
  }
  else if (prev_image != NULL) {
    ZnFreeImage(prev_image, NULL, NULL);
    *image_ptr = NULL;
  }

  return TCL_OK;
}

static void
ZnImageUpdate(void *client_data)
{
  ZnWInfo *wi = (ZnWInfo*) client_data;

  ZnDamageAll(wi);
}

static int
ZnImageParse(ClientData	client_data __unused,
	     Tcl_Interp	*interp __unused,
	     Tk_Window	tkwin __unused,
	     Tcl_Obj	*ovalue,
	     char	*widget_rec,
	     int	offset)
{
  ZnImage	*image_ptr = (ZnImage *) (widget_rec + offset);
  ZnImage	image, prev_image;
#ifdef PTK
  char		*value = Tcl_GetString(ovalue);
#else
  char		*value = (char *) ovalue;
#endif
  ZnWInfo	*wi = (ZnWInfo*) widget_rec;

  prev_image = *image_ptr;
  if ((value != NULL) && (*value != '\0')) {
    image = ZnGetImage(wi, value, ZnImageUpdate, wi);
    if (image == NULL) {
      return TCL_ERROR;
    }
    if (prev_image != NULL) {
      ZnFreeImage(prev_image, ZnImageUpdate, wi);
    }
    *image_ptr = image;
  }
  else if (prev_image != NULL) {
    ZnFreeImage(prev_image, ZnImageUpdate, wi);
    *image_ptr = NULL;
  }
  return TCL_OK;
}

static Tcl_Obj *
ZnImagePrint(ClientData		client_data __unused,
	     Tk_Window		tkwin __unused,
	     char		*widget_rec,
	     int		offset,
	     Tcl_FreeProc	**free_proc __unused)
{
  ZnImage image = *(ZnImage *) (widget_rec + offset);
#ifdef PTK
  return Tcl_NewStringObj(image?ZnNameOfImage(image):"", -1);
#else
  return (Tcl_Obj *) (image?ZnNameOfImage(image):"");
#endif
}


/*
 *----------------------------------------------------------------------
 *
 * ZnGetAlphaStipple --
 *	Need to be handled per screen/dpy toolkit wide, not on a
 *	widget basis.
 *
 *----------------------------------------------------------------------
 */
static Pixmap
ZnGetAlphaStipple(ZnWInfo	*wi,
		  unsigned int	val)
{
  if (val >= 255)
    return None;
  else
    return wi->alpha_stipples[(int) (val / 16)];
}

/*
 *----------------------------------------------------------------------
 *
 * ZnGetInactiveStipple --
 *
 *----------------------------------------------------------------------
 */
Pixmap
ZnGetInactiveStipple(ZnWInfo	*wi)
{
  return ZnGetAlphaStipple(wi, 128);
}


/*
 *----------------------------------------------------------------------
 *
 * ZnNeedRedisplay --
 *
 *----------------------------------------------------------------------
 */
void
ZnNeedRedisplay(ZnWInfo	*wi)
{
  if (ISCLEAR(wi->flags, ZN_UPDATE_PENDING) && ISSET(wi->flags, ZN_REALIZED)) {
    /*printf("scheduling an update\n");*/
    Tcl_DoWhenIdle(Redisplay, (ClientData) wi);
    SET(wi->flags, ZN_UPDATE_PENDING);
  }
}

/*
 *----------------------------------------------------------------------
 *
 * ZnGetGlContext --
 *
 *----------------------------------------------------------------------
 */
#ifdef GL
ZnGLContextEntry *
ZnGetGLContext(Display *dpy)
{
  ZnGLContextEntry *context_entry;
  
  for (context_entry = gl_contexts;
       context_entry && context_entry->dpy != dpy;
       context_entry = context_entry->next);

  return context_entry;
}

void
ZnGLMakeCurrent(Display		*dpy,
		Tk_Window	win)
{
  ZnGLContextEntry *context_entry;

  context_entry = ZnGetGLContext(dpy);
#ifdef WIN
  wglMakeCurrent(context_entry->hdc, context_entry->context);
#else
  glXMakeCurrent(dpy, win==NULL?DefaultRootWindow(dpy):Tk_WindowId(win),
		 context_entry->context);
#endif
}

void
ZnGLSwapBuffers(Display		*dpy,
		Tk_Window	win)
{
#ifdef WIN
  SwapBuffers(ZnGetGLContext(dpy)->hdc);
#else
  glXSwapBuffers(dpy, Tk_WindowId(win));
#endif  
}
#endif

/*
 *----------------------------------------------------------------------
 *
 * ZincObjCmd --
 *
 *	This procedure is invoked to process the "zinc" Tcl
 *	command.  It creates a new "zinc" widget.
 *
 *----------------------------------------------------------------------
 */
int
ZincObjCmd(ClientData		client_data,	/* Main window associated with
						 * interpreter. */
	   Tcl_Interp		*interp,	/* Current interpreter. */
	   int			argc,		/* Number of arguments. */
	   Tcl_Obj	*CONST	args[])		/* Argument strings. */
{
  Tk_Window	top_w = (Tk_Window) client_data;
  ZnWInfo	*wi;
  Tk_Window	tkwin;
  unsigned int	num;
  ZnBool	has_gl = False;
#ifndef _WIN32
#  if defined(GL) || defined(SHAPE)
  int		major_op, first_err, first_evt;
#  endif
#  ifdef GL
  int		major_glx, minor_glx;
  Display	*dpy = Tk_Display(top_w);
  Screen	*screen = Tk_Screen(top_w);
  int attribs[] = {
    GLX_RGBA,
    GLX_DOUBLEBUFFER,
    GLX_BUFFER_SIZE, 24,
    /*GLX_BUFFER_SIZE, 32,*/
    GLX_STENCIL_SIZE, 8,
    /*GLX_ALPHA_SIZE, 8,*/
    None
  };
#  endif
#endif

  InitZinc(interp);

#ifdef GL
#  ifdef _WIN32
  has_gl = True;
#  else
  if (XQueryExtension(dpy, "GLX", &major_op, &first_evt, &first_err)) {
    if (glXQueryExtension(dpy, &first_err, &first_evt)) {
      if (glXQueryVersion(dpy, &major_glx, &minor_glx)) {
	if ((major_glx == 1) && (minor_glx >= 1)) {
	  has_gl = True;
	}
      }
    }
  }
#  endif
#endif

  if (argc == 1) {
    Tcl_AppendResult(interp, VERSION, NULL);
    Tcl_AppendResult(interp, " X11", NULL);
#ifdef GL
#  ifdef _WIN32
    Tcl_AppendResult(interp, " GL", NULL);
#  else
    {
      XVisualInfo *visual = glXChooseVisual(dpy,
					    XScreenNumberOfScreen(screen),
					    attribs);
      if (has_gl && visual) {
	Tcl_AppendResult(interp, " GL", NULL);
      }
      XFree(visual);
    }
#  endif
#endif
    return TCL_OK;
  }

  tkwin = Tk_CreateWindowFromPath(interp, top_w, Tcl_GetString(args[1]), NULL);
  if (tkwin == NULL) {
    return TCL_ERROR;
  }
  Tk_SetClass(tkwin, "Zinc");
  
  /*
   * Allocate and initialize the widget record.
   */  
  wi = (ZnWInfo *) ZnMalloc(sizeof(ZnWInfo));
  wi->win = tkwin;
  wi->interp = interp;
  wi->dpy = Tk_Display(tkwin);
  wi->screen = Tk_Screen(tkwin);
  wi->flags = 0;
  wi->render = 0;
  wi->real_top = None;

  ASSIGN(wi->flags, ZN_HAS_GL, has_gl);

#if defined(SHAPE) && !defined(_WIN32)
  ASSIGN(wi->flags, ZN_HAS_X_SHAPE,
	 XQueryExtension(wi->dpy, "SHAPE", &major_op, &first_evt, &first_err));
  wi->reshape = wi->full_reshape = True;
#else
  CLEAR(wi->flags, ZN_HAS_X_SHAPE);
  wi->reshape = wi->full_reshape = False;
#endif

#ifdef PTK
#ifdef PTK_800
  wi->cmd = Lang_CreateWidget(interp, tkwin, (Tcl_CmdProc *) WidgetObjCmd,
			      (ClientData) wi, CmdDeleted);
#else
  wi->cmd = Lang_CreateWidget(interp, tkwin, WidgetObjCmd, (ClientData) wi, CmdDeleted);
#endif
#else
  wi->cmd = Tcl_CreateObjCommand(interp, Tk_PathName(tkwin), WidgetObjCmd,
				 (ClientData) wi, CmdDeleted);
#endif  
  wi->binding_table = 0;
  wi->fore_color = NULL;
  wi->back_color = NULL;
  wi->relief_grad = NULL;
  wi->bbox_color = NULL;
  wi->draw_bboxes = 0;
  wi->light_angle = 120;
  wi->follow_pointer = 0;
  wi->border_width = 0;
  wi->relief = ZN_RELIEF_FLAT;
  wi->opt_width = None;
  wi->opt_height = None;
  wi->font = 0;
  wi->map_text_font = 0;
#ifdef GL
  wi->font_tfi = NULL;
  wi->map_font_tfi = NULL;
#endif
  wi->map_distance_symbol = ZnUnspecifiedImage;
  wi->track_symbol = ZnUnspecifiedImage;
  wi->cursor = None;
  wi->hot_item = ZN_NO_ITEM;
  wi->hot_prev = ZN_NO_ITEM;
  wi->track_visible_history_size = 0;
  wi->track_managed_history_size = 0;
  wi->speed_vector_length = 0;
  wi->tile = ZnUnspecifiedImage;
  wi->confine = 0;
  wi->origin.x = wi->origin.y = 0;
  wi->scroll_xo = wi->scroll_yo = 0;
  wi->scroll_xc = wi->scroll_yc = 0;
  wi->x_scroll_incr = wi->y_scroll_incr = 0;
  wi->x_scroll_cmd = wi->y_scroll_cmd = NULL;
  wi->region = NULL;

  wi->id_table = (Tcl_HashTable *) ZnMalloc(sizeof(Tcl_HashTable));
  Tcl_InitHashTable(wi->id_table, TCL_ONE_WORD_KEYS);
  wi->t_table = (Tcl_HashTable *) ZnMalloc(sizeof(Tcl_HashTable));
  Tcl_InitHashTable(wi->t_table, TCL_STRING_KEYS);

  wi->obj_id = 1;
  wi->num_items = 0;

  wi->top_group = ZnCreateItem(wi, ZnGroup, 0, NULL);

#ifdef OM
  wi->om_group_id = 0;
  wi->om_group = wi->top_group;
  OmRegister((void *) wi, ZnSendTrackToOm, ZnSetLabelAngleFromOm, ZnQueryLabelPosition);
#endif
  wi->gc = 0;
  wi->draw_buffer = 0;
  wi->pick_aperture = 0;
  wi->state = 0;
  memset(&wi->pick_event, 0, sizeof(XEvent));
  wi->new_item = wi->current_item = ZN_NO_ITEM;
  wi->new_part = wi->current_part = ZN_NO_PART;
  wi->focus_item = ZN_NO_ITEM;
  wi->focus_field = ZN_NO_PART;

  CLEAR(wi->flags, ZN_MONITORING);
#ifndef _WIN32
  wi->total_draw_chrono = ZnNewChrono("Total draw time");
  wi->this_draw_chrono = ZnNewChrono("Last draw time");
#endif
  wi->damaged_area_w = wi->damaged_area_h = 0;
  
  wi->work_item_list = NULL;
  wi->work_pts = ZnListNew(8, sizeof(ZnPoint));
#ifdef GL
  wi->work_doubles = ZnListNew(8, sizeof(double));
#endif
  wi->work_xpts = ZnListNew(8, sizeof(XPoint));
  wi->work_strs = ZnListNew(8, sizeof(char *));
  
  /*
   * Text management init.
   */
  wi->text_info.sel_color = NULL;
  wi->text_info.sel_item = ZN_NO_ITEM;
  wi->text_info.sel_field = ZN_NO_PART;
  wi->text_info.sel_first = -1;
  wi->text_info.sel_last = -1;
  wi->text_info.anchor_item = ZN_NO_ITEM;
  wi->text_info.anchor_field = ZN_NO_PART;
  wi->text_info.sel_anchor = 0;
  wi->text_info.insert_color = NULL;
  wi->text_info.insert_width = 0;
  wi->text_info.cursor_on = False;
  wi->insert_on_time = 0;
  wi->insert_off_time = 0;
  wi->blink_handler = NULL;
  wi->take_focus = NULL;
  wi->highlight_width = 0;
  wi->highlight_color = NULL;
  wi->highlight_bg_color = NULL;
  ZnResetBBox(&wi->exposed_area);
  ZnResetBBox(&wi->damaged_area);
  
  ZnInitClipStack(wi);  
  ZnInitTransformStack(wi);

  /*
   * Allocate a GLU tesselator.
   */
  wi->tess = gluNewTess();
  wi->tess_combine_list = NULL;

  for (num = 0; num < ZN_NUM_ALPHA_STEPS; num++) {
    char	name[TCL_INTEGER_SPACE+12];

    sprintf(name, "AlphaStipple%d", num);
    wi->alpha_stipples[num] = Tk_GetBitmap(interp, tkwin, Tk_GetUid(name));
  }

  Tk_CreateEventHandler(tkwin,
			ExposureMask|StructureNotifyMask|FocusChangeMask,
			Event, (ClientData) wi);
  Tk_CreateEventHandler(tkwin, KeyPressMask|KeyReleaseMask|
			ButtonPressMask|ButtonReleaseMask|EnterWindowMask|
			LeaveWindowMask|PointerMotionMask|VirtualEventMask,
			Bind, (ClientData) wi);
  Tk_CreateSelHandler(tkwin, XA_PRIMARY, XA_STRING,
		      FetchSelection, (ClientData) wi, XA_STRING);

  if (Configure(interp, wi, argc-2, args+2, 0) != TCL_OK) {
    Tk_DestroyWindow(tkwin);
    return TCL_ERROR;
  }  
  
  wi->damaged_area.orig.x = wi->damaged_area.orig.y = 0;
  wi->damaged_area.corner.x = wi->width = wi->opt_width;
  wi->damaged_area.corner.y = wi->height = wi->opt_height;

  /*
   * Allocate double buffer pixmap/image.
   */
  if (wi->render) {
#ifdef GL
    ZnGLContextEntry *context_entry;
    ZnGLContext	     gl_context;

    ASSIGN(wi->flags, ZN_PRINT_CONFIG, (getenv("ZINC_GLX_INFO") != NULL));

    if (ISSET(wi->flags, ZN_HAS_GL)) {
#  ifdef _WIN32
      PIXELFORMATDESCRIPTOR pfd = { 
	sizeof(PIXELFORMATDESCRIPTOR),   // size of this pfd 
	1,                     // version number 
	PFD_DRAW_TO_WINDOW |
	PFD_SUPPORT_OPENGL |   // support OpenGL 
	PFD_DOUBLEBUFFER,      // double buffered 
	PFD_TYPE_RGBA,         // RGBA type 
	0,                     // Color bits ignored
	8, 0, 8, 0, 8, 0, 8, 0, // R, G, B, A (color shifts ignored )
	0,                     // accum bits ignored
	0, 0, 0, 0,            // accum R, G, B, A ignored
	0,                     // no depth buffer
	8,                     // 8 bits stencil buffer 
	0,                     // no auxiliary buffer 
	PFD_MAIN_PLANE,        // main layer 
	0,                     // reserved 
	0, 0, 0                // layer masks ignored 
      };
      int	ipixel;
      HWND	hwnd;
      HDC	hdc;

      Tk_MakeWindowExist(wi->win);
      hwnd = Tk_GetHWND(Tk_WindowId(wi->win));
      hdc = GetDC(hwnd);
      if (!hdc) {
	OutputDebugString("Unable to get the hdc\n");
      }
      /*
       * Look for a matching context already available.
       */
      context_entry = ZnGetGLContext(wi->dpy);
      if (context_entry) {
	gl_context = context_entry->context;
      }
      else {
	ipixel = ChoosePixelFormat(hdc, &pfd);
	/*      sprintf(msg, "ipixel=%d dwFlags=0x%x req=0x%x iPixelType=%d\n",
		ipixel,
		pfd.dwFlags,
		PFD_DRAW_TO_WINDOW|PFD_SUPPORT_OPENGL|PFD_DOUBLEBUFFER,	      
		pfd.iPixelType==PFD_TYPE_RGBA);
		OutputDebugString(msg);*/
	if (!ipixel) {
	  OutputDebugString("ChoosePixelFormat failed\n");
	}
	wi->render = (SetPixelFormat(hdc, ipixel, &pfd) == TRUE);
	if (wi->render) {
	  gl_context = wglCreateContext(hdc);
	  if (!gl_context) {
	    OutputDebugString("wglCreateContext failed\n");
	  }
	  else {
	    context_entry = ZnMalloc(sizeof(ZnGLContextEntry));
	    context_entry->context = gl_context;
	    context_entry->hdc = hdc;
	    context_entry->next = gl_contexts;
	    gl_contexts = context_entry;
	  }
	}
	ZnGLRelease(wi);
      }
#  else /* _WIN32 */
      XVisualInfo *gl_visual = NULL;
      Colormap	  colormap = 0;

      if (ISSET(wi->flags, ZN_PRINT_CONFIG)) {
	fprintf(stderr, "GLX version %d.%d\n", major_glx, minor_glx);
      }
      
      /*
       * Look for a matching context already available.
       */
      context_entry = ZnGetGLContext(wi->dpy);
      if (context_entry) {
	gl_context = context_entry->context;
	gl_visual = context_entry->visual;
	colormap = context_entry->colormap;
      }
      else {
	int val;

	gl_visual = glXChooseVisual(wi->dpy,
				    XScreenNumberOfScreen(wi->screen),
				    attribs);
	if (!gl_visual) {
	  fprintf(stderr, "No glx visual\n");
	  wi->render = 0;
	}
	else {
	  gl_context = glXCreateContext(wi->dpy, gl_visual,
					NULL, wi->render==1);
	  if (!gl_context) {
	    fprintf(stderr, "No glx context\n");
	    wi->render = 0;
	  }
	  else {
	    colormap = XCreateColormap(wi->dpy, RootWindowOfScreen(wi->screen),
				       gl_visual->visual, AllocNone);
	    context_entry = ZnMalloc(sizeof(ZnGLContextEntry));
	    context_entry->context = gl_context;
	    context_entry->visual = gl_visual;
	    context_entry->colormap = colormap;
	    context_entry->dpy = wi->dpy;
	    context_entry->max_tex_size = 64; /* Minimum value is always valid */
	    context_entry->max_line_width = 1;
	    context_entry->max_point_width = 1;
	    context_entry->next = gl_contexts;
	    gl_contexts = context_entry;
	    
	    if (ISSET(wi->flags, ZN_PRINT_CONFIG)) {
	      fprintf(stderr, "  Visual : 0x%x, ",
		      (int) gl_visual->visualid);      
	      glXGetConfig(wi->dpy, gl_visual, GLX_RGBA, &val);
	      fprintf(stderr, "RGBA : %d, ", val);
	      glXGetConfig(wi->dpy, gl_visual, GLX_DOUBLEBUFFER, &val);
	      fprintf(stderr, "Double Buffer : %d, ", val);
	      glXGetConfig(wi->dpy, gl_visual, GLX_STENCIL_SIZE, &val);
	      fprintf(stderr, "Stencil : %d, ", val);
	      glXGetConfig(wi->dpy, gl_visual, GLX_BUFFER_SIZE, &val);
	      fprintf(stderr, "depth : %d, ", val);
	      glXGetConfig(wi->dpy, gl_visual, GLX_RED_SIZE, &val);
	      fprintf(stderr, "red : %d, ", val);
	      glXGetConfig(wi->dpy, gl_visual, GLX_GREEN_SIZE, &val);
	      fprintf(stderr, "green : %d, ", val);
	      glXGetConfig(wi->dpy, gl_visual, GLX_BLUE_SIZE, &val);
	      fprintf(stderr, "blue : %d, ", val);
	      glXGetConfig(wi->dpy, gl_visual, GLX_ALPHA_SIZE, &val);
	      fprintf(stderr, "alpha : %d\n", val);
	      fprintf(stderr, "  Direct Rendering: %d\n",
		      glXIsDirect(wi->dpy, gl_context));
	    }
	  }
	}
      }
      if (gl_visual && colormap) {
	Tk_SetWindowVisual(wi->win, gl_visual->visual, 24, colormap);
      }
#  endif /* _WIN32 */
    }
    else {
      fprintf(stderr, "GL not available\n");
      wi->render = 0;
    }
#else /* GL */
    wi->render = 0;
#endif /* GL */
  }
  if (!wi->render) {
    wi->draw_buffer = Tk_GetPixmap(wi->dpy, RootWindowOfScreen(wi->screen),
				   wi->width, wi->height, Tk_Depth(wi->win));
  }
  
#ifdef PTK
  Tcl_SetObjResult(interp, LangWidgetObj(interp, tkwin));
#else
  Tcl_SetObjResult(interp, Tcl_NewStringObj(Tk_PathName(tkwin), -1));
#endif
  return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * EncodeItemPart --
 *
 *	Form a ClientData value from an item/part that is suitable
 *	as a key in a binding table.
 *
 *----------------------------------------------------------------------
 */
ClientData
EncodeItemPart(ZnItem	item,
	       int	part)
{
  if (part >= 0) {
    ZnFieldSet fs;
    if (!item->class->GetFieldSet) {
      return item;
    }
    fs = item->class->GetFieldSet(item);
    return (ClientData) (ZnFIELD.GetFieldStruct(fs, part % (int) ZnFIELD.NumFields(fs)));
  }
  else if (part == ZN_NO_PART) {
    return item;
  }
  return (ClientData) (((char *) item)-part);
}


/*
 *--------------------------------------------------------------
 *
 * All tag search procs below are lifted from tkCanvas.c, then
 * modified to match our needs.
 *
 *--------------------------------------------------------------
 */

/*
 *--------------------------------------------------------------
 *
 * TagSearchExprInit --
 *
 *      This procedure allocates and initializes one
 *	TagSearchExpr struct.
 *
 *--------------------------------------------------------------
 */
static void
TagSearchExprInit(TagSearchExpr	**expr_var)
{
  TagSearchExpr* expr = *expr_var;

  if (! expr) {
    expr = (TagSearchExpr *) ZnMalloc(sizeof(TagSearchExpr));
    expr->allocated = 0;
    expr->uids = NULL;
    expr->next = NULL;
  }
  expr->uid = NULL;
  expr->index = 0;
  expr->length = 0;
  *expr_var = expr;
}


/*
 *--------------------------------------------------------------
 *
 * TagSearchExprDestroy --
 *
 *      This procedure destroys one TagSearchExpr structure.
 *
 *--------------------------------------------------------------
 */
static void
TagSearchExprDestroy(TagSearchExpr	*expr)
{
  if (expr) {
    if (expr->uids) {
      ZnFree(expr->uids);
    }
    ZnFree(expr);
  }
}


/*
 *--------------------------------------------------------------
 *
 * TagSearchScanExpr --
 *
 *      This recursive procedure is called to scan a tag expression
 *      and compile it into an array of Tk_Uids.
 *
 * Results:
 *      The return value indicates if the tagOrId expression
 *      was successfully scanned (syntax).
 *      The information at *search is initialized
 *      such that a call to ZnTagSearchFirst, followed by
 *      successive calls to ZnTagSearchNext will return items
 *      that match tag.
 *
 * Side effects:
 *
 *--------------------------------------------------------------
 */
static int
TagSearchScanExpr(Tcl_Interp	*interp,	/* Current interpreter. */
		  ZnTagSearch	*search,	/* Search data */
		  TagSearchExpr *expr)		/* Compiled expression result */
{
  int	looking_for_tag; /* When true, scanner expects next char(s)
			  * to be a tag, else operand expected */
  int	found_tag;	/* One or more tags found */
  int	found_endquote;	/* For quoted tag string parsing */
  int	negate_result;	/* Pending negation of next tag value */
  char	*tag;		/* tag from tag expression string */
  char	c;

  negate_result = 0;
  found_tag = 0;
  looking_for_tag = 1;
  while (search->tag_index < search->tag_len) {
    c = search->tag[search->tag_index++];
    
    if (expr->allocated == expr->index) {
      expr->allocated += 15;
      if (expr->uids) {
	expr->uids = (Tk_Uid *) ZnRealloc((char *) expr->uids,
					  expr->allocated * sizeof(Tk_Uid));
      }
      else {
	expr->uids = (Tk_Uid *) ZnMalloc(expr->allocated * sizeof(Tk_Uid));
      }
    }
    
    if (looking_for_tag) {
      switch (c) {
      case ' ':	/* ignore unquoted whitespace */
      case '\t':
      case '\n':
      case '\r':
	break;
      case '!':	/* negate next tag or subexpr */
	if (looking_for_tag > 1) {
	  Tcl_AppendResult(interp, "Too many '!' in tag search expression",
			   (char *) NULL);
	  return TCL_ERROR;
	}
	looking_for_tag++;
	negate_result = 1;
	break;
      case '(':	/* scan (negated) subexpr recursively */
	if (negate_result) {
	  expr->uids[expr->index++] = neg_paren_uid;
	  negate_result = 0;
	}
	else {
	  expr->uids[expr->index++] = paren_uid;
	}
	if (TagSearchScanExpr(interp, search, expr) != TCL_OK) {
	  /* Result string should be already set
	   * by nested call to tag_expr_scan() */
	  return TCL_ERROR;
	}
	looking_for_tag = 0;
	found_tag = 1;
	break;
      case '"':	/* quoted tag string */
	if (negate_result) {
	  expr->uids[expr->index++] = neg_tag_val_uid;
	  negate_result = 0;
	}
	else {
	  expr->uids[expr->index++] = tag_val_uid;
	}
	tag = search->rewrite_buf;
	found_endquote = 0;
	while (search->tag_index < search->tag_len) {
	  c = search->tag[search->tag_index++];
	  if (c == '\\') {
	    c = search->tag[search->tag_index++];
	  }
	  if (c == '"') {
	    found_endquote = 1;
	    break;
	  }
	  *tag++ = c;
	}
	if (! found_endquote) {
	  Tcl_AppendResult(interp, "Missing endquote in tag search expression",
			   (char *) NULL);
	  return TCL_ERROR;
	}
	if (! (tag - search->rewrite_buf)) {
	  Tcl_AppendResult(interp,
			   "Null quoted tag string in tag search expression",
			   (char *) NULL);
	  return TCL_ERROR;
	}
	*tag++ = '\0';
	expr->uids[expr->index++] = Tk_GetUid(search->rewrite_buf);
	looking_for_tag = 0;
	found_tag = 1;
	break;
      case '&':	/* illegal chars when looking for tag */
      case '|':
      case '^':
      case ')':
	Tcl_AppendResult(interp, "Unexpected operator in tag search expression",
			 (char *) NULL);
	return TCL_ERROR;
      default:	/* unquoted tag string */
	if (negate_result) {
	  expr->uids[expr->index++] = neg_tag_val_uid;
	  negate_result = 0;
	}
	else {
	  expr->uids[expr->index++] = tag_val_uid;
	}
	tag = search->rewrite_buf;
	*tag++ = c;
	/* copy rest of tag, including any embedded whitespace */
	while (search->tag_index < search->tag_len) {
	  c = search->tag[search->tag_index];
	  if ((c == '!') || (c == '&') || (c == '|') || (c == '^') ||
	      (c == '(') || (c == ')') || (c == '"')) {
	    break;
	  }
	  *tag++ = c;
	  search->tag_index++;
	}
	/* remove trailing whitespace */
	while (1) {
	  c = *--tag;
	  /* there must have been one non-whitespace char,
	   *  so this will terminate */
	  if ((c != ' ') && (c != '\t') && (c != '\n') && (c != '\r')) {
	    break;
	  }
	}
	*++tag = '\0';
	expr->uids[expr->index++] = Tk_GetUid(search->rewrite_buf);
	looking_for_tag = 0;
	found_tag = 1;
      }
      
    }
    else {    /* ! looking_for_tag */  
      switch (c) {
      case ' '  :	/* ignore whitespace */
      case '\t' :
      case '\n' :
      case '\r' :
	break;
      case '&'  :	/* AND operator */
	c = search->tag[search->tag_index++];
	if (c != '&') {
	  Tcl_AppendResult(interp, "Singleton '&' in tag search expression",
			   (char *) NULL);
	  return TCL_ERROR;
	}
	expr->uids[expr->index++] = and_uid;
	looking_for_tag = 1;
	break;
      case '|'  :	/* OR operator */
	c = search->tag[search->tag_index++];
	if (c != '|') {
	  Tcl_AppendResult(interp, "Singleton '|' in tag search expression",
			   (char *) NULL);
	  return TCL_ERROR;
	}
	expr->uids[expr->index++] = or_uid;
	looking_for_tag = 1;
	break;
      case '^'  :	/* XOR operator */
	expr->uids[expr->index++] = xor_uid;
	looking_for_tag = 1;
	break;
      case ')'  :	/* end subexpression */
	expr->uids[expr->index++] = end_paren_uid;
	goto breakwhile;
      default   :	/* syntax error */
	Tcl_AppendResult(interp,
			 "Invalid boolean operator in tag search expression",
			 (char *) NULL);
	return TCL_ERROR;
      }
    }
  }
 breakwhile:
  if (found_tag && ! looking_for_tag) {
    return TCL_OK;
  }
  Tcl_AppendResult(interp, "Missing tag in tag search expression",
		   (char *) NULL);
  return TCL_ERROR;
}


/*
 *--------------------------------------------------------------
 *
 * TagSearchEvalExpr --
 *
 *      This recursive procedure is called to eval a tag expression.
 *
 * Results:
 *      The return value indicates if the tagOrId expression
 *      successfully matched the tags of the current item.
 *
 * Side effects:
 *
 *--------------------------------------------------------------
 */
static int
TagSearchEvalExpr(TagSearchExpr	*expr,	/* Search expression */
		  ZnItem	item)	/* Item being test for match */
{
  int		looking_for_tag; /* When true, scanner expects next char(s)
				  * to be a tag, else operand expected */
  int		negate_result;	/* Pending negation of next tag value */
  Tk_Uid	uid;
  int		result=0;	/* Value of expr so far */
  int		paren_depth;
  
  negate_result = 0;
  looking_for_tag = 1;
  while (expr->index < expr->length) {
    uid = expr->uids[expr->index++];
    if (looking_for_tag) {
      if (uid == tag_val_uid) {
	/*
	 * assert(expr->index < expr->length);
	 */
	uid = expr->uids[expr->index++];
	/*
	 * set result 1 if tag is found in item's tags
	 */
	result = ZnITEM.HasTag(item, uid) ? 1 : 0;
      }
      else if (uid == neg_tag_val_uid) {
	negate_result = ! negate_result;
	/*
	 * assert(expr->index < expr->length);
	 */
	uid = expr->uids[expr->index++];
	/*
	 * set result 1 if tag is found in item's tags
	 */
	result = ZnITEM.HasTag(item, uid) ? 1 : 0;
      }
      else if (uid == paren_uid) {
	/*
	 * evaluate subexpressions with recursion
	 */
	result = TagSearchEvalExpr(expr, item);
      }
      else if (uid == neg_paren_uid) {
	negate_result = ! negate_result;
	/*
	 * evaluate subexpressions with recursion
	 */
	result = TagSearchEvalExpr(expr, item);
	/*
	 * } else {
	 *  assert(0);
	 */
      }
      if (negate_result) {
	result = ! result;
	negate_result = 0;
      }
      looking_for_tag = 0;
    }
    else {    /* ! looking_for_tag */
      if (((uid == and_uid) && (!result)) || ((uid == or_uid) && result)) {
	/*
	 * short circuit expression evaluation
	 *
	 * if result before && is 0, or result before || is 1, then
	 * the expression is decided and no further evaluation is needed.
	 */
	paren_depth = 0;
	while (expr->index < expr->length) {
	  uid = expr->uids[expr->index++];
	  if ((uid == tag_val_uid) || (uid == neg_tag_val_uid)) {
	    expr->index++;
	    continue;
	  }
	  if ((uid == paren_uid) || (uid == neg_paren_uid)) {
	    paren_depth++;
	    continue;
	  } 
	  if (uid == end_paren_uid) {
	    paren_depth--;
	    if (paren_depth < 0) {
	      break;
	    }
	  }
	}
	return result;
	
      }
      else if (uid == xor_uid) {
	/*
	 * if the previous result was 1 then negate the next result.
	 */
	negate_result = result;
      }
      else if (uid == end_paren_uid) {
	return result;
	/*
	 * } else {
	 *  assert(0);
	 */
      }
      looking_for_tag = 1;
    }
  }
  /*
   * assert(! looking_for_tag);
   */
  return result;
}


static ZnItem
LookupGroupFromPath(ZnItem	 start,
		    Tk_Uid	 *names,
		    unsigned int num_names)
{
  Tk_Uid	name, *tags;
  unsigned int	count;
  ZnBool	recursive;
  ZnItem	result, current = ZnGroupHead(start);

  if (num_names == 0) {
    return start;
  }

  name = names[1];
  recursive = (names[0] == star_uid);
  /*  printf("LookupGroupFromPath; group: %d, nom: %s, recursive: %s\n",
      start->id, name, names[0]);*/
  while (current != ZN_NO_ITEM) {
    if ((current->class == ZnGroup) && (current->tags)) {
      tags = ZnListArray(current->tags);
      count = ZnListSize(current->tags);
      for (; count > 0; tags++, count--) {
	if (name == *tags) {
	  if (num_names > 2) {
	    result = LookupGroupFromPath(current, names+2, num_names-2);
	    return result;
	  }
	  else {
	    return current;
	  }
	}
      }
      /*
       * This group doesn't match try to search depth first.
       */
      if (recursive) {
	result = LookupGroupFromPath(current, names, num_names);
	if (result != ZN_NO_ITEM) {
	  return result;
	}
      }
    }
    current = current->next;
  }

  return ZN_NO_ITEM;
}


/*
 *--------------------------------------------------------------
 *
 * ZnTagSearchScan --
 *
 *      This procedure is called to initiate an enumeration of
 *      all items in a given zinc that contain a tag that matches
 *      the tagOrId expression.
 *
 * Results:
 *      The return value indicates if the tagOrId expression
 *      was successfully scanned (syntax).
 *      The information at *search is initialized such that a
 *	call to ZnTagSearchFirst, followed by successive calls
 *	to ZnTagSearchNext will return items that match tag.
 *
 * Side effects:
 *      search is linked into a list of searches in progress
 *      in zinc, so that elements can safely be deleted while
 *	the search is in progress.
 *
 *--------------------------------------------------------------
 */
static int
ZnTagSearchScan(ZnWInfo	  *wi,
		Tcl_Obj	  *tag_obj,	  /* Object giving tag value, NULL
					   * is the same as 'all'. */
		ZnTagSearch **search_var) /* Record describing tag search;
					   * will be initialized here. */
{
  Tk_Uid	tag;
  int		i;
  ZnTagSearch	*search;
  ZnItem	group = wi->top_group;
  ZnBool	recursive = True;

  if (tag_obj) {
    tag = Tcl_GetString(tag_obj);
  }
  else {
    tag = all_uid;
  }
  
  /*
   * Initialize the search.
   */
  if (*search_var) {
    search = *search_var;
  }
  else {
    /* Allocate primary search struct on first call */
    *search_var = search = (ZnTagSearch *) ZnMalloc(sizeof(ZnTagSearch));
    search->expr = NULL;
    
    /* Allocate buffer for rewritten tags (after de-escaping) */
    search->rewrite_buf_alloc = 100;
    search->rewrite_buf = ZnMalloc(search->rewrite_buf_alloc);
    search->item_stack = ZnListNew(16, sizeof(ZnItem));
  }
  TagSearchExprInit(&(search->expr));
  
  /* How long is the tagOrId ? */
  search->tag_len = strlen(tag);
  
  /*
   * Short-circuit impossible searches for null tags and
   * mark the search as 'over' for ZnTagSearchFirst and
   * ZnTagSearchNext. This test must not be migrated before
   * allocating search structures or special care must be
   * taken in ZnTagSearchDestroy to avoid deallocating unallocated
   * memory.
   */
  if (search->tag_len == 0) {
    search->over = True;
    return TCL_OK;
  }
  
  /*
   * If a path specification exists in the tag, strip it from the
   * tag and search for a matching group.
   */
  if (strpbrk(tag, ".*")) {
    Tk_Uid path;
    char	 c, *next;
    unsigned int id;
    Tcl_HashEntry *entry;

    ZnListEmpty(wi->work_strs);
    recursive = False;
    if ((*tag == '.') || (*tag == '*')) {
      recursive = (*tag == '*');
      tag++;
    }
    path = tag;
    while ((next = strpbrk(path, ".*"))) {
      if (isdigit(*path)) {
	if (path == tag) { /* Group id is ok only in first section. */
	  c = *next;
	  *next = '\0';
	  id = strtoul(path, NULL, 10);
	  *next = c;
	  group = wi->hot_item;
	  if ((group == ZN_NO_ITEM) || (group->id != id)) {
	    entry = Tcl_FindHashEntry(wi->id_table, (char *) id);
	    if (entry != NULL) {
	      group = (ZnItem) Tcl_GetHashValue(entry);
	    }
	    else {
	      Tcl_AppendResult(wi->interp, "unknown group in path \"",
			       tag, "\"", NULL);
	      return TCL_ERROR;
	    }
	  }
	  if (group->class != ZnGroup) {
	    Tcl_AppendResult(wi->interp, "item is not a group in path \"",
			     tag, "\"", NULL);
	    return TCL_ERROR;
	  }
	}
	else {
	  Tcl_AppendResult(wi->interp, "misplaced group id in path \"",
			   tag, "\"", NULL);
	  return TCL_ERROR;
	}
      }
      else {
	ZnListAdd(wi->work_strs,
		  (void *) (recursive ? &star_uid : &dot_uid),
		  ZnListTail);
	c = *next;
	*next = '\0';
	path = Tk_GetUid(path);
	*next = c;
	ZnListAdd(wi->work_strs, (void *) &path, ZnListTail);
      }
      recursive = (*next == '*');
      path = next+1;
    }

    group = LookupGroupFromPath(group,
				ZnListArray(wi->work_strs),
				ZnListSize(wi->work_strs));
    if (group == ZN_NO_ITEM) {
      Tcl_AppendResult(wi->interp, "path does not lead to a valid group\"",
		       tag, "\"", NULL);
      return TCL_ERROR;
    }

    /*
     * Adjust tag to strip the path.
     */
    tag = path;
    search->tag_len = strlen(tag);
    /*
     * If the tag consist only in a path description
     * assume that the tag all is implied.
     */
    if (search->tag_len == 0) {
      tag = all_uid;
      search->tag_len = strlen(tag);
    }
  }
  
  /*
   * Make sure there is enough buffer to hold rewritten tags (30%).
   */
  if ((unsigned int)(search->tag_len*1.3) >= search->rewrite_buf_alloc) {
    search->rewrite_buf_alloc = (unsigned int) (search->tag_len*1.3);
    search->rewrite_buf = ZnRealloc(search->rewrite_buf,
				    search->rewrite_buf_alloc);
  }
  
  /* Initialize search */
  search->wi = wi;
  search->over = False;
  search->type = 0;
  search->group = group;
  search->recursive = recursive;
  ZnListEmpty(search->item_stack);
  
  /*
   * Find the first matching item in one of several ways. If the tag
   * is a number then it selects the single item with the matching
   * identifier.
   */
  if (isdigit(*tag)) {
    char *end;
    
    search->id = strtoul(tag, &end, 0);
    if (*end == 0) {
      search->type = 1;
      return TCL_OK;
    }
  }
  
  /*
   * Pre-scan tag for at least one unquoted "&&" "||" "^" "!"
   *   if not found then use string as simple tag
   */
  for (i = 0; i < search->tag_len; i++) {
    if (tag[i] == '"') {
      i++;
      for ( ; i < search->tag_len; i++) {
	if (tag[i] == '\\') {
	  i++;
	  continue;
	}
	if (tag[i] == '"') {
	  break;
	}
      }
    }
    else {
      if (((tag[i] == '&') && (tag[i+1] == '&')) ||
	  ((tag[i] == '|') && (tag[i+1] == '|')) ||
	  (tag[i] == '^') || (tag[i] == '!')) {
	search->type = 4;
	break;
      }
    }
  }

  search->tag = tag;
  search->tag_index = 0;
  if (search->type == 4) {
    /*
     * an operator was found in the prescan, so
     * now compile the tag expression into array of Tk_Uid
     * flagging any syntax errors found
     */
    if (TagSearchScanExpr(wi->interp, search, search->expr) != TCL_OK) {
      /* Syntax error in tag expression */
      /* Result message set by TagSearchScanExpr */
      return TCL_ERROR;
    }
    search->expr->length = search->expr->index;
  }
  else {
    /*
     * For all other tags convert to a UID.
     */
    search->expr->uid = Tk_GetUid(tag);

    if (search->expr->uid == all_uid) {
      /*
       * All items match.
       */
      search->type = 2;
    }
    else {
      /*
       * Optimized single-tag search
       */
      search->type = 3;
    }
  }
  return TCL_OK;
}


/*
 *--------------------------------------------------------------
 *
 * ZnTagSearchFirst --
 *
 *      This procedure is called to get the first item
 *      item that matches a preestablished search predicate
 *      that was set by TagSearchScan.
 *
 * Results:
 *      The return value is a pointer to the first item, or NULL
 *      if there is no such item.  The information at *search
 *      is updated such that successive calls to ZnTagSearchNext
 *      will return successive items.
 *
 * Side effects:
 *      *search is linked into a list of searches in progress
 *      in zinc, so that elements can safely be deleted while
 *	the search is in progress.
 *
 *--------------------------------------------------------------
 */
static ZnItem
ZnTagSearchFirst(ZnTagSearch	*search)	/* Record describing tag search */
{
  ZnItem item, previous;

  /* short circuit impossible searches for null tags */
  if (search->over == True) {
    return ZN_NO_ITEM;
  }

  /*
   * Find the first matching item in one of several ways. If the tag
   * is a number then it selects the single item with the matching
   * identifier.  In this case see if the item being requested is the
   * hot item, in which case the search can be skipped.
   */
  if (search->type == 1) {
    Tcl_HashEntry *entry;
  
    item = search->wi->hot_item;
    previous = search->wi->hot_prev;
    if ((item == ZN_NO_ITEM) || (item->id != search->id) ||
	(previous == ZN_NO_ITEM) || (previous->next != item)) {
      entry = Tcl_FindHashEntry(search->wi->id_table, (char *) search->id);
      if (entry != NULL) {
	item = (ZnItem) Tcl_GetHashValue(entry);
	previous = item->previous;
      }
      else {
	previous = item = ZN_NO_ITEM;
      }
    }
    search->previous = previous;
    search->over = True;
    search->wi->hot_item = item;
    search->wi->hot_prev = previous;
    return item;
  }
  
  if (search->type == 2) {
    /*
     * All items match.
     */
    search->previous = ZN_NO_ITEM;
    search->current = ZnGroupHead(search->group);
    return search->current;
  }
  
  item = ZnGroupHead(search->group);
  previous = ZN_NO_ITEM;
  do {
    while (item != ZN_NO_ITEM) {
      if (search->type == 3) {
	/*
	 * Optimized single-tag search
	 */
	if (ZnITEM.HasTag(item, search->expr->uid)) {
	  search->previous = previous;
	  search->current = item;
	  return item;
	}
      }
      else {
	/*
	 * Type = 4.  Search for an item matching
	 * the tag expression.
	 */
	search->expr->index = 0;
	if (TagSearchEvalExpr(search->expr, item)) {
	  search->previous = previous;
	  search->current = item;
	  return item;
	}
      }
      if ((item->class == ZnGroup) && (search->recursive)) {
	ZnItem prev_group = (ZnItem) search->group;
	/*
	 * Explore the hierarchy depth first using the item stack
	 * to save the current node.
	 */
	/*printf("ZnTagSearchFirst diving for tag '%s', detph %d\n",
	       search->tag, ZnListSize(search->item_stack)/2);*/
	search->group = item;
	previous = item;
	if (item == prev_group) {
	  item = ZN_NO_ITEM;
	}
	else {
	  item = item->next;
	}
	ZnListAdd(search->item_stack, &previous, ZnListTail);
	ZnListAdd(search->item_stack, &item, ZnListTail);
	previous = ZN_NO_ITEM;
	item = ZnGroupHead(search->group);
      }
      else {
	previous = item;
	item = item->next;
      }
    }
    /*
     * Continue search on higher group level.
     */
    /*printf("ZnTagSearchFirst backup for tag, detph %d\n",
      ZnListSize(search->item_stack)/2);*/    
    while ((item == ZN_NO_ITEM) && ZnListSize(search->item_stack)) {
      item = *(ZnItem *) ZnListAt(search->item_stack, ZnListTail);
      ZnListDelete(search->item_stack, ZnListTail);
      previous = *(ZnItem *) ZnListAt(search->item_stack, ZnListTail);
      ZnListDelete(search->item_stack, ZnListTail);
    }
    if (item != ZN_NO_ITEM) {
      search->group = item->parent;
    }
  } while (item != ZN_NO_ITEM);

  search->previous = previous;
  search->over = True;
  
  return ZN_NO_ITEM;
}


/*
 *--------------------------------------------------------------
 *
 * ZnTagSearchNext --
 *
 *      This procedure returns successive items that match a given
 *      tag;  it should be called only after ZnTagSearchFirst has
 *      been used to begin a search.
 *
 * Results:
 *      The return value is a pointer to the next item that matches
 *      the tag expr specified to TagSearchScan, or NULL if no such
 *      item exists.  *search is updated so that the next call
 *      to this procedure will return the next item.
 *
 * Side effects:
 *      None.
 *
 *--------------------------------------------------------------
 */
static ZnItem
ZnTagSearchNext(ZnTagSearch	*search) /* Record describing search in progress. */
{
  ZnItem item, previous;

  if (search->over) {
    return ZN_NO_ITEM;
  }
  /*
   * Find next item in list (this may not actually be a suitable
   * one to return), and return if there are no items left.
   */
  previous = search->previous;
  if (previous == ZN_NO_ITEM) {
    item = ZnGroupHead(search->group);
  }
  else {
    item = previous->next;
  }

  if (item != search->current) {
    /*
     * The structure of the list has changed.  Probably the
     * previously-returned item was removed from the list.
     * In this case, don't advance previous;  just return
     * its new successor (i.e. do nothing here).
     */
  }
  else if ((item->class == ZnGroup) && (search->recursive)) {
    /*
     * Explore the hierarchy depth first using the item stack
     * to save the current node.
     */
    search->group = item;
    previous = item;
    item = item->next;
    /*printf("ZnTagSearchNext diving for all, pushing %d\n",
      item?item->id:0);*/
    ZnListAdd(search->item_stack, &previous, ZnListTail);
    ZnListAdd(search->item_stack, &item, ZnListTail);
    previous = ZN_NO_ITEM;
    item = ZnGroupHead(search->group);
  }
  else {
    previous = item;
    item = previous->next;
  }
  
  if (item == ZN_NO_ITEM) {
    while ((item == ZN_NO_ITEM) && ZnListSize(search->item_stack)) {
      /*
       * End of list at this level, back up one level.
       */
      item = *(ZnItem *) ZnListAt(search->item_stack, ZnListTail);
      ZnListDelete(search->item_stack, ZnListTail);
      previous = *(ZnItem *) ZnListAt(search->item_stack, ZnListTail);
      ZnListDelete(search->item_stack, ZnListTail);
    }
    if (item != ZN_NO_ITEM) {
      search->group = item->parent;
      /*printf("ZnTagSearchNext popping %d, previous %d, next %d\n",
	     item->id, (item->previous)?item->previous->id:0,
	     (item->next)?item->next->id:0);*/
    }
    else {
      /*
       * Or finish the search if at top.
       */
      search->over = True;
      return ZN_NO_ITEM;
    }
  }

  if (search->type == 2) {
    /*
     * All items match.
     */
    search->previous = previous;
    search->current = item;
    return item;
  }
  
  do {
    while (item != ZN_NO_ITEM) {
      if (search->type == 3) { 
	/*
	 * Optimized single-tag search
	 */
	if (ZnITEM.HasTag(item, search->expr->uid)) {
	  search->previous = previous;
	  search->current = item;
	  return item;
	}
      }
      else {
	/*
	 * Else.... evaluate tag expression
	 */
	search->expr->index = 0;
	if (TagSearchEvalExpr(search->expr, item)) {
	  search->previous = previous;
	  search->current = item;
	  return item;
	}
      }
      if ((item->class == ZnGroup) && (search->recursive)) {
	/*
	 * Explore the hierarchy depth first using the item stack
	 * to save the current node.
	 */
	/*printf("ZnTagSearchNext diving for tag, depth %d\n",
	  ZnListSize(search->item_stack)/2);*/
	search->group = item;
	previous = item;
        item = item->next;
	ZnListAdd(search->item_stack, &previous, ZnListTail);
	ZnListAdd(search->item_stack, &item, ZnListTail);
	previous = ZN_NO_ITEM;
	item = ZnGroupHead(search->group);
      }
      else {
	previous = item;
	item = item->next;
      }
    }
    /*printf("ZnTagSearchNext backup for tag, depth %d\n",
      ZnListSize(search->item_stack)/2);*/
    while ((item == ZN_NO_ITEM) && ZnListSize(search->item_stack)) {
      item = *(ZnItem *) ZnListAt(search->item_stack, ZnListTail);
      ZnListDelete(search->item_stack, ZnListTail);
      previous = *(ZnItem *) ZnListAt(search->item_stack, ZnListTail);
      ZnListDelete(search->item_stack, ZnListTail);
    }
    if (item != ZN_NO_ITEM) {
      search->group = item->parent;
    }
  } while (item != ZN_NO_ITEM);

  /*
   * Out of fuel.
   */
  search->previous = previous;
  search->over = True;
  
  return ZN_NO_ITEM;
}


/*
 *--------------------------------------------------------------
 *
 * ZnTagSearchDestroy --
 *
 *      This procedure destroys any dynamic structures that
 *      may have been allocated by TagSearchScan.
 *
 *--------------------------------------------------------------
 */
void
ZnTagSearchDestroy(ZnTagSearch	*search) /* Record describing tag search */
{
  if (search) {
    TagSearchExprDestroy(search->expr);
    ZnListFree(search->item_stack);
    ZnFree(search->rewrite_buf);
    ZnFree(search);
  }
}


/*
 *----------------------------------------------------------------------
 *
 * ZnItemWithTagOrId --
 *
 *	Return the first item matching the given tag or id. The
 *	function returns the item in 'item' and the operation
 *	status as the function's value.
 *
 *----------------------------------------------------------------------
 */
int
ZnItemWithTagOrId(ZnWInfo	*wi,
		  Tcl_Obj	*tag_or_id,
		  ZnItem	*item,
		  ZnTagSearch	**search_var)
{
  if (ZnTagSearchScan(wi, tag_or_id, search_var) != TCL_OK) {
    return TCL_ERROR;
  }
  *item = ZnTagSearchFirst(*search_var);
  return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * LayoutItems --
 *
 *	Perform layouts on items. It can position items horizontally,
 *	vertically, along a path or with respect to a reference item.
 *	It can also align on a grid, evenly space items and resize
 *	items to a common reference.
 *
 *----------------------------------------------------------------------
 */
static int
LayoutItems(ZnWInfo	*wi,
	    int		argc __unused,
	    Tcl_Obj	*CONST args[])
{
  int		index/*, result*/;
  /*ZnItem		item;*/
#ifdef PTK_800
  static char *layout_cmd_strings[] =
  #else
  static CONST char *layout_cmd_strings[] =
#endif
  {
    "align", "grid", "position", "scale", "space", NULL
  };
  enum		layout_cmds {
    ZN_L_ALIGN, ZN_L_GRID, ZN_L_POSITION, ZN_L_SCALE, ZN_L_SPACE
  };
  
  if (Tcl_GetIndexFromObj(wi->interp, args[0], layout_cmd_strings,
			  "layout command", 0, &index) != TCL_OK) {
    return TCL_ERROR;
  }
  switch((enum layout_cmds) index) {
    /*
     * align
     */
  case ZN_L_ALIGN:
    break;
    /*
     * grid
     */
  case ZN_L_GRID:
    break;
    /*
     * position
     */
  case ZN_L_POSITION:
    break;
    /*
     * scale
     */
  case ZN_L_SCALE:
    break;
    /*
     * space
     */
  case ZN_L_SPACE:
    break;
  }

  return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * SetOrigin --
 *
 *	This procedure is invoked to translate the viewed area so
 *	that the given point is displayed in the top left corner.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Zinc will be redisplayed to reflect the change in ciew.
 *      The scrollbars will be updated if there are any.
 *	The top group transform is modified to achieve the effect,
 *	it is not a good idea to mix view control and application
 *	control of the top group transform.
 *
 *----------------------------------------------------------------------
 */
static void
SetOrigin(ZnWInfo	*wi,
	  ZnReal	x_origin,
	  ZnReal	y_origin)
{
  int	left, right, top, bottom, delta;

   /*
    * If scroll increments have been set, round the window origin
    * to the nearest multiple of the increments.
    */
  if (wi->x_scroll_incr > 0) {
    if (x_origin >= 0) {
      x_origin += wi->x_scroll_incr/2;
    }
    else {
      x_origin = (-x_origin) + wi->x_scroll_incr/2;
    }
  }
  if (wi->y_scroll_incr > 0) {
    if (y_origin >= 0) {
      y_origin += wi->y_scroll_incr/2;
    }
    else {
      y_origin = (-y_origin) + wi->y_scroll_incr/2;
    }
  }

  /*
   * Adjust the origin if necessary to keep as much as possible of the
   * canvas in the view.  The variables left, right, etc. keep track of
   * how much extra space there is on each side of the view before it
   * will stick out past the scroll region.  If one side sticks out past
   * the edge of the scroll region, adjust the view to bring that side
   * back to the edge of the scrollregion (but don't move it so much that
   * the other side sticks out now).  If scroll increments are in effect,
   * be sure to adjust only by full increments.
   */
  if (wi->confine && (wi->region != NULL)) {
    left = (int) (x_origin - wi->scroll_xo);
    right = (int) (wi->scroll_xc - (x_origin + Tk_Width(wi->win)));
    top = (int) (y_origin - wi->scroll_yo);
    bottom = (int) (wi->scroll_yc - (y_origin + Tk_Height(wi->win)));
    if ((left < 0) && (right > 0)) {
      delta = (right > -left) ? -left : right;
      if (wi->x_scroll_incr > 0) {
	delta -= delta % wi->x_scroll_incr;
      }
      x_origin += delta;
    }
    else if ((right < 0) && (left > 0)) {
      delta = (left > -right) ? -right : left;
      if (wi->x_scroll_incr > 0) {
	delta -= delta % wi->x_scroll_incr;
      }
      x_origin -= delta;
    }
    if ((top < 0) && (bottom > 0)) {
      delta = (bottom > -top) ? -top : bottom;
      if (wi->y_scroll_incr > 0) {
	delta -= delta % wi->y_scroll_incr;
      }
      y_origin += delta;
    }
    else if ((bottom < 0) && (top > 0)) {
      delta = (top > -bottom) ? -bottom : top;
      if (wi->y_scroll_incr > 0) {
	delta -= delta % wi->y_scroll_incr;
      }
      y_origin -= delta;
    }
  }

  /*
   * If the requested origin is not already set, translate the
   * top group and update the scrollbars.
   */
  if ((wi->origin.x != x_origin) || (wi->origin.y != y_origin)) {
    wi->origin.x = x_origin;
    wi->origin.y = y_origin;
    ZnITEM.ResetTransfo(wi->top_group);
    ZnITEM.TranslateItem(wi->top_group, -x_origin, -y_origin, False);
    SET(wi->flags, ZN_UPDATE_SCROLLBARS);
  }
}


/*
 *----------------------------------------------------------------------
 *
 * ScrollFractions --
 *
 *	Given the range that's visible in the window and the "100%
 *	range", return a list of two real representing the scroll
 *	fractions.  This procedure is used for both x and y scrolling.
 *
 * Results:
 *	Return a string as a Tcl_Obj holding two real numbers
 *	describing the scroll fraction (between 0 and 1) corresponding
 *	to the arguments.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */
#ifdef PTK
static void
ScrollFractions(ZnReal	view1,	/* Lowest coordinate visible in the window. */
		ZnReal	view2,	/* Highest coordinate visible in the window. */
		ZnReal	region1,/* Lowest coordinate in the object. */
		ZnReal	region2,/* Highest coordinate in the object. */
		ZnReal	*first,
		ZnReal	*last)
#else
static Tcl_Obj *
ScrollFractions(ZnReal	view1,	/* Lowest coordinate visible in the window. */
		ZnReal	view2,	/* Highest coordinate visible in the window. */
		ZnReal	region1,/* Lowest coordinate in the object. */
		ZnReal	region2)/* Highest coordinate in the object. */
#endif
{
  ZnReal range, f1, f2;
  char   buffer[2*TCL_DOUBLE_SPACE+2];

  range = region2 - region1;
  if (range <= 0) {
    f1 = 0;
    f2 = 1.0;
  }
  else {
    f1 = (view1 - region1)/range;
    if (f1 < 0) {
      f1 = 0.0;
    }
    f2 = (view2 - region1)/range;
    if (f2 > 1.0) {
      f2 = 1.0;
    }
    if (f2 < f1) {
      f2 = f1;
    }
  }
#ifdef PTK
  *first = f1;
  *last = f2;
#else
  sprintf(buffer, "%g %g", f1, f2);
  return Tcl_NewStringObj(buffer, -1);
#endif
}


/*
 *--------------------------------------------------------------
 *
 * UpdateScrollbars --
 *
 *	This procedure is invoked whenever zinc has changed in
 *	a way that requires scrollbars to be redisplayed (e.g.
 *	the view has changed).
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	If there are scrollbars associated with zinc, then
 *	their scrolling commands are invoked to cause them to
 *	redisplay.  If errors occur, additional Tcl commands may
 *	be invoked to process the errors.
 *
 *--------------------------------------------------------------
 */
static void
UpdateScrollbars(ZnWInfo	*wi)
{
  int		result;
  Tcl_Interp	*interp;
  int		x_origin, y_origin, width, height;
  int		scroll_xo, scroll_xc, scroll_yo, scroll_yc;
#ifdef PTK
  LangCallback  *x_scroll_cmd, *y_scroll_cmd;
#else
  char		*x_scroll_cmd, *y_scroll_cmd;
#endif
  Tcl_Obj	*fractions;

  /*
   * Save all the relevant values from wi, because it might be
   * deleted as part of either of the two calls to Tcl_VarEval below.
   */
  interp = wi->interp;
  Tcl_Preserve((ClientData) interp);
  x_scroll_cmd = wi->x_scroll_cmd;
  if (x_scroll_cmd != NULL) {
    Tcl_Preserve((ClientData) x_scroll_cmd);
  }
  y_scroll_cmd = wi->y_scroll_cmd;
  if (y_scroll_cmd != NULL) {
    Tcl_Preserve((ClientData) y_scroll_cmd);
  }
  x_origin = (int) wi->origin.x;
  y_origin = (int) wi->origin.y;
  width = Tk_Width(wi->win);
  height = Tk_Height(wi->win);
  scroll_xo = wi->scroll_xo;
  scroll_xc = wi->scroll_xc;
  scroll_yo = wi->scroll_yo;
  scroll_yc = wi->scroll_yc;
  CLEAR(wi->flags, ZN_UPDATE_SCROLLBARS);
  if (wi->x_scroll_cmd != NULL) {
#ifdef PTK
    ZnReal first, last;
    ScrollFractions(x_origin, x_origin + width, scroll_xo, scroll_xc, &first, &last);
    result = LangDoCallback(interp, x_scroll_cmd, 0, 2, " %g %g", first, last);
#else
    fractions = ScrollFractions(x_origin, x_origin + width, scroll_xo, scroll_xc);
    result = Tcl_VarEval(interp, x_scroll_cmd, " ", Tcl_GetString(fractions), NULL);
#endif
    Tcl_DecrRefCount(fractions);
    if (result != TCL_OK) {
      Tcl_BackgroundError(interp);
    }
    Tcl_ResetResult(interp);
    Tcl_Release((ClientData) x_scroll_cmd);
  }
  
  if (y_scroll_cmd != NULL) {
#ifdef PTK
    ZnReal	first, last;
    ScrollFractions(y_origin, y_origin + height, scroll_yo, scroll_yc, &first, &last);
    result = LangDoCallback(interp, y_scroll_cmd, 0, 1, " %g %g", first, last);
#else
    fractions = ScrollFractions(y_origin, y_origin + height, scroll_yo, scroll_yc);
    result = Tcl_VarEval(interp, y_scroll_cmd, " ", Tcl_GetString(fractions), NULL);
#endif
    Tcl_DecrRefCount(fractions);
    if (result != TCL_OK) {
      Tcl_BackgroundError(interp);
    }
    Tcl_ResetResult(interp);
    Tcl_Release((ClientData) y_scroll_cmd);
  }
  Tcl_Release((ClientData) interp);
}


/*
 *----------------------------------------------------------------------
 *
 * ZnDoItem --
 *
 *	Either add a tag to an item or add the item id/part to the
 *	interpreter result, depending on the value of tag. If tag
 *	is NULL, the item id/part is added to the result, otherwise
 *	the tag is added to the item.
 *
 *----------------------------------------------------------------------
 */
void
ZnDoItem(Tcl_Interp	*interp,
	 ZnItem		item,
	 int		part,
	 Tk_Uid		tag_uid)
{
  if (tag_uid == NULL) {
    Tcl_Obj  *l;
    l = Tcl_GetObjResult(interp);
    Tcl_ListObjAppendElement(interp, l, Tcl_NewLongObj(item->id));
    if (part != ZN_NO_PART) {
      Tcl_ListObjAppendElement(interp, l, Tcl_NewIntObj(part));
    }
  }
  else {
    /*printf("Adding tag %s to item %d\n", tag_uid, item->id);*/
    ZnITEM.AddTag(item, tag_uid);
  }
}


/*
 *----------------------------------------------------------------------
 *
 * FindArea --
 *	Search the items that are enclosed or overlapping a given
 *	area of the widget. It is used by FindItems.
 *	If tag_uid is not NULL, all the items found are tagged with
 *	tag_uid. If tag_uid is NULL, the items found are added to the
 *	interp result. If enclosed is 1, the search look for
 *	items enclosed in the area. If enclosed is 0, it looks
 *	for overlapping and enclosed items.
 *	If an error occurs, a message is left in the interp result
 *	and TCL_ERROR is returned.
 *
 *----------------------------------------------------------------------
 */
static int
FindArea(ZnWInfo	*wi,
	 Tcl_Obj *CONST args[],
	 Tk_Uid		tag_uid,
	 ZnBool		enclosed,
	 ZnBool		recursive,
	 ZnBool		override_atomic,
	 ZnItem		group)
{
  ZnPos		pos;
  ZnBBox	area;
  ZnToAreaStruct ta;
  double	d;

  if (Tcl_GetDoubleFromObj(wi->interp, args[0], &d) == TCL_ERROR) {
    return TCL_ERROR;
  }
  area.orig.x = d;
  if (Tcl_GetDoubleFromObj(wi->interp, args[1], &d) == TCL_ERROR) {
    return TCL_ERROR;
  }
  area.orig.y = d;
  if (Tcl_GetDoubleFromObj(wi->interp, args[2], &d) == TCL_ERROR) {
    return TCL_ERROR;
  }
  area.corner.x = d;
  if (Tcl_GetDoubleFromObj(wi->interp, args[3], &d) == TCL_ERROR) {
    return TCL_ERROR;
  }
  area.corner.y = d;
  if (area.corner.x < area.orig.x) {
    pos = area.orig.x;
    area.orig.x = area.corner.x;
    area.corner.x = pos;
  }
  if (area.corner.y < area.orig.y) {
    pos = area.orig.y;
    area.orig.y = area.corner.y;
    area.corner.y = pos;
  }
  area.corner.x += 1;
  area.corner.y += 1;

  ta.tag_uid = tag_uid;
  ta.enclosed = enclosed;
  ta.in_group = group;
  ta.recursive = recursive;
  ta.override_atomic = override_atomic;
  ta.report = False;
  ta.area = &area;
  wi->top_group->class->ToArea(wi->top_group, &ta);
  
  return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * FindItems --
 *
 *	This procedure interprets the small object query langage for
 *	commands like addtag and find.
 *	If new_tag is NULL, the procedure collects all the objects
 *	matching the request and return them in the interpreter result.
 *	If new_tag is non NULL, it is interpreted as the tag to add to
 *	all matching objects. In this case the interpreter result is
 *	left empty.
 *
 *----------------------------------------------------------------------
 */
static int
FindItems(ZnWInfo	*wi,
	  int		argc,
	  Tcl_Obj *CONST args[],
	  Tcl_Obj	*tag,		/* NULL to search or tag to add tag. */
	  int		first,		/* First arg to process in args */
	  ZnTagSearch	**search_var)
{
  Tk_Uid	tag_uid = NULL;
  int		index, result;
  ZnItem	item;
  ZnPickStruct	ps;
  char		*str;
#ifdef PTK_800
  static char *search_cmd_strings[] =
#else
  static CONST char *search_cmd_strings[] =
#endif
  {
    "above", "ancestors", "atpriority", "below", "closest", "enclosed",
    "overlapping", "withtag", "withtype", NULL
  };
  enum		search_cmds {
    ZN_S_ABOVE, ZN_S_ANCESTORS, ZN_S_ATPRIORITY, ZN_S_BELOW, ZN_S_CLOSEST,
    ZN_S_ENCLOSED, ZN_S_OVERLAPPING, ZN_S_WITHTAG, ZN_S_WITHTYPE
  };
  
  if (Tcl_GetIndexFromObj(wi->interp, args[first], search_cmd_strings,
			  "search command", 0, &index) != TCL_OK) {
    return TCL_ERROR;
  }
  
  if (tag) {
    tag_uid = Tk_GetUid(Tcl_GetString(tag));
  }
  
  switch((enum search_cmds) index) {
    /*
     * above
     */
  case ZN_S_ABOVE:
    {
      if (argc != first+2) {
	Tcl_WrongNumArgs(wi->interp, first+1, args, "tagOrId");
	return TCL_ERROR;
      }
      result = ZnItemWithTagOrId(wi, args[first+1], &item, search_var);
      if (result == TCL_OK) {
	if ((item != ZN_NO_ITEM) && (item->previous != ZN_NO_ITEM)) {
	  ZnDoItem(wi->interp, item->previous, ZN_NO_PART, tag_uid);
	}
      }
      else {
	return TCL_ERROR;
      }
    }
    break;
    /*
     * ancestors
     */
  case ZN_S_ANCESTORS:
    {
      Tk_Uid uid = NULL;
      if ((argc != first+2) && (argc != first+3)) {
	Tcl_WrongNumArgs(wi->interp, first+1, args, "tagOrId ?withTag?");
	return TCL_ERROR;
      }
      result = ZnItemWithTagOrId(wi, args[first+1], &item, search_var);
      if (result == TCL_ERROR) {
	return TCL_ERROR;
      }
      if (item) {
	item = item->parent;
	if (argc == first+3) {
	  uid = Tk_GetUid(Tcl_GetString(args[first+2]));
	}
	while (item != ZN_NO_ITEM) {
	  if (!uid || ZnITEM.HasTag(item, uid)) {
	    ZnDoItem(wi->interp, item, ZN_NO_PART, tag_uid);
	  }
	  item = item->parent;
	}
      }
    }
    break;
      /*
       * atpriority
       */
  case ZN_S_ATPRIORITY:
    {
      int pri;
      
      if ((argc != first+2) && (argc != first+3)) {
	Tcl_WrongNumArgs(wi->interp, first+1, args, "pri ?tagOrId?");
	return TCL_ERROR;
      }
      if ((Tcl_GetIntFromObj(wi->interp, args[first+1], &pri) == TCL_ERROR) ||
	  (pri < 0)){
	return TCL_ERROR;
      }
      
      /*
       * Go through the item table and collect all items with
       * the given priority.
       */
      if (ZnTagSearchScan(wi, (argc == first+3) ? args[first+2] : NULL,
			  search_var) == TCL_ERROR) {
	return TCL_ERROR;
      }
      for (item = ZnTagSearchFirst(*search_var);
	   item != ZN_NO_ITEM; item = ZnTagSearchNext(*search_var)) {
	if (item->priority == (unsigned int) pri) {
	  ZnDoItem(wi->interp, item, ZN_NO_PART, tag_uid);
	}
      }
    }
    break;
    /*
     * below
     */
  case ZN_S_BELOW:
    {
      ZnItem	next;
      
      if (argc != first+2) {
	Tcl_WrongNumArgs(wi->interp, first+1, args, "tagOrId");
	return TCL_ERROR;
      }
      item = ZN_NO_ITEM;
      if (ZnTagSearchScan(wi, args[first+1], search_var) == TCL_ERROR) {
	return TCL_ERROR;
      }
      for (next = ZnTagSearchFirst(*search_var);
	   next != ZN_NO_ITEM; next = ZnTagSearchNext(*search_var)) {
	item = next;
      }
      if ((item != ZN_NO_ITEM) && (item->next != ZN_NO_ITEM)) {
	ZnDoItem(wi->interp, item->next, ZN_NO_PART, tag_uid);
      }
    }
    break;
    /*
     * closest
     */
  case ZN_S_CLOSEST:
    {
      int	halo = 1;
      ZnPoint	p;
      double	d;

      if ((argc < first+3) || (argc > first+6)) {
	Tcl_WrongNumArgs(wi->interp, first+1, args, "x y ?halo? ?start?, ?recursive?");
	return TCL_ERROR;      
      }
      if (Tcl_GetDoubleFromObj(wi->interp, args[first+1], &d) == TCL_ERROR) {
	return TCL_ERROR;
      }
      p.x = d;
      if (Tcl_GetDoubleFromObj(wi->interp, args[first+2], &d) == TCL_ERROR) {
	return TCL_ERROR;
      }
      p.y = d;
      if (argc > first+3) {
	if (Tcl_GetIntFromObj(wi->interp, args[first+3], &halo) == TCL_ERROR) {
	  return TCL_ERROR;
	}
	if (halo < 0) {
	  halo = 0;
	}
      }

      ps.in_group = ZN_NO_ITEM;
      ps.start_item = ZN_NO_ITEM;
      item = ZN_NO_ITEM;      
      if (argc > (first+4)) {
	result = ZnItemWithTagOrId(wi, args[first+4], &item, search_var);
	if ((result == TCL_OK) && (item != ZN_NO_ITEM)) {
	  if ((item->class == ZnGroup) && !ZnGroupAtomic) {
	    ps.in_group = item;
	  }
	  else {
	    ps.in_group = item->parent;
	    ps.start_item = item->next;
	  }
	}
      }
      ps.recursive = True;
      ps.override_atomic = False;
      if (argc > first+5) {
	result = Tcl_GetBooleanFromObj(wi->interp, args[first+5], &ps.recursive);
	if (result != TCL_OK) {
	  str = Tcl_GetString(args[first+5]);
	  if (strcmp(str, "override") != 0) { 
	    Tcl_AppendResult(wi->interp,
			     "recursive should be a boolean value or ",
			     "override \"", str, "\"", NULL);
	    return TCL_ERROR;
	  }
	  ps.recursive = True;
	  ps.override_atomic = True;
	}
      }
      /*
       * We always start the search at the top group to use the
       * transform and clip machinery of the group item. The items
       * are not required to cache the device coords, etc. So we need
       * to setup the correct context before calling the Pick method
       * for each item.
       */
      ps.aperture = halo;
      ps.point = &p;
      wi->top_group->class->Pick(wi->top_group, &ps);
      
      if (ps.a_item != ZN_NO_ITEM) {
	ZnDoItem(wi->interp, ps.a_item, ps.a_part, tag_uid);
	/*printf("first %d %d\n", ps.a_item->id, ps.a_part);*/
      }
    }
    break;
    /*
     * enclosed
     */
  case ZN_S_ENCLOSED:
    {
      if ((argc < first+5) || (argc > first+7)) {
	Tcl_WrongNumArgs(wi->interp, first+1, args, "x1 y1 x2 y2 ?inGroup? ?recursive?");
	return TCL_ERROR;
      }
      item = wi->top_group;
      if (argc > first+5) {
	result = ZnItemWithTagOrId(wi, args[first+5], &item, search_var);
	if ((result != TCL_OK) || (item == ZN_NO_ITEM) || (item->class != ZnGroup)) {
	  return TCL_ERROR;
	}
      }
      ps.recursive = True;
      ps.override_atomic = False;
      if (argc > first+5) {
	result = Tcl_GetBooleanFromObj(wi->interp, args[first+6], &ps.recursive);
	if (result != TCL_OK) {
	  str = Tcl_GetString(args[first+6]);
	  if (strcmp(str, "override") != 0) { 
	    Tcl_AppendResult(wi->interp,
			     "recursive should be a boolean value or ",
			     "override \"", str, "\"", NULL);
	    return TCL_ERROR;
	  }
	  ps.recursive = True;
	  ps.override_atomic = True;
	}
      }
      return FindArea(wi, args+first+1, tag_uid,
		      True, ps.recursive, ps.override_atomic,
		      item);
    }
    break;
    /*
     * overlapping
     */
  case ZN_S_OVERLAPPING:
    {
      if ((argc < first+5) || (argc > first+7)) {
	Tcl_WrongNumArgs(wi->interp, first+1, args, "x1 y1 x2 y2 ?inGroup? ?recursive?");
	return TCL_ERROR;
      }
      item = wi->top_group;
      if (argc > first+5) {
	result = ZnItemWithTagOrId(wi, args[first+5], &item, search_var);
	if ((result != TCL_OK) || (item == ZN_NO_ITEM) || (item->class != ZnGroup)) {
	  return TCL_ERROR;
	}
      }
      ps.recursive = True;
      ps.override_atomic = False;
      if (argc > first+5) {
	result = Tcl_GetBooleanFromObj(wi->interp, args[first+6], &ps.recursive);
	if (result != TCL_OK) {
	  str = Tcl_GetString(args[first+6]);
	  if (strcmp(str, "override") != 0) { 
	    Tcl_AppendResult(wi->interp,
			     "recursive should be a boolean value or ",
			     "override \"", str, "\"", NULL);
	    return TCL_ERROR;
	  }
	  ps.recursive = True;
	  ps.override_atomic = True;
	}
      }
      return FindArea(wi, args+first+1, tag_uid,
		      False, ps.recursive, ps.override_atomic,
		      item);
    }
    break;
    /*
     * withtag
     */
  case ZN_S_WITHTAG:
    {
      if (argc != first+2) {
	Tcl_WrongNumArgs(wi->interp, first+1, args, "tagOrId");
	return TCL_ERROR;
      }
      if (ZnTagSearchScan(wi, args[first+1], search_var) == TCL_ERROR) {
	return TCL_ERROR;
      }
      for (item = ZnTagSearchFirst(*search_var);
	   item != ZN_NO_ITEM; item = ZnTagSearchNext(*search_var)) {
	ZnDoItem(wi->interp, item, ZN_NO_PART, tag_uid);
      }
    }
    break;
    /*
     * withtype
     */
  case ZN_S_WITHTYPE:
    {
      ZnItemClass	cls;
      
      if ((argc != first+2) && (argc != first+3)) {
	Tcl_WrongNumArgs(wi->interp, first+1, args, "itemType ?tagOrId?");
	return TCL_ERROR;
      }
      cls = ZnLookupItemClass(Tcl_GetString(args[first+1]));
      if (!cls) {
	Tcl_AppendResult(wi->interp, "unknown item type \"",
			 Tcl_GetString(args[first+1]), "\"", NULL);
	return TCL_ERROR;
      }
      
      /*
       * Go through the item table and collect all items with
       * the given item type.
       */
      if (ZnTagSearchScan(wi, (argc == first+3) ? args[first+2] : NULL,
			  search_var) == TCL_ERROR) {
	return TCL_ERROR;
      }
      for (item = ZnTagSearchFirst(*search_var);
	   item != ZN_NO_ITEM; item = ZnTagSearchNext(*search_var)) {
	if (item->class == cls) {
	  ZnDoItem(wi->interp, item, ZN_NO_PART, tag_uid);
	}
      }
    }
    break;
  }
  
  return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * ZnParseCoordList --
 *
 *----------------------------------------------------------------------
 */
int
ZnParseCoordList(ZnWInfo	*wi,
		 Tcl_Obj	*arg,
		 ZnPoint	**pts,
		 char		**controls,
		 unsigned int	*num_pts,
		 ZnBool		*old_format)
{
  Tcl_Obj	**elems, **selems;
  int		i, result, num_elems, num_selems;
  ZnPoint	*p;
  int		old_style, len;
  char		*str;
  double	d;

  if (controls) {
    *controls = NULL;
  }
  if (old_format) {
    *old_format = True;
  }
  result = Tcl_ListObjGetElements(wi->interp, arg, &num_elems, &elems);
  if (result == TCL_ERROR) {
  coord_error:
    Tcl_AppendResult(wi->interp, " malformed coord list", NULL);
    return TCL_ERROR;
  }
  if (num_elems == 0) {
    *num_pts = 0;
    *pts = NULL;
    return TCL_OK;
  }

  /*
   * If first element is not a sublist, consider the whole list
   * as a flat array of coordinates in the old style. It can still
   * be a single point with or without a control flag.
   * If not, the list consists in sublists describing each point
   * with its control flag.
   */
  result = Tcl_GetDoubleFromObj(wi->interp, elems[0], &d);
  old_style = (result == TCL_OK);

  if (old_style) {
    if ((num_elems%2) == 0) {
      *num_pts = num_elems/2;
      ZnListAssertSize(wi->work_pts, *num_pts);
      *pts = p = (ZnPoint *) ZnListArray(wi->work_pts);
      for (i = 0; i < num_elems; i += 2, p++) {
	if (Tcl_GetDoubleFromObj(wi->interp, elems[i], &d) == TCL_ERROR) {
	  goto coord_error;
	}
	p->x = d;
	if (Tcl_GetDoubleFromObj(wi->interp, elems[i+1], &d) == TCL_ERROR) {
	  goto coord_error;
	}
	p->y = d;
	/*printf("Parsed a point: %g@%g, ", p->x, p->y);*/
      }
      /*printf("\n");*/
    }
    else if (num_elems == 3) {
      *num_pts = 1;
      ZnListAssertSize(wi->work_pts, *num_pts);
      *pts = p = (ZnPoint *) ZnListArray(wi->work_pts);
      if (Tcl_GetDoubleFromObj(wi->interp, elems[0], &d) == TCL_ERROR) {
	goto coord_error;
      }
      p->x = d;
      if (Tcl_GetDoubleFromObj(wi->interp, elems[1], &d) == TCL_ERROR) {
	goto coord_error;
      }
      p->y = d;
      if (controls) {
	if (! *controls) {
	  *controls = ZnMalloc(*num_pts * sizeof(char));
	  memset(*controls, 0, *num_pts * sizeof(char));
	}
	str = Tcl_GetStringFromObj(elems[2], &len);
	if (len) {
	  (*controls)[0] = str[0];
	}
      }
    }
    else {
      goto coord_error;
    }
  }
  else {
    Tcl_ResetResult(wi->interp);
    *num_pts = num_elems;
    ZnListAssertSize(wi->work_pts, *num_pts);
    *pts = p = (ZnPoint *) ZnListArray(wi->work_pts);
    for (i = 0; i < num_elems; i++, p++) {
      result = Tcl_ListObjGetElements(wi->interp, elems[i], &num_selems, &selems);
      if ((result == TCL_ERROR) || (num_selems < 2) || (num_selems > 3)) {
	goto coord_error;
      }
      if (Tcl_GetDoubleFromObj(wi->interp, selems[0], &d) == TCL_ERROR) {
	goto coord_error;
      }
      p->x = d;
      if (Tcl_GetDoubleFromObj(wi->interp, selems[1], &d) == TCL_ERROR) {
	goto coord_error;
      }
      p->y = d;
      if (controls) {
	if (num_selems == 3) {
	  if (! *controls) {
	    *controls = ZnMalloc(*num_pts * sizeof(char));
	    memset(*controls, 0, *num_pts * sizeof(char));
	  }
	  str = Tcl_GetStringFromObj(selems[2], &len);
	  if (len) {
	    (*controls)[i] = str[0];
	  }
	}
      }
    }
  }
  
  if (old_format) {
    *old_format = old_style;
  }
  return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * Contour --
 *
 *----------------------------------------------------------------------
 */
static int
Contour(ZnWInfo	*wi,
	int		argc,
	Tcl_Obj	*CONST	args[],
	ZnTagSearch	**search_var)
{
  ZnPoint	*points;
  ZnItem	item, shape;
  unsigned int	i, j, k,num_points;
  int		cmd, cw, result;
  int		winding_flag, revert = False;
  long		index;
  char		*controls;
  ZnBool	simple=False;
  ZnPoly	poly;
  ZnTransfo	t, inv;
  ZnContour	*contours;

  /* Keep this array in sync with ZnContourCmd in Types.h */
#ifdef PTK_800
  static char *op_strings[] =
#else
  static CONST char *op_strings[] =
#endif
  {
    "add", "remove", NULL
  };
  
  result = ZnItemWithTagOrId(wi, args[2], &item, search_var);
  if ((result == TCL_ERROR) || (item == ZN_NO_ITEM)){
    Tcl_AppendResult(wi->interp, "unknown item \"", Tcl_GetString(args[2]),
		     "\"", NULL);
    return TCL_ERROR;
  }
  if (!item->class->Contour) {
    if (item->class->GetClipVertices ||
	item->class->GetContours) {
      Tcl_SetObjResult(wi->interp, Tcl_NewIntObj(1));
    }
    else {
      Tcl_SetObjResult(wi->interp, Tcl_NewIntObj(0));
    }
    return TCL_OK;
  }
  if (argc == 3) {
    /*
     * Requesting the number of contours.
     */
    Tcl_SetObjResult(wi->interp, Tcl_NewIntObj(item->class->Contour(item, -1, 0, NULL)));
    return TCL_OK;    
  }
  /*
   * Get the sub-command
   */
  if (Tcl_GetIndexFromObj(wi->interp, args[3], op_strings,
			  "contour operation", 0, &cmd) != TCL_OK) {
    return TCL_ERROR;
  }
  /*
   * Get the winding flag.
   */
  if ((Tcl_GetIntFromObj(wi->interp, args[4], &winding_flag) != TCL_OK) ||
      (winding_flag < -1) || (winding_flag > 1)) {
    Tcl_AppendResult(wi->interp, " incorrect winding flag, should be -1, 0, 1, \"",
		     Tcl_GetString(args[4]), "\"", NULL);
    return TCL_ERROR;
  }
  index = ZnListTail;
  if (((argc == 6) && (cmd == ZN_CONTOUR_REMOVE)) || (argc == 7)) {
    /* Look for an index value. */
    if (Tcl_GetLongFromObj(wi->interp, args[5], &index) != TCL_OK) {
      Tcl_AppendResult(wi->interp, " incorrect contour index \"",
		       Tcl_GetString(args[5]), "\"", NULL);
      return TCL_ERROR;
    }
    argc--;
    args++;
  }

  if (cmd == ZN_CONTOUR_REMOVE) {
    Tcl_SetObjResult(wi->interp, Tcl_NewIntObj(item->class->Contour(item, ZN_CONTOUR_REMOVE, index, NULL)));
  }
  else {
    result = ZnItemWithTagOrId(wi, args[5], &shape, search_var);
    if ((result == TCL_ERROR) || (shape == ZN_NO_ITEM)) {
      Tcl_ResetResult(wi->interp);
      if (ZnParseCoordList(wi, args[5], &points,
			   &controls, &num_points, NULL) == TCL_ERROR) {
	return TCL_ERROR;
      }
      /*
       * Processing contours from an explicit list.
       */
      ZnPolyContour1(&poly, NULL, num_points, False);
      /*
       * Allocate a fresh point array, ZnParseCoordList returns a shared
       * array. The control array is not shared and can be passed along.
       */
      poly.contours[0].points = ZnMalloc(num_points*sizeof(ZnPoint));
      cw = poly.contours[0].cw = !ZnTestCCW(points, num_points);
      if (winding_flag != 0) {
	revert = cw ^ (winding_flag == -1);
      }
      if (revert) {
	/* Revert the contour */
	for (i = 0; i < num_points; i++) {
	  poly.contours[0].points[num_points-i-1] = points[i];
	}
	if (controls) {
	  char ch;
	  for (i = 0, j = num_points-1; i < j; i++, j--) {
	    ch = controls[i];
	    controls[i] = controls[j];
	    controls[j] = ch;
	  }
	}
      }
      else {
	memcpy(poly.contours[0].points, points, num_points*sizeof(ZnPoint));
      }
      poly.contours[0].controls = controls;
    }
    else {
      /*
       * Processing contours from an item
       */
      if (winding_flag == 0) {
	Tcl_AppendResult(wi->interp,
			 "Must supply an explicit winding direction (-1, 1)\nwhen adding a contour from an item",
			 NULL);
	return TCL_ERROR;
      }
      /*
       * If something has changed in the geometry we need to
       * update or the shape will be erroneous.
       */
      Update(wi);
      if (!shape->class->GetContours &&
	  !shape->class->GetClipVertices) {
	Tcl_AppendResult(wi->interp, "class: \"", shape->class->name,
			 "\" can't give a polygonal shape", NULL);
	return TCL_ERROR;
      }
      if (!shape->class->GetContours) {
	ZnTriStrip	tristrip;
	/*
	 * If there is no GetContours method try to use
	 * the GetClipVertices. It works only for simple
	 * shapes (i.e tose returning a bounding box).
	 */
	tristrip.num_strips = 0;
	/*
	 * GetClipVertices _may_ return a tristrip describing a fan
	 * this would lead to strange results. For now, this case
	 * should not appear, the items candidates to such a behavior
	 * export a GetContours method which has higher precedence.
	 */
	simple = shape->class->GetClipVertices(shape, &tristrip);
	ZnPolyContour1(&poly, tristrip.strip1.points, tristrip.strip1.num_points,
		       False);
	poly.contours[0].controls = NULL;
      }
      else {
	poly.num_contours = 0;
	simple = shape->class->GetContours(shape, &poly);
      }
      if (poly.num_contours == 0) {
	return TCL_OK;
      }
      /*
       * Compute the tranform to map the device points
       * into the coordinate space of item.
       */
      ZnITEM.GetItemTransform(item, &t);
      ZnTransfoInvert(&t, &inv);
      /*
       * Make a new transformed poly and unshare
       * the contour(s) returned by the item.
       */
      if (simple) {
	ZnPoint	p[4];
	p[0] = poly.contours[0].points[0];
	p[2] = poly.contours[0].points[1];
	if (winding_flag == -1) {
	  p[1].x = p[2].x;
	  p[1].y = p[0].y;
	  p[3].x = p[0].x;
	  p[3].y = p[2].y;
	}
	else {
	  p[1].x = p[0].x;
	  p[1].y = p[2].y;
	  p[3].x = p[2].x;
	  p[3].y = p[0].y;
	}
	points = ZnMalloc(4*sizeof(ZnPoint));
	ZnTransformPoints(&inv, p, points, 4);
	poly.contours[0].points = points;
	poly.contours[0].num_points = 4;
	poly.contours[0].cw = (winding_flag == -1);
	poly.contours[0].controls = NULL;
      }
      else {
	/* Unshare the contour array or use the static storage */
	contours = poly.contours;
	if (poly.num_contours == 1) {
	  poly.contours = &poly.contour1;
	}
	else {
	  poly.contours = ZnMalloc(poly.num_contours*sizeof(ZnContour));
	}
	for (i = 0; i < poly.num_contours; i++) {
	  points = contours[i].points;
	  num_points = contours[i].num_points;
	  cw = contours[i].cw;
	  poly.contours[i].num_points = num_points;
	  poly.contours[i].cw = cw;
	  if (contours[i].controls) {
	    /* 
	     * The controls array returned by GetContour is shared.
	     * Here we unshare it.
	     */
	    poly.contours[i].controls = ZnMalloc(num_points*sizeof(char));
	  }
	  /*
	   * Unshare the point array.
	   */
	  poly.contours[i].points = ZnMalloc(num_points*sizeof(ZnPoint));
	  ZnTransformPoints(&inv, points, poly.contours[i].points, num_points);
	  
	  if ((((poly.num_contours == 1) && ((winding_flag == -1) ^ cw)) ||
	       ((poly.num_contours > 1) && (winding_flag == -1)))) {
	    ZnPoint p;
	    
	    revert = True;
	    /* Revert the points */
	    poly.contours[i].cw = ! cw;
	    for (j = 0, k = num_points-1; j < k; j++, k--) {
	      p = poly.contours[i].points[j];
	      poly.contours[i].points[j] = poly.contours[i].points[k];
	      poly.contours[i].points[k] = p;
	    }
	    
	    /* Revert the controls */
	    if (contours[i].controls) {
	      for (j = 0; j < num_points; j++) {
		poly.contours[i].controls[num_points-j-1] = contours[i].controls[j];
	      }
	    }
	  }
	  else {
	    if (contours[i].controls) {
	      memcpy(poly.contours[i].controls, contours[i].controls, num_points);
	    }
	  }
	}
      }
    }

    result = item->class->Contour(item, ZN_CONTOUR_ADD, index, &poly);
    if (revert) {
      result = -result;
    }
    Tcl_SetObjResult(wi->interp, Tcl_NewIntObj(result));
    
    if (poly.contours != &poly.contour1) {
      /*
       * Must not use ZnPolyFree: the point and controls arrays
       * are passed along to the item and no longer ours.
       */
      ZnFree(poly.contours);
    }
  }

  return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * Coords --
 *
 *----------------------------------------------------------------------
 */
static int
Coords(ZnWInfo		*wi,
       int		argc,
       Tcl_Obj	*CONST	args[],
       ZnTagSearch	**search_var)
{
  ZnPoint	*points;
  ZnItem	item;
  unsigned int	num_points, i;
  int		result, cmd = ZN_COORDS_READ;
  long		index, contour = 0;
  char		*str, *controls = NULL;
  char		c[2] = " ";
  Tcl_Obj	*l, *entries[3];
  
  result = ZnItemWithTagOrId(wi, args[2], &item, search_var);
  if ((result == TCL_ERROR) || (item == ZN_NO_ITEM)) {
    Tcl_AppendResult(wi->interp, " unknown item \"",
		     Tcl_GetString(args[2]), "\"", NULL);
    return TCL_ERROR;
  }
  num_points = 0;
  /*printf("  coords: argc=%d, item %d class: %s\n",
    argc, item->id, item->class->name);*/
  if (argc == 3) {
    /* Get all coords of default contour (0). */
    if (item->class->Coords(item, 0, 0, ZN_COORDS_READ_ALL,
			    &points, &controls, &num_points) == TCL_ERROR) {
      return TCL_ERROR;
    }
  coords_read:
    /*printf("  coords: read %d points, first is %g@%g\n",
      num_points, points->x, points->y);*/
    l = Tcl_GetObjResult(wi->interp);
    if (item->class->pos_offset >= 0) {
      /*
       * Special case for items with a -position. In this case
       * we are guaranteed to return exactly one point, it is
       * then more friendly to return it flat instead of as the
       * only element of a list.
       */
      Tcl_ListObjAppendElement(wi->interp, l, Tcl_NewDoubleObj(points->x));
      Tcl_ListObjAppendElement(wi->interp, l, Tcl_NewDoubleObj(points->y));      
    }
    else {
      for (i = 0; i < num_points; i++, points++) {
	entries[0] = Tcl_NewDoubleObj(points->x);
	entries[1] = Tcl_NewDoubleObj(points->y);
	if (controls && controls[i]) {
	  c[0] = controls[i];
	  entries[2] = Tcl_NewStringObj(c, -1);
	  Tcl_ListObjAppendElement(wi->interp, l, Tcl_NewListObj(3, entries));
	}
	else {
	  Tcl_ListObjAppendElement(wi->interp, l, Tcl_NewListObj(2, entries));
	}
      }
    }
    return TCL_OK;
  }

  /*
   * See if it is an ADD or REMOVE op.
   */
  i = 3;
  str = Tcl_GetString(args[3]);
  if ((str[0] == 'a') && (strcmp(str, "add") == 0)) {
    if ((argc < 5) || (argc > 7)) {
      Tcl_WrongNumArgs(wi->interp, 1, args,
		       "coords tagOrId add ?contour? ?index? coordList");
      return TCL_ERROR;
    }
    cmd = ZN_COORDS_ADD;
    i++;
  }
  else if ((str[0] == 'r') && (strcmp(str, "remove") == 0)) {
    if ((argc != 5) && (argc != 6)) {
      Tcl_WrongNumArgs(wi->interp, 1, args,
		       "coords tagOrId remove ?contour? index");
      return TCL_ERROR;
    }
    cmd = ZN_COORDS_REMOVE;
    i++;
  }
  
  /*
   * Try to see if the next param is a vertex index,
   * a contour index or a coord list.
   */
  /* printf("  coords: arg %d is %s\n", i, Tcl_GetString(args[i])); */
  if (Tcl_GetLongFromObj(wi->interp, args[i], &index) != TCL_OK) {
    Tcl_ResetResult(wi->interp);
    if (((argc == 5) && (cmd != ZN_COORDS_ADD) && (cmd != ZN_COORDS_REMOVE)) ||
	(argc == 6) || (argc == 7)) {
      Tcl_AppendResult(wi->interp, " incorrect contour index \"",
		       Tcl_GetString(args[i]), "\"", NULL);
      return TCL_ERROR;
    }
    else if ((argc == 5) && (cmd != ZN_COORDS_ADD)) {
      Tcl_AppendResult(wi->interp, " incorrect coord index \"",
		       Tcl_GetString(args[i]), "\"", NULL);
      return TCL_ERROR;
    }
    else if (ZnParseCoordList(wi, args[argc-1], &points,
			      &controls, &num_points, NULL) == TCL_ERROR) {
      return TCL_ERROR;
    }
    if (cmd == ZN_COORDS_ADD) {
      /* Append coords at end of default contour (0). */
      if (item->class->Coords(item, 0, 0, ZN_COORDS_ADD_LAST,
			      &points, &controls, &num_points) == TCL_ERROR) {
	return TCL_ERROR;
      }
    }
    else {
      /* Set all coords of default contour (0). */
      if (item->class->Coords(item, 0, 0, ZN_COORDS_REPLACE_ALL,
			      &points, &controls, &num_points) == TCL_ERROR) {
	return TCL_ERROR;
      }
    }
    if (controls) {
      ZnFree(controls);
    }
    return TCL_OK;
  }

  contour = index;
  if (argc == 4) {
    /* Get all coords of contour. */
    if (item->class->Coords(item, contour, 0, ZN_COORDS_READ_ALL,
			    &points, &controls, &num_points) == TCL_ERROR) {
      return TCL_ERROR;
    }
    goto coords_read;
  }
  else if ((argc == 5) && (cmd == ZN_COORDS_REMOVE)) {
    /*  Remove coord at index in default contour (0). */
    if (item->class->Coords(item, 0, index, ZN_COORDS_REMOVE,
			    &points, &controls, &num_points) == TCL_ERROR) {
      return TCL_ERROR;
    }
    return TCL_OK;
  }
  /*
   * Try to see if the next param is a vertex index or a coord list.
   */
  i++;
  /*printf("  coords: arg %d is %s\n", i, Tcl_GetString(args[i]));*/
  if (Tcl_GetLongFromObj(wi->interp, args[i], &index) != TCL_OK) {
    Tcl_ResetResult(wi->interp);
    if ((argc == 7) || ((argc == 6) && (cmd != ZN_COORDS_ADD))) {
      Tcl_AppendResult(wi->interp, " incorrect coord index \"",
		       Tcl_GetString(args[i]), "\"", NULL);
      return TCL_ERROR;
    }
    else if (ZnParseCoordList(wi, args[argc-1], &points,
			      &controls, &num_points, NULL) == TCL_ERROR) {
      return TCL_ERROR;
    }
    if (cmd == ZN_COORDS_ADD) {
      /* Append coords at end of contour. */
      if (item->class->Coords(item, contour, 0, ZN_COORDS_ADD_LAST,
			      &points, &controls, &num_points) == TCL_ERROR) {
	return TCL_ERROR;
      }
    }
    else {
      /* Set all coords of contour. */
      if (item->class->Coords(item, contour, 0, ZN_COORDS_REPLACE_ALL,
			      &points, &controls, &num_points) == TCL_ERROR) {
	return TCL_ERROR;
      }
    }
    if (controls) {
      ZnFree(controls);
    }
    return TCL_OK;
  }
  if (argc == 5) {
    /* Get coord of contour at index. */
    if (item->class->Coords(item, contour, index, ZN_COORDS_READ,
			    &points, &controls, &num_points) == TCL_ERROR) {
      return TCL_ERROR;
    }
    if (num_points) {
      /*printf("  coords: read contour:%d, index:%d, point is %g@%g\n",
	contour, index, points->x, points->y);    */
      l = Tcl_GetObjResult(wi->interp);
      Tcl_ListObjAppendElement(wi->interp, l, Tcl_NewDoubleObj(points->x));
      Tcl_ListObjAppendElement(wi->interp, l, Tcl_NewDoubleObj(points->y));
      if (controls && controls[0]) {
	c[0] = controls[0];
	Tcl_ListObjAppendElement(wi->interp, l, Tcl_NewStringObj(c, -1));
      }
    }
    return TCL_OK;
  }
  else if ((argc == 6) && (cmd == ZN_COORDS_REMOVE)) {
    /*  Remove coord of contour at index. */
    if (item->class->Coords(item, contour, index, ZN_COORDS_REMOVE,
			    &points, &controls, &num_points) == TCL_ERROR) {
      return TCL_ERROR;
    }
    return TCL_OK;
  }
  
  /* Set a single coord or add coords at index in contour. */
  if (ZnParseCoordList(wi, args[argc-1], &points,
		       &controls, &num_points, NULL) == TCL_ERROR) {
    return TCL_ERROR;
  }
  if (argc == 6) {
    num_points = 1;
    cmd = ZN_COORDS_REPLACE;
  }
  if (item->class->Coords(item, contour, index, cmd,
			  &points, &controls, &num_points) == TCL_ERROR) {
    return TCL_ERROR;
  }
  if (controls) {
    ZnFree(controls);
  }
  return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * WidgetObjCmd --
 *
 *	This procedure is invoked to process the Tcl command
 *	that corresponds to a widget managed by this module.
 *	See the user documentation for details on what it does.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */
static int
WidgetObjCmd(ClientData		client_data,	/* Information about the widget. */
	     Tcl_Interp		*interp,	/* Current interpreter. */
	     int		argc,		/* Number of arguments. */
	     Tcl_Obj *CONST	args[])		/* Arguments. */
{
  ZnWInfo	*wi = (ZnWInfo *) client_data;
  int		length, result, cmd_index, index;
  ZnItem	item, item2;
  int		field = ZN_NO_PART;
  unsigned int	num = 0, i, j;
  char		*end, *str;
  ZnTransfo	*t = NULL;
  Tcl_Obj	*l;
  ZnTagSearch	*search_var = NULL;
  Tcl_HashEntry *entry;
  ZnPoint	*points;
  ZnPoint	p;
  unsigned int	num_points;
  ZnList	to_points;
  Tcl_Obj	*entries[3];
  char		c[] = "c";
  double	d;

#ifdef PTK_800
  static char *sub_cmd_strings[] =
#else
  static CONST char *sub_cmd_strings[] =
#endif
  {
    "add", "addtag", "anchorxy", "bbox", "becomes", "bind",
    "cget", "chggroup", "clone", "configure", "contour",
    "coords", "currentpart", "cursor", "dchars",
    "dtag", "fieldbbox", "find", "fit", "focus", "gdelete",
    "gettags", "gname", "group", "hasanchors", "hasfields",
    "hastag", "index", "insert", "itemcget", "itemconfigure",
    "layout", "lower", "monitor", "numparts", "postscript",
    "raise", "remove", "rotate", "scale", "select", "skew",
    "smooth", "tapply", "tcompose", "tdelete", "tget",
    "transform", "translate", "treset", "trestore", "tsave",
    "tset", "type", "vertexat", "xview", "yview", NULL
  };
  enum		sub_cmds {
    ZN_W_ADD, ZN_W_ADDTAG, ZN_W_ANCHORXY, ZN_W_BBOX, ZN_W_BECOMES, ZN_W_BIND,
    ZN_W_CGET, ZN_W_CHGGROUP, ZN_W_CLONE, ZN_W_CONFIGURE,
    ZN_W_CONTOUR, ZN_W_COORDS, ZN_W_CURRENTPART, ZN_W_CURSOR, ZN_W_DCHARS,
    ZN_W_DTAG, ZN_W_FIELD_BBOX, ZN_W_FIND, ZN_W_FIT, ZN_W_FOCUS, ZN_W_GDELETE,
    ZN_W_GETTAGS, ZN_W_GNAME, ZN_W_GROUP, ZN_W_HASANCHORS, ZN_W_HASFIELDS,
    ZN_W_HASTAG, ZN_W_INDEX, ZN_W_INSERT, ZN_W_ITEMCGET, ZN_W_ITEMCONFIGURE,
    ZN_W_LAYOUT, ZN_W_LOWER, ZN_W_MONITOR, ZN_W_NUMPARTS, ZN_W_POSTSCRIPT,
    ZN_W_RAISE, ZN_W_REMOVE, ZN_W_ROTATE, ZN_W_SCALE, ZN_W_SELECT, ZN_W_SKEW,
    ZN_W_SMOOTH, ZN_W_TAPPLY, ZN_W_TCOMPOSE, ZN_W_TDELETE, ZN_W_TGET,
    ZN_W_TRANSFORM, ZN_W_TRANSLATE, ZN_W_TRESET, ZN_W_TRESTORE, ZN_W_TSAVE,
    ZN_W_TSET, ZN_W_TYPE, ZN_W_VERTEX_AT, ZN_W_XVIEW, ZN_W_YVIEW
  };
#ifdef PTK_800
  static char *sel_cmd_strings[] =
#else
  static CONST char *sel_cmd_strings[] =
#endif
  {
    "adjust", "clear", "from", "item", "to", NULL
  };
  enum		sel_cmds {
    ZN_SEL_ADJUST, ZN_SEL_CLEAR, ZN_SEL_FROM, ZN_SEL_ITEM, ZN_SEL_TO
  };

  
  if (argc < 2) {
    Tcl_WrongNumArgs(interp, 1, args, "subcommand ?args?");
    return TCL_ERROR;
  }

  Tcl_Preserve((ClientData) wi);
  
  if (Tcl_GetIndexFromObj(interp, args[1], sub_cmd_strings,
			  "subcommand", 0, &cmd_index) != TCL_OK) {
    goto error;
  }
  result = TCL_OK;

  /*printf("executing command \"%s\", argc=%d\n",
    Tcl_GetString(args[1]), argc);*/
  switch((enum sub_cmds) cmd_index) {
    /*
     * add
     */
  case ZN_W_ADD:
    {
      ZnItem	  group;
      ZnItemClass cls;
      
      if (argc == 2) { /* create subcommand alone, return the list of known
			* object types. */
	ZnItemClass	*classes = ZnListArray(ZnItemClassList());
	
	num = ZnListSize(ZnItemClassList());
	l = Tcl_GetObjResult(interp);
	for (i = 0; i < num; i++) {
	  Tcl_ListObjAppendElement(interp, l, Tcl_NewStringObj(classes[i]->name, -1));
	}
	goto done;
      }
      if ((argc < 4)) {
      add_err:
	Tcl_WrongNumArgs(interp, 1, args, "add type group ?args?");
	goto error;
      }
      str = Tcl_GetString(args[2]);
      if (str[0] == '-') {
	goto add_err;
      }
      cls = ZnLookupItemClass(str);
      if (!cls) {
	Tcl_AppendResult(interp, "unknown item type \"", str, "\"", NULL);
	goto error;
      }
      result = ZnItemWithTagOrId(wi, args[3], &group, &search_var);
      if ((result == TCL_ERROR) || (group == ZN_NO_ITEM) ||
	  (group->class != ZnGroup)) {
	Tcl_AppendResult(interp, ", group item expected, got \"",
			 Tcl_GetString(args[3]), "\"", NULL);
	goto error;
      }
      
      argc -= 4;
      args += 4;
      item = ZnCreateItem(wi, cls, &argc, &args);
      if (item == ZN_NO_ITEM) {
	goto error;
      }
      ZnITEM.InsertItem(item, group, ZN_NO_ITEM, True);
      if (ZnITEM.ConfigureItem(item, ZN_NO_PART, argc, args, True) == TCL_ERROR) {
	goto error;
      }
      wi->hot_item = item;
      wi->hot_prev = item->previous;
      l = Tcl_NewLongObj(item->id);
      Tcl_SetObjResult(interp, l);
    }
    break;
    /*
     * addtag
     */
  case ZN_W_ADDTAG:
    {
      if (argc < 4) {
	Tcl_WrongNumArgs(interp, 1, args, "addtag tag searchCommand ?arg arg ...?");
	goto error;
      }
      result = FindItems(wi, argc, args, args[2], 3, &search_var);
    }
    break;
    /*
     * anchorxy
     */
  case ZN_W_ANCHORXY:
    {
      Tk_Anchor	anchor;
      
      if (argc != 4) {
	Tcl_WrongNumArgs(interp, 1, args, "anchorxy tagOrId anchor");
	goto error;
      }
      result = ZnItemWithTagOrId(wi, args[2], &item, &search_var);
      if ((result == TCL_ERROR) || (item == ZN_NO_ITEM) ||
	  !item->class->has_anchors) {
	Tcl_AppendResult(interp, "unknown item or doesn't support anchors \"",
			 Tcl_GetString(args[2]), NULL);
	goto error;
      }
      if (Tk_GetAnchor(interp, Tcl_GetString(args[3]), &anchor)) {
	goto error;
      }
      /*
       * If something has changed in the geometry we need to
       * update or the anchor location will be erroneous.
       */
      Update(wi);
      item->class->GetAnchor(item, anchor, &p);
      l = Tcl_GetObjResult(wi->interp);
      Tcl_ListObjAppendElement(wi->interp, l, Tcl_NewDoubleObj(p.x));
      Tcl_ListObjAppendElement(wi->interp, l, Tcl_NewDoubleObj(p.y));
    }
    break;
    /*
     * becomes
     */
  case ZN_W_BECOMES:
    {
      Tcl_AppendResult(interp, "Command not yet implemented", NULL);
      goto error;
    }
    break;
    /*
     * bbox
     */
  case ZN_W_BBOX:
    {
      ZnBBox	bbox;

      if (argc < 3) {
	Tcl_WrongNumArgs(interp, 1, args, "bbox tagOrId ?tagOrId ...?");
	goto error;
      }
      argc -= 2;
      args += 2;
      
      Update(wi);
      ZnResetBBox(&bbox);

      for (i = 0; i < (unsigned int) argc; i++) {
	if (ZnTagSearchScan(wi, args[i], &search_var) == TCL_ERROR) {
	  goto error;
	}	
	for (item = ZnTagSearchFirst(search_var);
	     item != ZN_NO_ITEM; item = ZnTagSearchNext(search_var)) {
	  ZnAddBBoxToBBox(&bbox, &item->item_bounding_box);
	}
      }
      if (!ZnIsEmptyBBox(&bbox)) {
	l = Tcl_GetObjResult(interp);
	Tcl_ListObjAppendElement(interp, l, Tcl_NewDoubleObj(bbox.orig.x));
	Tcl_ListObjAppendElement(interp, l, Tcl_NewDoubleObj(bbox.orig.y));
	Tcl_ListObjAppendElement(interp, l, Tcl_NewDoubleObj(bbox.corner.x));
	Tcl_ListObjAppendElement(interp, l, Tcl_NewDoubleObj(bbox.corner.y));
      }
    }
    break;
    /*
     * fieldbbox
     */
  case ZN_W_FIELD_BBOX:
    {
      ZnBBox	bbox;
      ZnDim	width, height;
      ZnFieldSet fs;

      if (argc != 4) {
	Tcl_WrongNumArgs(interp, 1, args, "fieldbbox tagOrId fieldNo|label");
	goto error;
      }
      
      Update(wi);
      ZnResetBBox(&bbox);

      result = ZnItemWithTagOrId(wi, args[2], &item, &search_var);
      if ((result == TCL_ERROR) || (item == ZN_NO_ITEM) ||
	  ! item->class->GetFieldSet) {
	Tcl_AppendResult(interp, ", unknown item or doesn't support fields\" ",
			 Tcl_GetString(args[2]), "\"", NULL);
	goto error;
      }
      fs = item->class->GetFieldSet(item);
      if (Tcl_GetIntFromObj(wi->interp, args[3], &field) != TCL_ERROR) {
	ZnFIELD.GetFieldBBox(fs, field, &bbox);
      }
      else {
	str = Tcl_GetString(args[3]);
	if (strcmp(str, "label") == 0) {
	  ZnFIELD.GetLabelBBox(fs, &width, &height);
	  p.x = ZnNearestInt(fs->label_pos.x);
	  p.y = ZnNearestInt(fs->label_pos.y);
	  ZnAddPointToBBox(&bbox, p.x, p.y);
	  p.x += width;
	  p.y += height;
	  ZnAddPointToBBox(&bbox, p.x, p.y);
	}
	else {
	  Tcl_AppendResult(interp, ", fieldbbox second parameter should be",
			   "either a field index or the tag \"label\"",
			   NULL);
	  goto error;
	}
      }
      
      if (!ZnIsEmptyBBox(&bbox)) {
	l = Tcl_GetObjResult(interp);
	Tcl_ListObjAppendElement(interp, l, Tcl_NewDoubleObj(bbox.orig.x));
	Tcl_ListObjAppendElement(interp, l, Tcl_NewDoubleObj(bbox.orig.y));
	Tcl_ListObjAppendElement(interp, l, Tcl_NewDoubleObj(bbox.corner.x));
	Tcl_ListObjAppendElement(interp, l, Tcl_NewDoubleObj(bbox.corner.y));
      }
    }
    break;
    /*
     * bind
     */
  case ZN_W_BIND:
    {
      ClientData	elem = 0;
      int		part = ZN_NO_PART;
      
      if ((argc < 3) || (argc > 6)) {
	Tcl_WrongNumArgs(interp, 1, args,
			 "bind tagOrId ?part? ?sequence? ?command?");
	goto error;
      }
      /*
       * Test if (a) an itemid or (b) an itemid:part or
       * (c) an item part or (d) a tag is provided.
       */
      str = Tcl_GetString(args[2]);

      argc -= 3;
      args += 3;

      if (isdigit(str[0])) {
	int	id;
	
	id = strtoul(str, &end, 0);
	if ((*end != 0) && (*end != ':')) {
	  goto bind_a_tag;
	}
	entry = Tcl_FindHashEntry(wi->id_table, (char *) id);
	if (entry == NULL) {
	  Tcl_AppendResult(interp, "item \"", str, "\" doesn't exist", NULL);
	  goto error;
	}
	item = elem = Tcl_GetHashValue(entry);
	if (!elem) {
	  goto error;
	}

	if (*end == ':') {
	  /*
	   * The part is provided with the id (old method).
	   */
	  end++;
	part_encode:
	  if (item->class->Part) {
	    l = Tcl_NewStringObj(end, -1);
	    if (item->class->Part(item, &l, &part) == TCL_ERROR) {
	      goto error;
	    }
	    elem = EncodeItemPart(item, part);
	  }
	  else {
	    Tcl_AppendResult(interp, "item \"", str, "\" doesn't have parts", NULL);
	    goto error;
	  }
	}
	else {
	  /*
	   * Check if a part is given in the next parameter
	   * (alternative method for providing a part).
	   */
	  if (argc > 3) {
	    str = Tcl_GetString(args[0]);
	    if (str[0] != '<') {
	      end = str;
	      argc--;
	      args++;
	      goto part_encode;
	    }
	  }
	}
	/*printf("adding element 0x%X to the binding table of item 0x%X\n", elem, item);*/
      }
      else {
      bind_a_tag:
	elem = (ClientData) Tk_GetUid(str);
      }
      
      /*
       * Make a binding table if the widget doesn't already have one.
       */ 
      if (wi->binding_table == NULL) {
	wi->binding_table = Tk_CreateBindingTable(interp);
      }
      
      if (argc == 2) {
	int	    append = 0;
	unsigned long mask;

	str = Tcl_GetString(args[1]);
	if (str[0] == 0) {
	  result = Tk_DeleteBinding(interp, wi->binding_table, elem,
				    Tcl_GetString(args[0]));
	  goto done;
	}
#ifdef PTK
	mask = Tk_CreateBinding(interp, wi->binding_table,
				elem, Tcl_GetString(args[0]), args[1], append);
#else
	if (str[0] == '+') {
	  str++;
	  append = 1;
	}
	mask = Tk_CreateBinding(interp, wi->binding_table,
				elem, Tcl_GetString(args[0]), str, append);
#endif
	if (mask == 0) {
	  goto error;
	}
	if (mask & (unsigned) ~(ButtonMotionMask | Button1MotionMask |
				Button2MotionMask | Button3MotionMask |
				Button4MotionMask | Button5MotionMask | 
				ButtonPressMask | ButtonReleaseMask |
				EnterWindowMask | LeaveWindowMask |
				KeyPressMask | KeyReleaseMask |
				PointerMotionMask | VirtualEventMask)) {
	  Tk_DeleteBinding(interp, wi->binding_table, elem, Tcl_GetString(args[3]));
	  Tcl_ResetResult(interp);
	  Tcl_AppendResult(interp, "requested illegal events; ",
			   "only key, button, motion, enter, leave ",
			   "and virtual events may be used", NULL);
	  goto error;
	}
      }
      else if (argc == 1) {
#ifdef PTK
	Tcl_Obj	*command;
	command = Tk_GetBinding(interp, wi->binding_table, elem,
				Tcl_GetString(args[0]));
	if (command == NULL) {
	  char *string = Tcl_GetString(Tcl_GetObjResult(interp));
	  /*
	   * Ignore missing binding errors.  This is a special hack
	   * that relies on the error message returned by FindSequence
	   * in tkBind.c.
	   */
	  if (string[0] != '\0') {
	    goto error;
	  }
	  else {
	    Tcl_ResetResult(interp);
	  }
	}
	else {
	  Tcl_SetObjResult(interp, command);
	}
#else
	CONST char *command;
	command = Tk_GetBinding(interp, wi->binding_table, elem,
				Tcl_GetString(args[0]));
	if (command == NULL) {
	  goto error;
	}
	Tcl_SetObjResult(interp, Tcl_NewStringObj(command, -1));
#endif
      }
      else {
	Tk_GetAllBindings(interp, wi->binding_table, elem);
      }
    }
    break;
    /*
     * cget
     */
  case ZN_W_CGET:
    {
      if (argc != 3) {
	Tcl_WrongNumArgs(interp, 1, args, "cget option");
	goto error;
      }
      result = Tk_ConfigureValue(interp, wi->win, config_specs,
				 (char *) wi, Tcl_GetString(args[2]), 0);
    }
    break;
    /*
     * chggroup
     */
  case ZN_W_CHGGROUP:
    {
      ZnItem	grp, scan;
      int	adjust=0;
      ZnTransfo inv, t, t2, *this_one=NULL;
      
      if ((argc != 4) && (argc != 5)) {
	Tcl_WrongNumArgs(interp, 1, args, "chggroup tagOrIg group ?adjustTransform?");
	goto error;
      }
      result = ZnItemWithTagOrId(wi, args[2], &item, &search_var);
      if ((result == TCL_ERROR) || (item == ZN_NO_ITEM)) {
	goto error;
      }
      result = ZnItemWithTagOrId(wi, args[3], &grp, &search_var);
      if ((result == TCL_ERROR) || (grp == ZN_NO_ITEM)|| (grp->class != ZnGroup)) {
	goto error;
      }
      if (item->parent == grp) {
	/*
	 * Nothing to be done, the item is already in the
	 * target group.
	 */
	goto done;
      }
      /*
       * Check the ancestors to find if item is an
       * ancestor of grp, which would lead to a
       * forbidden move.
       */
      for (scan = grp; scan && (scan != item); scan = scan->parent);
      if (scan == item) {
	Tcl_AppendResult(interp, "\"", Tcl_GetString(args[3]),
			 "\" is a descendant of \"", Tcl_GetString(args[2]),
			 "\" and can't be used as its parent", NULL);
	goto error;
      }
      if (argc == 5) {
	if (Tcl_GetBooleanFromObj(interp, args[4], &adjust) != TCL_OK) {
	  goto error;
	}
      }
      if ((item->parent == grp) || (item->parent == ZN_NO_ITEM)) {
	goto done;
      }
      if (adjust) {
	ZnITEM.GetItemTransform(grp, &t);
	ZnTransfoInvert(&t, &inv);
	ZnITEM.GetItemTransform(item->parent, &t);
	ZnTransfoCompose(&t2, &t, &inv);
	this_one = &t2;
	if (item->transfo) {
	  ZnTransfoCompose(&t, item->transfo, &t2);
	  this_one = &t;
	}
      }
      ZnITEM.ExtractItem(item);
      ZnITEM.InsertItem(item, grp, ZN_NO_ITEM, True);
      /*
       * The item can be a group in which case we must
       * use the ZN_TRANSFO_FLAG to force an update of
       * the children. In all other case ZN_COORDS_FLAG
       * is enough.
       */
      ZnITEM.Invalidate(item,
		      item->class==ZnGroup?ZN_TRANSFO_FLAG:ZN_COORDS_FLAG);
      if (adjust) {
	ZnITEM.SetTransfo(item, this_one);
      }
    }
    break;
    /*
     * clone
     */
  case ZN_W_CLONE:
    {
      if (argc < 3) {
	Tcl_WrongNumArgs(interp, 1, args, "clone tagOrId ?option value ...?");
	goto error;
      }
      result = ZnItemWithTagOrId(wi, args[2], &item, &search_var);
      if ((result == TCL_ERROR) ||
	  (item == ZN_NO_ITEM) || (item == wi->top_group)) {
	goto error;
      }
      argc -= 3;
      args += 3;
      item2 = ZnITEM.CloneItem(item);
      ZnITEM.InsertItem(item2, item->parent, ZN_NO_ITEM, True);
      if (ZnITEM.ConfigureItem(item2, ZN_NO_PART, argc, args, False) == TCL_ERROR) {
	goto error;
      }
      l = Tcl_NewLongObj(item2->id);
      Tcl_SetObjResult(interp, l);
    }
    break;
    /*
     * configure
     */
  case ZN_W_CONFIGURE:
    {
      if (argc == 2) {
	result = Tk_ConfigureInfo(interp, wi->win, config_specs,
				  (char *) wi, (char *) NULL, 0);
      }
      else if (argc == 3) {
	result = Tk_ConfigureInfo(interp, wi->win, config_specs,
				  (char *) wi, Tcl_GetString(args[2]), 0);
      }
      else {
	result = Configure(interp, wi, argc-2, args+2, TK_CONFIG_ARGV_ONLY);
      }
    }
    break;
    /*
     * contour
     */
  case ZN_W_CONTOUR:
    {
      if ((argc < 3) || (argc > 7)) {
	Tcl_WrongNumArgs(interp, 1, args,
			 "contour tagOrId ?operator windingFlag? ?index? ?coordListOrTagOrId?");
	goto error;
      }
      if (Contour(wi, argc, args, &search_var) == TCL_ERROR) {
	goto error;
      }
      break;
    }
    /*
     * coords
     */
  case ZN_W_COORDS:
    {
      if ((argc < 3) || (argc > 7)) {
	Tcl_WrongNumArgs(interp, 1, args,
			 "coords tagOrId ?add/remove? ?contour? ?index? ?coordList?");
	goto error;
      }
      if (Coords(wi, argc, args, &search_var) == TCL_ERROR) {
	goto error;
      }
    }
    break;
    /*
     * currentpart
     */
  case ZN_W_CURRENTPART:
    {
      ZnBool	only_fields = False;
      if ((argc != 2) && (argc != 3)) {
	Tcl_WrongNumArgs(interp, 1, args, "currentpart ?onlyFields?");
	goto error;
      }
      if (argc == 3) {
	if (Tcl_GetBooleanFromObj(interp, args[2], &only_fields) != TCL_OK) {
	  goto error;
	}
      }
      if ((wi->current_item != ZN_NO_ITEM) &&
	  (wi->current_item->class->Part != NULL) &&
	  ((wi->current_part >= 0) || !only_fields)) {
	l = NULL;
	wi->current_item->class->Part(wi->current_item, &l, &wi->current_part);
	Tcl_SetObjResult(interp, l);
      }
    }
    break;
    /*
     * cursor
     */
  case ZN_W_CURSOR:
    {
      if ((argc != 4) && (argc != 5)) {
	Tcl_WrongNumArgs(interp, 1, args, "cursor tagOrId ?field? index");
	goto error;
      }
      if (ZnTagSearchScan(wi, args[2], &search_var) == TCL_ERROR) {
	goto error;
      }
      if (argc == 5) {
	if (Tcl_GetIntFromObj(interp, args[3], &field) != TCL_OK) {
	  field = ZN_NO_PART;
	  if (Tcl_GetString(args[3])[0] != 0) { 
	    Tcl_AppendResult(interp, "invalid field index \"",
			     Tcl_GetString(args[3]),
			     "\", should be a positive integer", NULL);
	    goto error;
	  }
	}
	argc--;
	args++;
      }
      for (item = ZnTagSearchFirst(search_var);
	   item != ZN_NO_ITEM; item = ZnTagSearchNext(search_var)) {
	if ((item->class->Cursor == NULL) ||
	    (item->class->Index == NULL)) {
	  continue;
	}
	result = (*item->class->Index)(item, field, args[3], &index);
	if (result != TCL_OK) {
	  goto error;
	}
	
	(*item->class->Cursor)(item, field, index);
	if ((item == wi->focus_item) && (field == wi->focus_field) &&
	    wi->text_info.cursor_on) {
	  ZnITEM.Invalidate(item, ZN_DRAW_FLAG);
	}
      }
    }
    break;
    /*
     * dchars
     */
  case ZN_W_DCHARS:
    {
      int	 first, last;
      ZnTextInfo *ti = &wi->text_info;

      if ((argc < 4) || (argc > 6)) {
	Tcl_WrongNumArgs(interp, 1, args, "dchars tagOrId ?field? first ?last?");
	goto error;
      }
      if (ZnTagSearchScan(wi, args[2], &search_var) == TCL_ERROR) {
	goto error;
      }
      if (argc == 6) {
	if (Tcl_GetIntFromObj(interp, args[3], &field) != TCL_OK) {
	  field = ZN_NO_PART;
	  if (Tcl_GetString(args[3])[0] != 0) { 
	    Tcl_AppendResult(interp, "invalid field index \"",
			     Tcl_GetString(args[3]),
			     "\", should be a positive integer", NULL);
	    goto error;
	  }
	}
	argc--;
	args++;
      }
      for (item = ZnTagSearchFirst(search_var);
	   item != ZN_NO_ITEM; item = ZnTagSearchNext(search_var)) {
	if ((item->class->Index == NULL) ||
	    (item->class->DeleteChars == NULL)) {
	  continue;
	}
	result = (*item->class->Index)(item, field, args[3], &first);
	if (result != TCL_OK) {
	  goto error;
	}
	if (argc == 5) {
	  result = (*item->class->Index)(item, field, args[4], &last);
	  if (result != TCL_OK) {
	    goto error;
	  }
	}
	else {
	  last = first;
	}
	(*item->class->DeleteChars)(item, field, &first, &last);

	/*
	 * Update indexes for the selection to reflect the
	 * change.
	 */
	if ((ti->sel_item == item) && (ti->sel_field == field)) {
	  int count = last + 1 - first;
	  if (ti->sel_first > first) {
	    ti->sel_first -= count;
	    if (ti->sel_first < first) {
	      ti->sel_first = first;
	    }
	  }
	  if (ti->sel_last >= first) {
	    ti->sel_last -= count;
	    if (ti->sel_last < (first-1)) {
	      ti->sel_last = first-1;
	    }
	  }
	  if (ti->sel_first >= ti->sel_last) {
	    ti->sel_item = ZN_NO_ITEM;
	    ti->sel_field = ZN_NO_PART;
	  }
	  if ((ti->anchor_item == item) && (ti->anchor_field == field) &&
	      (ti->sel_anchor > first)) {
	    ti->sel_anchor -= count;
	    if (ti->sel_anchor < first) {
	      ti->sel_anchor = first;
	    }
	  }
	}
      }
    }
    break;
    /*
     * dtag
     */
  case ZN_W_DTAG:
    {
      Tk_Uid		tag;
      
      if ((argc != 3) && (argc != 4)) {
	Tcl_WrongNumArgs(interp, 1, args, "dtag tagOrId ?tagToDelete?");
	goto error;
      }
      if (argc == 4) {
	tag = Tk_GetUid(Tcl_GetString(args[3]));
      }
      else {
	tag = Tk_GetUid(Tcl_GetString(args[2]));
      }
      if (ZnTagSearchScan(wi, args[2], &search_var) == TCL_ERROR) {
	goto error;
      }
      for (item = ZnTagSearchFirst(search_var);
	   item != ZN_NO_ITEM; item = ZnTagSearchNext(search_var)) {
	ZnITEM.RemoveTag(item, (char *) tag);
      }
    }
    break;
    /*
     * find
     */
  case ZN_W_FIND:
    {
      if (argc < 3) {
	Tcl_WrongNumArgs(interp, 1, args, "find searchCommand ?arg arg ...?");
	goto error;
      }
      result = FindItems(wi, argc, args, NULL, 2, &search_var);
    }
    break;
    /*
     * fit
     */
  case ZN_W_FIT:
    {
      if (argc != 4) {
	Tcl_WrongNumArgs(interp, 1, args, "fit coordList error");
	goto error;
      }
      if (ZnParseCoordList(wi, args[2], &points,
			   NULL, &num_points, NULL) == TCL_ERROR) {
	return TCL_ERROR;
      }
      if (Tcl_GetDoubleFromObj(interp, args[3], &d) == TCL_ERROR) {
	goto error;
      }
      to_points = ZnListNew(32, sizeof(ZnPoint));
      ZnFitBezier(points, num_points, d, to_points);
      points = (ZnPoint *) ZnListArray(to_points);
      num_points = ZnListSize(to_points);
      l = Tcl_GetObjResult(interp);
      for (i = 0; i < num_points; i++, points++) {
	entries[0] = Tcl_NewDoubleObj(points->x);
	entries[1] = Tcl_NewDoubleObj(points->y);
	if (i % 3) {
	  entries[2] = Tcl_NewStringObj(c, -1);
	  Tcl_ListObjAppendElement(interp, l, Tcl_NewListObj(3, entries));
	}
	else {
	  Tcl_ListObjAppendElement(interp, l, Tcl_NewListObj(2, entries));
	}
      } 
      ZnListFree(to_points);
    }
    break;
    /*
     * focus
     */
  case ZN_W_FOCUS:
    {
      if (argc > 4) {
	Tcl_WrongNumArgs(interp, 1, args, "focus ?tagOrId? ?field?");
	goto error;
      }
      item = wi->focus_item;
      if (argc == 2) {
	field = wi->focus_field;
	if (item != ZN_NO_ITEM) {
	  l = Tcl_GetObjResult(interp);
	  Tcl_ListObjAppendElement(interp, l, Tcl_NewLongObj(item->id));
	  if (field != ZN_NO_PART) {
	    Tcl_ListObjAppendElement(interp, l, Tcl_NewIntObj(field));
	  }
	  else {
	    Tcl_ListObjAppendElement(interp, l, Tcl_NewStringObj("", -1));
	  }
	}
	break;
      }
      if ((item != ZN_NO_ITEM) && (item->class->Cursor != NULL) &&
	  ISSET(wi->flags, ZN_GOT_FOCUS)) {
	ZnITEM.Invalidate(item, ZN_COORDS_FLAG);
      }
      if (Tcl_GetString(args[2])[0] == 0) {
	wi->focus_item = ZN_NO_ITEM;
	wi->focus_field = ZN_NO_PART;
	break;
      }
      if (ZnItemWithTagOrId(wi, args[2], &item, &search_var) == TCL_ERROR) {
	goto error;
      }
      if (argc == 4) {
	if (Tcl_GetIntFromObj(interp, args[3], &field) != TCL_OK) {
	  field = ZN_NO_PART;
	  if (Tcl_GetString(args[3])[0] != 0) { 
	    Tcl_AppendResult(interp, "invalid field index \"",
			     Tcl_GetString(args[3]),
			     "\", should be a positive integer", NULL);
	    goto error;
	  }
	}
      }
      wi->focus_item = item;
      wi->focus_field = field;
      if (ISSET(wi->flags, ZN_GOT_FOCUS) && (item->class->Cursor != NULL)) {
	ZnITEM.Invalidate(wi->focus_item, ZN_COORDS_FLAG);
      }
    }
    break;
    /*
     * gdelete
     */
  case ZN_W_GDELETE:
    {
      if (argc != 3) {
	Tcl_WrongNumArgs(interp, 1, args, "gdelete gName");
	goto error;
      }
      ZnDeleteGradientName(Tcl_GetString(args[2]));
    }    
    break;
    /*
     * gettags
     */
  case ZN_W_GETTAGS:
    {
      Tk_Uid	*tags;
      
      if (argc != 3) {
	Tcl_WrongNumArgs(interp, 1, args, "gettags tagOrId");
	goto error;
      }
      result = ZnItemWithTagOrId(wi, args[2], &item, &search_var);
      if ((result == TCL_ERROR) || (item == ZN_NO_ITEM)) {
	goto error;
      }
      if (!item->tags || !ZnListSize(item->tags)) {
	goto done;
      }
      else {
	num = ZnListSize(item->tags);
	tags = (Tk_Uid *) ZnListArray(item->tags);
	l = Tcl_GetObjResult(interp);
	for (i = 0; i < num; i++) {
	  Tcl_ListObjAppendElement(interp, l, Tcl_NewStringObj(tags[i], -1));
	}
      }
    }
    break;
    /*
     * gname
     */
  case ZN_W_GNAME:
    {
      ZnBool	ok;
      
      if ((argc != 3) && (argc != 4)) {
	Tcl_WrongNumArgs(interp, 1, args, "gname ?grad? gName");
	goto error;
      }
      if (argc == 3) {
	l = Tcl_NewBooleanObj(ZnGradientNameExists(Tcl_GetString(args[2])));
	Tcl_SetObjResult(interp, l);
      }
      else {
	ok = ZnNameGradient(interp, wi->win, Tcl_GetString(args[2]),
			    Tcl_GetString(args[3]));
	if (!ok) {
	  goto error;
	}
      }
    }
    break;
    /*
     * group
     */
  case ZN_W_GROUP:
    {
      if (argc != 3) {
	Tcl_WrongNumArgs(interp, 1, args, "group tagOrId");
	goto error;
      }
      result = ZnItemWithTagOrId(wi, args[2], &item, &search_var);
      if ((result == TCL_ERROR) || (item == ZN_NO_ITEM)) {
	goto error;
      }
      if (item->parent != ZN_NO_ITEM) {
	l = Tcl_NewLongObj(item->parent->id);
	Tcl_SetObjResult(interp, l);
      }
      else {
	/*
	 * Top group is its own parent.
	 */
	l = Tcl_NewLongObj(item->id);
	Tcl_SetObjResult(interp, l);
      }
    }
    break;
    /*
     * hasanchors
     */
  case ZN_W_HASANCHORS:
    {
      if (argc != 3) {
	Tcl_WrongNumArgs(interp, 1, args, "hasanchors tagOrId");
	goto error;
      }
      result = ZnItemWithTagOrId(wi, args[2], &item, &search_var);
      if ((result == TCL_ERROR) || (item == ZN_NO_ITEM)) {
	goto error;
      }
      l = Tcl_NewBooleanObj(item->class->has_anchors?1:0);
      Tcl_SetObjResult(interp, l);
    }
    break;
    /*
     * hasfields
     */
  case ZN_W_HASFIELDS:
    {
      if (argc != 3) {
	Tcl_WrongNumArgs(interp, 1, args, "hasfields tagOrId");
	goto error;
      }
      result = ZnItemWithTagOrId(wi, args[2], &item, &search_var);
      if ((result == TCL_ERROR) || (item == ZN_NO_ITEM)) {
	goto error;
      }
      l =  Tcl_NewBooleanObj(item->class->GetFieldSet?1:0);
      Tcl_SetObjResult(interp, l);
    }
    break;
    /*
     * hastag
     */
  case ZN_W_HASTAG:
    {
      if (argc != 4) {
	Tcl_WrongNumArgs(interp, 1, args, "hastag tagOrId tag");
	goto error;
      }
      result = ZnItemWithTagOrId(wi, args[2], &item, &search_var);
      if ((result == TCL_ERROR) || (item == ZN_NO_ITEM)) {
	goto error;
      }
      l = Tcl_NewBooleanObj(ZnITEM.HasTag(item,
					  Tk_GetUid(Tcl_GetString(args[3]))));
      Tcl_SetObjResult(interp, l);
    }
    break;
    /*
     * index
     */
  case ZN_W_INDEX:
    {
      if ((argc != 4) && (argc != 5)) {
	Tcl_WrongNumArgs(interp, 1, args, "index tagOrId ?field? string");
	goto error;
      }
      if (ZnTagSearchScan(wi, args[2], &search_var) == TCL_ERROR) {
	goto error;
      }
      if (argc == 5) {
	if (Tcl_GetIntFromObj(interp, args[3], &field) != TCL_OK) {
	  field = ZN_NO_PART;
	  if (Tcl_GetString(args[3])[0] != 0) {
	    Tcl_AppendResult(interp, "invalid field index \"",
			     Tcl_GetString(args[3]),
			     "\", should be a positive integer", NULL);
	    goto error;
	  }
	}
	argc--;
	args++;
      }
      for (item = ZnTagSearchFirst(search_var);
	   item != ZN_NO_ITEM; item = ZnTagSearchNext(search_var)) {
	if (item->class->Index != NULL) {
	  result = (*item->class->Index)(item, field, args[3], &index);
	  if (result != TCL_OK) {
	    goto error;
	  }
	  l = Tcl_NewIntObj(index);
	  Tcl_SetObjResult(interp, l);
	  goto done;
	}
      }
      Tcl_AppendResult(interp, "can't find an indexable item \"",
		       Tcl_GetString(args[2]), "\"", NULL);
      goto error;
    }
    break;
    /*
     * insert
     */
  case ZN_W_INSERT:
    {
      ZnTextInfo *ti = &wi->text_info;
      char	 *chars;

      if ((argc != 5) && (argc != 6)) {
	Tcl_WrongNumArgs(interp, 1, args, "insert tagOrId ?field? before string");
	goto error;
      }
      if (ZnTagSearchScan(wi, args[2], &search_var) == TCL_ERROR) {
	goto error;
      }
      if (argc == 6) {
	if (Tcl_GetIntFromObj(interp, args[3], &field) != TCL_OK) {
	  field = ZN_NO_PART;
	  if (Tcl_GetString(args[3])[0] != 0) { 
	    Tcl_AppendResult(interp, "invalid field index \"",
			     Tcl_GetString(args[3]),
			     "\", should be a positive integer", NULL);
	    goto error;
	  }
	}
	argc--;
	args++;
      }
      for (item = ZnTagSearchFirst(search_var);
	   item != ZN_NO_ITEM; item = ZnTagSearchNext(search_var)) {
	if ((item->class->Index == NULL) ||
	    (item->class->InsertChars == NULL)) {
	  continue;
	}
	result = (*item->class->Index)(item, field, args[3], &index);
	if (result != TCL_OK) {
	  goto error;
	}
	chars = Tcl_GetString(args[4]);
	(*item->class->InsertChars)(item, field, &index, chars);
	/*
	 * Inserting characters invalidates selection indices.
	 */
	if ((ti->sel_item == item) && (ti->sel_field == field)) {
	  length = strlen(chars);
	  if (ti->sel_first >= index) {
	    ti->sel_first += length;
	  }
	  if (ti->sel_last >= index) {
	    ti->sel_last += length;
	  }
	  if ((ti->anchor_item == item) && (ti->anchor_field == field) &&
	      (ti->sel_anchor >= index)) {
	    ti->sel_anchor += length;
	  }
	}
      }
    }
    break;
    /*
     * itemcget
     */
  case ZN_W_ITEMCGET:
    {
      if (argc < 4) {
      itemcget_syntax:
	Tcl_WrongNumArgs(interp, 1, args, "itemcget tagOrId ?field? option");
	goto error;
      }    
      result = ZnItemWithTagOrId(wi, args[2], &item, &search_var);
      if ((result == TCL_ERROR) || (item == ZN_NO_ITEM)) {
	goto error;
      }
      if (argc == 5) {
	if (Tcl_GetIntFromObj(interp, args[3], &field) != TCL_OK) {
	  field = ZN_NO_PART;
	  if (Tcl_GetString(args[3])[0] != 0) {
	    Tcl_AppendResult(interp, "invalid field index \"",
			     Tcl_GetString(args[3]),
			     "\", should be a positive integer", NULL);
	    goto error;
	  }
	}
	argc--;
	args++;
      }
      if (argc != 4) {
	goto itemcget_syntax;	  
      }
      if (ZnITEM.QueryItem(item, field, 1, &args[3]) != TCL_OK) {
	goto error;
      }
    }
    break;
    /*
     * itemconfigure
     */
  case ZN_W_ITEMCONFIGURE:
    {
      if (argc < 3) {
	Tcl_WrongNumArgs(interp, 1, args,
			 "itemconfigure tagOrId ?field? option value ?option value? ...");
	goto error;
      }
      if (ZnTagSearchScan(wi, args[2], &search_var) == TCL_ERROR) {
	goto error;
      }
      if ((argc > 3) && (Tcl_GetString(args[3])[0] != '-')) {
	if (Tcl_GetIntFromObj(interp, args[3], &field) != TCL_OK) {
	  field = ZN_NO_PART;
	  if (Tcl_GetString(args[3])[0] != 0) { 
	    Tcl_AppendResult(interp, "invalid field index \"",
			     Tcl_GetString(args[3]),
			     "\", should be a positive integer", NULL);
	    goto error;
	  }
	}
	argc--;
	args++;
      }
      argc -= 3;
      args += 3;
      for (item = ZnTagSearchFirst(search_var);
	   item != ZN_NO_ITEM; item = ZnTagSearchNext(search_var)) {
	if (argc < 2) {
	  if (field == ZN_NO_PART) {
	    result = ZnAttributesInfo(wi, item, item->class->attr_desc, argc, args);
	  }
	  else if (item->class->GetFieldSet) {
	    ZnFieldSet fs = item->class->GetFieldSet(item);
	    if (field < (int) ZnFIELD.NumFields(fs)) {
	      result = ZnAttributesInfo(wi, ZnFIELD.GetFieldStruct(fs, field),
					ZnFIELD.attr_desc, argc, args);
	    }
	    else {
	      Tcl_AppendResult(interp, "field index out of bound", NULL);
	      goto error;
	    }
	  }
	  else {
	    Tcl_AppendResult(interp, "the item does not support fields", NULL);
	    goto error;
	  }
	  goto done;
	}
	else {
	  result = ZnITEM.ConfigureItem(item, field, argc, args, False);
	}
	if (result == TCL_ERROR) {
	  goto error;
	}
      }
    }
    break;
    /*
     * layout
     */
  case ZN_W_LAYOUT:
    {
      if (argc < 4) {
	Tcl_WrongNumArgs(interp, 1, args,
			 "layout operator ?args...? tagOrId ?tagOrId...?");
	goto error;
      }
      if (LayoutItems(wi, argc-2, args+2) == TCL_ERROR) {
	goto error;
      }
    }
    break;
    /*
     * lower
     */
  case ZN_W_LOWER:
    {
      ZnItem      first, group, mark = ZN_NO_ITEM;
      
      if (argc < 3) {
	Tcl_WrongNumArgs(interp, 1, args, "lower tagOrId ?belowThis?");
	goto error;
      }
      if (argc == 4) {
	if (ZnTagSearchScan(wi, args[3], &search_var) == TCL_ERROR) {
	  goto error;
	}
	for (item = ZnTagSearchFirst(search_var);
	     item != ZN_NO_ITEM; item = ZnTagSearchNext(search_var)) {
	  mark = item;
	}
	if (mark == ZN_NO_ITEM) {
	  Tcl_AppendResult(interp, "unknown tag or item \"",
			   Tcl_GetString(args[3]), "\"", NULL);
	  goto error;
	}
      }
      if (ZnTagSearchScan(wi, args[2], &search_var) == TCL_ERROR) {
	goto error;
      }
      item = ZnTagSearchFirst(search_var);
      if ((item == ZN_NO_ITEM) || (item == wi->top_group)) {
	goto done;
      }
      first = item;
      if (mark == ZN_NO_ITEM) {
	mark = ZnGroupTail(item->parent);
      }
      group = mark->parent;
      do {
	if ((item->parent == group) && (item != mark)) {
	  ZnITEM.UpdateItemPriority(item, mark, False);
	  mark = item;
	}
	item = ZnTagSearchNext(search_var);
      }
      while ((item != ZN_NO_ITEM) && (item != first));
    }
    break;
    /*
     * monitor
     */
  case ZN_W_MONITOR:
    {
#ifndef _WIN32
      ZnBool  on_off;
      
      if ((argc != 2) && (argc != 3)) {
	Tcl_WrongNumArgs(interp, 1, args, "monitor ?onOff?");
	goto error;
      }
      if (argc == 3) {
	if (Tcl_GetBooleanFromObj(interp, args[2], &on_off) != TCL_OK) {
	  goto error;
	}
	ASSIGN(wi->flags, ZN_MONITORING, on_off);
	if (on_off == True) {
	  ZnResetChronos(wi->total_draw_chrono);
	  ZnResetChronos(wi->this_draw_chrono);
	}
      }
      if ((argc == 2) || (on_off == False)) {
	long ttime, ltime;
	int  num_actions;
	ZnGetChrono(wi->total_draw_chrono, &ttime, &num_actions);
	ZnGetChrono(wi->this_draw_chrono, &ltime, NULL);
	l = Tcl_GetObjResult(interp);
	Tcl_ListObjAppendElement(interp, l, Tcl_NewIntObj(num_actions));
	Tcl_ListObjAppendElement(interp, l, Tcl_NewIntObj(ltime));
	Tcl_ListObjAppendElement(interp, l, Tcl_NewIntObj(ttime));
	/*Tcl_ListObjAppendElement(interp, l, Tcl_NewIntObj(wi->damaged_area_w));
	Tcl_ListObjAppendElement(interp, l, Tcl_NewIntObj(wi->damaged_area_h));
	Tcl_ListObjAppendElement(interp, l, Tcl_NewIntObj(ttime));*/
      }
#endif
    }
    break;
    /*
     * numparts
     */
  case ZN_W_NUMPARTS:
    {
      if (argc != 3) {
	Tcl_WrongNumArgs(interp, 1, args, "numparts tagOrId");
	goto error;
      }
      result = ZnItemWithTagOrId(wi, args[2], &item, &search_var);
      if ((result == TCL_ERROR) || (item == ZN_NO_ITEM)) {
	goto error;
      }
      l = Tcl_NewIntObj((int) item->class->num_parts);
      Tcl_SetObjResult(interp, l);
    }
    break;
    /*
     * postscript
     */
  case ZN_W_POSTSCRIPT:
    {
      if (ZnPostScriptCmd(wi, argc, args) != TCL_OK) {
	goto error;
      }
    }
    break;
    /*
     * raise
     */
  case ZN_W_RAISE:
    {
      ZnItem      group, mark = ZN_NO_ITEM;
      
      if (argc < 3) {
	Tcl_WrongNumArgs(interp, 1, args, "raise tagOrId ?aboveThis?");
	goto error;
      }
      if (argc == 4) {
	/*
	 * Find the topmost item with the tag.
	 */
	if (ZnTagSearchScan(wi, args[3], &search_var) == TCL_ERROR) {
	  goto error;
	}
	mark = ZnTagSearchFirst(search_var);
	if (mark == ZN_NO_ITEM) {
	  Tcl_AppendResult(interp, "unknown tag or item \"",
			   Tcl_GetString(args[3]), "\"", NULL);
	  goto error;
	}
      }
      if (ZnTagSearchScan(wi, args[2], &search_var) == TCL_ERROR) {
	goto error;
      }
      item = ZnTagSearchFirst(search_var);
      if ((item == ZN_NO_ITEM) || (item == wi->top_group)) {
	goto done;
      }
      if (mark == ZN_NO_ITEM) {
	mark = ZnGroupHead(item->parent);
      }
      group = mark->parent;
      for (; item != ZN_NO_ITEM; item = ZnTagSearchNext(search_var)) {
	if (item->parent != group) {
	  continue;
	}
	if (item != mark) {
	  ZnITEM.UpdateItemPriority(item, mark, True);
	}
      }
    }
    break;
    /*
     * remove
     */
  case ZN_W_REMOVE:
    {
      unsigned int num_fields;
      
      if (argc < 3) {
	Tcl_WrongNumArgs(interp, 1, args, "remove tagOrId ?tagOrId ...?");
	goto error;
      }
      argc -= 2;
      args += 2;
      for (i = 0; i < (unsigned int) argc; i++) {
	if (ZnTagSearchScan(wi, args[i], &search_var) == TCL_ERROR) {
	  goto error;
	}
	for (item = ZnTagSearchFirst(search_var);
	     item != ZN_NO_ITEM; item = ZnTagSearchNext(search_var)) {
	  if (item == wi->top_group) {
	    continue;
	  }
	  if (wi->binding_table != NULL) {
	    Tk_DeleteAllBindings(wi->binding_table, (ClientData) item);
	    if (item->class->GetFieldSet) {
	      num_fields = ZnFIELD.NumFields(item->class->GetFieldSet(item));
	      for (j = 0; j < num_fields; j++) {
		Tk_DeleteAllBindings(wi->binding_table,
				     (ClientData) EncodeItemPart(item, j));
	      }
	    }
	    for (j = 0; j < item->class->num_parts; j++) {
	      Tk_DeleteAllBindings(wi->binding_table,
				   (ClientData) EncodeItemPart(item, -(int)(j+2)));
	    }
	  }
	  ZnITEM.DestroyItem(item);
	}
      }
    }
    break;
    /*
     * rotate
     */
  case ZN_W_ROTATE:
    {
      ZnBool	deg=False;

      if ((argc < 4) && (argc > 7)) {
	Tcl_WrongNumArgs(interp, 1, args, "rotate tagOrIdOrTransform angle ?degree? ?centerX centerY?");
	goto error;
      }
      
      if (argc > 5) {
	if (Tcl_GetDoubleFromObj(interp, args[argc-2], &d) == TCL_ERROR) {
	  goto error;
	}
	p.x = d;
	if (Tcl_GetDoubleFromObj(interp, args[argc-1], &d) == TCL_ERROR) {
	  goto error;
	}
	p.y = d;
      }
      if ((argc == 5) || (argc == 7)) {
	if (Tcl_GetBooleanFromObj(interp, args[4], &deg) != TCL_OK) {
	  goto error;
	}
	
      }
      entry = Tcl_FindHashEntry(wi->t_table, Tcl_GetString(args[2]));
      if (entry != NULL) {
	t = (ZnTransfo *) Tcl_GetHashValue(entry);
      }
      else {
	if (ZnTagSearchScan(wi, args[2], &search_var) == TCL_ERROR) {
	  goto error;
	}
      }
      if (Tcl_GetDoubleFromObj(interp, args[3], &d) == TCL_ERROR) {
	goto error;
      }

      if (t) {
	if (argc > 5) {
	  ZnTranslate(t, -p.x, -p.y, False);
	}
	if (deg) {
	  ZnRotateDeg(t, d);
	}
	else {
	  ZnRotateRad(t, d);
	}
	if (argc > 5) {
	  ZnTranslate(t, p.x, p.y, False);
	}
      }
      else {
	for (item = ZnTagSearchFirst(search_var);
	     item != ZN_NO_ITEM; item = ZnTagSearchNext(search_var)) {
	  ZnITEM.RotateItem(item, d, deg, (argc > 5) ? &p : NULL);
	}
      }
    }
    break;
    /*
     * scale
     */
  case ZN_W_SCALE:
    {
      ZnPoint   scale;
      
      if ((argc != 5) && (argc != 7)) {
	Tcl_WrongNumArgs(interp, 1, args,
			 "scale tagOrIdOrTransform xFactor yFactor ?centerX centerY?");
	goto error;
      }
      entry = Tcl_FindHashEntry(wi->t_table, Tcl_GetString(args[2]));
      if (entry != NULL) {
	t = (ZnTransfo *) Tcl_GetHashValue(entry);
      }
      else {
	if (ZnTagSearchScan(wi, args[2], &search_var) == TCL_ERROR) {
	  goto error;
	}
      }
      if (Tcl_GetDoubleFromObj(interp, args[3], &d) == TCL_ERROR) {
	goto error;
      }
      scale.x = d;
      if (Tcl_GetDoubleFromObj(interp, args[4], &d) == TCL_ERROR) {
	goto error;
      }
      scale.y = d;
      if (argc == 7) {
	if (Tcl_GetDoubleFromObj(interp, args[5], &d) == TCL_ERROR) {
	  goto error;
	}
	p.x = d;
	if (Tcl_GetDoubleFromObj(interp, args[6], &d) == TCL_ERROR) {
	  goto error;
	}
	p.y = d;
      }

      if (t) {
	if (argc == 7) {
	  ZnTranslate(t, -p.x, -p.y, False);
	}
	ZnScale(t, scale.x, scale.y);
	if (argc == 7) {
	  ZnTranslate(t, p.x, p.y, False);
	}
      }
      else {
	for (item = ZnTagSearchFirst(search_var);
	     item != ZN_NO_ITEM; item = ZnTagSearchNext(search_var)) {
	  ZnITEM.ScaleItem(item, scale.x, scale.y, (argc == 7) ? &p : NULL);
	}
      }
    }
    break;
    /*
     * select
     */
  case ZN_W_SELECT:
    {
      ZnTextInfo *ti = &wi->text_info;

      if (argc < 3) {
	Tcl_WrongNumArgs(interp, 1, args, "select option ?tagOrId? ?arg?");
	goto error;
      }
      if (argc >= 4) {
	if (ZnTagSearchScan(wi, args[3], &search_var) == TCL_ERROR) {
	  goto error;
	}
	for (item = ZnTagSearchFirst(search_var);
	     item != ZN_NO_ITEM; item = ZnTagSearchNext(search_var)) {
	  if ((item->class->Index != NULL) &&
	      (item->class->Selection != NULL)) {
	    break;
	  }
	}
	if (item == ZN_NO_ITEM) {
	  Tcl_AppendResult(interp, "can't find an indexable item \"",
			   Tcl_GetString(args[3]), "\"", NULL);
	  goto error;
	}
      }
      if (Tcl_GetIndexFromObj(interp, args[2], sel_cmd_strings,
			      "selection option", 0, &cmd_index) != TCL_OK) {
	goto error;
      }
      if ((argc == 5) || (argc == 6)) {
	if (argc == 6) {
	  if (Tcl_GetIntFromObj(interp, args[4], &field) != TCL_OK) {
	    field = ZN_NO_PART;
	    if (Tcl_GetString(args[4])[0] != 0) {
	      Tcl_AppendResult(interp, "invalid field index \"",
			       Tcl_GetString(args[4]),
			       "\", should be a positive integer", NULL);
	      goto error;
	    }
	  }
	  argc--;
	  args++;
	}
	result = item->class->Index(item, field, args[4], &index);
	if (result != TCL_OK) {
	  goto error;
	}
      }
      switch ((enum sel_cmds) cmd_index) {
      case ZN_SEL_ADJUST:
	if (argc != 5) {
	  Tcl_WrongNumArgs(interp, 1, args, "select adjust tagOrId ?field? index");
	  goto error;
	}
	if ((ti->sel_item == item) && (ti->sel_field == field)) {
	  if (index < (ti->sel_first + ti->sel_last)/2) {
	    ti->sel_anchor = ti->sel_last+1;
	  }
	  else {
	    ti->sel_anchor = ti->sel_first;
	  }
	}
	SelectTo(item, field, index);
	break;
      case ZN_SEL_CLEAR:
	if (argc != 3) {
	  Tcl_WrongNumArgs(interp, 1, args, "select clear");
	  goto error;
	}
	if (ti->sel_item != ZN_NO_ITEM) {
	  ZnITEM.Invalidate(ti->sel_item, ZN_DRAW_FLAG);
	  ti->sel_item = ZN_NO_ITEM;
	  ti->sel_field = ZN_NO_PART;
	}
	break;
      case ZN_SEL_FROM:
	if (argc != 5) {
	  Tcl_WrongNumArgs(interp, 1, args, "select from tagOrId ?field? index");
	  goto error;
	}
	ti->anchor_item = item;
	ti->anchor_field = field;
	ti->sel_anchor = index;
	break;
      case ZN_SEL_ITEM:
	if (argc != 3) {
	  Tcl_WrongNumArgs(interp, 1, args, "select item");
	  goto error;
	}
	if (ti->sel_item != ZN_NO_ITEM) {
	  l = Tcl_GetObjResult(interp);
	  Tcl_ListObjAppendElement(interp, l, Tcl_NewLongObj(ti->sel_item->id));
	  if (ti->sel_field != ZN_NO_PART) {
	    Tcl_ListObjAppendElement(interp, l, Tcl_NewIntObj(ti->sel_field));
	  }
	  else {
	    Tcl_ListObjAppendElement(interp, l, Tcl_NewStringObj("", -1));
	  }
	}
	break;
      case ZN_SEL_TO:
	if (argc != 5) {
	  Tcl_WrongNumArgs(interp, 1, args, "select to tagOrId ?field? index");
	  goto error;
	}
	SelectTo(item, field, index);
	break;
      }
    }
    break;
    /*
     * Skew
     */
  case ZN_W_SKEW:
    {
      double   x_skew, y_skew;
      
      if (argc != 5) {
	Tcl_WrongNumArgs(interp, 1, args, "skew tagOrIdOrTransform xSkewAngle ySkewAngle");
	goto error;
      }
      entry = Tcl_FindHashEntry(wi->t_table, Tcl_GetString(args[2]));
      if (entry != NULL) {
	t = (ZnTransfo *) Tcl_GetHashValue(entry);
      }
      else {
	if (ZnTagSearchScan(wi, args[2], &search_var) == TCL_ERROR) {
	  goto error;
	}
      }
      if (Tcl_GetDoubleFromObj(interp, args[3], &x_skew) == TCL_ERROR) {
	goto error;
      }
      if (Tcl_GetDoubleFromObj(interp, args[4], &y_skew) == TCL_ERROR) {
	goto error;
      }
      
      if (t) {
	ZnSkewRad(t, x_skew, y_skew);
      }
      else {
	for (item = ZnTagSearchFirst(search_var);
	     item != ZN_NO_ITEM; item = ZnTagSearchNext(search_var)) {
	  ZnITEM.SkewItem(item, x_skew, y_skew);
	}
      }
    }
    break;
    /*
     * smooth
     */
  case ZN_W_SMOOTH:
    {
      if (argc != 3) {
	Tcl_WrongNumArgs(interp, 1, args, "smooth coordList");
	goto error;
      }
      if (ZnParseCoordList(wi, args[2], &points,
			   NULL, &num_points, NULL) == TCL_ERROR) {
	return TCL_ERROR;
      }
      to_points = ZnListNew(32, sizeof(ZnPoint));
      ZnSmoothPathWithBezier(points, num_points, to_points);
      points = (ZnPoint *) ZnListArray(to_points);
      num_points = ZnListSize(to_points);
      l = Tcl_GetObjResult(interp);
      for (i = 0; i < num_points; i++, points++) {
	entries[0] = Tcl_NewDoubleObj(points->x);
	entries[1] = Tcl_NewDoubleObj(points->y);
	Tcl_ListObjAppendElement(interp, l, Tcl_NewListObj(2, entries));
      }
      ZnListFree(to_points);
    }
    break;
    /*
     * tapply
     */
  case ZN_W_TAPPLY:
    {
      Tcl_AppendResult(interp, "Command not yet implemented", NULL);
      goto error;
    }
    break;
    /*
     * tcompose
     */
  case ZN_W_TCOMPOSE:
    {
      ZnTransfo		*to;
      ZnBool		invert=False;
      ZnTransfo		res_t, inv_t;

      if ((argc != 4) && (argc != 5)) {
	Tcl_WrongNumArgs(interp, 1, args, "tcompose transformTo aTransform ?invert?");
	goto error;
      }
      if (argc == 5) {
	if (Tcl_GetBooleanFromObj(interp, args[4], &invert) != TCL_OK) {
	  goto error;
	}
	argc--;
      }
      
      entry = Tcl_FindHashEntry(wi->t_table, Tcl_GetString(args[3]));
      if (entry != NULL) {
	t = (ZnTransfo *) Tcl_GetHashValue(entry);
      }
      else {
	result = ZnItemWithTagOrId(wi, args[3], &item, &search_var);
	if ((result == TCL_ERROR) || (item == ZN_NO_ITEM)) {
	  Tcl_ResetResult(interp);
	  Tcl_AppendResult(interp, "\"", Tcl_GetString(args[3]),
			   "\" must be either a tag, ",
			   "an id or a transform name", (char *) NULL);
	  goto error;
	}
	t = item->transfo;
      }

      entry = Tcl_FindHashEntry(wi->t_table, Tcl_GetString(args[2]));
      if (entry != NULL) {
	to = (ZnTransfo *) Tcl_GetHashValue(entry);
      }
      else {
	result = ZnItemWithTagOrId(wi, args[2], &item, &search_var);
	if ((result == TCL_ERROR) || (item == ZN_NO_ITEM)) {
	  Tcl_ResetResult(interp);
	  Tcl_AppendResult(interp, "\"", Tcl_GetString(args[2]),
			   "\" must be either a tag, ",
			   "an id or a transform name", (char *) NULL);
	  goto error;
	}
	to = item->transfo;
      }

      if (invert) {
	ZnTransfoInvert(t, &inv_t);
	ZnTransfoCompose(&res_t, to, &inv_t);
      }
      else {
	ZnTransfoCompose(&res_t, to, t);
      }

      if (item != ZN_NO_ITEM) {
	/* Set back the transform in the item */
	ZnITEM.SetTransfo(item, &res_t);
      }
      else {
	ZnTransfoFree(to);
	Tcl_SetHashValue(entry, ZnTransfoDuplicate(&res_t));
      }

      break;
    }
    /*
     * tdelete
     */
  case ZN_W_TDELETE:
    {
      if (argc != 3) {
	Tcl_WrongNumArgs(interp, 1, args, "tdelete tName");
	goto error;
      }
      entry = Tcl_FindHashEntry(wi->t_table, Tcl_GetString(args[2]));
      if (entry == NULL) {
	Tcl_AppendResult(interp, "\"", Tcl_GetString(args[2]),
			 "\" must be a transform name", (char *) NULL);
	goto error;
      }
      t = (ZnTransfo *) Tcl_GetHashValue(entry);
      ZnTransfoFree(t);
      Tcl_DeleteHashEntry(entry);
    }
    break;
    /*
     * tget
     */
  case ZN_W_TGET:
    {
      ZnPoint	scale, trans;
      ZnReal	rotation, skewxy;
      ZnBool	raw=1, get_trans=0, get_rot=0;
      ZnBool	get_scale=0, get_skew=0;
      ZnTransfo	tid;

      if ((argc != 3) && (argc != 4)) {
      err_tget:
	Tcl_WrongNumArgs(interp, 1, args, "tget transform ?all|translation|scale|rotation|skew?");
	goto error;
      }
      if (argc == 4) {
	raw = 0;
	str = Tcl_GetString(args[3]);
	length = strlen(str);
	if (strncmp(str, "all", length) == 0) {
	  get_scale = get_rot = get_trans = get_skew = 1;
	}
	else if (strncmp(str, "translation", length) == 0) {
	  get_trans = 1;
	}
	else if (strncmp(str, "scale", length) == 0) {
	  get_scale = 1;
	}
	else if (strncmp(str, "rotation", length) == 0) {
	  get_rot = 1;
	}
	else if (strncmp(str, "skew", length) == 0) {
	  get_skew = 1;
	}
	else {
	  goto err_tget;
	}
      }
      entry = Tcl_FindHashEntry(wi->t_table, Tcl_GetString(args[2]));
      if (entry != NULL) {
	t = (ZnTransfo *) Tcl_GetHashValue(entry);
      }
      else {
	result = ZnItemWithTagOrId(wi, args[2], &item, &search_var);
	if ((result == TCL_ERROR) || (item == ZN_NO_ITEM)) {
	  Tcl_ResetResult(interp);
	  Tcl_AppendResult(interp, "\"", Tcl_GetString(args[3]),
			   "\" must be either a tag, ",
			   "an id or a transform name", (char *) NULL);
	  goto error;
	}
	t = item->transfo;
      }
      l = Tcl_GetObjResult(interp);
      if (raw) {
	if (!t) {
	  ZnTransfoSetIdentity(&tid);
	  t = &tid;
	}
	for (i = 0; i < 6; i++) {
	  Tcl_ListObjAppendElement(interp, l, Tcl_NewDoubleObj(t->_[i/2][i%2]));
	}
      }
      else {
	ZnTransfoDecompose(t, get_scale?&scale:NULL, get_trans?&trans:NULL,
			   get_rot?&rotation:NULL, get_skew?&skewxy:NULL);
	if (get_trans) {
	  Tcl_ListObjAppendElement(interp, l, Tcl_NewDoubleObj(trans.x));
	  Tcl_ListObjAppendElement(interp, l, Tcl_NewDoubleObj(trans.y));
	}
	if (get_scale) {
	  Tcl_ListObjAppendElement(interp, l, Tcl_NewDoubleObj(scale.x));
	  Tcl_ListObjAppendElement(interp, l, Tcl_NewDoubleObj(scale.y));
	}
	if (get_rot) {
	  Tcl_ListObjAppendElement(interp, l, Tcl_NewDoubleObj(rotation));
	}
	if (get_skew) {
	  Tcl_ListObjAppendElement(interp, l, Tcl_NewDoubleObj(skewxy));
	}
      }
      break;
    }
    /*
     * transform
     */
  case ZN_W_TRANSFORM:
    {
      char	*controls, *tag;
      ZnPoint	*p, xp;
      ZnTransfo	*from_t=NULL, *to_t=NULL, *result_t;
      ZnTransfo	t1, t2, t3;
      ZnBool	old_format;

      if ((argc != 4) && (argc != 5)) {
	Tcl_WrongNumArgs(interp, 1, args, "transform ?tagOrIdFrom? tagOrIdTo coordlist");
	goto error;
      }
      
      if (argc == 5) {
	/*
	 * Setup the source transform.
	 */
	tag = Tcl_GetString(args[2]);
	if (strcmp(tag, "device") == 0) {
	  from_t = &t1;
	  ZnTransfoSetIdentity(from_t);
	}
	else {
	  entry = Tcl_FindHashEntry(wi->t_table, tag);
	  if (entry != NULL) {
	    /* from is a named transform */
	    from_t = (ZnTransfo *) Tcl_GetHashValue(entry);
	  }
	  else {
	    result = ZnItemWithTagOrId(wi, args[2], &item, &search_var);
	    if ((result == TCL_ERROR) || (item == ZN_NO_ITEM)) {
	      Tcl_ResetResult(interp);
	      Tcl_AppendResult(interp, "\"", Tcl_GetString(args[argc-2]),
			       "\" must be either identity or a tag or ",
			       "an id or a transform name", (char *) NULL);
	      goto error;
	    }
	    ZnITEM.GetItemTransform(item, &t1);
	    from_t = &t1;
	  }
	}
      }
      /*
       * Setup the destination transform
       */
      tag = Tcl_GetString(args[argc-2]);
      if (strcmp(tag, "device") == 0) {
	to_t = &t2;
	ZnTransfoSetIdentity(to_t);
      }
      else {
	entry = Tcl_FindHashEntry(wi->t_table, tag);
	if (entry != NULL) {
	  /* to is a named transform */
	  to_t = (ZnTransfo *) Tcl_GetHashValue(entry);
	}
	else {
	  result = ZnItemWithTagOrId(wi, args[argc-2], &item, &search_var);
	  if ((result == TCL_ERROR) || (item == ZN_NO_ITEM)) {
	    Tcl_ResetResult(interp);
	    Tcl_AppendResult(interp, "\"", Tcl_GetString(args[argc-2]),
			     "\" must be either identity a tag or ",
			     "an id or a transform name", (char *) NULL);
	    goto error;
	  }
	  ZnITEM.GetItemTransform(item, &t2);
	  to_t = &t2;
	}
      }
      ZnTransfoInvert(to_t, &t3);
      to_t = &t3;
      result_t = to_t;

      if (argc == 5) {
	ZnTransfoCompose(&t2, from_t, to_t);
	result_t = &t2;
      }
      /*ZnPrintTransfo(&t);
	ZnPrintTransfo(&inv);*/
      
      if (ZnParseCoordList(wi, args[argc-1], &p,
			   &controls, &num_points, &old_format) == TCL_ERROR) {
	Tcl_AppendResult(interp, " invalid coord list \"",
			 Tcl_GetString(args[argc-1]), "\"", NULL);
	goto error;
      }
      l = Tcl_GetObjResult(interp);
      if (old_format) {
	for (i = 0; i < num_points; i++, p++) {
	  ZnTransformPoint(result_t, p, &xp);
	  /*printf("p->x=%g, p->y=%g, xp.x=%g, xp.y=%g\n", p->x, p->y, xp.x, xp.y);*/
	  Tcl_ListObjAppendElement(interp, l, Tcl_NewDoubleObj(xp.x));
	  Tcl_ListObjAppendElement(interp, l, Tcl_NewDoubleObj(xp.y));
	  /* The next case only applies for a one point
	   * list with a control flag.
	   */
	  if (controls && controls[i]) {
	    c[0] = controls[i];
	    Tcl_ListObjAppendElement(interp, l, Tcl_NewStringObj(c, -1));
	  }
	}
      }
      else {
	for (i = 0; i < num_points; i++, p++) {
	  ZnTransformPoint(result_t, p, &xp);
	  /*printf("p->x=%g, p->y=%g, xp.x=%g, xp.y=%g\n", p->x, p->y, xp.x, xp.y);*/
	  entries[0] = Tcl_NewDoubleObj(xp.x);
	  entries[1] = Tcl_NewDoubleObj(xp.y);
	  if (controls && controls[i]) {
	    c[0] = controls[i];
	    entries[2] = Tcl_NewStringObj(c, -1);
	    Tcl_ListObjAppendElement(interp, l, Tcl_NewListObj(3, entries));
	  }
	  else {
	    Tcl_ListObjAppendElement(interp, l, Tcl_NewListObj(2, entries));
	  }
	}
      }
    }
    break;
    /*
     * translate
     */
  case ZN_W_TRANSLATE:
    {
      ZnBool abs = False;

      if ((argc != 5) && (argc != 6)) {
	Tcl_WrongNumArgs(interp, 1, args, "translate tagOrIdorTransform xAmount yAmount ?abs?");
	goto error;
      }
      entry = Tcl_FindHashEntry(wi->t_table, Tcl_GetString(args[2]));
      if (entry != NULL) {
	t = (ZnTransfo *) Tcl_GetHashValue(entry);
      }
      else {
	if (ZnTagSearchScan(wi, args[2], &search_var) == TCL_ERROR) {
	  goto error;
	}
      }
      if (Tcl_GetDoubleFromObj(interp, args[3], &d) == TCL_ERROR) {
	goto error;
      }
      p.x = d;
      if (Tcl_GetDoubleFromObj(interp, args[4], &d) == TCL_ERROR) {
	goto error;
      }
      p.y = d;
      if (argc == 6) {
	if (Tcl_GetBooleanFromObj(interp, args[5], &abs) == TCL_ERROR) {
	  goto error;
	}
      }

      if (t) {
	ZnTranslate(t, p.x, p.y, abs);
      }
      else {
	for (item = ZnTagSearchFirst(search_var);
	     item != ZN_NO_ITEM; item =ZnTagSearchNext(search_var)) {
	  ZnITEM.TranslateItem(item, p.x, p.y, abs);
	}
      }
    }
    break;
    /*
     * treset
     */
  case ZN_W_TRESET:
    {
      if (argc != 3) {
	Tcl_WrongNumArgs(interp, 1, args, "treset tagOrIdOrTransform");
	goto error;
      }
      entry = Tcl_FindHashEntry(wi->t_table, Tcl_GetString(args[2]));
      if (entry != NULL) {
	t = (ZnTransfo *) Tcl_GetHashValue(entry);
      }
      else {
	if (ZnTagSearchScan(wi, args[2], &search_var) == TCL_ERROR) {
	  goto error;
	}
      }

      if (t) {
	ZnTransfoSetIdentity(t);
      }
      else {
	for (item = ZnTagSearchFirst(search_var);
	     item != ZN_NO_ITEM; item = ZnTagSearchNext(search_var)) {
	  ZnITEM.ResetTransfo(item);
	}
      }
    }
    break;
    /*
     * trestore
     */
  case ZN_W_TRESTORE:
    {
      if (argc != 4) {
	Tcl_WrongNumArgs(interp, 1, args, "trestore tagOrId tName");
	goto error;
      }
      entry = Tcl_FindHashEntry(wi->t_table, Tcl_GetString(args[argc-1]));
      if (entry == NULL) {
	Tcl_AppendResult(interp, "\"", Tcl_GetString(args[argc-1]),
			 "\" must be a transform name", (char *) NULL);
	goto error;
      }
      t = (ZnTransfo *) Tcl_GetHashValue(entry);
      if (ZnTagSearchScan(wi, args[2], &search_var) == TCL_ERROR) {
	goto error;
      }
      for (item = ZnTagSearchFirst(search_var);
	   item != ZN_NO_ITEM; item = ZnTagSearchNext(search_var)) {
	ZnITEM.SetTransfo(item, t);
      }
    }
    break;
    /*
     * tsave
     */
  case ZN_W_TSAVE:
    {
      int	is_ident, new, invert=0;
      ZnTransfo	*inv, ident;
      char	*from;

      if ((argc != 3) && (argc != 4) && (argc != 5)) {
	Tcl_WrongNumArgs(interp, 1, args, "tsave ?tagOrIdOrTransform? tName ?invert?");
	goto error;
      }
      if (argc == 3) {
	entry = Tcl_FindHashEntry(wi->t_table, Tcl_GetString(args[2]));
	l = Tcl_NewBooleanObj(entry != NULL);
	Tcl_SetObjResult(interp, l);
	goto done;
      }
      from = Tcl_GetString(args[2]);
      is_ident = strcmp(from, "identity") == 0;
      if (is_ident) {
	t = &ident;
	ZnTransfoSetIdentity(t);
      }
      else {
	entry = Tcl_FindHashEntry(wi->t_table, from);
	if (entry != NULL) {
	  t = (ZnTransfo *) Tcl_GetHashValue(entry);
	}
	else {
	  result = ZnItemWithTagOrId(wi, args[2], &item, &search_var);
	  if ((result == TCL_ERROR) || (item == ZN_NO_ITEM)) {
	    goto error;
	  }
	  t = item->transfo;
	}
      }
      if (argc == 5) {
	if (Tcl_GetBooleanFromObj(interp, args[4], &invert) != TCL_OK) {
	  goto error;
	}
	argc--;
      }
      entry = Tcl_CreateHashEntry(wi->t_table, Tcl_GetString(args[argc-1]), &new);
      if (!new) {
	ZnTransfoFree((ZnTransfo *) Tcl_GetHashValue(entry));
      }
      if (invert && !is_ident) {
	inv = ZnTransfoNew();
	ZnTransfoInvert(t, inv);
	Tcl_SetHashValue(entry, inv);
      }
      else {
	Tcl_SetHashValue(entry, ZnTransfoDuplicate(t));
      }
    }
    break;
    /*
     * tset
     */
  case ZN_W_TSET:
    {
      ZnTransfo	    new;

      if (argc != 9) {
	Tcl_WrongNumArgs(interp, 1, args,
			 "tset tagOrIdorTransform m00 m01 m10 m11 m20 m21");
	goto error;
      }
      
      for (i = 0; i < 6; i++) {
	if (Tcl_GetDoubleFromObj(interp, args[3+i], &d) == TCL_ERROR) {
	  goto error;
	}
	new._[i/2][i%2] = d;
      }
      entry = Tcl_FindHashEntry(wi->t_table, Tcl_GetString(args[2]));
      if (entry != NULL) {
	t = (ZnTransfo *) Tcl_GetHashValue(entry);
	*t = new;
      }
      else {
	result = ZnItemWithTagOrId(wi, args[2], &item, &search_var);
	if ((result == TCL_ERROR) || (item == ZN_NO_ITEM)) {
	  Tcl_ResetResult(interp);
	  Tcl_AppendResult(interp, "\"", Tcl_GetString(args[2]),
			   "\" must be either a tag, ",
			   "an id or a transform name", (char *) NULL);
	  goto error;
	}
	ZnITEM.SetTransfo(item, &new);
      }
      break;
    }
    /*
     * type
     */
  case ZN_W_TYPE:
    {
      if (argc != 3) {
	Tcl_WrongNumArgs(interp, 1, args, "type tagOrId");
	goto error;
      }
      result = ZnItemWithTagOrId(wi, args[2], &item, &search_var);
      if (result == TCL_ERROR) {
	goto error;
      }
      if (item != ZN_NO_ITEM) {
	l = Tcl_NewStringObj(item->class->name, -1);
	Tcl_SetObjResult(interp, l);      
      }
    }
    break;
    /*
     * vertexat
     */
  case ZN_W_VERTEX_AT:
    {
      int contour, vertex, o_vertex;
      
      if (argc != 5) {
	Tcl_WrongNumArgs(interp, 1, args, " vertexat tagOrId x y");
	goto error;
      }
      if (ZnTagSearchScan(wi, args[2], &search_var) == TCL_ERROR) {
	goto error;
      }
      for (item = ZnTagSearchFirst(search_var);
	   item != ZN_NO_ITEM; item = ZnTagSearchNext(search_var)) {
	if (item->class->PickVertex != NULL) {
	  break;
	}
      }
      if (item == ZN_NO_ITEM) {
	Tcl_AppendResult(interp, "can't find a suitable item \"",
			 Tcl_GetString(args[2]), "\"", NULL);
	goto error;
      }
      if (Tcl_GetDoubleFromObj(interp, args[3], &d) == TCL_ERROR) {
	goto error;
      }
      p.x = d;
      if (Tcl_GetDoubleFromObj(interp, args[4], &d) == TCL_ERROR) {
	goto error;
      }
      p.y = d;
      item->class->PickVertex(item, &p, &contour, &vertex, &o_vertex);
      l = Tcl_GetObjResult(interp);
      Tcl_ListObjAppendElement(interp, l, Tcl_NewIntObj(contour));
      Tcl_ListObjAppendElement(interp, l, Tcl_NewIntObj(vertex));
      Tcl_ListObjAppendElement(interp, l, Tcl_NewIntObj(o_vertex));
      break;
    }

    /* xview */
  case ZN_W_XVIEW:
    {
      int	count, type;
      ZnReal	new_x=0.0, fraction;
      
      if (argc == 2) {
#ifdef PTK
	ZnReal	first, last;
	ScrollFractions(wi->origin.x, wi->origin.x + Tk_Width(wi->win),
			wi->scroll_xo, wi->scroll_xc, &first, &last);
	Tcl_DoubleResults(interp, 2, 0, first, last);
#else
	Tcl_SetObjResult(interp,
			 ScrollFractions(wi->origin.x, wi->origin.x + Tk_Width(wi->win),
					 wi->scroll_xo, wi->scroll_xc));
#endif
      }
      else {
	type = Tk_GetScrollInfoObj(interp, argc, args, &fraction, &count);
	switch (type) {
	case TK_SCROLL_ERROR:
	  result = TCL_ERROR;
	  goto done;
	case TK_SCROLL_MOVETO:
	  new_x = (wi->scroll_xo + (int) (fraction * (wi->scroll_xc - wi->scroll_xo) + 0.5));
	  break;
	case TK_SCROLL_PAGES:
	  new_x = (int) (wi->origin.x + count * 0.9 * Tk_Width(wi->win));
	  break;
	case TK_SCROLL_UNITS:
	  if (wi->x_scroll_incr > 0) {
	    new_x = wi->origin.x + count * wi->x_scroll_incr;
	  }
	  else {
	    new_x = (int) (wi->origin.x + count * 0.1 * Tk_Width(wi->win));
	  }
	  break;
	}
	SetOrigin(wi, new_x, wi->origin.y);
      }
      break;
    }
    
    /*yview */
  case ZN_W_YVIEW:
    {
      int	count, type;
      ZnReal	new_y = 0.0, fraction;

      if (argc == 2) {
#ifdef PTK
	ZnReal	first, last;
	ScrollFractions(wi->origin.y, wi->origin.y + Tk_Height(wi->win),
			wi->scroll_yo, wi->scroll_yc, &first, &last);
	Tcl_DoubleResults(interp, 2, 0, first, last);
#else
	Tcl_SetObjResult(interp,
			 ScrollFractions(wi->origin.y, wi->origin.y + Tk_Height(wi->win),
					 wi->scroll_yo, wi->scroll_yc));
#endif
      }
      else {
	type = Tk_GetScrollInfoObj(interp, argc, args, &fraction, &count);
	switch (type) {
	case TK_SCROLL_ERROR:
	  result = TCL_ERROR;
	  goto done;
	case TK_SCROLL_MOVETO:
	  new_y = (wi->scroll_yo + (int) (fraction * (wi->scroll_yc - wi->scroll_yo) + 0.5));
	  break;
	case TK_SCROLL_PAGES:
	  new_y = (int) (wi->origin.y + count * 0.9 * Tk_Height(wi->win));
	  break;
	case TK_SCROLL_UNITS:
	  if (wi->y_scroll_incr > 0) {
	    new_y = wi->origin.y + count * wi->y_scroll_incr;
	  }
	  else {
	    new_y = (int) (wi->origin.y + count * 0.1 * Tk_Height(wi->win));
	  }
	  break;
	}
	SetOrigin(wi, wi->origin.x, new_y);
      }
      break;
    }
  }
  
 done:
  ZnTagSearchDestroy(search_var);
  if (wi->work_item_list) {
    ZnListFree(wi->work_item_list);
    wi->work_item_list = NULL;
  }
  Tcl_Release((ClientData) wi);
  return result;
  
 error:
  ZnTagSearchDestroy(search_var);
  if (wi->work_item_list) {
    ZnListFree(wi->work_item_list);
    wi->work_item_list = NULL;
  }
  Tcl_Release((ClientData) wi);
  return TCL_ERROR;
}


/*
 *----------------------------------------------------------------------
 *
 * Configure --
 *
 *	This procedure is called to process an args/argc list in
 *	conjunction with the Tk option database to configure (or
 *	reconfigure) a Zinc widget.
 *
 * Results:
 *	The return value is a standard Tcl result.  If TCL_ERROR is
 *	returned, then interp->result contains an error message.
 *
 * Side effects:
 *	Configuration information, such as colors, border width,
 *	etc. get set for the widget;  old resources get freed,
 *	if there were any.
 *
 *----------------------------------------------------------------------
 */
static int
Configure(Tcl_Interp		*interp,/* Used for error reporting. */
	  ZnWInfo		*wi,	/* Information about widget. */
	  int			argc,	/* Number of valid entries in args. */
	  Tcl_Obj	*CONST	args[],	/* Arguments. */
	  int			flags)	/* Flags to pass to Tk_ConfigureWidget. */
{
#define CONFIG_PROBE(offset) (ISSET(config_specs[offset].specFlags, \
                                    TK_CONFIG_OPTION_SPECIFIED))
  ZnBool  init;
  int	  render;

  init = wi->fore_color == NULL;
  render = wi->render;
  if (Tk_ConfigureWidget(interp, wi->win, config_specs, argc,
#ifdef PTK
			 (Tcl_Obj **) args, (char *) wi, flags) != TCL_OK)
#else
			 (CONST char **) args, (char *) wi, flags|TK_CONFIG_OBJS) != TCL_OK)
#endif
  {
    return TCL_ERROR;
  }
  if (!init) {
    if (wi->render != render) {
      ZnWarning("It is not possible to change the -render option after widget creation.\n");
    }
    wi->render = render;
  }

#ifdef GL
  if (CONFIG_PROBE(FONT_SPEC) || !wi->font_tfi) {
    if (wi->font_tfi) {
      ZnFreeTexFont(wi->font_tfi);
      wi->font_tfi = NULL;
    }
  }
  if (CONFIG_PROBE(MAP_TEXT_FONT_SPEC) || !wi->map_font_tfi) {
    if (wi->map_font_tfi) {
      ZnFreeTexFont(wi->map_font_tfi);
      wi->map_font_tfi = NULL;
    }
  }
#endif

  /*
   * Maintain the pick aperture within meaningful bounds.
   */
  if (wi->pick_aperture < 0) {
    wi->pick_aperture = 0;
  }
  if (CONFIG_PROBE(BACK_COLOR_SPEC) || !wi->relief_grad) {
    XColor	   *color;
    unsigned short alpha;

    Tk_SetWindowBackground(wi->win, ZnGetGradientPixel(wi->back_color, 0.0));
    if (wi->relief_grad) {
      ZnFreeGradient(wi->relief_grad);
      wi->relief_grad = NULL;
    }
    if (wi->relief != ZN_RELIEF_FLAT) {
      color = ZnGetGradientColor(wi->back_color, 0.0, &alpha);
      wi->relief_grad = ZnGetReliefGradient(interp, wi->win,
					    Tk_NameOfColor(color), alpha);
    }
  }
  if (CONFIG_PROBE(BACK_COLOR_SPEC) || CONFIG_PROBE(LIGHT_ANGLE_SPEC)) {
    ZnDamageAll(wi);
  }
  if (CONFIG_PROBE(RELIEF_SPEC)) {
    ZnNeedRedisplay(wi);
  }
  
  wi->inset = wi->border_width + wi->highlight_width;
  if (CONFIG_PROBE(BORDER_WIDTH_SPEC) ||
      CONFIG_PROBE(HIGHLIGHT_THICKNESS_SPEC)) {
    ZnDamageAll(wi);
  }
  if (CONFIG_PROBE(SPEED_VECTOR_LENGTH_SPEC) ||
      CONFIG_PROBE(VISIBLE_HISTORY_SIZE_SPEC) ||
      CONFIG_PROBE(MANAGED_HISTORY_SIZE_SPEC)) {
    ZnITEM.InvalidateItems(wi->top_group, ZnTrack);
  }
  if (CONFIG_PROBE(MAP_DISTANCE_SYMBOL_SPEC)) {
    ZnITEM.InvalidateItems(wi->top_group, ZnMap);
  }
  if (CONFIG_PROBE(TRACK_SYMBOL_SPEC)) {
    ZnITEM.InvalidateItems(wi->top_group, ZnTrack);
    ZnITEM.InvalidateItems(wi->top_group, ZnWayPoint);
  }
  
  /*
   * Request the new geometry.
   */
  if (CONFIG_PROBE(WIDTH_SPEC) || CONFIG_PROBE(HEIGHT_SPEC) ||
      CONFIG_PROBE(BORDER_WIDTH_SPEC) ||
      CONFIG_PROBE(HIGHLIGHT_THICKNESS_SPEC) || ISCLEAR(wi->flags, ZN_REALIZED)) {
    Tk_GeometryRequest(wi->win, wi->opt_width, wi->opt_height);
  }

  if (CONFIG_PROBE(TILE_SPEC)) {
    ZnDamageAll(wi);
  }
  
  /*
   * Update the registration with the overlap manager.
   */
#ifdef OM
  if (CONFIG_PROBE(OVERLAP_MANAGER_SPEC)) {
    Tcl_HashEntry	*entry;
    ZnItem		grp;

    if (wi->om_group != ZN_NO_ITEM) {
      OmUnregister((void *) wi);
      wi->om_group = ZN_NO_ITEM;
    }
    if (wi->om_group_id != 0) {
      entry = Tcl_FindHashEntry(wi->id_table, (char *) wi->om_group_id);
      if (entry != NULL) {
	grp = (ZnItem) Tcl_GetHashValue(entry);
	if (grp->class == ZnGroup) {
	  OmRegister((void *) wi, ZnSendTrackToOm,
		     ZnSetLabelAngleFromOm, ZnQueryLabelPosition);
	  wi->om_group = grp;
	}
      }
    }
  }
#endif

  if (CONFIG_PROBE(INSERT_WIDTH_SPEC) && wi->focus_item) {
    ZnITEM.Invalidate(wi->focus_item, ZN_COORDS_FLAG);
  }
  /*
   * Update the blinking cursor timing if on/off time has changed.
   */
  if (ISSET(wi->flags, ZN_GOT_FOCUS) &&
      (CONFIG_PROBE(INSERT_ON_TIME_SPEC) ||
       CONFIG_PROBE(INSERT_OFF_TIME_SPEC))) {
    Focus(wi, True);
  }

  if (CONFIG_PROBE(SCROLL_REGION_SPEC)) {
    /*
     * Compute the scroll region
     */
    wi->scroll_xo = wi->scroll_yo = 0;
    wi->scroll_xc = wi->scroll_yc = 0;
    if (wi->region != NULL) {
      int	 argc2;
#ifdef PTK
      Arg	*args2;
#else
      CONST char **args2;
#endif

#ifdef PTK
      if (Tcl_ListObjGetElements(interp, wi->region, &argc2, &args2) != TCL_OK)
#else
      if (Tcl_SplitList(interp, wi->region, &argc2, &args2) != TCL_OK)
#endif
      {
	return TCL_ERROR;
      }
      if (argc2 != 4) {
	Tcl_AppendResult(interp, "bad scrollRegion \"", wi->region, "\"", (char *) NULL);
      badRegion:
#ifndef PTK
	ZnFree(wi->region);
	ZnFree(args2);
#endif
	wi->region = NULL;
	return TCL_ERROR;
      }
#ifdef PTK
#ifdef PTK_800
      if ((Tk_GetPixels(interp, wi->win, LangString(args2[0]), &wi->scroll_xo) != TCL_OK) ||
	  (Tk_GetPixels(interp, wi->win, LangString(args2[1]), &wi->scroll_yo) != TCL_OK) ||
	  (Tk_GetPixels(interp, wi->win, LangString(args2[2]), &wi->scroll_xc) != TCL_OK) ||
	  (Tk_GetPixels(interp, wi->win, LangString(args2[3]), &wi->scroll_yc) != TCL_OK))
#else
      if ((Tk_GetPixelsFromObj(interp, wi->win, args2[0], &wi->scroll_xo) != TCL_OK) ||
	  (Tk_GetPixelsFromObj(interp, wi->win, args2[1], &wi->scroll_yo) != TCL_OK) ||
	  (Tk_GetPixelsFromObj(interp, wi->win, args2[2], &wi->scroll_xc) != TCL_OK) ||
	  (Tk_GetPixelsFromObj(interp, wi->win, args2[3], &wi->scroll_yc) != TCL_OK))
#endif
#else
      if ((Tk_GetPixels(interp, wi->win, args2[0], &wi->scroll_xo) != TCL_OK) ||
	  (Tk_GetPixels(interp, wi->win, args2[1], &wi->scroll_yo) != TCL_OK) ||
	  (Tk_GetPixels(interp, wi->win, args2[2], &wi->scroll_xc) != TCL_OK) ||
	  (Tk_GetPixels(interp, wi->win, args2[3], &wi->scroll_yc) != TCL_OK))
#endif
      {
	goto badRegion;
      }
    }
  }

  if (CONFIG_PROBE(SCROLL_REGION_SPEC) ||
      CONFIG_PROBE(CONFINE_SPEC)) {
    SetOrigin(wi, wi->origin.x, wi->origin.y);
    SET(wi->flags, ZN_UPDATE_SCROLLBARS);
  }
  
  if (CONFIG_PROBE(FOLLOW_POINTER_SPEC)) {
    if (wi->follow_pointer) {
      /* Flag has just been turned on, process
       * the last known positional event to update
       * the item under pointer.
       */
      if (wi->pick_event.type == ButtonPress ||
	  wi->pick_event.type == ButtonRelease ||
	  wi->pick_event.type == MotionNotify ||
	  wi->pick_event.type == EnterNotify ||
	  wi->pick_event.type == LeaveNotify) {
	Tcl_Preserve((ClientData) wi);
	CLEAR(wi->flags, ZN_INTERNAL_NEED_REPICK);
	PickCurrentItem(wi, &wi->pick_event);
	Tcl_Release((ClientData) wi);
      }
    }
  }
  
  return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * Focus --
 *
 *	This procedure is called whenever a zinc gets or loses the
 *	input focus.  It's also called whenever the window is
 *	reconfigured while it has the focus.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The cursor gets turned on or off.
 *
 *----------------------------------------------------------------------
 */
static void
Blink(ClientData	client_data)
{
  ZnWInfo *wi = (ZnWInfo *) client_data;

  if (ISCLEAR(wi->flags, ZN_GOT_FOCUS) || (wi->insert_off_time == 0)) {
    return;
  }
  if (wi->text_info.cursor_on) {
    wi->text_info.cursor_on = 0;
    wi->blink_handler = Tcl_CreateTimerHandler(wi->insert_off_time,
					       Blink, client_data);
  }
  else {
    wi->text_info.cursor_on = 1;
    wi->blink_handler = Tcl_CreateTimerHandler(wi->insert_on_time,
					       Blink, client_data);
  }
  if ((wi->focus_item != ZN_NO_ITEM) &&
      (wi->focus_item->class->Cursor != NULL)) {
    ZnITEM.Invalidate(wi->focus_item, ZN_DRAW_FLAG);
  }
}

static void
Focus(ZnWInfo	*wi,
      ZnBool		got_focus)
{
  Tcl_DeleteTimerHandler(wi->blink_handler);
  if (got_focus) {
    SET(wi->flags, ZN_GOT_FOCUS);
    wi->text_info.cursor_on = 1;
    if (wi->insert_off_time != 0) {
      wi->blink_handler = Tcl_CreateTimerHandler(wi->insert_off_time,
						 Blink, (ClientData) wi);
    }
  }
  else {
    CLEAR(wi->flags, ZN_GOT_FOCUS);
    wi->text_info.cursor_on = 0;
    wi->blink_handler = (Tcl_TimerToken) NULL;
  }
  if ((wi->focus_item != ZN_NO_ITEM) &&
      (wi->focus_item->class->Cursor != NULL)){
    ZnITEM.Invalidate(wi->focus_item, ZN_COORDS_FLAG);
  }
  /*printf("focus %s\n", got_focus ? "in" : "out");*/
  if (wi->highlight_width > 0) {
    ZnNeedRedisplay(wi);
  }
#ifdef GL_DAMAGE
  if (wi->render) {
    /*    ZnDamageAll(wi);*/
  }
#endif
}


/*
 *----------------------------------------------------------------------
 *
 * Event --
 *
 *	This procedure is invoked by the Tk dispatcher for various
 *	events on Zincs.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	When the window gets deleted, internal structures get
 *	cleaned up.  When it gets exposed, it is redisplayed.
 *
 *----------------------------------------------------------------------
 */
static void
Event(ClientData client_data,	/* Information about widget. */
      XEvent	*event)	/* Information about event. */
{
  ZnWInfo	*wi = (ZnWInfo *) client_data;
  XGCValues	values;

  /*printf("=============== DEBUT %s %d EVENT ==================\n",
         event->type == MapNotify ? "MAP":
	 event->type == Expose? "EXPOSE" :
	 event->type == ConfigureNotify ? "CONFIGURE" :
	 event->type == DestroyNotify ? "DESTROY" :
	 "??", event->type);*/
  if (event->type == MapNotify) {
    if (!wi->gc) {
      SET(wi->flags, ZN_REALIZED);

      if (wi->render) {
#ifdef GL
	GLfloat		r[2]; /* Min, Max */
	GLint		i[1];
	ZnGLContextEntry *ce;

	ce = ZnGetGLContext(wi->dpy);
	ZnGLMakeCurrent(wi->dpy, 0);
	glGetFloatv(ZN_GL_LINE_WIDTH_RANGE, r);
	ce->max_line_width = r[1];
	glGetFloatv(ZN_GL_POINT_SIZE_RANGE, r);
	ce->max_point_width = r[1];
	glGetIntegerv(GL_MAX_TEXTURE_SIZE, i);
	ce->max_tex_size = (unsigned int) i[0];

	if (!wi->font_tfi) {
	  wi->font_tfi = ZnGetTexFont(wi, wi->font);
	}
	if (!wi->map_font_tfi) {
	  wi->map_font_tfi = ZnGetTexFont(wi, wi->map_text_font);
	}

	if (ISSET(wi->flags, ZN_PRINT_CONFIG)) {
	  fprintf(stderr, "OpenGL version %s\n",
		  (char *) glGetString(GL_VERSION));
	  fprintf(stderr, "  Rendering engine: %s, ",
		  (char *) glGetString(GL_RENDERER));
	  fprintf(stderr, "  Vendor: %s\n",
		  (char *) glGetString(GL_VENDOR));
	  fprintf(stderr, "  Available extensions: %s\n",
		  (char *) glGetString(GL_EXTENSIONS));
	  fprintf(stderr, "Max antialiased line width: %g\n",
		  ce->max_line_width);
	  fprintf(stderr, "Max antialiased point size: %g\n",
		  ce->max_point_width);
	  fprintf(stderr, "Max texture size: %d\n",
		  ce->max_tex_size);
	}

	/*ZnGLRelease(wi);*/
#endif
      }

      /*
       * Get the work GC and suppress GraphicExpose
       * and NoExpose events reception.
       */
      wi->gc = XCreateGC(wi->dpy, Tk_WindowId(wi->win), 0, NULL);
      values.graphics_exposures = False;
      XChangeGC(wi->dpy, wi->gc, GCGraphicsExposures, &values);

      /*
       * Set the real top window above us.
       */
      {
	Window	  parent, root, *children;
	Tk_Window top_level;
	int       num_children;
	
	top_level = wi->win;
	while (!Tk_IsTopLevel(top_level)) {
	  top_level = Tk_Parent(top_level);
	}
	XQueryTree(wi->dpy, Tk_WindowId(top_level), &root, &parent,
		   &children, &num_children);
	if (root == parent) {
	  wi->real_top = Tk_WindowId(top_level);
	}
	else {
	  wi->real_top = parent;
	}
	if (children) {
	  XFree(children);
	}
      }
      ZnNeedRedisplay(wi);
    }
  }
  else if (event->type == Expose) {
    ZnBBox	bbox;
    ZnDim	width, height;

    bbox.orig.x = (((XExposeEvent*) event)->x);
    bbox.orig.y = (((XExposeEvent*) event)->y);
    width = ((XExposeEvent*) event)->width;
    height = ((XExposeEvent*) event)->height;
    if (bbox.orig.x < 0) {
      width += bbox.orig.x;
      bbox.orig.x = 0;
    }
    if (bbox.orig.y < 0) {
      height += bbox.orig.y;
      bbox.orig.y = 0;
    }
    bbox.corner.x = MIN(wi->width, bbox.orig.x + width);
    bbox.corner.y = MIN(wi->height, bbox.orig.y + height);
    
    /*printf("expose %d %d %d %d\n",
	   ((XExposeEvent*) event)->x, ((XExposeEvent*) event)->y,
	   ((XExposeEvent*) event)->width, ((XExposeEvent*) event)->height);*/
    /*
     * Add the exposed area to the expose region and
     * schedule an asynchronous redisplay of the window
     * if we are done adding exposed parts.
     */
    ZnAddBBoxToBBox(&wi->exposed_area, &bbox);
    if (/*(((XExposeEvent*) event)->count == 0) &&*/
	!ZnIsEmptyBBox(&wi->exposed_area)) {
      ZnNeedRedisplay(wi);
    }
  }
  /*
   * Resize the double buffer pixmap and prepare to redisplay
   * the whole scene. The transform parameters are not
   * modified as a result of the resize. If the application
   * need such change, it can bind a handler on <Configure>.
   */
  else if (event->type == ConfigureNotify) {
    int    int_width, int_height;
    ZnBBox bbox;

    int_width = Tk_Width(wi->win);
    int_height = Tk_Height(wi->win);
    
    if ((wi->width != int_width) || (wi->height != int_height)) {
      bbox.orig.x = bbox.orig.y = 0;
      bbox.corner.x = MAX(wi->width, int_width);
      bbox.corner.y = MAX(wi->height, int_height);
      wi->opt_width = wi->width = int_width;
      wi->opt_height = wi->height = int_height;

      ZnResetTransformStack(wi);

      SET(wi->flags, ZN_UPDATE_SCROLLBARS);
      /*
       * The call below is needed in order to recenter the view if
       * it's confined and the scroll region is smaller than the
       * window.
       */
      SetOrigin(wi, wi->origin.x, wi->origin.y);

      ZnDamage(wi, &bbox);
      ZnITEM.Invalidate(wi->top_group, ZN_TRANSFO_FLAG);
      
      /*
       * Reallocate the double buffer pixmap/image.
       */
      if (!wi->render) {
	/*printf("reallocating double buffer\n");*/
	Tk_FreePixmap(wi->dpy, wi->draw_buffer);
	wi->draw_buffer = Tk_GetPixmap(wi->dpy, RootWindowOfScreen(wi->screen),
				       int_width, int_height,
				       DefaultDepthOfScreen(wi->screen));
      }
    }
    else {
      /*
       * In case of a window reconfiguration following a change
       * of border size, set the exposed area to force a copy
       * of the back buffer to the screen.
       */
      bbox.orig.x = bbox.orig.y = 0;
      bbox.corner.x = Tk_Width(wi->win);
      bbox.corner.y = Tk_Height(wi->win);
      ZnAddBBoxToBBox(&wi->exposed_area, &bbox);      
    }
    ZnNeedRedisplay(wi);
  }
  /*
   * Take into account that the window has been actually cancelled.
   * Remove the corresponding widget command, unregister any
   * pending Redisplay and eventually free the widget's memory.
   */
  else if (event->type == DestroyNotify) {
    if (wi->win != NULL) {
      wi->win = NULL;
      CLEAR(wi->flags, ZN_REALIZED);
#ifdef PTK
      Lang_DeleteWidget(wi->interp, wi->cmd);
#else 
      Tcl_DeleteCommandFromToken(wi->interp, wi->cmd);
#endif
    }
    if (ISSET(wi->flags, ZN_UPDATE_PENDING)) {
      Tcl_CancelIdleCall(Redisplay, (ClientData) wi);
    }
    Tcl_EventuallyFree((ClientData) wi, Destroy);
  }
  else if (event->type == FocusIn) {
    if (event->xfocus.detail != NotifyInferior) {
      Focus(wi, True);
    }
  }
  else if (event->type == FocusOut) {
    if (event->xfocus.detail != NotifyInferior) {
      Focus(wi, False);
    }
  }
  
  /*printf("=============== FIN %s EVENT ==================\n",
	 event->type == MapNotify ? "MAP":
	 event->type == Expose? "EXPOSE" :
	 event->type == ConfigureNotify ? "CONFIGURE" :
	 event->type == DestroyNotify ? "DESTROY" :
	 "??");*/
}


/*
 *----------------------------------------------------------------------
 *
 * DoEvent --
 *
 *	Trigger the bindings associated with an event.
 *
 *----------------------------------------------------------------------
 */
static void
DoEvent(ZnWInfo	*wi,
	XEvent	*event,
	ZnBool	bind_item, /* Controls whether item bindings will trigger.
			    * Useful for Enter/Leaves between fields */
	ZnBool	bind_part) /* Controls whether part bindings will trigger.
			    * Useful for precise control of Enter/Leaves
			    * during grabs. */
{
#define NUM_STATIC 4
  ClientData		items[NUM_STATIC], *its;
  static unsigned int	worksize = 128, len, num, num_tags;
  static char		*workspace = NULL;
  unsigned int		i, ptr;
  ClientData		*tag_list = NULL;
  ZnItem		item;
  int			part;

#define BIND_ITEM(test)			\
  if (bind_item && (test)) {		\
    its[ptr] = (ClientData) all_uid;	\
    ptr++;				\
    for (i = 0; i < num_tags; i++) {	\
      its[ptr] = tag_list[i];		\
      ptr++;				\
    }					\
    its[ptr] = (ClientData) item;	\
    ptr++;				\
  }

  if (wi->binding_table == NULL) {
    /*printf("no bindings\n");*/
    return;
  }

  item = wi->current_item;
  part = wi->current_part;
  if ((event->type == KeyPress) || (event->type == KeyRelease)) {
    item = wi->focus_item;
    part = wi->focus_field;
  }
  
  if ((item == ZN_NO_ITEM) || !item->class->IsSensitive(item, ZN_NO_PART)) {
    return;
  }
  
  /*
   * Set up an array with all the relevant elements for processing
   * this event.  The relevant elements are (a) the event's item/part
   * tag (i.e item:part), (b) the event's item, (c) the tags
   * associated with the event's item, and (d) the tag 'all'.
   */
  num = 0;
  num_tags = 0;
  its = items;
  bind_part = (bind_part &&
	       (part != ZN_NO_PART) &&
	       item->class->IsSensitive(item, part) &&
	       (wi->current_item->class->num_parts ||
		wi->current_item->class->GetFieldSet));

  /*printf("type=%s, current=%d, new=%d --> %s, currentp %d, newp %d\n",
	 event->type==EnterNotify?"<Enter>":
	 event->type==LeaveNotify?"<Leave>":
	 event->type==MotionNotify?"<Motion>":"other",
	 wi->current_item?wi->current_item->id:0,
	 wi->new_item?wi->new_item->id:0,
	 bind_item?"bind":"nobind",
	 wi->current_part, wi->new_part);*/
  if (bind_item) {
    num += 2;
  }
  if (bind_part) {
    num++;
    if (!workspace) {
      workspace = ZnMalloc(worksize);
    }
  }
  if (item->tags) {
    num_tags = ZnListSize(item->tags);
    if (bind_item) {
      num += num_tags;
    }
    if (bind_part) {
      num += num_tags;
    }
    tag_list = (ClientData *) ZnListArray(item->tags);
    if (num > NUM_STATIC) {
      its = (ClientData *) ZnMalloc(num*sizeof(ClientData));
    }
  }

  ptr = 0;

  BIND_ITEM(event->type != LeaveNotify);
  
  if (bind_part) {
    /*
     * Add here a binding for each tag suffixed by :part
     */
    for (i = 0; i < num_tags; i++) {
      len = strlen(tag_list[i])+ TCL_INTEGER_SPACE;
      if (worksize < len) {
	worksize = len + 10;
	workspace = ZnRealloc(workspace, len);
      }
      sprintf(workspace, "%s:%d", (char *) tag_list[i], part);
      its[ptr] = (ClientData) Tk_GetUid(workspace);
      ptr++;
    }
    /*
     * Add here a binding for id:part
     */
    its[ptr] = EncodeItemPart(item, part);
    ptr++;
  }
  
  BIND_ITEM(event->type == LeaveNotify);

  /*
   * Invoke the binding system.
   */
  if (wi->win != NULL) {
    Tk_BindEvent(wi->binding_table, event, wi->win, (int) num, its);
  }
  if (its != items) {
    ZnFree(its);
  }

#undef BIND_ITEM
}


/*
 *----------------------------------------------------------------------
 *
 * PickCurrentItem --
 *
 *	Finds the topmost item/field that contains the pointer and mark
 *	it has the current item. Generates Enter/leave events on the
 *	old and new current items/fields has necessary.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The current item/field may change.  If it does,
 *	then the commands associated with item entry and exit
 *	could do just about anything.  A binding script could
 *	delete the widget, so callers should protect themselves
 *	with Tcl_Preserve and Tcl_Release.
 *
 * Note:
 *	See the Bind function's note.
 *
 *----------------------------------------------------------------------
 */
static void
PickCurrentItem(ZnWInfo	*wi,
		XEvent	*event)
{
  int	 button_down;
  ZnItem item;
  ZnBool back_inside = False;
  ZnBool grab_release = False;

  /*printf("PickCurrent current=%d, new=%d\n",
	 wi->current_item?wi->current_item->id:0,
	 wi->new_item?wi->new_item->id:0);*/
  /*
   * Check whether or not a button is down.  If so, we'll log entry
   * and exit into and out of the current item, but not entry into
   * any other item.  This implements a form of grabbing equivalent
   * to what the X server does for windows.
   */
  button_down = wi->state
    & (Button1Mask|Button2Mask|Button3Mask|Button4Mask|Button5Mask);
  if (!button_down) {
    CLEAR(wi->flags, ZN_GRABBED_ITEM);
    CLEAR(wi->flags, ZN_GRABBED_PART);
  }
  
  /*
   * Save information about this event in the widget.  The saved event
   * is used for two purposes:
   *
   * 1. Event bindings: if the current item changes, fake events are
   *    generated to allow item-enter and item-leave bindings to trigger.
   * 2. Reselection: if the current item gets deleted, can use the
   *    saved event to find a new current item.
   * Translate MotionNotify events into EnterNotify events, since that's
   * what gets reported to item handlers.
   */
  if (event != &wi->pick_event) {
    if ((event->type == MotionNotify) || (event->type == ButtonRelease)) {
      wi->pick_event.xcrossing.type = EnterNotify;
      wi->pick_event.xcrossing.serial = event->xmotion.serial;
      wi->pick_event.xcrossing.send_event = event->xmotion.send_event;
      wi->pick_event.xcrossing.display = event->xmotion.display;
      wi->pick_event.xcrossing.window = event->xmotion.window;
      wi->pick_event.xcrossing.root = event->xmotion.root;
      wi->pick_event.xcrossing.subwindow = None;
      wi->pick_event.xcrossing.time = event->xmotion.time;
      wi->pick_event.xcrossing.x = event->xmotion.x;
      wi->pick_event.xcrossing.y = event->xmotion.y;
      wi->pick_event.xcrossing.x_root = event->xmotion.x_root;
      wi->pick_event.xcrossing.y_root = event->xmotion.y_root;
      wi->pick_event.xcrossing.mode = NotifyNormal;
      wi->pick_event.xcrossing.detail = NotifyNonlinear;
      wi->pick_event.xcrossing.same_screen = event->xmotion.same_screen;
      wi->pick_event.xcrossing.focus = False;
      wi->pick_event.xcrossing.state = event->xmotion.state;
    }
    else {
      wi->pick_event = *event;
    }
  }

  /*
   * If this is a recursive call (there's already a partially completed
   * call pending on the stack;  it's in the middle of processing a
   * Leave event handler for the old current item) then just return;
   * the pending call will do everything that's needed.
   */
  if (ISSET(wi->flags, ZN_REPICK_IN_PROGRESS)) {
    fprintf(stderr, "PickCurrentItem recursive\n");
    return;
  }

  /*
   * A LeaveNotify event automatically means that there's no current
   * object, so the check for closest item can be skipped.
   */
  item = wi->new_item;
  if (wi->pick_event.type != LeaveNotify) {
    ZnPickStruct ps;
    ZnReal	 dist;
    ZnPoint	 p;
    
    p.x = wi->pick_event.xcrossing.x;
    p.y = wi->pick_event.xcrossing.y;
    ps.point = &p;
    ps.in_group = ZN_NO_ITEM;
    ps.start_item = ZN_NO_ITEM;
    ps.aperture = wi->pick_aperture;
    ps.recursive = True;
    ps.override_atomic = False;
    dist = wi->top_group->class->Pick(wi->top_group, &ps);
    if (dist == 0.0) {
      wi->new_item = ps.a_item;
      wi->new_part = ps.a_part;
    }
    else {
      wi->new_item = ZN_NO_ITEM;
      wi->new_part = ZN_NO_PART;
    }
  }
  else {
    wi->new_item = ZN_NO_ITEM;
    wi->new_part = ZN_NO_PART;
  }
  /*
   * This state is needed to do a valid detection
   * of Enter during a grab.
   */
  back_inside = (wi->new_item != item);

  /*printf("------ PickCurrentItem current: %d %d, new %d %d\n",
	 wi->current_item==ZN_NO_ITEM?0:wi->current_item->id, wi->current_part,
	 wi->new_item==ZN_NO_ITEM?0:wi->new_item->id, wi->new_part);*/

  if ((wi->new_item == wi->current_item) &&
      (wi->new_part == wi->current_part) &&
      ISCLEAR(wi->flags, ZN_GRABBED_ITEM) &&
      ISCLEAR(wi->flags, ZN_GRABBED_PART)) {
    /*
     * Nothing to do: the current item/part hasn't changed.
     */
    return;
  }

  /*
   * Simulate a LeaveNotify event on the previous current item.
   * Remove the "current" tag from the previous current item.
   */
  if ((wi->current_item != ZN_NO_ITEM) &&
      (((wi->new_item != wi->current_item) && ISCLEAR(wi->flags, ZN_GRABBED_ITEM)) ||
       ((wi->new_part != wi->current_part) && ISCLEAR(wi->flags, ZN_GRABBED_PART)))) {
    XEvent event;

    item = wi->current_item;
    event = wi->pick_event;
    event.type = LeaveNotify;
    
    /*printf("== LEAVE %d %d ==\n", wi->current_item->id, wi->current_part);*/
    /*
     * If the event's detail happens to be NotifyInferior the
     * binding mechanism will discard the event.  To be consistent,
     * always use NotifyAncestor.
     */
    event.xcrossing.detail = NotifyAncestor;
    SET(wi->flags, ZN_REPICK_IN_PROGRESS);
    DoEvent(wi, &event,
	    wi->new_item != wi->current_item, ISCLEAR(wi->flags, ZN_GRABBED_PART));
    CLEAR(wi->flags, ZN_REPICK_IN_PROGRESS);
    
    /*
     * The check on item below is needed because there could be an
     * event handler for <LeaveNotify> that deletes the current item.
     */
    if ((item == wi->current_item) && !button_down) {
      /*printf("^^^ Removing 'current' from %d\n", wi->current_item->id);*/
      ZnITEM.RemoveTag(item, current_uid);
    }    
    /*
     * Note:  during DoEvent above, it's possible that
     * wi->new_item got reset to NULL because the
     * item was deleted.
     */
  }

  /*
   * Special note:  it's possible that wi->new_item == wi->current_item
   * here. This can happen, for example, if a grab was set or
   * if there is only a change in the part number.
   */
  if ((wi->new_item != wi->current_item) && button_down) {
    SET(wi->flags, ZN_GRABBED_ITEM);
  }
  else {
    grab_release = ISSET(wi->flags, ZN_GRABBED_ITEM);
    CLEAR(wi->flags, ZN_GRABBED_ITEM);
    wi->current_item = wi->new_item;
  }
  if ((wi->new_part != wi->current_part) && button_down) {
    SET(wi->flags, ZN_GRABBED_PART);
  }
  else {
    CLEAR(wi->flags, ZN_GRABBED_PART);
    wi->current_part = wi->new_part;
  }
  
  if (!grab_release &&
      (ISSET(wi->flags, ZN_GRABBED_PART) || ISSET(wi->flags, ZN_GRABBED_ITEM))) {
    return;
  }
  
  if (wi->current_item != ZN_NO_ITEM) {
    XEvent event;
    /*
     * Add the tag 'current' to the current item under the pointer.
     */
    /*printf("Adding 'current' to %d\n", wi->current_item->id);*/
    ZnDoItem((Tcl_Interp *) NULL, wi->current_item, ZN_NO_PART, current_uid);
    /*
     * Then emit a fake Enter event on it.
     */
    /*printf("== ENTER %d %d ==\n",wi->current_item->id, wi->current_part);*/
    event = wi->pick_event;
    event.type = EnterNotify;
    event.xcrossing.detail = NotifyAncestor;
    DoEvent(wi, &event,
	    back_inside, !(grab_release && ISSET(wi->flags, ZN_GRABBED_PART)));
  }
}


/*
 *----------------------------------------------------------------------
 *
 * Bind --
 *
 *	This procedure is invoked by the Tk dispatcher to handle
 *	events associated with bindings on items.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Depends on the command invoked as part of the binding
 *	(if there was any).
 *
 * Note:
 *	This has been taken as is from the Tk canvas. It might not
 *	not be fully adequate for the purpose. But at least this
 *	provides two benefits: a/ It is believe to be correct and
 *	b/ users are accustomed to its behavior.
 *
 *----------------------------------------------------------------------
 */
static void
Bind(ClientData	client_data,	/* Information about widget. */
     XEvent	*event)		/* Information about event. */
{
  ZnWInfo	*wi = (ZnWInfo *) client_data;

  Tcl_Preserve((ClientData) wi);

  /*
   * This code below keeps track of the current modifier state in
   * wi->state.  This information is used to defer repicks of
   * the current item while buttons are down.
   */
  if ((event->type == ButtonPress) || (event->type == ButtonRelease)) {
    int mask;
    
    switch (event->xbutton.button) {
    case Button1:
      mask = Button1Mask;
      break;
    case Button2:
      mask = Button2Mask;
      break;
    case Button3:
      mask = Button3Mask;
      break;
    case Button4:
      mask = Button4Mask;
      break;
    case Button5:
      mask = Button5Mask;
      break;
    default:
      mask = 0;
      break;
    }    
    /*
     * For button press events, repick the current item using the
     * button state before the event, then process the event.  For
     * button release events, first process the event, then repick
     * the current item using the button state *after* the event
     * (the button has logically gone up before we change the
     * current item).
     */
    
    if (event->type == ButtonPress) {
      /*
       * On a button press, first repick the current item using
       * the button state before the event, the process the event.
       */      
      wi->state = event->xbutton.state;
      PickCurrentItem(wi, event);
      wi->state ^= mask;
      if (wi->current_item != ZN_NO_ITEM) {
	DoEvent(wi, event, True, True);
      }
    }
    else {
      /*
       * Button release: first process the event, with the button
       * still considered to be down.  Then repick the current
       * item under the assumption that the button is no longer down.
       */
      wi->state = event->xbutton.state;
      DoEvent(wi, event, True, True);
      event->xbutton.state ^= mask;
      wi->state = event->xbutton.state;
      PickCurrentItem(wi, event);
      event->xbutton.state ^= mask;
    }
    goto done;
  }
  
  else if ((event->type == EnterNotify) || (event->type == LeaveNotify)) {
#ifdef GL_DAMAGE
    /*
     * Kludge to prevent incorrect redrawing after
     * a move window when using GL. This code force
     * a full window redraw on window enter/leaves
     * hoping it will realign the back buffer soon
     * enough to make the bug unnoticed.
     */
    if (wi->render) {
      /*      ZnDamageAll(wi);*/
    }
#endif
  
    wi->state = event->xcrossing.state;
    PickCurrentItem(wi, event);
    goto done;
  }
  
  else if (event->type == MotionNotify) {
    wi->state = event->xmotion.state;
    if (wi->follow_pointer) {
      PickCurrentItem(wi, event);
    }
    else {
      /* Copy the event for later processing
       * and skip the picking phase.
       */
      wi->pick_event = *event;
    }
  }

  DoEvent(wi, event, True, True);
  
done:
  Tcl_Release((ClientData) wi);
}


/*
 *----------------------------------------------------------------------
 *
 * LostSelection --
 *
 *	This procedure is called back by Tk when the selection is
 *	grabbed away from a zinc widget.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The existing selection is unhighlighted, and the window is
 *	marked as not containing a selection.
 *
 *----------------------------------------------------------------------
 */
static void
LostSelection(ClientData	client_data)
{
  ZnWInfo	*wi = (ZnWInfo *) client_data;
  ZnTextInfo	*ti = &wi->text_info;
  
  if (ti->sel_item != ZN_NO_ITEM) {
    ZnITEM.Invalidate(ti->sel_item, ZN_DRAW_FLAG);
  }
  ti->sel_item = ZN_NO_ITEM;
  ti->sel_field = ZN_NO_PART;
}


/*
 *----------------------------------------------------------------------
 *
 * SelectTo --
 *
 *	Modify the selection by moving its un-anchored end.  This could
 *	make the selection either larger or smaller.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The selection changes.
 *
 *----------------------------------------------------------------------
 */
static void
SelectTo(ZnItem	item,
	 int	field,
	 int	index)
{
  ZnWInfo	*wi = item->wi;
  ZnTextInfo	*ti = &wi->text_info;
  int		old_first, old_last, old_field;
  ZnItem	old_sel_item;

  old_first = ti->sel_first;
  old_last = ti->sel_last;
  old_sel_item = ti->sel_item;
  old_field = ti->sel_field;
  
  /*
   * Grab the selection if we don't own it already.
   */
  if (ti->sel_item == ZN_NO_ITEM) {
    Tk_OwnSelection(wi->win, XA_PRIMARY, LostSelection, (ClientData) wi);
  }
  else if ((ti->sel_item != item) || (ti->sel_field != field)) {
    ZnITEM.Invalidate(ti->sel_item, ZN_DRAW_FLAG);
  }
  ti->sel_item = item;
  ti->sel_field = field;
  
  if ((ti->anchor_item != item) || (ti->anchor_field) != field) {
    ti->anchor_item = item;
    ti->anchor_field = field;
    ti->sel_anchor = index;
  }
  if (ti->sel_anchor <= index) {
    ti->sel_first = ti->sel_anchor;
    ti->sel_last = index;
  }
  else {
    ti->sel_first = index;
    ti->sel_last = ti->sel_anchor;
  }
  if ((ti->sel_first != old_first) ||
      (ti->sel_last != old_last) ||
      (item != old_sel_item)) {
    ZnITEM.Invalidate(item, ZN_DRAW_FLAG);
  }
}


/*
 *--------------------------------------------------------------
 *
 * FetchSelection --
 *
 *	This procedure is invoked by Tk to return part or all of
 *	the selection, when the selection is in a zinc widget.
 *	This procedure always returns the selection as a STRING.
 *
 * Results:
 *	The return value is the number of non-NULL bytes stored
 *	at buffer.  Buffer is filled (or partially filled) with a
 *	NULL-terminated string containing part or all of the selection,
 *	as given by offset and maxBytes.
 *
 * Side effects:
 *	None.
 *
 *--------------------------------------------------------------
 */
static int
FetchSelection( ClientData	client_data,
		int		offset,		/* Offset within selection of first
						 * character to be returned. */
		char		*buffer,	/* Location in which to place
						 * selection. */
		int		max_bytes)	/* Maximum number of bytes to place
						 * at buffer, not including terminating
						 * NULL character. */
{
  ZnWInfo	*wi = (ZnWInfo *) client_data;
  ZnTextInfo	*ti = &wi->text_info;
  
  if (ti->sel_item == ZN_NO_ITEM) {
    return -1;
  }
  if (ti->sel_item->class->Selection == NULL) {
    return -1;
  }
  return (*ti->sel_item->class->Selection)(ti->sel_item, ti->sel_field,
					   offset, buffer, max_bytes);
}


/*
 *----------------------------------------------------------------------
 *
 * CmdDeleted --
 *
 *	This procedure is invoked when a widget command is deleted. If
 *	the widget isn't already in the process of being destroyed,
 *	this command destroys it.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The widget is destroyed.
 *
 *----------------------------------------------------------------------
 */
static void
CmdDeleted(ClientData client_data) /* Pointer to widget record for widget. */
{
  ZnWInfo	*wi = (ZnWInfo *) client_data;

  if (wi->win != NULL) {
    Tk_DestroyWindow(wi->win);
    wi->win = NULL;
  }
}


/*
 *----------------------------------------------------------------------
 *
 * Destroy --
 *
 *	This procedure is invoked by Tk_EventuallyFree or Tk_Release
 *	to clean up the internal structure of the widget at a safe time
 *	(when no-one is using it anymore).
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Everything associated with the widget is freed up.
 *
 *----------------------------------------------------------------------
 */
static void
Destroy(char *mem_ptr)		/* Info about the widget. */
{
  ZnWInfo	*wi = (ZnWInfo *) mem_ptr;
  unsigned int	num;
  Tcl_HashSearch search;
  Tcl_HashEntry	*entry;

  /*printf("Destroy begining\n");*/
  /*
   * This procedure could be invoked either because the window was
   * destroyed and the command was then deleted (in which case win
   * is NULL) or because the command was deleted, and then this procedure
   * destroys the widget.
   */
  /*
   * Unregister form the overlap manager.
   */
#ifdef OM
  if (wi->om_group != ZN_NO_ITEM) {
    OmUnregister((void *) wi);
  }
#endif

  /*
   * Print remaining items.
   */
  
  /* Free all items. */
  /*fprintf(stderr, "Item count before cleanup: %d\n", wi->num_items);*/
  ZnITEM.DestroyItem(wi->top_group);
  /*fprintf(stderr, "Remaining item count: %d\n", wi->num_items);*/
  /*
   * Remove the redisplay scheduled by the cleanup.
   * It will fire when the widget will be gone and
   * will corrupt memory.
   */
  if (ISSET(wi->flags, ZN_UPDATE_PENDING)) {
    Tcl_CancelIdleCall(Redisplay, (ClientData) wi);
  }

#ifdef GL
  if (wi->font_tfi) {
    ZnFreeTexFont(wi->font_tfi);
    wi->font_tfi = NULL;
  }
  if (wi->map_font_tfi) {
    ZnFreeTexFont(wi->map_font_tfi);
    wi->map_font_tfi = NULL;
  }
#endif
  /*
  if (wi->font) {
    Tk_FreeFont(wi->font);
  }
  if (wi->map_text_font) {
    Tk_FreeFont(wi->map_text_font);
  }*/
  
  for (num = 0; num < ZN_NUM_ALPHA_STEPS; num++) {
    if (wi->alpha_stipples[num] != None) {
      Tk_FreeBitmap(wi->dpy, wi->alpha_stipples[num]);
      wi->alpha_stipples[num] = None;
    }
  }

  Tcl_DeleteHashTable(wi->id_table);
  ZnFree(wi->id_table);

  /*
   * Free the transform table contents.
   */
  entry = Tcl_FirstHashEntry(wi->t_table, &search);
  while (entry != NULL) {
    ZnTransfoFree((ZnTransfo *) Tcl_GetHashValue(entry));
    entry = Tcl_NextHashEntry(&search);
  }
  Tcl_DeleteHashTable(wi->t_table);
  ZnFree(wi->t_table);

  if (wi->binding_table != NULL) {
    Tk_DeleteBindingTable(wi->binding_table);
  }

  /* Free the tile */
  if (wi->tile != ZnUnspecifiedImage) {
    ZnFreeImage(wi->tile, ZnImageUpdate, wi);
    wi->tile = ZnUnspecifiedImage;
  }

  /* Free the symbols */
  if (wi->map_distance_symbol != ZnUnspecifiedImage) {
    ZnFreeImage(wi->map_distance_symbol, NULL, NULL);
    wi->map_distance_symbol = ZnUnspecifiedImage;
  }
  if (wi->track_symbol != ZnUnspecifiedImage) {
    ZnFreeImage(wi->track_symbol, NULL, NULL);
    wi->track_symbol = ZnUnspecifiedImage;
  }

  /* Free the double buffer pixmap/image */
  if (wi->draw_buffer) {
    Tk_FreePixmap(wi->dpy, wi->draw_buffer);
    wi->draw_buffer = 0;
  }

  if (wi->fore_color) {
    ZnFreeGradient(wi->fore_color);
    wi->fore_color = NULL;
  }
  if (wi->back_color) {
    ZnFreeGradient(wi->back_color);
    wi->back_color = NULL;
  }
  if (wi->relief_grad) {
    ZnFreeGradient(wi->relief_grad);
    wi->relief_grad = NULL;
  }
  if (wi->gc) {
    XFreeGC(wi->dpy, wi->gc);
    wi->gc = 0;
  }

  Tcl_DeleteTimerHandler(wi->blink_handler);
  
  Tk_FreeOptions(config_specs, (char *) wi, wi->dpy, 0);

  /*
   * Should be empty by now.
   */
  ZnFreeTransformStack(wi);
  ZnFreeClipStack(wi);

  ZnListFree(wi->work_pts);
#ifdef GL
  ZnListFree(wi->work_doubles);
#endif
  ZnListFree(wi->work_xpts);
  ZnListFree(wi->work_strs);

#ifndef _WIN32
  ZnFreeChrono(wi->total_draw_chrono);
  ZnFreeChrono(wi->this_draw_chrono);
#endif

  if (wi->tess) {
    gluDeleteTess(wi->tess);
  }

  ZnFree(wi);
  /*printf("Destroy ending\n");*/
}


/*
 **********************************************************************************
 *
 * ZnDamage --
 *
 **********************************************************************************
 */
void
ZnDamage(ZnWInfo	*wi,
	 ZnBBox		*damage)
{
  if ((damage == NULL) || ZnIsEmptyBBox(damage)) {
    return;
  }
  
  /*printf("damaging area: %g %g %g %g\n", damage->orig.x,
    damage->orig.y, damage->corner.x, damage->corner.y);*/

  if (ZnIsEmptyBBox(&wi->damaged_area)) {
    wi->damaged_area.orig.x = damage->orig.x;
    wi->damaged_area.orig.y = damage->orig.y;
    wi->damaged_area.corner.x = damage->corner.x;
    wi->damaged_area.corner.y = damage->corner.y;
    ZnNeedRedisplay(wi);
  }
  else {
    wi->damaged_area.orig.x = MIN(wi->damaged_area.orig.x, damage->orig.x);
    wi->damaged_area.orig.y = MIN(wi->damaged_area.orig.y, damage->orig.y);
    wi->damaged_area.corner.x = MAX(wi->damaged_area.corner.x, damage->corner.x);
    wi->damaged_area.corner.y = MAX(wi->damaged_area.corner.y, damage->corner.y);
  }
  /*printf("damaged area: %g %g %g %g\n", wi->damaged_area.orig.x,
	 wi->damaged_area.orig.y, wi->damaged_area.corner.x,
	 wi->damaged_area.corner.y);*/
}

void
ZnDamageAll(ZnWInfo *wi)
{
  ZnBBox bbox;

  bbox.orig.x = bbox.orig.y = 0;
  bbox.corner.x = Tk_Width(wi->win);
  bbox.corner.y = Tk_Height(wi->win);
  ZnDamage(wi, &bbox);
}

static void
ClampDamageArea(ZnWInfo	*wi)
{
  int	width, height;

  if (wi->damaged_area.orig.x < wi->inset) {
    wi->damaged_area.orig.x = wi->inset;
  }
  if (wi->damaged_area.orig.y < wi->inset) {
    wi->damaged_area.orig.y = wi->inset;
  }
  if (wi->damaged_area.corner.x < wi->inset) {
    wi->damaged_area.corner.x = wi->inset;
  }
  if (wi->damaged_area.corner.y < wi->inset) {
    wi->damaged_area.corner.y = wi->inset;
  }
  width = wi->width - wi->inset;
  height = wi->height - wi->inset;
  if (wi->damaged_area.orig.x > width) {
    wi->damaged_area.orig.x = width;
  }
  if (wi->damaged_area.orig.y > height) {
    wi->damaged_area.orig.y = height;
  }
  if (wi->damaged_area.corner.x > width) {
    wi->damaged_area.corner.x = width;
  }
  if (wi->damaged_area.corner.y > height) {
    wi->damaged_area.corner.y = height;
  }
}


/*
 **********************************************************************************
 *
 * Update --
 *
 **********************************************************************************
 */
static void
Update(ZnWInfo	*wi)
{
  /*
   * Give the overlap manager a chance to do its work.
   */
#ifdef OM
  if ((wi->om_group != ZN_NO_ITEM) && ZnGroupCallOm(wi->om_group)) {  
    ZnPoint scale={1.0,1.0};
    if (wi->om_group->transfo) {
      ZnTransfoDecompose(wi->om_group->transfo, &scale,
			    NULL, NULL, NULL);
    }
    OmProcessOverlap((void *) wi, wi->width, wi->height, scale.x);
    ZnGroupSetCallOm(wi->om_group, False);
  }
#endif

  if (ISSET(wi->top_group->inv_flags, ZN_COORDS_FLAG) ||
      ISSET(wi->top_group->inv_flags, ZN_TRANSFO_FLAG)) {
    wi->top_group->class->ComputeCoordinates(wi->top_group, False);
  }
}


/*
 **********************************************************************************
 *
 * Repair --
 *
 **********************************************************************************
 */
static void
Repair(ZnWInfo	*wi)
{
  XGCValues	values;
  ZnPoint	p[5];
  ZnTriStrip	tristrip;
#ifdef GL
  XColor	*color;
#endif
  int		int_width = Tk_Width(wi->win);
  int		int_height = Tk_Height(wi->win);

  if (wi->render) {
#ifdef GL
#ifdef GL_DAMAGE
    ClampDamageArea(wi);
    /*
     * Merge the exposed area.
     */
    ZnAddBBoxToBBox(&wi->damaged_area, &wi->exposed_area);
    if (ZnIsEmptyBBox(&wi->damaged_area)) {
      return;
    }
#endif

    ZnGLMakeCurrent(wi->dpy, wi->win);
    glEnable(GL_POINT_SMOOTH);
    glEnable(GL_LINE_SMOOTH);
#if 0
    glEnable(GL_POLYGON_SMOOTH);   /*  expensive ? */
#endif

    glEnable(GL_BLEND);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    glClearStencil(0);    
    color = ZnGetGradientColor(wi->back_color, 0.0, NULL);
    glClearColor((GLfloat) color->red/65536, (GLfloat) color->green/65536,
		 (GLfloat) color->blue/65536, 0.0);

    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

    /*
     * Init the composite group alpha.
     */
    wi->alpha = 100;
    
    glViewport(0, 0, (GLsizei) int_width, int_height);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0.0, (GLfloat) int_width, (GLfloat) int_height, 0.0, -1.0, 1.0);
    glMatrixMode(GL_MODELVIEW);
    
#ifdef GL_DAMAGE
    glEnable(GL_SCISSOR_TEST);

    /*
     * Set the damaged area as the scissor area.
     */
    wi->damaged_area.orig.x = ZnNearestInt(wi->damaged_area.orig.x);
    wi->damaged_area.orig.y = ZnNearestInt(wi->damaged_area.orig.y);
    wi->damaged_area.corner.x = ZnNearestInt(wi->damaged_area.corner.x);
    wi->damaged_area.corner.y = ZnNearestInt(wi->damaged_area.corner.y);
    glScissor((int) wi->damaged_area.orig.x,
	      int_height - (int) wi->damaged_area.corner.y,
	      (int) (wi->damaged_area.corner.x - wi->damaged_area.orig.x),
	      (int) (wi->damaged_area.corner.y - wi->damaged_area.orig.y));
    /*printf("Repair : limiting to: %g %g %g %g\n",
	   wi->damaged_area.orig.x, wi->damaged_area.orig.y,
	   wi->damaged_area.corner.x, wi->damaged_area.corner.y);*/
#else
    /*
     * We do not use the damaged area for GL rendering,
     * set it to the whole area.
     */
    wi->damaged_area.orig.x = wi->damaged_area.orig.y = wi->inset;
    wi->damaged_area.corner.x = int_width-wi->inset;
    wi->damaged_area.corner.y = int_height-wi->inset;
#endif
    
    /*
     * Clear the GL buffers.
     */
    glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    /*
     * Setup the background tile if needed.
     */
    if (wi->tile != ZnUnspecifiedImage) {
      ZnBBox bbox;
      
      bbox.orig.x = bbox.orig.y = 0.0;
      bbox.corner.x = int_width;
      bbox.corner.y = int_height;
      
      ZnRenderTile(wi, wi->tile, NULL, NULL, NULL, (ZnPoint *) &bbox);
    }

    wi->top_group->class->Render(wi->top_group);

    if ((wi->border_width > 0) || (wi->highlight_width > 0)) {
      unsigned short alpha;

#ifdef GL_DAMAGE
      glDisable(GL_SCISSOR_TEST);
#endif
      if (wi->highlight_width > 0) {
	color = ZnGetGradientColor(ISSET(wi->flags, ZN_GOT_FOCUS)?wi->highlight_color:
				   wi->highlight_bg_color, 0.0, &alpha);
	alpha = ZnComposeAlpha(alpha, 100);
	glColor4us(color->red, color->green, color->blue, alpha);
	
	glBegin(GL_QUAD_STRIP);
	glVertex2d(0.0, 0.0);
	glVertex2i(wi->highlight_width, wi->highlight_width);
	glVertex2i(int_width, 0);
	glVertex2i(int_width - wi->highlight_width, wi->highlight_width);
	glVertex2i(int_width, int_height);
	glVertex2i(int_width - wi->highlight_width, int_height - wi->highlight_width);
	glVertex2i(0, int_height);
	glVertex2i(wi->highlight_width, int_height - wi->highlight_width);
	glVertex2i(0, 0);
	glVertex2i(wi->highlight_width, wi->highlight_width);
	glEnd();
      }
      if (wi->border_width > 0) {
	if (wi->relief != ZN_RELIEF_FLAT) {
	  p[4].x = p[4].y = p[3].y = p[1].x = wi->highlight_width;
	  p[0] = p[4];
	  p[3].x = p[2].x = int_width - wi->highlight_width;
	  p[2].y = p[1].y = int_height - wi->highlight_width;
	  ZnRenderPolygonRelief(wi, wi->relief, wi->relief_grad,
				False, p, 5, (ZnReal) wi->border_width);
	}
	else {
	  color = ZnGetGradientColor(wi->back_color, 0.0, &alpha);
	  alpha = ZnComposeAlpha(alpha, 100);
	  glColor4us(color->red, color->green, color->blue, alpha);
	  
	  glBegin(GL_QUAD_STRIP);
	  glVertex2d(0.0, 0.0);
	  glVertex2i(wi->highlight_width, wi->highlight_width);
	  glVertex2i(int_width, 0);
	  glVertex2i(int_width - wi->highlight_width, wi->highlight_width);
	  glVertex2i(int_width, int_height);
	  glVertex2i(int_width - wi->highlight_width, int_height - wi->highlight_width);
	  glVertex2i(0, int_height);
	  glVertex2i(wi->highlight_width, int_height - wi->highlight_width);
	  glVertex2i(0, 0);
	  glVertex2i(wi->highlight_width, wi->highlight_width);
	  glEnd();
	}
      }
#ifdef GL_DAMAGE
      glEnable(GL_SCISSOR_TEST);
#endif
    }

    glFlush();
    /* Switch the GL buffers. */
    ZnGLSwapBuffers(wi->dpy, wi->win);
    /*ZnGLRelease(wi);*/

    /*
     * Wait the end of GL update if we need to synchronize
     * to monitor perfs.
     */
    if (ISSET(wi->flags, ZN_MONITORING)) {
      ZnGLWaitGL();
    }
#endif
  }
  else {
    XRectangle	r, rs[4];
    ZnBBox	merge;

    ClampDamageArea(wi);
    /*
     * Merge the damaged area with the exposed area.
     */
    ZnResetBBox(&merge);
    ZnCopyBBox(&wi->damaged_area, &merge);
    ZnAddBBoxToBBox(&merge, &wi->exposed_area);
    if (!ZnIsEmptyBBox(&merge)) {
      
      /* Set the whole damaged area as clip rect. */
      wi->damaged_area.orig.x = r.x = ZnNearestInt(wi->damaged_area.orig.x);
      wi->damaged_area.orig.y = r.y = ZnNearestInt(wi->damaged_area.orig.y);
      wi->damaged_area.corner.x = ZnNearestInt(wi->damaged_area.corner.x);
      wi->damaged_area.corner.y = ZnNearestInt(wi->damaged_area.corner.y);
      r.width = (unsigned short) (wi->damaged_area.corner.x - wi->damaged_area.orig.x);
      r.height = (unsigned short) (wi->damaged_area.corner.y - wi->damaged_area.orig.y);
      p[0] = wi->damaged_area.orig;
      p[1] = wi->damaged_area.corner;
      ZnTriStrip1(&tristrip, p, 2, False);
      ZnPushClip(wi, &tristrip, True, True);
      
      /* Fill the background of the double buffer pixmap. */
      if (wi->tile == ZnUnspecifiedImage) {
	values.foreground = ZnGetGradientPixel(wi->back_color, 0.0);
	values.fill_style = FillSolid;
	XChangeGC(wi->dpy, wi->gc, GCFillStyle|GCForeground, &values);
      }
      else {
	values.fill_style = FillTiled;
	values.tile = ZnImagePixmap(wi->tile, wi->win);
	values.ts_x_origin = values.ts_y_origin = 0;
	XChangeGC(wi->dpy, wi->gc,
		  GCFillStyle|GCTile|GCTileStipXOrigin|GCTileStipYOrigin,
		  &values);
      }
      XFillRectangle(wi->dpy, wi->draw_buffer, wi->gc, r.x, r.y, r.width, r.height);
      
      /* Draw the items */
      /*printf("Drawing\n");*/
      wi->top_group->class->Draw(wi->top_group);
      
      ZnPopClip(wi, True);
      
      /*
       * Send the merged area back to screen.
       */
      merge.orig.x = MAX(merge.orig.x, wi->inset);
      merge.orig.y = MAX(merge.orig.y, wi->inset);
      merge.corner.x = MIN(merge.corner.x, int_width-wi->inset);
      merge.corner.y = MIN(merge.corner.y, int_height-wi->inset);
      ZnBBox2XRect(&merge, &r);
      XCopyArea(wi->dpy,
		wi->draw_buffer, Tk_WindowId(wi->win), wi->gc,
		r.x, r.y, r.width, r.height, r.x, r.y);
    }
    
    /*
     * Redraw the borders.
     */
    if (wi->border_width > 0) {
      Pixmap      save;
      
      save = wi->draw_buffer;
      wi->draw_buffer = Tk_WindowId(wi->win);
      if (wi->relief_grad != ZN_RELIEF_FLAT) {
	r.x = r.y = wi->highlight_width;
	r.width = int_width - 2*wi->highlight_width;
	r.height = int_height - 2*wi->highlight_width;
	ZnDrawRectangleRelief(wi, wi->relief, wi->relief_grad, &r,
			      (ZnDim) wi->border_width);
      }
      else {
	XSetForeground(wi->dpy, wi->gc, ZnGetGradientPixel(wi->back_color, 0.0));
	XSetFillStyle(wi->dpy, wi->gc, FillSolid);
	rs[0].x = rs[0].y = wi->highlight_width;
	rs[0].width = int_width - 2*wi->highlight_width;
	rs[0].height = wi->border_width;
	rs[1].x = int_width - wi->highlight_width - wi->border_width;
	rs[1].y = 0;
	rs[1].width = wi->border_width;
	rs[1].height = int_height - 2*wi->highlight_width;
	rs[2].x = 0;
	rs[2].y = int_height - wi->highlight_width - wi->border_width;
	rs[2].width = rs[0].width;
	rs[2].height = wi->border_width;
	rs[3].x = rs[3].y = wi->highlight_width;
	rs[3].width = wi->border_width;
	rs[3].height = rs[1].height;
	XFillRectangles(wi->dpy, Tk_WindowId(wi->win), wi->gc, rs, 4);
      }
      wi->draw_buffer = save;
    }
    if (wi->highlight_width > 0) {
      XSetForeground(wi->dpy, wi->gc,
		     ZnGetGradientPixel(ISSET(wi->flags, ZN_GOT_FOCUS)?wi->highlight_color:
					wi->highlight_bg_color, 0.0));
      XSetFillStyle(wi->dpy, wi->gc, FillSolid);
      rs[0].x = rs[0].y = 0;
      rs[0].width = int_width;
      rs[0].height = wi->highlight_width;
      rs[1].x = int_width - wi->highlight_width;
      rs[1].y = 0;
      rs[1].width = wi->highlight_width;
      rs[1].height = int_height;
      rs[2].x = 0;
      rs[2].y = int_height - wi->highlight_width;
      rs[2].width = int_width;
      rs[2].height = wi->highlight_width;
      rs[3].x = rs[3].y = 0;
      rs[3].width = wi->highlight_width;
      rs[3].height = int_height;
      XFillRectangles(wi->dpy, Tk_WindowId(wi->win), wi->gc, rs, 4);
    }
  }
}


/*
 *----------------------------------------------------------------------
 *
 * Redisplay --
 *
 *	This procedure redraws the contents of a Zinc window.
 *	It is invoked as a do-when-idle handler, so it only runs
 *	when there's nothing else for the application to do.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Information appears on the screen.
 *
 *----------------------------------------------------------------------
 */

static void
Redisplay(ClientData client_data)	/* Information about the widget. */
{
  ZnWInfo	*wi = (ZnWInfo *) client_data;
  
  CLEAR(wi->flags, ZN_UPDATE_PENDING);
  if (ISCLEAR(wi->flags, ZN_REALIZED) || !Tk_IsMapped(wi->win)) {
    return;
  }

  if (ISSET(wi->flags, ZN_MONITORING)) {
#ifndef _WIN32
    ZnXStartChrono(wi->total_draw_chrono, wi->dpy, Tk_WindowId(wi->win));
    ZnResetChronos(wi->this_draw_chrono);
    ZnXStartChrono(wi->this_draw_chrono, wi->dpy, Tk_WindowId(wi->win));
#endif
  }
  
  do {
    /*
     * Update the items.
     */
    Update(wi);
    
    /*
     * Do enter/leave processing after the overlap manager
     * has finished with the items. Do it has many times
     * as needed, each round may trigger callbacks that
     * result in moved items and so forth. It can even
     * lead to the widget destruction, this is the reason
     * for Tcl_Preserve/Tcl_Release.
     */
    if (ISSET(wi->flags, ZN_INTERNAL_NEED_REPICK)) {
      Tk_Window	tkwin;

      if (wi->follow_pointer) {
	Tcl_Preserve((ClientData) wi);
	CLEAR(wi->flags, ZN_INTERNAL_NEED_REPICK);
	PickCurrentItem(wi, &wi->pick_event);
	tkwin = wi->win;
	Tcl_Release((ClientData) wi);
	if (tkwin == NULL) {
	  return;
	}
      }
      else if (ISCLEAR(wi->top_group->inv_flags, ZN_COORDS_FLAG) &&
	       ISCLEAR(wi->top_group->inv_flags, ZN_TRANSFO_FLAG)) {
	/* Don't repick now but escape the loop if
	 * the geometry is updated. */
	break;
      }
    }
  }
  while (ISSET(wi->top_group->inv_flags, ZN_COORDS_FLAG) ||
	 ISSET(wi->top_group->inv_flags, ZN_TRANSFO_FLAG) ||
	 ISSET(wi->flags, ZN_INTERNAL_NEED_REPICK));
  
  /*
   * Repair the scene where it is no longer up to date,
   * then send the merged area back to the screen.
   */
  Repair(wi);
  
  /*
   * Reset the exposed & damaged areas.
   */
  ZnResetBBox(&wi->exposed_area);
  ZnResetBBox(&wi->damaged_area);

  if (ISSET(wi->flags, ZN_MONITORING)) {
#ifndef _WIN32
    ZnXStopChrono(wi->total_draw_chrono, wi->dpy, Tk_WindowId(wi->win));
    ZnXStopChrono(wi->this_draw_chrono, wi->dpy, Tk_WindowId(wi->win));
#endif
  }

  if (ISSET(wi->flags, ZN_UPDATE_SCROLLBARS)) {
    UpdateScrollbars(wi);
  }
}


static void
InitZinc(Tcl_Interp *interp) {
  static ZnBool	inited = False;
  unsigned int	i, x, y, bit;
  char		name[TCL_INTEGER_SPACE + 20];
  
  if (inited) {
    return;
  }
  
  /*
   * Add the specific bitmaps.
   */
  for (i = 0; i < sizeof(SYMBOLS_BITS)/(SYMBOL_WIDTH*SYMBOL_HEIGHT/8); i++) {
    sprintf(name, "AtcSymbol%d", i+1);
    Tk_DefineBitmap(interp, Tk_GetUid(name),
		    SYMBOLS_BITS[i], SYMBOL_WIDTH, SYMBOL_HEIGHT);
  }
  
  for (i = 0; i < ZN_NUM_ALPHA_STEPS; i++) {
    for (y = 0; y < 4; y++) {
      bitmaps[i][y][0] = 0;
      for (x = 0; x < 4; x++) {
	/*
	 * Use the dither4x4 matrix to determine if this bit is on
	 */
	bit = (i >= dither4x4[y][x]) ? 1 : 0;
	/*
	 * set the bit in the array used to make the X Bitmap
	 * mirror the pattern in x & y to make an 8x8 bitmap.
	 */
	if (bit) {
	  bitmaps[i][y][0] |= (1 << x);
	  bitmaps[i][y][0] |= (1 << (4 + x));
	} 
      }
      bitmaps[i][y][1] = bitmaps[i][y][2] = bitmaps[i][y][3] = bitmaps[i][y][0];
      bitmaps[i][y+4][0] = bitmaps[i][y+4][1] = bitmaps[i][y][0];
      bitmaps[i][y+4][2] = bitmaps[i][y+4][3] = bitmaps[i][y][0];
      bitmaps[i][y+8][0] = bitmaps[i][y+8][1] = bitmaps[i][y][0];
      bitmaps[i][y+8][2] = bitmaps[i][y+8][3] = bitmaps[i][y][0];
      bitmaps[i][y+12][0] = bitmaps[i][y+12][1] = bitmaps[i][y][0];
      bitmaps[i][y+12][2] = bitmaps[i][y+12][3] = bitmaps[i][y][0];
      bitmaps[i][y+16][0] = bitmaps[i][y+16][1] = bitmaps[i][y][0];
      bitmaps[i][y+16][2] = bitmaps[i][y+16][3] = bitmaps[i][y][0];
      bitmaps[i][y+20][0] = bitmaps[i][y+20][1] = bitmaps[i][y][0];
      bitmaps[i][y+20][2] = bitmaps[i][y+20][3] = bitmaps[i][y][0];
      bitmaps[i][y+24][0] = bitmaps[i][y+24][1] = bitmaps[i][y][0];
      bitmaps[i][y+24][2] = bitmaps[i][y+24][3] = bitmaps[i][y][0];
      bitmaps[i][y+28][0] = bitmaps[i][y+28][1] = bitmaps[i][y][0];
      bitmaps[i][y+28][2] = bitmaps[i][y+28][3] = bitmaps[i][y][0];
    }
    sprintf(name, "AlphaStipple%d", i);
    Tk_DefineBitmap(interp, Tk_GetUid(name), (char *) bitmaps[i], 32, 32);
  }
  
  /*
   * Initialize the item module.
   */
  ZnItemInit();
  
  all_uid = Tk_GetUid("all");
  current_uid = Tk_GetUid("current");  
  and_uid = Tk_GetUid("&&");
  or_uid = Tk_GetUid("||");
  xor_uid = Tk_GetUid("^");
  paren_uid = Tk_GetUid("(");
  end_paren_uid = Tk_GetUid(")");
  neg_paren_uid = Tk_GetUid("!(");
  tag_val_uid = Tk_GetUid("!!");
  neg_tag_val_uid = Tk_GetUid("!");
  dot_uid = Tk_GetUid(".");
  star_uid = Tk_GetUid("*");

  /*
   * Initialise Overlap manager library.
   */
#ifdef OM
  OmInit();
#endif

  inited = True;
}

#ifdef BUILD_Tkzinc
#   undef TCL_STORAGE_CLASS
#   define TCL_STORAGE_CLASS DLLEXPORT
#endif

/*
 *----------------------------------------------------------------------
 *
 * Tkzinc_Init --
 *
 *	This procedure is invoked by Tcl_AppInit in tkAppInit.c to
 *	initialize the widget.
 *
 *----------------------------------------------------------------------
 */
EXTERN int
Tkzinc_Init(Tcl_Interp *interp)	/* Used for error reporting. */
{
#ifndef PTK
  if (
# ifdef USE_TCL_STUBS
      Tcl_InitStubs(interp, "8.4", 0)
# else
      Tcl_PkgRequire(interp, "Tcl", "8.4", 0)
# endif
      == NULL) {
    return TCL_ERROR;
  }

  if (
# ifdef USE_TK_STUBS
      Tk_InitStubs(interp, "8.4", 0)
# else
      Tcl_PkgRequire(interp, "Tk", "8.4", 0)
# endif
      == NULL) {
    return TCL_ERROR;
  }
#endif
  /*
   * Create additional commands
   */
  Tcl_CreateObjCommand(interp, "zinc", ZincObjCmd,
		       (ClientData) Tk_MainWindow(interp),
		       (Tcl_CmdDeleteProc *) NULL);
  Tcl_CreateObjCommand(interp, "mapinfo", ZnMapInfoObjCmd,
		       (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
  Tcl_CreateObjCommand(interp, "videomap", ZnVideomapObjCmd,
		       (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);

    
#ifndef PTK
  if (Tcl_PkgProvide(interp, "Tkzinc", VERSION) == TCL_ERROR) {
    return TCL_ERROR;
  }
#endif

  return TCL_OK;
}

EXTERN int
Tkzinc_debug_Init(Tcl_Interp *interp)	/* Used for error reporting. */
{
  return Tkzinc_Init(interp);
}

#ifdef _WIN32
/*
 *----------------------------------------------------------------------
 *
 * DllEntryPoint --
 *
 *	This wrapper function is used by Windows to invoke the
 *	initialization code for the DLL.  If we are compiling
 *	with Visual C++, this routine will be renamed to DllMain.
 *	routine.
 *
 * Results:
 *	Returns TRUE;
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */
BOOL APIENTRY
DllEntryPoint(HINSTANCE	hInst __unused,	   /* Library instance handle. */
	      DWORD	reason __unused,   /* Reason this function is being called. */
	      LPVOID	reserved __unused) /* Not used. */
{
    return TRUE;
}
#endif
