/*
 * Field.c -- Implementation of fields.
 *
 * Authors              : Patrick Lecoanet.
 * Creation date        : 
 *
 * $Id: Field.c,v 1.33 2005/10/18 09:32:23 lecoanet Exp $
 */

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


#include "Item.h"
#include "Types.h"
#include "WidgetInfo.h"
#include "Draw.h"
#include "Geo.h"
#include "tkZinc.h"

#include <string.h>
#include <stdlib.h>


static const char rcsid[] = "$Id: Field.c,v 1.33 2005/10/18 09:32:23 lecoanet Exp $";
static const char compile_id[]="$Compile: " __FILE__ " " __DATE__ " " __TIME__ " $";


#define FIELD_SENSITIVE_BIT     1
#define FIELD_VISIBLE_BIT       2
#define FILLED_BIT              4
#define TEXT_ON_TOP_BIT         8
#define CACHE_OK                16


/*
 * Field record.
 */
typedef struct _FieldStruct {
  /* Public data */
  ZnGradient    *color;
  ZnGradient    *fill_color;
  ZnGradient    *border_color;
  char          *text;
  ZnImage       image;
  ZnImage       tile;
  Tk_Font       font;
  unsigned short flags;
  ZnBorder      border_edges;
  Tk_Justify    alignment;
  ZnReliefStyle relief;
  ZnDim         relief_thickness;
  ZnAutoAlign   auto_alignment;
  
  /* Private data */
  ZnGradient    *gradient;
  ZnPoint       *grad_geo;
  short         orig_x;
  short         orig_y;
  short         corner_x;
  short         corner_y;
  int           insert_index;
#ifdef GL
  ZnTexFontInfo *tfi;
#endif
} FieldStruct, *Field;


/*
 * The -text, -image, -border, -relief, -visible and
 * -filled attributes set the ZN_COORDS_FLAG to update
 * the leader that might protude if not clipped by the text.
 */
static ZnAttrConfig field_attrs[] = {
  { ZN_CONFIG_ALIGNMENT, "-alignment", NULL,
    Tk_Offset(FieldStruct, alignment), 0, ZN_DRAW_FLAG, False },
  { ZN_CONFIG_AUTO_ALIGNMENT, "-autoalignment", NULL,
    Tk_Offset(FieldStruct, auto_alignment), 0, ZN_DRAW_FLAG, False },
  { ZN_CONFIG_GRADIENT, "-backcolor", NULL,
    Tk_Offset(FieldStruct, fill_color), 0,
    ZN_DRAW_FLAG|ZN_BORDER_FLAG, False },
  { ZN_CONFIG_EDGE_LIST, "-border", NULL,
    Tk_Offset(FieldStruct, border_edges), 0, ZN_DRAW_FLAG, False },
  { ZN_CONFIG_GRADIENT, "-bordercolor", NULL,
    Tk_Offset(FieldStruct, border_color), 0, ZN_DRAW_FLAG, False },
  { ZN_CONFIG_GRADIENT, "-color", NULL,
    Tk_Offset(FieldStruct, color), 0, ZN_DRAW_FLAG, False },
  { ZN_CONFIG_BOOL, "-filled", NULL,
    Tk_Offset(FieldStruct, flags), FILLED_BIT, ZN_COORDS_FLAG, False },
  { ZN_CONFIG_BITMAP, "-fillpattern", NULL,
    Tk_Offset(FieldStruct, tile), 0, ZN_DRAW_FLAG, False },
  { ZN_CONFIG_FONT, "-font", NULL,
    Tk_Offset(FieldStruct, font), 0, ZN_COORDS_FLAG|ZN_CLFC_FLAG, False },
  { ZN_CONFIG_IMAGE, "-image", NULL,
    Tk_Offset(FieldStruct, image), 0,
    ZN_COORDS_FLAG|ZN_CLFC_FLAG, False },
  { ZN_CONFIG_RELIEF, "-relief", NULL,
    Tk_Offset(FieldStruct, relief), 0, ZN_DRAW_FLAG, False },
  { ZN_CONFIG_DIM, "-reliefthickness", NULL,
    Tk_Offset(FieldStruct, relief_thickness), 0, ZN_DRAW_FLAG, False },
  { ZN_CONFIG_BOOL, "-sensitive", NULL,
    Tk_Offset(FieldStruct, flags),
    FIELD_SENSITIVE_BIT, ZN_REPICK_FLAG, False },
  { ZN_CONFIG_STRING, "-text", NULL,
    Tk_Offset(FieldStruct, text), 0, ZN_COORDS_FLAG|ZN_CLFC_FLAG, False },
  { ZN_CONFIG_IMAGE, "-tile", NULL,
    Tk_Offset(FieldStruct, tile), 0, ZN_COORDS_FLAG, False },
  { ZN_CONFIG_BOOL, "-visible", NULL,
    Tk_Offset(FieldStruct, flags), FIELD_VISIBLE_BIT, 
    ZN_COORDS_FLAG|ZN_CLFC_FLAG, False }, /* Keep ZN_COORDS_FLAG here */

  { ZN_CONFIG_END, NULL, NULL, 0, 0, 0, False }
};

static  void GetLabelBBox(ZnFieldSet field_set, ZnDim *w, ZnDim *h);



/*
 **********************************************************************************
 *
 * ComputeFieldAttachment --
 *      Compute the location/size of the field, computing attachments if any.
 * 
 **********************************************************************************
 */
static void
ComputeFieldAttachment(ZnFieldSet       field_set,
                       unsigned int     field,
                       ZnBBox           *field_bbox)
{
  
  ZnBBox        ref_bbox;
  ZnDim         real_width, real_height;
  unsigned int  ref_field, num_fields;
  char          x_attach, y_attach, x_dim, y_dim;
  short         width_spec, height_spec;
  int           x_spec, y_spec, icon_width=0, icon_height=0;
  Field         fptr;
  Tk_FontMetrics fm;

  /*printf("ComputeFieldAttachment in\n");*/
  fptr = &field_set->fields[field];
  if (ISSET(fptr->flags, CACHE_OK)) {
    field_bbox->orig.x = (ZnPos) fptr->orig_x;
    field_bbox->orig.y = (ZnPos) fptr->orig_y;
    field_bbox->corner.x = fptr->corner_x;
    field_bbox->corner.y = fptr->corner_y;
    /*printf("ComputeFieldAttachment in cache\n");*/
    return;
  }

  /*
   * Preset this field to a default position/size and pretend
   * its cache is ok to break any deadlocks.
   */
  fptr->orig_x = fptr->orig_y = 0;
  fptr->corner_x = fptr->corner_y = 0;
  field_bbox->orig.x = field_bbox->orig.y = 0;
  field_bbox->corner.x = field_bbox->corner.y = 0;
  SET(fptr->flags, CACHE_OK);

  num_fields = ZnLFNumFields(field_set->label_format);
  ZnLFGetField(field_set->label_format, field,
               &x_attach, &y_attach, &x_dim, &y_dim,
               &x_spec, &y_spec, &width_spec, &height_spec);
  
  /*
   * First try to compute the field size which may be a factor
   * of the field content (but not a factor of other fields).
   */
  if ((fptr->image != ZnUnspecifiedImage) &&
      ((x_dim == ZN_LF_DIM_ICON) || (y_dim == ZN_LF_DIM_ICON) ||
       (x_dim == ZN_LF_DIM_AUTO) || (y_dim == ZN_LF_DIM_AUTO))) {
    ZnSizeOfImage(fptr->image, &icon_width, &icon_height);
  }

  switch (x_dim) {
  case ZN_LF_DIM_FONT:
    real_width = (ZnDim) (width_spec*Tk_TextWidth(fptr->font, "N", 1)/100);
    break;
  case ZN_LF_DIM_ICON:
    real_width = (ZnDim) (width_spec*icon_width/100);
    break;
  case ZN_LF_DIM_AUTO:
    {
      int       len = 0;
      ZnDim     text_width;

      if (fptr->text) {
        len = strlen(fptr->text);
      }
      real_width = 0.0;
      if (fptr->image != ZnUnspecifiedImage) {
        real_width = (ZnDim) icon_width;
      }
      if (len) {
        /*
         * The 4 extra pixels are needed for border and padding.
         */
        text_width = (ZnDim) Tk_TextWidth(fptr->font, fptr->text, len) + 4;
        real_width = text_width < real_width ? real_width : text_width;
      }
      real_width += (ZnDim) width_spec;
      if (real_width < 0) {
        real_width = 0;
      }
      break;
    }
  case ZN_LF_DIM_LABEL:
    {
      ZnDim     lh;

      GetLabelBBox(field_set, &real_width, &lh);
      break;
    }
  case ZN_LF_DIM_PIXEL:
  default:
    real_width = (ZnDim) width_spec;
    break;
  }
  /*printf("field %d, width = %g\n", field, real_width);*/

  switch (y_dim) {
  case ZN_LF_DIM_FONT:
    {
      Tk_GetFontMetrics(fptr->font, &fm);
      real_height = (ZnDim) (height_spec*(fm.ascent + fm.descent)/100);
      break;
    }
  case ZN_LF_DIM_ICON:
    real_height = (ZnDim) (height_spec*icon_height/100);
    break;
  case ZN_LF_DIM_AUTO:
    {
      ZnDim     text_height;
      
      real_height = 0.0;
      if (fptr->image != ZnUnspecifiedImage) {
        real_height = (ZnDim) icon_height;
      }
      if (fptr->text && strlen(fptr->text)) {
        Tk_GetFontMetrics(fptr->font, &fm);
        text_height = (ZnDim) (fm.ascent + fm.descent);
        real_height = text_height < real_height ? real_height : text_height;
      }
      real_height += (ZnDim) height_spec;
      if (real_height < 0) {
        real_height = 0;
      }
      break;
    }
  case ZN_LF_DIM_LABEL:
    {
      ZnDim     lw;
      
      GetLabelBBox(field_set, &lw, &real_height);
      break;
    }
  case ZN_LF_DIM_PIXEL:
  default:
    real_height = (ZnDim) height_spec;
    break;
  }
  /*printf("field %d, height = %g\n", field, real_height);*/

  /*
   * Update the cache with the newly computed infos
   * (breaking of deadlocks).
   */
  field_bbox->corner.x = real_width;
  field_bbox->corner.y = real_height;
  fptr->corner_x = (short) real_width;
  fptr->corner_y = (short) real_height;

  /*
   * Then try to deduce the position, resolving any attachments
   * if needed.
   */
  
  /*
   * Do the x axis.
   */
  if (x_dim != ZN_LF_DIM_LABEL) {
    if (x_attach == ZN_LF_ATTACH_PIXEL) {
      field_bbox->orig.x = (ZnPos) x_spec;
      field_bbox->corner.x = field_bbox->orig.x + real_width;
    }
    else {
      ref_field = x_spec;
      field_bbox->orig.x = field_bbox->corner.x = 0;
      if (ref_field >= num_fields) {
        ZnWarning ("Attached (x) to an inexistant field geometry\n");
      }
      else {
        ComputeFieldAttachment(field_set, ref_field, &ref_bbox);
        switch (x_attach) {
        case ZN_LF_ATTACH_FWD:
          if (ISSET(field_set->fields[ref_field].flags, FIELD_VISIBLE_BIT)) {
            field_bbox->orig.x = ref_bbox.corner.x;
          }
          else {
            field_bbox->orig.x = ref_bbox.orig.x;
          }
          field_bbox->corner.x = field_bbox->orig.x + real_width;
          break;
        case ZN_LF_ATTACH_BWD:
          if (ISSET(field_set->fields[ref_field].flags, FIELD_VISIBLE_BIT)) {
            field_bbox->corner.x = ref_bbox.orig.x;
          }
          else {
            field_bbox->corner.x = ref_bbox.corner.x;
          }
          field_bbox->orig.x = field_bbox->corner.x - real_width;
          break;
        case ZN_LF_ATTACH_LEFT:
          field_bbox->orig.x = ref_bbox.orig.x;
          field_bbox->corner.x = field_bbox->orig.x + real_width;
          break;
        case ZN_LF_ATTACH_RIGHT:
          if (ISSET(field_set->fields[ref_field].flags, FIELD_VISIBLE_BIT)) {
            field_bbox->corner.x = ref_bbox.corner.x;
          }
          else {
            field_bbox->corner.x = ref_bbox.orig.x;
          }
          field_bbox->orig.x = field_bbox->corner.x - real_width;       
          break;
        }
      }
    }
    /*printf("field %d, x = %g\n", field, field_bbox->orig.x);*/
  }
  
  /*
   * Then the y axis.
   */
  if (y_dim != ZN_LF_DIM_LABEL) {
    if (y_attach == ZN_LF_ATTACH_PIXEL) {
      field_bbox->orig.y = (ZnPos) y_spec;
      field_bbox->corner.y = field_bbox->orig.y + real_height;
    }
    else {
      ref_field = y_spec;
      field_bbox->orig.y = field_bbox->corner.y = 0;
      if (ref_field >= num_fields) {
        ZnWarning ("Attached (y) to an inexistant field geometry\n");
      }
      else {
        ComputeFieldAttachment(field_set, ref_field, &ref_bbox);
        switch (y_attach) {
        case ZN_LF_ATTACH_FWD:
          if (ISSET(field_set->fields[ref_field].flags, FIELD_VISIBLE_BIT)) {
            field_bbox->orig.y = ref_bbox.corner.y;
          }
          else {
            field_bbox->orig.y = ref_bbox.orig.y;
          }
          field_bbox->corner.y = field_bbox->orig.y + real_height;
          break;
        case ZN_LF_ATTACH_BWD:
          if (ISSET(field_set->fields[ref_field].flags, FIELD_VISIBLE_BIT)) {
            field_bbox->corner.y = ref_bbox.orig.y;
          }
          else {
            field_bbox->corner.y = ref_bbox.corner.y;
          }
          field_bbox->orig.y = field_bbox->corner.y - real_height;
          break;
        case ZN_LF_ATTACH_LEFT:
          field_bbox->orig.y = ref_bbox.orig.y;
          field_bbox->corner.y = field_bbox->orig.y + real_height;
          break;
        case ZN_LF_ATTACH_RIGHT:
          if (ISSET(field_set->fields[ref_field].flags, FIELD_VISIBLE_BIT)) {
            field_bbox->corner.y = ref_bbox.corner.y;
          }
          else {
            field_bbox->corner.y = ref_bbox.orig.y;
          }
          field_bbox->orig.y = field_bbox->corner.y - real_height;      
          break;
        }
      }
    }
    /*printf("field %d, y = %g\n", field, field_bbox->orig.y);*/
  }
  
  fptr->orig_x = (short) field_bbox->orig.x;
  fptr->orig_y = (short) field_bbox->orig.y;
  fptr->corner_x = (short) field_bbox->corner.x;
  fptr->corner_y = (short) field_bbox->corner.y;
  SET(fptr->flags, CACHE_OK);

  /*printf("ComputeFieldAttachment out\n");*/
}


/*
 **********************************************************************************
 *
 * ClearFieldCache --
 *      Reset the geometric cache of all fields depending on a given field (or
 *      of all fields if the field is < 0). Clear also the label bounding box
 *      cache if some action has been taken on a field.
 * 
 **********************************************************************************
 */
static void
ClearFieldCache(ZnFieldSet      field_set,
                int             field)
{
  unsigned int  i, num_fields; 
  ZnBool        clear_bbox;
  int           x_spec, y_spec;
  char          x_attach, y_attach, x_dim, y_dim;
  short         width_spec, height_spec;

  if (!field_set->num_fields) {
    return;
  }
  if (field < 0) {
    for (i = 0; i < field_set->num_fields; i++) {
      CLEAR(field_set->fields[i].flags, CACHE_OK);
    }
    field_set->label_width = field_set->label_height = -1.0;
    return;
  }

  clear_bbox = False;
  if (!field_set->label_format) {
    return;
  }
  num_fields = ZnLFNumFields(field_set->label_format);
  if ((unsigned int) field >= num_fields) {
    return;
  }
  ZnLFGetField(field_set->label_format, (unsigned int) field,
               &x_attach, &y_attach, &x_dim, &y_dim,                  
               &x_spec, &y_spec, &width_spec, &height_spec);
  if ((x_dim != ZN_LF_DIM_PIXEL) || (y_dim != ZN_LF_DIM_PIXEL)) {
    CLEAR(field_set->fields[field].flags, CACHE_OK);
    clear_bbox = True;
  }
  for (i = 0; i < num_fields; i++) {
    ZnLFGetField(field_set->label_format, i,
                 &x_attach, &y_attach, &x_dim, &y_dim,
                 &x_spec, &y_spec, &width_spec, &height_spec);
    if ((x_attach == ZN_LF_ATTACH_PIXEL) && (y_attach == ZN_LF_ATTACH_PIXEL)) {
      continue;
    }
    if (x_attach != ZN_LF_ATTACH_PIXEL) {
      if ((x_spec == field) && ISSET(field_set->fields[i].flags, CACHE_OK)) {
        CLEAR(field_set->fields[i].flags, CACHE_OK);
        ClearFieldCache(field_set, (int) i);
        clear_bbox = True;
      }
    }
    if (y_attach != ZN_LF_ATTACH_PIXEL) {
      if ((y_spec == field) && ISSET(field_set->fields[i].flags, CACHE_OK)) {
        CLEAR(field_set->fields[i].flags, CACHE_OK);
        ClearFieldCache(field_set, (int) i);
        clear_bbox = True;
      }
    }
  }

  if (clear_bbox) {
    field_set->label_width = field_set->label_height = -1.0;
  }
}


/*
 **********************************************************************************
 *
 * GetLabelBBox --
 * 
 **********************************************************************************
 */
static void
GetLabelBBox(ZnFieldSet field_set,
             ZnDim      *w,
             ZnDim      *h)
{
  ZnBBox        bbox, tmp_bbox;
  ZnLabelFormat lf;
  unsigned int  i, num_fields;
  ZnDim         clip_w, clip_h;
  
  /*printf("GetLabelBBox in\n");*/
  if ((field_set->label_width >= 0.0) && (field_set->label_height >= 0.0)) {
    *w = field_set->label_width;
    *h = field_set->label_height;
    /*printf("GetLabelBBox in cache\n");*/
    return;
  }

  lf = field_set->label_format;
  if (lf == NULL) {
    *w = *h = field_set->label_width = field_set->label_height = 0.0;
    /*printf("GetLabelBBox no labelformat\n");*/
    return;
  }

  ZnResetBBox(&bbox);
  num_fields = ZnLFNumFields(lf);
  for (i = 0; i < num_fields; i++) {
    ComputeFieldAttachment(field_set, i, &tmp_bbox);
    /*printf("field %d bbox %g %g %g %g\n", i, tmp_bbox.orig.x, tmp_bbox.orig.y,
      tmp_bbox.corner.x, tmp_bbox.corner.y);*/
    ZnAddBBoxToBBox(&bbox, &tmp_bbox);
  }
  field_set->label_width = bbox.corner.x;
  field_set->label_height = bbox.corner.y;

  /*printf("GetLabelBBox size before clipping; w = %g, h = %g\n",
         field_set->label_width, field_set->label_height);*/
  if (ZnLFGetClipBox(lf, &clip_w, &clip_h)) {
    if (clip_w < field_set->label_width) {
      field_set->label_width = clip_w;
    }
    if (clip_h < field_set->label_height) {
      field_set->label_height = clip_h;
    }
  }
  
  *w = field_set->label_width;
  *h = field_set->label_height;
  /*printf("GetLabelBBox returns computed size; w = %g, h = %g\n", *w, *h);*/
}


/*
 **********************************************************************************
 *
 * GetFieldBBox --
 *      Compute the location of the field described
 *      by the field entry index in the item current LabelFormat.
 * 
 **********************************************************************************
 */
static void
GetFieldBBox(ZnFieldSet         field_set,
             unsigned int       index,
             ZnBBox             *field_bbox)
{
  ZnReal        ox, oy;
  
  /*printf("GetFieldBBox in\n");*/
  if (field_set->label_format) {
    ox = ZnNearestInt(field_set->label_pos.x);
    oy = ZnNearestInt(field_set->label_pos.y);
    ComputeFieldAttachment(field_set, index, field_bbox);
    field_bbox->orig.x += ox;
    field_bbox->orig.y += oy;
    field_bbox->corner.x += ox;
    field_bbox->corner.y += oy;
  }
  else {
    ZnResetBBox(field_bbox);
  }
  /*printf("GetFieldBBox out\n");*/
}


/*
 **********************************************************************************
 *
 * ComputeFieldTextLocation -- 
 *      Compute the position of the text in a field. This is a position
 *      that we can give to XDrawText. The position is deduced from the
 *      field bounding box passed in bbox.
 *      Return also the text bounding box.
 *
 **********************************************************************************
 */
static void
ComputeFieldTextLocation(Field          fptr,
                         ZnBBox         *bbox,
                         ZnPoint        *pos,
                         ZnBBox         *text_bbox)
{
  ZnDim         w, h;
  Tk_FontMetrics fm;

  Tk_GetFontMetrics(fptr->font, &fm);
  w = 0;
  if (fptr->text) {
    int width;
    Tk_MeasureChars(fptr->font, fptr->text, strlen(fptr->text), -1, 0, &width);
    w = width;
  }
  h = fm.ascent + fm.descent;
  text_bbox->orig.y = (bbox->orig.y + bbox->corner.y - h) / 2.0;
  text_bbox->corner.y = text_bbox->orig.y + h;
  pos->y = text_bbox->orig.y + fm.ascent;
  
  switch (fptr->alignment) {
  case TK_JUSTIFY_LEFT:
    text_bbox->orig.x = bbox->orig.x + 2;
    break;
  case TK_JUSTIFY_RIGHT:
    text_bbox->orig.x = bbox->corner.x - w - 2;
    break;
  default:
    text_bbox->orig.x = ZnNearestInt((bbox->orig.x + bbox->corner.x - w) / 2.0);
    break;
  }
  text_bbox->corner.x = text_bbox->orig.x + w;
  pos->x = text_bbox->orig.x;
}


/*
 **********************************************************************************
 *
 * LeaderToLabel --
 *      Compute the segment part of segment <start, end> that lies
 *      outside the fields of item.
 *
 **********************************************************************************
 */
static void
LeaderToLabel(ZnFieldSet        field_set,
              ZnPoint   *start,
              ZnPoint   *end)
{
  int           b_num;
  ZnPoint       delta, inf, sup;
  ZnPos         xt=0, yu=0, yw=0, xv=0;
  Field         fptr;
  unsigned int  i;
  ZnBBox        field_bbox;

  /* Intersection points :                                              */
  /*   T |xt / delta_y  U |x1           V |y1           W |yw / delta_x */
  /*     |y2              |yu / delta_x   |xv / delta_y   |x2           */
  /*                                            */
  /* y = ax + b;                                */
  /* a = delta_y / delta_x                      */
  /* b = (y * delta_x - x * delta_y) / delta_x  */

  delta.x = start->x - end->x;
  delta.y = start->y - end->y;
  b_num   = (int) (start->y*delta.x - start->x*delta.y);
  
  for (i = 0; i < ZnLFNumFields(field_set->label_format); i++) {
    fptr = &field_set->fields[i];
    /*
     * If the field is made invisible or has no graphics of
     * its own, don't clip.
     */
    if (ISCLEAR(fptr->flags, FIELD_VISIBLE_BIT) ||
        (!fptr->text &&
         ISCLEAR(fptr->flags, FILLED_BIT) &&
         (fptr->border_edges == ZN_NO_BORDER) &&
         (fptr->relief == ZN_RELIEF_FLAT) &&
         (fptr->image == ZnUnspecifiedImage))) {
      continue;
    }

    /*
     * field_bbox is in absolute device coordinates.
     */
    GetFieldBBox(field_set, i, &field_bbox);

    /*
     * Adjust leader on real text, not on field boundaries. This is
     * important when there are leading and trailing spaces.
     */
    if (fptr->text &&
        ISCLEAR(fptr->flags, FILLED_BIT) &&
        (fptr->border_edges == ZN_NO_BORDER) &&
        (fptr->relief == ZN_RELIEF_FLAT) &&
        (fptr->image == ZnUnspecifiedImage)) {
      ZnBBox   text_bbox;
      ZnPoint  text_pos;        /* dummy */
      int      space_width;
      int      scan_forw, scan_back;
      space_width = Tk_TextWidth(fptr->font, " ", 1);

      ComputeFieldTextLocation(fptr, &field_bbox, &text_pos, &text_bbox);
      /*
       * Correct adjusments made by ComputeFieldTextLocation (Vincent Pomey).
       *
       * PLC: IMHO, this is to compensate for exotic fonts like 'symbolesATC'.
       *      I am not planning to port this to Tk for two reasons:
       *          1/ Current positions are no longer implemented as characters
       *      and 2/ Tk does not give access (easily) to lbearings and rbearings.
       * This patch has been contributed by Phidias team. I don't know the
       * problem it was meant to solve.
       * text_bbox.x -= fptr->font->per_char[fptr->text[0]].lbearing + 3;
       * text_bbox.width += fptr->font->per_char[fptr->text[0]].lbearing + 3;
       */
      /*
       * Change bbox according to leading and trailing spaces.
       */
      scan_forw = 0;
      while (fptr->text[scan_forw] == ' ') {
        /* leading spaces */
        text_bbox.orig.x += space_width;
        scan_forw++;
      }

      /*
       * Empty text.
       */
      if (!fptr->text || (fptr->text[scan_forw] == 0)) {
        continue;
      }
      
      scan_back = strlen(fptr->text)-1;
      while ((fptr->text[scan_back] == ' ') && (scan_back > scan_forw)) {
        /* trailing spaces */
        text_bbox.corner.x -= space_width;
        scan_back--;
      }

      field_bbox = text_bbox;
    }

    if (field_bbox.corner.x <= field_bbox.orig.x) {
      continue;
    }
    
    if ((start->x >= field_bbox.orig.x) && (start->x < field_bbox.corner.x) &&
        (start->y >= field_bbox.orig.y) && (start->y < field_bbox.corner.y)) {
      end->x = start->x;
      end->y = start->y;
    }
    if (delta.x) {
      yu = (field_bbox.orig.x*delta.y + b_num) / delta.x;
      yw = (field_bbox.corner.x*delta.y + b_num) / delta.x;
    }
    if (delta.y) {
      xt = (field_bbox.corner.y*delta.x - b_num) / delta.y;
      xv = (field_bbox.orig.y*delta.x - b_num) / delta.y;
    }
    
    inf.x = MIN(start->x, end->x);
    sup.x = MAX(start->x, end->x);
    inf.y = MIN(start->y, end->y);
    sup.y = MAX(start->y, end->y);
    
    if (delta.x) {
      if ((yu >= field_bbox.orig.y) && (yu <= field_bbox.corner.y) &&
          (field_bbox.orig.x >= inf.x) && (field_bbox.orig.x <= sup.x) &&
          (yu >= inf.y) && (yu <= sup.y)) {
        end->x = field_bbox.orig.x;
        end->y = yu;
        inf.x = MIN(start->x, end->x);
        sup.x = MAX(start->x, end->x);
        inf.y = MIN(start->y, end->y);
        sup.y = MAX(start->y, end->y);
      }
      if ((yw >= field_bbox.orig.y) && (yw <= field_bbox.corner.y) &&
          (field_bbox.corner.x >= inf.x) && (field_bbox.corner.x <= sup.x) &&
          (yw >= inf.y) && (yw <= sup.y)) {
        end->x = field_bbox.corner.x;
        end->y = yw;
        inf.x = MIN(start->x, end->x);
        sup.x = MAX(start->x, end->x);
        inf.y = MIN(start->y, end->y);
        sup.y = MAX(start->y, end->y);
      }
    }
    if (delta.y) {
      if ((xt >= field_bbox.orig.x) && (xt <= field_bbox.corner.x) &&
          (xt >= inf.x) && (xt <= sup.x) &&
          (field_bbox.corner.y >= inf.y) && (field_bbox.corner.y <= sup.y)) {
        end->x = xt;
        end->y = field_bbox.corner.y;
        inf.x = MIN(start->x, end->x);
        sup.x = MAX(start->x, end->x);
        inf.y = MIN(start->y, end->y);
        sup.y = MAX(start->y, end->y);
      }
      if ((xv >= field_bbox.orig.x) && (xv <= field_bbox.corner.x) &&
          (xv >= inf.x) && (xv <= sup.x) &&
          (field_bbox.orig.y >= inf.y) && (field_bbox.orig.y <= sup.y)) {
        end->x = xv;
        end->y = field_bbox.orig.y;
        inf.x = MIN(start->x, end->x);
        sup.x = MAX(start->x, end->x);
        inf.y = MIN(start->y, end->y);
        sup.y = MAX(start->y, end->y);
      }
    }
  }
}


/*
 **********************************************************************************
 *
 * InitFields --
 *
 *      Perform the init of each field in a ZnFieldSet. The number of such
 *      fields must have been inited before calling this fun.
 *
 **********************************************************************************
 */
static void
InitFields(ZnFieldSet   field_set)
{
  ZnWInfo               *wi = field_set->item->wi;
  Field                 field;
  unsigned int          i, num_fields;

  /*printf("size of a field = %d\n", sizeof(FieldStruct));*/
  
  if (!field_set->num_fields) {
    return;
  }
  num_fields = field_set->num_fields;
  field_set->fields = (Field) ZnMalloc(num_fields*sizeof(FieldStruct));

  for (i = 0; i < num_fields; i++){
    field = &field_set->fields[i];

    field->color = ZnGetGradientByValue(wi->fore_color);
    field->fill_color = ZnGetGradientByValue(wi->back_color);
    field->border_color = ZnGetGradientByValue(wi->fore_color);
    SET(field->flags, FIELD_VISIBLE_BIT);
    SET(field->flags, FIELD_SENSITIVE_BIT);
    CLEAR(field->flags, FILLED_BIT);
    CLEAR(field->flags, CACHE_OK);
    field->text = NULL;
    field->image = ZnUnspecifiedImage;
    field->tile = ZnUnspecifiedImage;
    field->font = Tk_GetFont(wi->interp, wi->win, Tk_NameOfFont(wi->font));
#ifdef GL
    field->tfi = ZnGetTexFont(wi, field->font);
#endif
    field->border_edges = ZN_NO_BORDER;
    field->alignment = TK_JUSTIFY_LEFT;
    field->auto_alignment.automatic = False;

    field->relief = ZN_RELIEF_FLAT;
    field->relief_thickness = 2;
    SET(field->flags, TEXT_ON_TOP_BIT);

    field->gradient = NULL;
    field->grad_geo = NULL;
  }
  field_set->label_pos.x = field_set->label_pos.y = 0.0;
  field_set->label_width = field_set->label_height = -1.0;
}


/*
 **********************************************************************************
 *
 * CloneFields --
 *
 **********************************************************************************
 */
static void
CloneFields(ZnFieldSet  field_set)
{
  ZnWInfo       *wi = field_set->item->wi;
  Field         field, fields_ret;
  unsigned int  i, num_fields;
  char          *text;

  num_fields = field_set->num_fields;
  if (!num_fields) {
    return;
  }
  if (field_set->label_format) {
    field_set->label_format = ZnLFDuplicate(field_set->label_format);
  }
  fields_ret = (Field) ZnMalloc(num_fields*sizeof(FieldStruct));
  memcpy(fields_ret, field_set->fields, num_fields*sizeof(FieldStruct));
  field_set->fields = fields_ret;
  
  for (i = 0; i < num_fields; i++) {
    field = &fields_ret[i];
    if (field->gradient) {
      field->gradient = ZnGetGradientByValue(field->gradient);
    }
    if (field->grad_geo) {
      ZnPoint *grad_geo = ZnMalloc(4*sizeof(ZnPoint));
      memcpy(grad_geo, field->grad_geo, 4*sizeof(ZnPoint));
      field->grad_geo = grad_geo;    
    }    
    if (field->image != ZnUnspecifiedImage) {
      field->image = ZnGetImageByValue(field->image, ZnUpdateItemImage, field_set->item);
    }
    if (field->tile != ZnUnspecifiedImage) {
      field->tile = ZnGetImageByValue(field->tile, ZnUpdateItemImage, field_set->item);
    }
    field->font = Tk_GetFont(wi->interp, wi->win, Tk_NameOfFont(field->font));
#ifdef GL
    field->tfi = ZnGetTexFont(wi, field->font);
#endif
    field->color = ZnGetGradientByValue(field->color);
    field->fill_color = ZnGetGradientByValue(field->fill_color);
    field->border_color = ZnGetGradientByValue(field->border_color);

    if (field->text) {
      text = (char *) ZnMalloc((strlen(field->text) + 1) * sizeof(char));
      strcpy(text, field->text);
      field->text = text;
    }
  }
}


/*
 **********************************************************************************
 *
 * ConfigureField -- 
 *
 **********************************************************************************
 */
static int
ConfigureField(ZnFieldSet       fs,
               int              field,
               int              argc,
               Tcl_Obj *CONST   argv[],
               int              *flags)
{
  unsigned int  i;
  Field         fptr;
  ZnBBox        bbox;
  ZnWInfo       *wi = fs->item->wi;
  XColor        *color;
  unsigned short alpha;
  int           old_num_chars, num_chars;
#ifdef GL
  Tk_Font       old_font;
#endif

  if ((field < 0) || ((unsigned int) field >= fs->num_fields)) {
    Tcl_AppendResult(wi->interp, "invalid field index", NULL);
    return TCL_ERROR;
  }
  
  fptr = &fs->fields[field];
#ifdef GL
  old_font = fptr->font;
#endif
  old_num_chars = 0;
  if (fptr->text) {
    old_num_chars = Tcl_NumUtfChars(fptr->text, (int) strlen(fptr->text));
  }

  if (ZnConfigureAttributes(wi, fs->item, fptr, field_attrs,
                            argc, argv, flags) == TCL_ERROR) {
    return TCL_ERROR;
  }

  num_chars = 0;
  if (fptr->text) {
    num_chars = Tcl_NumUtfChars(fptr->text, (int) strlen(fptr->text));
  }
  if (old_num_chars != num_chars) {
    ZnTextInfo *ti = &wi->text_info;
    /*
     * The text has changed, update the selection and
     * insertion pos to keep them valid.
     */
    if ((fs->item == ti->sel_item) && (field == ti->sel_field)) {
      if (ti->sel_last > num_chars) {
        ti->sel_last = num_chars;
      }
      if (ti->sel_first >= ti->sel_last) {
        ti->sel_item = ZN_NO_ITEM;
        ti->sel_field = ZN_NO_PART;
      }
      if ((ti->anchor_item == fs->item) && (ti->anchor_field == field) &&
          (ti->sel_anchor > num_chars)) {
        ti->sel_anchor = num_chars;
      }
    }
    if (fptr->insert_index > num_chars) {
      fptr->insert_index = num_chars;
    }
  }

#ifdef GL
  if (old_font != fptr->font) {
    if (fptr->tfi) {
      ZnFreeTexFont(fptr->tfi);
      fptr->tfi = ZnGetTexFont(wi, fptr->font);
    }
  }
#endif

  if (ISSET(*flags, ZN_REPICK_FLAG)) {
    SET(wi->flags, ZN_INTERNAL_NEED_REPICK);
  }
  if (ISSET(*flags, ZN_CLFC_FLAG)) {
    ClearFieldCache(fs, field);
  }
  
  if (fptr->gradient &&
      (ISSET(*flags, ZN_BORDER_FLAG) || (fptr->relief == ZN_RELIEF_FLAT))) {
    ZnFreeGradient(fptr->gradient);
    fptr->gradient = NULL;
  }
  if ((fptr->relief != ZN_RELIEF_FLAT) && !fptr->gradient) {
    color = ZnGetGradientColor(fptr->border_color, 51.0, &alpha);
    fptr->gradient = ZnGetReliefGradient(wi->interp, wi->win,
                                              Tk_NameOfColor(color), alpha);
    if (fptr->gradient == NULL) {
      return TCL_ERROR;      
    }
  }

  /*
   * This is done here to limit the redraw to the area of the
   * modified fields.
   */
  if (ISCLEAR(*flags, ZN_COORDS_FLAG) &&
      fs->label_format && ISSET(*flags, ZN_DRAW_FLAG)) {
    for (i = 0; i < ZnLFNumFields(fs->label_format); i++) {
      if (i == (unsigned int) field) {
        GetFieldBBox(fs, i, &bbox);
        ZnDamage(wi, &bbox);
        break;
      }
    }
  }

  return TCL_OK;
}


/*
 **********************************************************************************
 *
 * QueryField --
 *
 **********************************************************************************
 */
static int
QueryField(ZnFieldSet           fs,
           int                  field,
           int                  argc,
           Tcl_Obj *CONST       argv[])
{
  if ((field < 0) || ((unsigned int) field >= fs->num_fields)) {
    Tcl_AppendResult(fs->item->wi->interp, "invalid field index \"", NULL);
    return TCL_ERROR;
  }
    
  if (ZnQueryAttribute(fs->item->wi->interp, &fs->fields[field], field_attrs,
                       argv[0]) == TCL_ERROR) {
    return TCL_ERROR;
  }
  
  return TCL_OK;
}


/*
 **********************************************************************************
 *
 * FreeFields --
 *
 **********************************************************************************
 */
static void
FreeFields(ZnFieldSet   field_set)
{
  unsigned int  i, num_fields;
  Field         field;

  if (field_set->label_format) {
    ZnLFDelete(field_set->label_format);
  }
  
  num_fields = field_set->num_fields;
  for (i = 0; i < num_fields; i++) {
    field = &field_set->fields[i];
    
    if (field->text) {
      ZnFree(field->text);
    }
    if (field->gradient) {
      ZnFreeGradient(field->gradient);
    }
    if (field->grad_geo) {
      ZnFree(field->grad_geo);
    }
    if (field->image != ZnUnspecifiedImage) {
      ZnFreeImage(field->image, ZnUpdateItemImage, &field->image);
      field->image = ZnUnspecifiedImage;
    }
    if (field->tile != ZnUnspecifiedImage) {
      ZnFreeImage(field->tile, ZnUpdateItemImage, &field->tile);
      field->tile = ZnUnspecifiedImage;
    }
    Tk_FreeFont(field->font);
#ifdef GL
    if (field->tfi) {
      ZnFreeTexFont(field->tfi);
    }
#endif
    ZnFreeGradient(field->color);
    ZnFreeGradient(field->fill_color);
    ZnFreeGradient(field->border_color);
  }
  if (num_fields) {
    ZnFree(field_set->fields);
  }
}


/*
 **********************************************************************************
 *
 * FieldIndex,
 * FieldInsertChars,
 * FieldDeleteChars,
 * FieldCursor,
 * FieldSelection --
 *      These functions implement text edition in fields. The behavior
 * is the same as for Text items.
 *
 **********************************************************************************
 */
static int
FieldPointToChar(ZnFieldSet     fs,
                 unsigned int   field,
                 int            x,
                 int            y)
{
  Field         fptr;
  int           byte_index;
  ZnBBox        f_bbox, t_bbox;
  ZnPoint       t_orig;
  unsigned int  num_bytes, n, dummy;

  fptr = &fs->fields[field];
  num_bytes = 0;
  byte_index = 0;
  if (fptr->text) {
    num_bytes = strlen(fptr->text);
  }

  if (num_bytes == 0) {
    return 0;
  }
  
  GetFieldBBox(fs, field, &f_bbox);
  ComputeFieldTextLocation(fptr, &f_bbox, &t_orig, &t_bbox);

  /*
   * Point above text, returns index 0.
   */
  if (y < t_bbox.orig.y) {
    return 0;
  }
  
  if (y < t_bbox.corner.y) {
    if (x < t_bbox.orig.x) {
      /*
       * Point to the left of the current line, returns
       * index of first char.
       */
      return 0;
    }
    if (x >= t_bbox.corner.x) {
      /*
       * Point to the right of the current line, returns
       * index past the last char.
       */
      byte_index = num_bytes;
      goto convrt;
    }
    n = Tk_MeasureChars(fptr->font, fptr->text, num_bytes,
                        x + 2 - (int) t_bbox.orig.x, TK_PARTIAL_OK, &dummy);
    byte_index = n - 1;
    goto convrt;
  }
  /*
   * Point below all lines, return the index after
   * the last char.
   */ 
  byte_index = num_bytes;
 convrt:
  return Tcl_NumUtfChars(fptr->text, byte_index);
}

static int
WordMoveFromIndex(char  *text,
                  int   index,
                  int   fwd)
{
  char const *strp;

  if (!text) {
    return index;
  }

  strp = Tcl_UtfAtIndex(text, index);
  if (fwd) {
    while ((strp[1] == ' ') || (strp[1] == '\n')) {
      strp++;
    }
    while ((strp[1] != ' ') && (strp[1] != '\n') && strp[1]) {
      strp++;
    }
    return Tcl_NumUtfChars(text, strp + 1 - text);
  }
  else {
    while ((strp != text) && ((strp[-1] == ' ') || (strp[-1] == '\n'))) {
      strp--;
    }
    while ((strp != text) && (strp[-1] != ' ') && (strp[-1] != '\n')) {
      strp--;
    }
    return Tcl_NumUtfChars(text, strp - text);
  }
}

static int
FieldIndex(ZnFieldSet   fs,
           int          field,
           Tcl_Obj      *index_spec,
           int          *index)
{
  Field         fptr;
  ZnWInfo       *wi = fs->item->wi;
  ZnTextInfo    *ti = &wi->text_info;
  unsigned int  length;
  int           c, x, y;
  double        tmp;
  char          *end, *p;

  if ((field < 0) || ((unsigned int) field >= fs->num_fields)) {
    *index = 0;
    return TCL_OK;
  }

  fptr = &fs->fields[field];

  p = Tcl_GetString(index_spec);
  c = p[0];
  length = strlen(p);
  
  if ((c == 'e') && (strncmp(p, "end", length) == 0)) {
    *index = fptr->text ? Tcl_NumUtfChars(fptr->text, (int) strlen(fptr->text)) : 0;
  }
  else if ((c == 'e') && (length > 1) && (strncmp(p, "eol", length) == 0)) {
    *index = fptr->text ? Tcl_NumUtfChars(fptr->text, (int) strlen(fptr->text)) : 0;
  }
  else if ((c == 'b') && (length > 1) && (strncmp(p, "bol", length) == 0)) {
    *index = 0;
  }
  else if ((c == 'e') && (length > 1) && (strncmp(p, "eow", length) == 0)) {
    *index = WordMoveFromIndex(fptr->text, fptr->insert_index, 1);
  }
  else if ((c == 'b') && (length > 1) && (strncmp(p, "bow", length) == 0)) {
    *index = WordMoveFromIndex(fptr->text, fptr->insert_index, 0);
  }
  else if ((c == 'u') && (strncmp(p, "up", length) == 0)) {
    *index = fptr->insert_index;
  }
  else if ((c == 'd') && (strncmp(p, "down", length) == 0)) {
    *index = fptr->insert_index;
  }
  else if ((c == 'i') && (strncmp(p, "insert", length) == 0)) {
    *index = fptr->insert_index;
  }
  else if ((c == 's') && (strncmp(p, "sel.first", length) == 0) &&
           (length >= 5)) {
    if ((ti->sel_item != fs->item) || (ti->sel_field != field)) {
    sel_err:
      Tcl_AppendResult(wi->interp, "selection isn't in field", (char *) NULL);
      return TCL_ERROR;
    }
    *index = ti->sel_first;
  }
  else if ((c == 's') && (strncmp(p, "sel.last", length) == 0) &&
           (length >= 5)) {
    if ((ti->sel_item != fs->item) || (ti->sel_field != field)) {
      goto sel_err;
    }
    /*
     * We return a modified selection end so that it reflect
     * the text index of the last character _not_ the insertion
     * point between the last and the next.
     */
    *index = ti->sel_last-1;
  }
  else if (c == '@') {
    p++;
    tmp = strtod(p, &end);
    if ((end == p) || (*end != ',')) {
      goto badIndex;
    }
    x = (int) tmp;
    p = end+1;
    tmp = strtod(p, &end);
    if ((end == p) || (*end != 0)) {
      goto badIndex;
    }
    y = (int) tmp;
    
    *index = FieldPointToChar(fs, (unsigned int) field, x, y);
  }
  else if (Tcl_GetIntFromObj(wi->interp, index_spec, index) == TCL_OK) {
    int num_chars = fptr->text ? Tcl_NumUtfChars(fptr->text, (int) strlen(fptr->text)) : 0;
    if (*index < 0){
      *index = 0;
    }
    else if (*index > num_chars) {
      *index = num_chars;
    }
  }
  else {
  badIndex:
    Tcl_AppendResult(wi->interp, "bad index \"", p, "\"", (char *) NULL);
    return TCL_ERROR;
  }
  
  return TCL_OK;  
}

static ZnBool
FieldInsertChars(ZnFieldSet     fs,
                 int            field,
                 int            *index,
                 char           *chars)
{
  Field          fptr;
  ZnTextInfo    *ti = &fs->item->wi->text_info;
  int           num_chars, num_bytes, chars_added;
  unsigned int  byte_index, bytes_added = strlen(chars);
  char          *new;

  if ((field < 0) || ((unsigned int) field >= fs->num_fields)) {
    return False;
  }

  if (bytes_added == 0) {
    return False;
  }

  fptr = &fs->fields[field];
  num_chars = 0;
  num_bytes = 0;
  if (fptr->text) {
    num_bytes = strlen(fptr->text);
    num_chars = Tcl_NumUtfChars(fptr->text, num_bytes);
  }
  if (*index < 0) {
    *index = 0;
  }
  if (*index > num_chars) {
    *index = num_chars;
  }
  chars_added = Tcl_NumUtfChars(chars, (int) bytes_added);

  if (fptr->text) {
    byte_index = Tcl_UtfAtIndex(fptr->text, *index) - fptr->text;
    new = ZnMalloc(num_bytes + bytes_added + 1);
    /*
     * Copy the part before and the part after the new
     * text (if any).
     */
    memcpy(new, fptr->text, (size_t) byte_index);
    strcpy(new + byte_index + bytes_added, fptr->text + byte_index);
    ZnFree(fptr->text);
  }
  else {
    byte_index = 0;
    new = ZnMalloc(num_bytes + 1);
    new[num_bytes] = 0;
  }
  /*
   * Insert the new text.
   */
  memcpy(new + byte_index, chars, bytes_added);
  fptr->text = new;
  
  if (fptr->insert_index >= *index) {
    fptr->insert_index += chars_added;
  }
  if ((ti->sel_item == fs->item) && (ti->sel_field == field)) {
    if (ti->sel_first >= *index) {
      ti->sel_first += chars_added;
    }
    if (ti->sel_last >= *index) {
      ti->sel_last += chars_added;
    }
    if ((ti->anchor_item == fs->item) && (ti->anchor_field == field) &&
        (ti->sel_anchor >= *index)) {
      ti->sel_anchor += chars_added;
    }
  }

  /*
   * Need to redo the fields layout (maybe).
   */
  ClearFieldCache(fs, field);
  return True;
}

static ZnBool
FieldDeleteChars(ZnFieldSet     fs,
                 int            field,
                 int            *first,
                 int            *last)
{
  Field          fptr;
  ZnTextInfo    *ti = &fs->item->wi->text_info;
  unsigned int  char_count, byte_count;
  unsigned int  num_bytes, num_chars, first_offset;
  char          *new;

  if ((field < 0) || ((unsigned int) field >= fs->num_fields)) {
    return False;
  }

  fptr = &fs->fields[field];
  num_chars = 0;
  num_bytes = 0;
  if (fptr->text) {
    num_bytes = strlen(fptr->text);
    num_chars = Tcl_NumUtfChars(fptr->text, (int) num_bytes);
  }
  if (num_chars == 0) {
    return False;
  }
  
  if (*first < 0) {
    *first = 0;
  }
  if (*last >= (int) num_chars) {
    *last = num_chars-1;
  }
  if (*first > *last) {
    return False;
  }
  char_count = *last + 1 - *first;
  first_offset = Tcl_UtfAtIndex(fptr->text, *first)-fptr->text;
  byte_count = Tcl_UtfAtIndex(fptr->text + first_offset, (int) char_count) -
    (fptr->text+first_offset);

  if (num_bytes - byte_count) {
    new = ZnMalloc(num_bytes + 1 - byte_count);
    memcpy(new, fptr->text, (size_t) first_offset);
    strcpy(new + first_offset, fptr->text + first_offset + byte_count);
    ZnFree(fptr->text);
    fptr->text = new;
  }
  else {
    ZnFree(fptr->text);
    fptr->text = NULL;
  }

  /*
   * Update the cursor to reflect the new string.
   */
  if (fptr->insert_index > *first) {
    fptr->insert_index -= char_count;
    if (fptr->insert_index < *first) {
      fptr->insert_index = *first;
    }
  }
  if ((ti->sel_item == fs->item) && (ti->sel_field == field)) {
    if (ti->sel_first > *first) {
      ti->sel_first -= char_count;
      if (ti->sel_first < *first) {
        ti->sel_first = *first;
      }
    }
    if (ti->sel_last >= *first) {
      ti->sel_last -= char_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;
    }
    if ((ti->anchor_item == fs->item) && (ti->anchor_field == field) &&
        (ti->sel_anchor > *first)) {
      ti->sel_anchor -= char_count;
      if (ti->sel_anchor < *first) {
        ti->sel_anchor = *first;
      }
    }
  }
  
  /*
   * Need to redo the fields layout (maybe).
   */
  ClearFieldCache(fs, field);
  return True;
}

static void
FieldCursor(ZnFieldSet  fs,
            int         field,
            int         index)
{
  Field fptr;
  int   num_chars;

  if ((field < 0) || ((unsigned int) field >= fs->num_fields)) {
    return;
  }

  fptr = &fs->fields[field];
  num_chars = 0;
  if (fptr->text) {
    num_chars = Tcl_NumUtfChars(fptr->text, (int) strlen(fptr->text));
  }

  if (index < 0) {
    fptr->insert_index = 0;
  }
  else if (index > num_chars) {
    fptr->insert_index = num_chars;
  }
  else {
    fptr->insert_index = index;
  }
}

static int
FieldSelection(ZnFieldSet       fs,
               int      field,
               int      offset,
               char     *chars,
               int      max_bytes)
{
  Field    fptr;
  int      count;
  char  const *sel_first, *sel_last;
  ZnTextInfo *ti;

  if ((field < 0) || ((unsigned int) field >= fs->num_fields)) {
    return 0;
  }
  ti = &fs->item->wi->text_info;
  if ((ti->sel_first < 0) ||
      (ti->sel_first > ti->sel_last)) {
    return 0;
  }
  
  fptr = &fs->fields[field];
  if (!fptr->text) {
    return 0;
  }

  sel_first = Tcl_UtfAtIndex(fptr->text, ti->sel_first);
  sel_last = Tcl_UtfAtIndex(sel_first, ti->sel_last + 1 - ti->sel_first);
  count = sel_last - sel_first - offset;
  if (count <= 0) {
    return 0;
  }
  if (count > max_bytes) {
    count = max_bytes;
  }

  memcpy(chars, sel_first + offset, (size_t) count);
  chars[count] = 0;
  
  return count;
}


/*
 **********************************************************************************
 *
 * ComputeFieldImageLocation -- 
 *      Compute the bounding box of the pixmap in a field. The position is
 *      deduced from the field bounding box passed in bbox.
 *
 **********************************************************************************
 */
static void
ComputeFieldImageLocation(Field         fptr,
                          ZnBBox        *bbox,
                          ZnBBox        *pm_bbox)
{
  int   width, height;

  ZnSizeOfImage(fptr->image, &width, &height);
  pm_bbox->orig.y = (bbox->orig.y + bbox->corner.y - height) / 2;
  pm_bbox->corner.y = pm_bbox->orig.y + height;
  
  switch (fptr->alignment) {
  case TK_JUSTIFY_LEFT:
    pm_bbox->orig.x = bbox->orig.x;
    break;
  case TK_JUSTIFY_RIGHT:
    pm_bbox->orig.x = bbox->corner.x - width - 1;
    break;
  default:
    pm_bbox->orig.x = (bbox->orig.x + bbox->corner.x - width) / 2;
    break;
  }
  pm_bbox->corner.x = pm_bbox->orig.x + width;
}


/*
 **********************************************************************************
 *
 * FieldsEngine -- 
 *
 **********************************************************************************
 */
static void
FieldsEngine(ZnFieldSet field_set,
             void       (*cb)())
{
  ZnWInfo       *wi = field_set->item->wi;
  /*int         i;      This one *NEED* to be an int */
  unsigned int  i, num_fields, num_chars;
  Field         fptr;
  ZnTextInfo    *ti = &wi->text_info;
  ZnBBox        lclip_bbox, fclip_bbox, bbox, *global_clip_box;
  ZnBBox        tmp_bbox, text_bbox, pm_bbox;
  ZnPoint       pts[2];
  ZnTriStrip    tristrip;
  ZnPoint       text_pos;
  ZnBool        restore = False;
  ZnDim         lwidth, lheight;
  ZnReal        val;
  int           cursor;
  int           sel_start, sel_stop;

  if (!field_set->num_fields) {
    return;
  }

  if (field_set->label_format && ZnLFNumFields(field_set->label_format)) {
    bbox.orig.x = ZnNearestInt(field_set->label_pos.x);
    bbox.orig.y = ZnNearestInt(field_set->label_pos.y);
    GetLabelBBox(field_set, &lwidth, &lheight);
    bbox.corner.x = bbox.orig.x + lwidth;
    bbox.corner.y = bbox.orig.y + lheight;
    ZnCurrentClip(wi, NULL, &global_clip_box, NULL);

    if (!wi->render) {
      ZnIntersectBBox(global_clip_box, &bbox, &lclip_bbox);
      if (ZnIsEmptyBBox(&lclip_bbox)) {
        return;
      }
    }
    else {
      lclip_bbox = bbox;
    }

    num_fields = ZnLFNumFields(field_set->label_format);
    for (i = 0; i < num_fields; i++) {
      fptr = &field_set->fields[i];

      if (ISCLEAR(fptr->flags, FIELD_VISIBLE_BIT)) {
        continue;
      }
      
      GetFieldBBox(field_set, i, &bbox);
      ZnIntersectBBox(&lclip_bbox, &bbox, &fclip_bbox);
      if (ZnIsEmptyBBox(&fclip_bbox)) {
        continue;
      }
      
      /* we must call XSetClipRectangles only if it's required  */
      val = fclip_bbox.orig.x - bbox.orig.x;
      restore = val > 0;
      val = fclip_bbox.orig.y - bbox.orig.y;
      restore |= val > 0;
      val = fclip_bbox.corner.x - bbox.corner.x;
      restore |= val < 0;
      val = fclip_bbox.corner.y - bbox.corner.y;
      restore |= val < 0;

      cursor = ((field_set->item == wi->focus_item) &&
                ((unsigned int) wi->focus_field == i) &&
                ISSET(wi->flags, ZN_GOT_FOCUS) && ti->cursor_on) ? 0 : -1;
      sel_start = -1, sel_stop = -1;
      ComputeFieldTextLocation(fptr, &bbox, &text_pos, &text_bbox);

      if (fptr->text) {
        if (cursor != -1) {
          cursor = Tk_TextWidth(fptr->font, fptr->text,
                                Tcl_UtfAtIndex(fptr->text,
                                               fptr->insert_index)-fptr->text);
        }
        num_chars  = Tcl_NumUtfChars(fptr->text, (int) strlen(fptr->text));
        if (num_chars) {
          if ((field_set->item == ti->sel_item) && ((unsigned int) ti->sel_field == i) &&
              (ti->sel_last >= 0) && (ti->sel_first <= (int) num_chars)) {
            sel_start = Tk_TextWidth(fptr->font, fptr->text,
                                     Tcl_UtfAtIndex(fptr->text,
                                                    ti->sel_first)-fptr->text);
            sel_stop = Tk_TextWidth(fptr->font, fptr->text,
                                    Tcl_UtfAtIndex(fptr->text,
                                                   ti->sel_last)-fptr->text);
          }
          
          ZnIntersectBBox(&fclip_bbox, &text_bbox, &tmp_bbox);
          
          val = tmp_bbox.orig.x - text_bbox.orig.x;
          restore |= val > 0;
          val = tmp_bbox.orig.y - text_bbox.orig.y;
          restore |= val > 0;
          val = tmp_bbox.corner.x - text_bbox.corner.x;
          restore |= val < 0;
          val = tmp_bbox.corner.y - text_bbox.corner.y;
          restore |= val < 0;
        }
      }

      if (fptr->image != ZnUnspecifiedImage) {
        ComputeFieldImageLocation(fptr, &bbox, &pm_bbox);

        ZnIntersectBBox(&fclip_bbox, &pm_bbox, &tmp_bbox);
        
        val = tmp_bbox.orig.x - pm_bbox.orig.x;
        restore |= val > 0;
        val = tmp_bbox.orig.y - pm_bbox.orig.y;
        restore |= val > 0;
        val = tmp_bbox.corner.x - pm_bbox.corner.x;
        restore |= val < 0;
        val = tmp_bbox.corner.y - pm_bbox.corner.y;
        restore |= val < 0;
      }
      
      /*restore = True;*/
      if (restore) {
        /* we must clip. */
        /*printf("clip: %d\n", i);*/
        pts[0] = fclip_bbox.orig;
        pts[1] = fclip_bbox.corner;
        ZnTriStrip1(&tristrip, pts, 2, False);
        ZnPushClip(wi, &tristrip, True, True);
      }

      (*cb)(wi, fptr, &bbox, &pm_bbox,
            &text_pos, &text_bbox, cursor, sel_start, sel_stop);

      if (restore) {
        /* Restore the previous clip. */
        ZnPopClip(wi, True);
        restore = False;
      }
    }
  }
}


/*
 **********************************************************************************
 *
 * DrawFields -- 
 *
 **********************************************************************************
 */
static void
DrawField(ZnWInfo       *wi,
          Field         fptr,
          ZnBBox        *bbox,
          ZnBBox        *pm_bbox,
          ZnPoint       *text_pos,
          ZnBBox        *text_bbox,
          int           cursor,
          int           sel_start,
          int           sel_stop)
{
  ZnTextInfo    *ti = &wi->text_info;
  XGCValues     values;
  XRectangle    r;
  int           j, xs, num_bytes;
  int           pw, ph, fw, fh;
  TkRegion      clip_region;
  ZnBool        simple;
  Pixmap        pixmap;
  TkRegion      photo_region, clip;

  ZnBBox2XRect(bbox, &r);

  /*
   * Draw the background.
   */
  if (ISSET(fptr->flags, FILLED_BIT)) {
    values.foreground = ZnGetGradientPixel(fptr->fill_color, 0.0);
    
    if (fptr->tile != ZnUnspecifiedImage) {
      if (!ZnImageIsBitmap(fptr->tile)) { /* Fill tiled */
        values.fill_style = FillTiled;
        values.tile = ZnImagePixmap(fptr->tile, wi->win);
        values.ts_x_origin = (int) bbox->orig.x;
        values.ts_y_origin = (int) bbox->orig.y;
        XChangeGC(wi->dpy, wi->gc,
                  GCTileStipXOrigin|GCTileStipYOrigin|GCFillStyle|GCTile,
                  &values);
      }
      else { /* Fill stippled */
        values.fill_style = FillStippled;
        values.stipple = ZnImagePixmap(fptr->tile, wi->win);
        values.ts_x_origin = (int) bbox->orig.x;
        values.ts_y_origin = (int) bbox->orig.y;
        XChangeGC(wi->dpy, wi->gc,
                  GCTileStipXOrigin|GCTileStipYOrigin|GCFillStyle|GCStipple|GCForeground,
                  &values);
      }
    }
    else { /* Fill solid */
      values.fill_style = FillSolid;
      XChangeGC(wi->dpy, wi->gc, GCForeground|GCFillStyle, &values);
    }
    XFillRectangle(wi->dpy, wi->draw_buffer, wi->gc, r.x, r.y, r.width, r.height);
  }
  
  /*
   * Draw the image and the text, which is in back depends on
   * the value of text_on_top.
   */
  for (j = 0; j < 2; j++) {
    if ((j == 0 && ISSET(fptr->flags, TEXT_ON_TOP_BIT)) ||
        (j == 1 && ISCLEAR(fptr->flags, TEXT_ON_TOP_BIT))) {
      /*
       * Draw the image.
       */
      if (fptr->image != ZnUnspecifiedImage) {
        pw = ZnNearestInt(pm_bbox->corner.x - pm_bbox->orig.x);
        ph = ZnNearestInt(pm_bbox->corner.y - pm_bbox->orig.y);
        fw = ZnNearestInt(bbox->corner.x - bbox->orig.x);
        fh = ZnNearestInt(bbox->corner.y - bbox->orig.y);

        pixmap = ZnImagePixmap(fptr->image, wi->win);
        photo_region = ZnImageRegion(fptr->image);
        ZnCurrentClip(wi, &clip_region, NULL, &simple);
        clip = TkCreateRegion();
        /*
         * ZnImageRegion may fail: perl/Tk 800.24 doesn't support
         * some internal TkPhoto functions.
         * This is a workaround using a rectangular region based
         * on the image size.
         */
        if (photo_region == NULL) {
          XRectangle rect;
          rect.x = rect.y = 0;
          rect.width = pw;
          rect.height = ph;
          TkUnionRectWithRegion(&rect, clip, clip);
        }
        else {
          ZnUnionRegion(clip, photo_region, clip);
        }
        ZnOffsetRegion(clip, (int) pm_bbox->orig.x, (int) pm_bbox->orig.y);
        TkIntersectRegion(clip_region, clip, clip);
        TkSetRegion(wi->dpy, wi->gc, clip);
        XCopyArea(wi->dpy, pixmap, wi->draw_buffer, wi->gc,
                  (int) ZnNearestInt(bbox->orig.x-pm_bbox->orig.x),
                  (int) ZnNearestInt(bbox->orig.y-pm_bbox->orig.y),
                  (unsigned int) MIN(pw, fw),
                  (unsigned int) MIN(ph, fh),
                  (int) MAX(bbox->orig.x, pm_bbox->orig.x),
                  (int) MAX(bbox->orig.y, pm_bbox->orig.y));

        TkSetRegion(wi->dpy, wi->gc, clip_region);
        TkDestroyRegion(clip);
      }
    }
    else if (fptr->text) {
      /*
       * Draw the text.
       */
      num_bytes = strlen(fptr->text);
      if (num_bytes) {
        if (sel_start >= 0) {
          values.foreground = ZnGetGradientPixel(ti->sel_color, 0.0);
          values.fill_style = FillSolid;
          XChangeGC(wi->dpy, wi->gc, GCForeground|GCFillStyle, &values);
          XFillRectangle(wi->dpy, wi->draw_buffer, wi->gc,
                         (int) (text_bbox->orig.x+sel_start),
                         (int) text_bbox->orig.y,
                         (unsigned int) (sel_stop-sel_start),
                         (unsigned int) (text_bbox->corner.y-text_bbox->orig.y));
        }
        values.foreground = ZnGetGradientPixel(fptr->color, 0.0);
        values.fill_style = FillSolid;
        values.font = Tk_FontId(fptr->font);
        XChangeGC(wi->dpy, wi->gc, GCForeground | GCFillStyle | GCFont, &values);
        Tk_DrawChars(wi->dpy, wi->draw_buffer, wi->gc, fptr->font,
                     fptr->text, num_bytes, (int) text_pos->x, (int) text_pos->y);
      }
    }
  }
  if (cursor >= 0) {
    values.line_width = ti->insert_width;
    values.foreground = ZnGetGradientPixel(ti->insert_color, 0.0);
    values.fill_style = FillSolid;
    XChangeGC(wi->dpy, wi->gc, GCForeground|GCLineWidth|GCFillStyle, &values);
    xs = (int) text_bbox->orig.x + cursor;
    XDrawLine(wi->dpy, wi->draw_buffer, wi->gc,
              xs, (int) text_bbox->orig.y,
              xs, (int) text_bbox->corner.y);
  }
  
  /*
   * Draw the border relief.
   */
  if ((fptr->relief != ZN_RELIEF_FLAT) && (fptr->relief_thickness > 1)) {
    ZnDrawRectangleRelief(wi, fptr->relief, fptr->gradient,
                          &r, fptr->relief_thickness);
  }
  
  /*
   * Draw the border line.
   */
  if (fptr->border_edges != ZN_NO_BORDER) {
    values.foreground = ZnGetGradientPixel(fptr->border_color, 0.0);
    values.line_width = 0;
    values.line_style = LineSolid;
    values.fill_style = FillSolid;
    XChangeGC(wi->dpy, wi->gc,
              GCForeground | GCLineWidth | GCLineStyle | GCFillStyle, &values);
    if (fptr->border_edges & ZN_LEFT_BORDER) {
      XDrawLine(wi->dpy, wi->draw_buffer, wi->gc, r.x, r.y,
                r.x, r.y + r.height - 1);
    }
    if (fptr->border_edges & ZN_RIGHT_BORDER) {
      XDrawLine(wi->dpy, wi->draw_buffer, wi->gc, 
                r.x + r.width - 1, r.y,
                r.x + r.width - 1, r.y + r.height - 1);
    }
    if (fptr->border_edges & ZN_TOP_BORDER) {
      XDrawLine(wi->dpy, wi->draw_buffer, wi->gc, 
                r.x, r.y, r.x + r.width - 1, r.y);
    }
    if (fptr->border_edges & ZN_BOTTOM_BORDER) {
      XDrawLine(wi->dpy, wi->draw_buffer, wi->gc, 
                r.x, r.y + r.height - 1,
                r.x + r.width - 1, r.y + r.height - 1);
    }
    if (fptr->border_edges & ZN_OBLIQUE) {
      XDrawLine(wi->dpy, wi->draw_buffer, wi->gc, 
                r.x, r.y, r.x + r.width - 1, r.y + r.height - 1);
    }
    if (fptr->border_edges & ZN_COUNTER_OBLIQUE) {
      XDrawLine(wi->dpy, wi->draw_buffer, wi->gc, 
                r.x, r.y + r.height - 1,
                r.x + r.width - 1, r.y);
    }
  }
}

static void
DrawFields(ZnFieldSet   field_set)
{
  FieldsEngine(field_set, DrawField);
}


/*
 **********************************************************************************
 *
 * RenderFields -- 
 *
 **********************************************************************************
 */
#ifdef GL
static void
FieldRenderCB(void *closure)
{
  ZnBBox *bbox = (ZnBBox *) closure;
  
  glBegin(GL_QUADS);
  glVertex2d(bbox->orig.x, bbox->orig.y);
  glVertex2d(bbox->orig.x, bbox->corner.y);
  glVertex2d(bbox->corner.x, bbox->corner.y);
  glVertex2d(bbox->corner.x, bbox->orig.y);
  glEnd();
}

static void
RenderField(ZnWInfo     *wi,
            Field       fptr,
            ZnBBox      *bbox,
            ZnBBox      *pm_bbox,
            ZnPoint     *text_pos,
            ZnBBox      *text_bbox,
            int         cursor,
            int         sel_start,
            int         sel_stop)
{
  unsigned short alpha;
  unsigned int  j, num_bytes;
  XColor        *color;
  ZnReal        xs;
  ZnTextInfo    *ti = &wi->text_info;

  ZnGLMakeCurrent(wi->dpy, wi);
  /*
   * Draw the background.
   */
  if (ISSET(fptr->flags, FILLED_BIT)) {
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    if (!ZnGradientFlat(fptr->fill_color)) {
#if 0 /* TODO_GL Faire le dégradé dans le fond des champs. */
      int       type = fptr->fill_color->type;
      ZnBool    fast = (type == ZN_AXIAL_GRADIENT) && !fptr->grad_geo;

      RenderGradient(wi, fptr->fill_color,
                     fast ? NULL : FieldRenderCB,
                     bbox, fast ? (ZnPoint *) bbox : fptr->grad_geo);
#endif
    }
    else {
      if (fptr->tile != ZnUnspecifiedImage) { /* Fill tiled/stippled */
        ZnRenderTile(wi, fptr->tile, fptr->fill_color, FieldRenderCB, bbox,
                     (ZnPoint *) bbox);
      }
      else { /* Fill solid */
        color = ZnGetGradientColor(fptr->fill_color, 0.0, &alpha);
        alpha = ZnComposeAlpha(alpha, wi->alpha);
        glColor4us(color->red, color->green, color->blue, alpha);
        FieldRenderCB(bbox);
      }
    }
  }

  /*
   * Draw the image and the text, which one is back depends on
   * the value of text_on_top.
   */
  for (j = 0; j < 2; j++) {
    if ((j == 0 && ISSET(fptr->flags, TEXT_ON_TOP_BIT)) ||
        (j == 1 && ISCLEAR(fptr->flags, TEXT_ON_TOP_BIT))) {
      /*
       * Draw the image.
       */
      if (fptr->image != ZnUnspecifiedImage) {
        ZnRenderIcon(wi, fptr->image, fptr->fill_color,
                     &pm_bbox->orig, False);
      }
    }
    else if (fptr->text) {
      /*
       * Draw the text.
       */
      num_bytes = strlen(fptr->text);
      if (num_bytes) {
        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
        if (sel_start >= 0) {
          color = ZnGetGradientColor(ti->sel_color, 0.0, &alpha);
          alpha = ZnComposeAlpha(alpha, wi->alpha);
          glColor4us(color->red, color->green, color->blue, alpha);
          glBegin(GL_QUADS);
          glVertex2d(text_bbox->orig.x+sel_start, text_bbox->orig.y);
          glVertex2d(text_bbox->orig.x+sel_stop, text_bbox->orig.y);
          glVertex2d(text_bbox->orig.x+sel_stop, text_bbox->corner.y);
          glVertex2d(text_bbox->orig.x+sel_start, text_bbox->corner.y);
          glEnd();
        }
        glEnable(GL_TEXTURE_2D);
        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
        color = ZnGetGradientColor(fptr->color, 0.0, &alpha);
        alpha = ZnComposeAlpha(alpha, wi->alpha);
        glColor4us(color->red, color->green, color->blue, alpha);
        glBindTexture(GL_TEXTURE_2D, ZnTexFontTex(fptr->tfi));
        glPushMatrix();
        glTranslated(text_pos->x, text_pos->y, 0.0);
        ZnRenderString(fptr->tfi, fptr->text, num_bytes);
        glPopMatrix();
        glDisable(GL_TEXTURE_2D);
      }
    }
    if (cursor >= 0) {
      glLineWidth((GLfloat) ti->insert_width);
      color = ZnGetGradientColor(ti->insert_color, 0.0, &alpha);
      alpha = ZnComposeAlpha(alpha, wi->alpha);
      glColor4us(color->red, color->green, color->blue, alpha);
      xs = text_bbox->orig.x + cursor;
      glBegin(GL_LINES);
      glVertex2d(xs, text_bbox->orig.y);
      glVertex2d(xs, text_bbox->corner.y);
      glEnd();
    }
  }
  
  /*
   * Draw the border relief.
   */  
  if ((fptr->relief != ZN_RELIEF_FLAT) && (fptr->relief_thickness > 1)) {
    ZnPoint p[5];

    p[0].x = bbox->orig.x;
    p[0].y = bbox->orig.y;
    p[2].x = bbox->corner.x;
    p[2].y = bbox->corner.y;
    p[1].x = p[0].x;
    p[1].y = p[2].y;
    p[3].x = p[2].x;
    p[3].y = p[0].y;
    p[4] = p[0];
    ZnRenderPolygonRelief(wi, fptr->relief, fptr->gradient,
                          False, p, 5, fptr->relief_thickness);
  }

  /*
   * Draw the border line.
   */
  if (fptr->border_edges != ZN_NO_BORDER) {
    color = ZnGetGradientColor(fptr->border_color, 0.0, &alpha);
    alpha = ZnComposeAlpha(alpha, wi->alpha);
    glColor4us(color->red, color->green, color->blue, alpha);
    glLineWidth(1.5);
    ZnSetLineStyle(wi, ZN_LINE_SIMPLE);
    glBegin(GL_LINES);
    if (fptr->border_edges & ZN_LEFT_BORDER) {
      glVertex2d(bbox->orig.x, bbox->orig.y);
      glVertex2d(bbox->orig.x, bbox->corner.y);
    }
    if (fptr->border_edges & ZN_RIGHT_BORDER) {
      glVertex2d(bbox->corner.x, bbox->orig.y);
      glVertex2d(bbox->corner.x, bbox->corner.y);
    }
    if (fptr->border_edges & ZN_TOP_BORDER) {
      glVertex2d(bbox->orig.x, bbox->orig.y);
      glVertex2d(bbox->corner.x, bbox->orig.y);
    }
    if (fptr->border_edges & ZN_BOTTOM_BORDER) {
      glVertex2d(bbox->orig.x, bbox->corner.y);
      glVertex2d(bbox->corner.x, bbox->corner.y);
    }
    if (fptr->border_edges & ZN_OBLIQUE) {
      glVertex2d(bbox->orig.x, bbox->orig.y);
      glVertex2d(bbox->corner.x, bbox->corner.y);
    }
    if (fptr->border_edges & ZN_COUNTER_OBLIQUE) {
      glVertex2d(bbox->orig.x, bbox->corner.y);
      glVertex2d(bbox->corner.x, bbox->orig.y);
    }
    glEnd();
  }
}
#endif

#ifdef GL
static void
RenderFields(ZnFieldSet field_set)
{
  /*  glDisable(GL_LINE_SMOOTH);*/
  FieldsEngine(field_set, RenderField);
  /*  glEnable(GL_LINE_SMOOTH);*/
}
#else
static void
RenderFields(ZnFieldSet field_set)
{
}
#endif


/*
 **********************************************************************************
 *
 * PostScriptFields --
 *
 **********************************************************************************
 */
static int
PsField(ZnWInfo *wi,
        ZnBool  prepass,
        Field   fptr,
        ZnBBox  *bbox,
        ZnBBox  *pm_bbox,
        ZnPoint *text_pos,
        ZnBBox  *text_bbox)
{
  int j;
  char path[250];

  /*
   * Must set the clip rect for the whole field, not only for stipple fill.
   */
  if (ISSET(fptr->flags, FILLED_BIT)) {
    if (fptr->tile != ZnUnspecifiedImage) {
      if (!ZnImageIsBitmap(fptr->tile)) { /* Fill tiled */
        /* TODO No support yet */
      }
      else { /* Fill stippled */
        Tcl_AppendResult(wi->interp, "gsave\n", NULL);
        if (Tk_PostscriptColor(wi->interp, wi->ps_info,
                               ZnGetGradientColor(fptr->fill_color, 0.0, NULL)) != TCL_OK) {
          return TCL_ERROR;
        }
        if (Tk_PostscriptStipple(wi->interp, wi->win, wi->ps_info,
                                 ZnImagePixmap(fptr->tile, wi->win)) != TCL_OK) {
          return TCL_ERROR;
        }
        Tcl_AppendResult(wi->interp, "grestore\n", NULL);
      }
    }
    else { /* Fill solid */
      if (Tk_PostscriptColor(wi->interp, wi->ps_info,
                             ZnGetGradientColor(fptr->fill_color, 0.0, NULL)) != TCL_OK) {
        return TCL_ERROR;
      }
      Tcl_AppendResult(wi->interp, "fill\n", NULL);
    }
  }

  /*
   * Draw the image and the text, which is in back depends on
   * the value of text_on_top.
   */
  for (j = 0; j < 2; j++) {
    if ((j == 0 && ISSET(fptr->flags, TEXT_ON_TOP_BIT)) ||
        (j == 1 && ISCLEAR(fptr->flags, TEXT_ON_TOP_BIT))) {
      /*
       * Draw the image.
       */
      if (fptr->image != ZnUnspecifiedImage) {
	int w, h;

        Tcl_AppendResult(wi->interp, "gsave\n", NULL);
        sprintf(path, "%.15g %.15g translate 1 -1 scale\n",
                pm_bbox->orig.x, pm_bbox->corner.y);
        Tcl_AppendResult(wi->interp, path, NULL);
        w = ZnNearestInt(pm_bbox->corner.x - pm_bbox->orig.x);
        h = ZnNearestInt(pm_bbox->corner.y - pm_bbox->orig.y);
        if (Tk_PostscriptImage(ZnImageTkImage(fptr->image), wi->interp, wi->win,
                               wi->ps_info, 0, 0, w, h, prepass) != TCL_OK) {
          return TCL_ERROR;
        }
        Tcl_AppendResult(wi->interp, "grestore\n", NULL);
      }
    }
    else if (fptr->text) {
      Tcl_AppendResult(wi->interp, "gsave\n", NULL);
      if (Tk_PostscriptFont(wi->interp, wi->ps_info, fptr->font) != TCL_OK) {
        return TCL_ERROR;
      }
      if (Tk_PostscriptColor(wi->interp, wi->ps_info,
                             ZnGetGradientColor(fptr->color, 0.0, NULL)) != TCL_OK) {
        return TCL_ERROR;
      }
      /*
       * TODO pourquoi la text_bbox ne donne pas un texte centré verticalement ?
       * Apparement la fonte PostScript n'est pas centrée comme la fonte X.
       * Il faut donc opérer le calcul dans le code PostScript de DrawText.
       */
      sprintf(path, "%.15g %.15g translate 1 -1 scale 0 0 [\n",
              text_bbox->orig.x, text_bbox->orig.y);
      Tcl_AppendResult(wi->interp, path, NULL);
      /*
       * strlen should do the work of counting _bytes_ in the utf8 string.
       */
      ZnPostscriptString(wi->interp, fptr->text, strlen(fptr->text));
      Tcl_AppendResult(wi->interp, "] 0 0.0 0.0 0.0 false DrawText\n", NULL);
      Tcl_AppendResult(wi->interp, "grestore\n", NULL);
    }
  }

  /*
   * Draw the border relief.
   */
  if ((fptr->relief != ZN_RELIEF_FLAT) && (fptr->relief_thickness > 1)) {
  }

  /*
   * Draw the border line.
   */
  if (fptr->border_edges != ZN_NO_BORDER) {
    if (Tk_PostscriptColor(wi->interp, wi->ps_info,
                           ZnGetGradientColor(fptr->border_color, 0.0, NULL)) != TCL_OK) {
      return TCL_ERROR;
    }
    Tcl_AppendResult(wi->interp, "1 setlinewidth 0 setlinejoin 2 setlinecap\n", NULL);
    if (fptr->border_edges & ZN_LEFT_BORDER) {
      sprintf(path, "%.15g %.15g moveto %.15g %.15g lineto stroke\n",
              bbox->orig.x, bbox->orig.y, bbox->orig.x, bbox->corner.y);
      Tcl_AppendResult(wi->interp, path, NULL);
    }
    if (fptr->border_edges & ZN_RIGHT_BORDER) {
      sprintf(path, "%.15g %.15g moveto %.15g %.15g lineto stroke\n",
              bbox->corner.x, bbox->orig.y, bbox->corner.x, bbox->corner.y);
      Tcl_AppendResult(wi->interp, path, NULL);
    }
    if (fptr->border_edges & ZN_TOP_BORDER) {
      sprintf(path, "%.15g %.15g moveto %.15g %.15g lineto stroke\n",
              bbox->orig.x, bbox->orig.y, bbox->corner.x, bbox->orig.y);
      Tcl_AppendResult(wi->interp, path, NULL);
    }
    if (fptr->border_edges & ZN_BOTTOM_BORDER) {
      sprintf(path, "%.15g %.15g moveto %.15g %.15g lineto stroke\n",
              bbox->orig.x, bbox->corner.y, bbox->corner.x, bbox->corner.y);
      Tcl_AppendResult(wi->interp, path, NULL);
    }
    if (fptr->border_edges & ZN_OBLIQUE) {
      sprintf(path, "%.15g %.15g moveto %.15g %.15g lineto stroke\n",
              bbox->orig.x, bbox->orig.y, bbox->corner.x, bbox->corner.y);
      Tcl_AppendResult(wi->interp, path, NULL);
    }
    if (fptr->border_edges & ZN_COUNTER_OBLIQUE) {
      sprintf(path, "%.15g %.15g moveto %.15g %.15g lineto stroke\n",
              bbox->corner.x, bbox->orig.y, bbox->orig.x, bbox->corner.y);
      Tcl_AppendResult(wi->interp, path, NULL);
    }
  }

  return TCL_OK;
}

static int
PostScriptFields(ZnFieldSet field_set,
                 ZnBool     prepass,
                 ZnBBox     *area)
{
  ZnWInfo *wi = field_set->item->wi;
  ZnBBox  lclip_bbox, fclip_bbox;
  ZnBBox  bbox, text_bbox, pm_bbox;
  ZnPoint text_pos;
  int     i, num_fields;
  ZnDim   lwidth, lheight;
  Field   fptr;
  char    path[250];
  
  if (!field_set->num_fields) {
    return TCL_OK;
  }


  if (field_set->label_format && ZnLFNumFields(field_set->label_format)) {
    /*
     * Fields are drawn with respect to a point already converted
     * to device space, so we need to reinstate the initial transform.
     */
    Tcl_AppendResult(wi->interp, "/InitialTransform load setmatrix\n", NULL);

    lclip_bbox.orig.x = ZnNearestInt(field_set->label_pos.x);
    lclip_bbox.orig.y = ZnNearestInt(field_set->label_pos.y);
    GetLabelBBox(field_set, &lwidth, &lheight);
    lclip_bbox.corner.x = lclip_bbox.orig.x + lwidth;
    lclip_bbox.corner.y = lclip_bbox.orig.y + lheight;
    
    num_fields = ZnLFNumFields(field_set->label_format);
    for (i = 0; i < num_fields; i++) {
      fptr = &field_set->fields[i];

      if (ISCLEAR(fptr->flags, FIELD_VISIBLE_BIT)) {
        continue;
      }
      
      GetFieldBBox(field_set, i, &bbox);
      ZnIntersectBBox(&lclip_bbox, &bbox, &fclip_bbox);
      if (ZnIsEmptyBBox(&fclip_bbox)) {
        /* The field is outside the label bbox */
        continue;
      }
      
      /*
       * Setup a clip area around the field
       */
      Tcl_AppendResult(wi->interp, "gsave\n", NULL);
      sprintf(path, "%.15g %.15g moveto %.15g %.15g lineto %.15g %.15g lineto %.15g %.15g",
              fclip_bbox.orig.x, fclip_bbox.orig.y, fclip_bbox.corner.x+1, fclip_bbox.orig.y,
              fclip_bbox.corner.x+1, fclip_bbox.corner.y+1, fclip_bbox.orig.x,
              fclip_bbox.corner.y+1);
      Tcl_AppendResult(wi->interp, path, " lineto closepath clip\n", NULL);

      if (fptr->text) {
        ComputeFieldTextLocation(fptr, &bbox, &text_pos, &text_bbox);
      }
      if (fptr->image != ZnUnspecifiedImage) {
        ComputeFieldImageLocation(fptr, &bbox, &pm_bbox);
      }

      if (PsField(wi, prepass, fptr, &bbox, &pm_bbox, &text_pos, &text_bbox) != TCL_OK) {
        return TCL_ERROR;
      }

      Tcl_AppendResult(wi->interp, "grestore\n", NULL);
    }
  }

  return TCL_OK;
}


/*
 **********************************************************************************
 *
 * IsFieldsSensitive --
 *
 **********************************************************************************
 */
static ZnBool
IsFieldSensitive(ZnFieldSet     field_set,
                 int            part)
{
  if ((part >= 0) && ((unsigned int) part < field_set->num_fields)) {
    return ISSET(field_set->fields[part].flags, FIELD_SENSITIVE_BIT);
  }
  else {
    return False;
  }
}


/*
 **********************************************************************************
 *
 * FieldsPick --
 *      Return the first field that contains <x, y>.
 *      A field is selected if <x, y> is over a non transparent area.
 *      Such areas are : a solid filled background, a text, an icon.
 *      This does *NOT* do with *GLOBAL* visible and sensitive.
 *      But we need to take into account local field visible and
 *      sensitive as they modifiy local transparency. Local means
 *      within a single item.
 *
 **********************************************************************************
 */
static double
FieldsPick(ZnFieldSet   field_set,
           ZnPoint      *p,
           int          *part)
{
  Field         fptr;
  ZnBBox        bbox;
  unsigned int  best_field = 0;
  int           i;
  ZnReal        new_dist, dist = 1e40;

  if (!field_set->num_fields) {
    return dist;
  }

  if (field_set->label_format) {
    for (i = ZnLFNumFields(field_set->label_format)-1; i >= 0; i--) {    
      fptr = &field_set->fields[i];
      
      if (ISCLEAR(fptr->flags, FIELD_VISIBLE_BIT) &&
          ISCLEAR(fptr->flags, FIELD_SENSITIVE_BIT)) {
        continue;
      }
      
      GetFieldBBox(field_set, (unsigned int) i, &bbox);

      new_dist = ZnRectangleToPointDist(&bbox, p);
      if (new_dist < dist) {
        dist = new_dist;
        best_field = i;
      }
      if (dist <= 0.0) {
        dist = 0.0;
        break;
      }
    }
  }
  
  *part = best_field;
  return dist;
}


/*
 **********************************************************************************
 *
 * FieldsToArea --
 *      Return -1 if no field is in the given area, 1 if they are
 *      all in it or 0 if there is some overlap. The function consider
 *      only fields that are either sensible or visible.
 *
 **********************************************************************************
 */
static int
FieldsToArea(ZnFieldSet field_set,
             ZnBBox     *area)
{
  Field         fptr;
  ZnBBox        bbox;
  int           i, inside = -1;
  ZnBool        first_done = False;
  
  if (!field_set->num_fields) {
    return inside;
  }

  for (i = ZnLFNumFields(field_set->label_format)-1; i >= 0; i--) {
    fptr = &field_set->fields[i];

    if (ISCLEAR(fptr->flags, FIELD_VISIBLE_BIT) &&
        ISCLEAR(fptr->flags, FIELD_SENSITIVE_BIT)) {
      continue;
    }

    GetFieldBBox(field_set, (unsigned int) i, &bbox);
    if (!first_done) {
      first_done = True;
      inside = ZnBBoxInBBox(&bbox, area);
      if (inside == 0) {
        return 0;
      }
    }
    else {
      if (ZnBBoxInBBox(&bbox, area) != inside) {
        return 0;
      }
    }
  }

  return inside;
}


/*
 **********************************************************************************
 *
 * SetFieldsAutoAlign --
 *
 **********************************************************************************
 */
static void
SetFieldsAutoAlign(ZnFieldSet   fs,
                   int          alignment)
{
  unsigned int  i;
  Field         field;

  if (!fs->num_fields) {
    return;
  }
  if ((alignment >= ZN_AA_LEFT) && (alignment <= ZN_AA_RIGHT)) {
    for (i = 0; i < fs->num_fields; i++) {
      field = &fs->fields[i];
      if (field->auto_alignment.automatic) {
        field->alignment = field->auto_alignment.align[alignment];
      }
    }
  }
}

static char *
GetFieldStruct(ZnFieldSet       fs,
               int              field)
{
  if ((unsigned int) field >= fs->num_fields) {
    return NULL;
  }
  return (char *) &fs->fields[field];
}


static unsigned int
NumFields(ZnFieldSet    fs)
{
  return fs->num_fields;
}


struct _ZnFIELD ZnFIELD = {
  field_attrs,

  InitFields,
  CloneFields,
  FreeFields,
  ConfigureField,
  QueryField,
  DrawFields,
  RenderFields,
  PostScriptFields,
  FieldsToArea,
  IsFieldSensitive,
  FieldsPick,
  FieldIndex,
  FieldInsertChars,
  FieldDeleteChars,
  FieldCursor,
  FieldSelection,
  LeaderToLabel,
  GetLabelBBox,
  GetFieldBBox,
  SetFieldsAutoAlign,
  ClearFieldCache,
  GetFieldStruct,
  NumFields
};