The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/*
 * Tabular.c -- Implementation of Tabular item.
 *
 * Authors              : Patrick Lecoanet.
 * Creation date        :
 *
 * $Id: Tabular.c,v 1.30 2005/05/10 07:59:48 lecoanet Exp $
 */

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


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

#include <ctype.h>
#include <stdlib.h>


static const char rcsid[] = "$Id: Tabular.c,v 1.30 2005/05/10 07:59:48 lecoanet Exp $";
static const char compile_id[]="$Compile: " __FILE__ " " __DATE__ " " __TIME__ " $";


/*
 **********************************************************************************
 *
 * Specific Tabular item record
 *
 **********************************************************************************
 */
typedef struct _TabularItemStruct {
  ZnItemStruct          header;

  /* Public data */
  ZnPoint               pos;
  Tk_Anchor             anchor;
  Tk_Anchor             connection_anchor;

  /* Private data */
  ZnFieldSetStruct      field_set;
} TabularItemStruct, *TabularItem;


static ZnAttrConfig     tabular_attrs[] = {
  { ZN_CONFIG_ANCHOR, "-anchor", NULL,
    Tk_Offset(TabularItemStruct, anchor), 0, ZN_COORDS_FLAG, False },
  { ZN_CONFIG_BOOL, "-composealpha", NULL,
    Tk_Offset(TabularItemStruct, header.flags), ZN_COMPOSE_ALPHA_BIT,
    ZN_DRAW_FLAG, False },
  { ZN_CONFIG_BOOL, "-composerotation", NULL,
    Tk_Offset(TabularItemStruct, header.flags), ZN_COMPOSE_ROTATION_BIT,
    ZN_COORDS_FLAG, False },
  { ZN_CONFIG_BOOL, "-composescale", NULL,
    Tk_Offset(TabularItemStruct, header.flags), ZN_COMPOSE_SCALE_BIT,
    ZN_COORDS_FLAG, False },
  { ZN_CONFIG_ITEM, "-connecteditem", NULL,
    Tk_Offset(TabularItemStruct, header.connected_item), 0,
    ZN_COORDS_FLAG|ZN_ITEM_FLAG, False },
  { ZN_CONFIG_ANCHOR, "-connectionanchor", NULL,
    Tk_Offset(TabularItemStruct, connection_anchor), 0, ZN_COORDS_FLAG, False },
  { ZN_CONFIG_LABEL_FORMAT, "-labelformat", NULL,
    Tk_Offset(TabularItemStruct, field_set.label_format), 0,
    ZN_COORDS_FLAG|ZN_CLFC_FLAG, False },
  { ZN_CONFIG_UINT, "-numfields", NULL,
    Tk_Offset(TabularItemStruct, field_set.num_fields), 0, 0, True },
  { ZN_CONFIG_POINT, "-position", NULL,
    Tk_Offset(TabularItemStruct, pos), 0, ZN_COORDS_FLAG, False},
  { ZN_CONFIG_PRI, "-priority", NULL,
    Tk_Offset(TabularItemStruct, header.priority), 0,
    ZN_DRAW_FLAG|ZN_REPICK_FLAG, False },
  { ZN_CONFIG_BOOL, "-sensitive", NULL,
    Tk_Offset(TabularItemStruct, header.flags), ZN_SENSITIVE_BIT,
    ZN_REPICK_FLAG, False },
  { ZN_CONFIG_TAG_LIST, "-tags", NULL,
    Tk_Offset(TabularItemStruct, header.tags), 0, 0, False },
  { ZN_CONFIG_BOOL, "-visible", NULL,
    Tk_Offset(TabularItemStruct, header.flags), ZN_VISIBLE_BIT,
    ZN_DRAW_FLAG|ZN_REPICK_FLAG|ZN_VIS_FLAG, False },
  
  { ZN_CONFIG_END, NULL, NULL, 0, 0, 0, False }
};


/*
 **********************************************************************************
 *
 * Init --
 *
 **********************************************************************************
 */
static int
Init(ZnItem             item,
     int                *argc,
     Tcl_Obj *CONST     *args[])
{
  ZnWInfo       *wi = item->wi;
  TabularItem   tab = (TabularItem) item;
  ZnFieldSet    field_set = &tab->field_set;
  int           num_fields;

  item->priority = 1;

  SET(item->flags, ZN_VISIBLE_BIT);
  SET(item->flags, ZN_SENSITIVE_BIT);
  SET(item->flags, ZN_COMPOSE_ALPHA_BIT);
  SET(item->flags, ZN_COMPOSE_SCALE_BIT);
  SET(item->flags, ZN_COMPOSE_ROTATION_BIT);

  tab->anchor = TK_ANCHOR_NW;
  tab->connection_anchor = TK_ANCHOR_SW;
  tab->pos.x = tab->pos.y = 0.0;

  field_set->item = item;
  field_set->label_format = NULL;
  
  /*
   * Then try to see if some fields are needed.
   */
  if ((*argc > 0) && (Tcl_GetString((*args)[0])[0] != '-') &&
      (Tcl_GetIntFromObj(wi->interp, (*args)[0], &num_fields) != TCL_ERROR)) {
    field_set->num_fields = num_fields;
    *args += 1;
    *argc -= 1;
    ZnFIELD.InitFields(field_set);
  }
  else {
    Tcl_AppendResult(wi->interp, " number of fields expected", NULL);
    return TCL_ERROR;
  }    
  
  item->part_sensitive = 0;

  return TCL_OK;
}


/*
 **********************************************************************************
 *
 * Clone --
 *
 **********************************************************************************
 */
static void
Clone(ZnItem    item)
{
  ZnFieldSet    fs = &((TabularItem) item)->field_set;

  ZnFIELD.CloneFields(fs);
  fs->item = item;
}


/*
 **********************************************************************************
 *
 * Destroy --
 *
 **********************************************************************************
 */
static void
Destroy(ZnItem  item)
{
  ZnFIELD.FreeFields(&((TabularItem) item)->field_set);
}


/*
 **********************************************************************************
 *
 * Configure --
 *
 **********************************************************************************
 */
static int
Configure(ZnItem        item,
          int           argc,
          Tcl_Obj *CONST argv[],
          int           *flags)
{
  ZnItem        old_connected;

  old_connected = item->connected_item;
  if (ZnConfigureAttributes(item->wi, item, item, tabular_attrs,
                            argc, argv, flags) == TCL_ERROR) {
    return TCL_ERROR;
  }
  if (ISSET(*flags, ZN_ITEM_FLAG)) {
    /*
     * If the new connected item is not appropriate back up
     * to the old one.
     */
    if ((item->connected_item == ZN_NO_ITEM) ||
        (ISSET(item->connected_item->class->flags, ZN_CLASS_HAS_ANCHORS) &&
         (item->parent == item->connected_item->parent))) {
      ZnITEM.UpdateItemDependency(item, old_connected);
    }
    else {
      item->connected_item = old_connected;
    }
  }  

  return TCL_OK;
}


/*
 **********************************************************************************
 *
 * Query --
 *
 **********************************************************************************
 */
static int
Query(ZnItem            item,
      int               argc,
      Tcl_Obj *CONST    argv[])
{
  if (ZnQueryAttribute(item->wi->interp, item, tabular_attrs, argv[0]) == TCL_ERROR) {
    return TCL_ERROR;
  }

  return TCL_OK;
}


/*
 **********************************************************************************
 *
 * ComputeCoordinates --
 *
 **********************************************************************************
 */
static void
ComputeCoordinates(ZnItem       item,
                   ZnBool       force)
{
  TabularItem   tab = (TabularItem) item;
  ZnWInfo       *wi = item->wi;
  ZnFieldSet    field_set = &tab->field_set;
  ZnDim         width, height;
  
  ZnResetBBox(&item->item_bounding_box);
  if (field_set->label_format && field_set->num_fields) {
    ZnFIELD.GetLabelBBox(field_set, &width, &height);

    /*
     * The connected item support anchors, this is checked by
     * configure.
     */
    if (item->connected_item != ZN_NO_ITEM) {
      item->connected_item->class->GetAnchor(item->connected_item,
                                             tab->connection_anchor,
                                             &field_set->label_pos);
    }
    else {
      ZnPoint pos;
      pos.x = pos.y = 0;
      ZnTransformPoint(wi->current_transfo, &pos,
                          &field_set->label_pos);
    }

    ZnAnchor2Origin(&field_set->label_pos, width, height, tab->anchor,
                    &field_set->label_pos);

    /*
     * Setup the item bounding box.
     */
    item->item_bounding_box.orig = field_set->label_pos;
    item->item_bounding_box.corner.x = field_set->label_pos.x + width;
    item->item_bounding_box.corner.y = field_set->label_pos.y + height;
    /*
     * Need to slightly increase the bbox for GL thick lines
     */
#ifdef GL
    item->item_bounding_box.orig.x -= 1;
    item->item_bounding_box.orig.y -= 1;
    item->item_bounding_box.corner.x += 1;
    item->item_bounding_box.corner.y += 1;
#endif

    /*
     * Update connected items.
     */
    SET(item->flags, ZN_UPDATE_DEPENDENT_BIT);
  }
}


/*
 **********************************************************************************
 *
 * ToArea --
 *      Tell if the object is entirely outside (-1),
 *      entirely inside (1) or in between (0).
 *
 **********************************************************************************
 */
static int
ToArea(ZnItem   item,
       ZnToArea ta)
{
  return ZnFIELD.FieldsToArea(&((TabularItem) item)->field_set, ta->area);
}


/*
 **********************************************************************************
 *
 * Draw --
 *
 **********************************************************************************
 */
static void
Draw(ZnItem     item)
{
  ZnFIELD.DrawFields(&((TabularItem) item)->field_set);
}


/*
 **********************************************************************************
 *
 * Render --
 *
 **********************************************************************************
 */
static void
Render(ZnItem   item)
{
  ZnFIELD.RenderFields(&((TabularItem) item)->field_set);
}


/*
 **********************************************************************************
 *
 * IsSensitive --
 *
 **********************************************************************************
 */
static ZnBool
IsSensitive(ZnItem      item,
            int         item_part)
{
  if (ISCLEAR(item->flags, ZN_SENSITIVE_BIT) ||
      !item->parent->class->IsSensitive(item->parent, ZN_NO_PART)) {
    return False;
  }
  if (item_part == ZN_NO_PART) {
    return ISSET(item->flags, ZN_SENSITIVE_BIT);
  }
  else {    
    return ZnFIELD.IsFieldSensitive(&((TabularItem) item)->field_set, item_part);
  }
}


/*
 **********************************************************************************
 *
 * Pick --
 *      We tell what our label tells.
 *
 **********************************************************************************
 */
static double
Pick(ZnItem     item,
     ZnPick     ps)
{
  int best_part;
  double dist;
  
  dist = ZnFIELD.FieldsPick(&((TabularItem) item)->field_set, ps->point, &best_part);
  /*printf("tabular %d reporting part %d, distance %lf\n",
    item->id, best_part, dist);*/
  if (dist <= 0.0) {
    dist = 0.0;
  }
  
  ps->a_part = best_part;
  return dist;
}


/*
 **********************************************************************************
 *
 * PostScript --
 *
 **********************************************************************************
 */
static int
PostScript(ZnItem item,
           ZnBool prepass,
           ZnBBox *area)
{
  return ZnFIELD.PostScriptFields(&((TabularItem) item)->field_set, prepass, area);
}


/*
 **********************************************************************************
 *
 * GetFieldSet --
 *
 **********************************************************************************
 */
static ZnFieldSet
GetFieldSet(ZnItem      item)
{
  return &((TabularItem) item)->field_set;
}


/*
 **********************************************************************************
 *
 * GetAnchor --
 *
 **********************************************************************************
 */
static void
GetAnchor(ZnItem        item,
          Tk_Anchor     anchor,
          ZnPoint       *p)
{
  ZnFieldSet    field_set = &((TabularItem) item)->field_set;
  ZnDim width, height;
  
  if (field_set->label_format) {
    ZnFIELD.GetLabelBBox(field_set, &width, &height);
    ZnOrigin2Anchor(&field_set->label_pos, width, height, anchor, p);
  }
  else {
    p->x = p->y = 0.0;
  }
}


/*
 **********************************************************************************
 *
 * GetClipVertices --
 *      Get the clipping shape.
 *      Never ever call ZnTriFree on the tristrip returned by GetClipVertices.
 *
 **********************************************************************************
 */
static ZnBool
GetClipVertices(ZnItem          item,
                ZnTriStrip      *tristrip)
{
  ZnFieldSet    field_set = &((TabularItem) item)->field_set;
  ZnDim         width, height;
  ZnPoint       *points;
  
  if (field_set->label_format) {
    ZnFIELD.GetLabelBBox(field_set, &width, &height);
    ZnListAssertSize(ZnWorkPoints, 2);
    points = (ZnPoint *) ZnListArray(ZnWorkPoints);
    ZnTriStrip1(tristrip, points, 2, False);
    points[0] = field_set->label_pos;
    points[1].x = points[0].x + width;
    points[1].y = points[0].y + height;
  }

  return True;
}


/*
 **********************************************************************************
 *
 * Coords --
 *      Return or edit the item origin. This doesn't take care of
 *      the possible attachment. The change will be effective at the
 *      end of the attachment.
 *
 **********************************************************************************
 */
static int
Coords(ZnItem           item,
       int              contour,
       int              index,
       int              cmd,
       ZnPoint          **pts,
       char             **controls,
       unsigned int     *num_pts)
{
  TabularItem   tabular = (TabularItem) item;
  
  if ((cmd == ZN_COORDS_ADD) || (cmd == ZN_COORDS_ADD_LAST) || (cmd == ZN_COORDS_REMOVE)) {
    Tcl_AppendResult(item->wi->interp,
                     " tabulars can't add or remove vertices", NULL);
    return TCL_ERROR;
  }
  else if ((cmd == ZN_COORDS_REPLACE) || (cmd == ZN_COORDS_REPLACE_ALL)) {
    if (*num_pts == 0) {
      Tcl_AppendResult(item->wi->interp,
                       " coords command need 1 point on tabulars", NULL);
      return TCL_ERROR;
    }
    tabular->pos = (*pts)[0];
    ZnITEM.Invalidate(item, ZN_COORDS_FLAG);
  }
  else if ((cmd == ZN_COORDS_READ) || (cmd == ZN_COORDS_READ_ALL)) {
    *num_pts = 1;
    *pts = &tabular->pos;
  }
  return TCL_OK;
}


/*
 **********************************************************************************
 *
 * Part --
 *      Convert a private part from/to symbolic representation.
 *
 **********************************************************************************
 */
static int
Part(ZnItem     item,
     Tcl_Obj    **part_spec,
     int        *part)
{
  char  *part_str;
  char  *end;
  
  if (*part_spec) {
    part_str = Tcl_GetString(*part_spec);
    if (strlen(part_str) == 0) {
      *part = ZN_NO_PART;
    }
    else if (isdigit(part_str[0])) {
      *part = strtol(part_str, &end, 0);
      if ((*end != 0) || (*part < 0) ||
          ((unsigned int) *part >= ((TabularItem) item)->field_set.num_fields)) {
        goto part_error;
      }
    }
    else {
    part_error:
      Tcl_AppendResult(item->wi->interp, " invalid item part specification", NULL);
      return TCL_ERROR; 
    }
  }
  else {
    if (*part >= 0) {
      *part_spec = Tcl_NewIntObj(*part);
    }
    else {
      *part_spec = Tcl_NewStringObj("", -1);
    }
  }
  return TCL_OK;  
}


/*
 **********************************************************************************
 *
 * Index --
 *      Parse a text index and return its value and aa
 *      error status (standard Tcl result).
 *
 **********************************************************************************
 */
static int
Index(ZnItem    item,
      int       field,
      Tcl_Obj   *index_spec,
      int       *index)
{
  return ZnFIELD.FieldIndex(&((TabularItem) item)->field_set, field,
                          index_spec, index);
}


/*
 **********************************************************************************
 *
 * InsertChars --
 *
 **********************************************************************************
 */
static void
InsertChars(ZnItem      item,
            int         field,
            int         *index,
            char        *chars)
{
  if (ZnFIELD.FieldInsertChars(&((TabularItem) item)->field_set,
                             field, index, chars)) {
    ZnITEM.Invalidate(item, ZN_COORDS_FLAG);
  }
}


/*
 **********************************************************************************
 *
 * DeleteChars --
 *
 **********************************************************************************
 */
static void
DeleteChars(ZnItem      item,
            int         field,
            int         *first,
            int         *last)
{
  if (ZnFIELD.FieldDeleteChars(&((TabularItem) item)->field_set,
                             field, first, last)) {
    ZnITEM.Invalidate(item, ZN_COORDS_FLAG);
  }
}


/*
 **********************************************************************************
 *
 * Cursor --
 *
 **********************************************************************************
 */
static void
TabularCursor(ZnItem    item,
              int       field,
              int       index)
{
  ZnFIELD.FieldCursor(&((TabularItem) item)->field_set, field, index);
}


/*
 **********************************************************************************
 *
 * Selection --
 *
 **********************************************************************************
 */
static int
Selection(ZnItem        item,
          int           field,
          int           offset,
          char          *chars,
          int           max_chars)
{
  return ZnFIELD.FieldSelection(&((TabularItem) item)->field_set, field,
                              offset, chars, max_chars);
}


/*
 **********************************************************************************
 *
 * Exported functions structs --
 *
 **********************************************************************************
 */
static ZnItemClassStruct TABULAR_ITEM_CLASS = {
  "tabular",
  sizeof(TabularItemStruct),
  tabular_attrs,
  0,
  ZN_CLASS_HAS_ANCHORS|ZN_CLASS_ONE_COORD, /* flags */
  Tk_Offset(TabularItemStruct, pos),
  Init,
  Clone,
  Destroy,
  Configure,
  Query,
  GetFieldSet,
  GetAnchor,
  GetClipVertices,
  NULL,                 /* GetContours */
  Coords,
  InsertChars,
  DeleteChars,
  TabularCursor,
  Index,
  Part,
  Selection,
  NULL,                 /* Contour */
  ComputeCoordinates,
  ToArea,
  Draw,
  Render,
  IsSensitive,
  Pick,
  NULL,                 /* PickVertex */
  PostScript
};

ZnItemClassId ZnTabular = (ZnItemClassId) &TABULAR_ITEM_CLASS;