/*
 * Tabular.c -- Implementation of Tabular item.
 *
 * Authors		: Patrick Lecoanet.
 * Creation date	:
 *
 * $Id: Tabular.c,v 1.22 2004/03/03 10:16:24 lecoanet Exp $
 */

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


#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.22 2004/03/03 10:16:24 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) ||
	(item->connected_item->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 __unused,
      Tcl_Obj *CONST	argv[])
{
  if (ZnQueryAttribute(item->wi, item, tabular_attrs, argv[0]) == TCL_ERROR) {
    return TCL_ERROR;
  }

  return TCL_OK;
}


/*
 **********************************************************************************
 *
 * ComputeCoordinates --
 *
 **********************************************************************************
 */
static void
ComputeCoordinates(ZnItem	item,
		   ZnBool	force __unused)
{
  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;

    /*
     * 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 void
PostScript(ZnItem	item __unused,
	   ZnBool	prepass __unused)
{
}


/*
 **********************************************************************************
 *
 * 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(item->wi->work_pts, 2);
    points = (ZnPoint *) ZnListArray(item->wi->work_pts);
    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 __unused,
       int		index __unused,
       int		cmd,
       ZnPoint		**pts,
       char		**controls __unused,
       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 = {
  sizeof(TabularItemStruct),
  0,			/* num_parts */
  True,			/* has_anchors */
  "tabular",
  tabular_attrs,
  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;
