/*****************************************************************************/
/*                           Jean-Eloi Dussartre                             */
/*                          M.I.T. Project Athena.                           */
/*                              January 1990.                                */
/*                   The Spreadsheet Widget implementation.                  */
/*                     The AtTable Object implementation.                    */
/*****************************************************************************/

#include <Xm/Xm.h>
#include <X11/StringDefs.h>
#include <At/Text.h>
#include <Xm/Text.h>
#include <Xm/ScrollBar.h>
#include "Table.h"
#include "SpreadsheetP.h"

#ifndef MIN
#define MIN(a,b)	((a) < (b)) ? (a) : (b)
#endif
#ifndef MAX
#define MAX(a,b)	((a) > (b)) ? (a) : (b)
#endif


/* #include "SpreadsheetL.h" */

/* The Translation Table goes there. */

static char defaultTranslations [] =
    "<Btn1Down>: start-selection()\n\
";

static char cursorTranslations[] =
       "<Key>Delete:		delete-previous-character() \n\
	Meta<Key>Delete:	kill-previous-word() \n\
	Ctrl<Key>D:		delete-next-character() \n\
	Meta<Key>D:		kill-next-word() \n\
	Ctrl<Key>K:		kill-to-end-of-line() \n\
	Ctrl<Key>W:		kill-selection() \n\
	Ctrl<Key>Y:		unkill() \n\
	Ctrl<Key>F:		forward-character() \n\
	Ctrl<Key>B:		backward-character() \n\
	Ctrl<Key>A:		beginning-of-line() \n\
	Ctrl<Key>E:		end-of-line() \n\
	Meta<Key>F:		forward-word() \n\
	Meta<Key>B:		backward-word() \n\
	Ctrl<Key>Up:		AtSpreadsheetMoveCursor(t)\n\
	Ctrl<Key>Down:		AtSpreadsheetMoveCursor(b)\n\
	Ctrl<Key>Left:		AtSpreadsheetMoveCursor(<)\n\
	Ctrl<Key>Right:		AtSpreadsheetMoveCursor(>)\n\
	Shift<Key>Up:		AtSpreadsheetMoveCursor(U)\n\
	Shift<Key>Down:		AtSpreadsheetMoveCursor(D)\n\
	Shift<Key>Left:		AtSpreadsheetMoveCursor(L)\n\
	Shift<Key>Right:	AtSpreadsheetMoveCursor(R)\n\
	<Key>Up:		AtSpreadsheetMoveCursor(u)\n\
	<Key>Down:		AtSpreadsheetMoveCursor(d)\n\
	<Key>Left:		AtSpreadsheetMoveCursor(l)\n\
	<Key>Right:		AtSpreadsheetMoveCursor(r)\n\
	<Key>Tab:		AtSpreadsheetMoveCursor(r)\n\
	<Key>Return:		AtSpreadsheetMoveCursor(<) \
				AtSpreadsheetMoveCursor(d)\n\
	~Ctrl~Meta<Key>:	self-insert()\n\
";
    



/* The resource structures go here */

#define offset(FIELD)  XtOffset(AtSpreadsheetWidget, spreadsheet . FIELD)

static XtResource resources[] =
{
  {XtNtable,                 XtCTable,        XtRAtTable,sizeof(AtTableObject),
   offset(table),            XtRImmediate,      NULL                         },
  {XtNtopRow,                XtCTopRow,         XtRShort,    sizeof (short),
   offset(topRow),           XtRImmediate,      (caddr_t)0                   },
  {XtNleftColumn,            XtCLeftColumn,     XtRShort,    sizeof (short),
   offset(leftColumn),       XtRImmediate,      (caddr_t)0                   },
  {XtNcursorRow,             XtCCursorRow,      XtRShort,    sizeof (short),
   offset(cursorRow),        XtRImmediate,      (caddr_t)0                   },
  {XtNcursorColumn,          XtCCursorColumn,   XtRShort,    sizeof(short),
   offset(cursorColumn),     XtRImmediate,      (caddr_t)0                   },
  {XtNvisibleRows,           XtCVisibleRows,    XtRShort,    sizeof (short),
   offset(visibleRows),      XtRImmediate,      (caddr_t)AtSpreadsheetBESTFIT},
  {XtNvisibleColumns,        XtCVisibleColumns, XtRShort,    sizeof(short),
   offset(visibleColumns),   XtRImmediate,      (caddr_t)AtSpreadsheetBESTFIT},
  {XtNeditable,              XtCEditable,       XtRBoolean, sizeof (Boolean),
   offset(editable),         XtRImmediate,      (caddr_t)True                },
  {XtNverticalLines,         XtCGridLines,      XtRBoolean,  sizeof(Boolean),
   offset(verticalLines),    XtRImmediate,      (caddr_t)True                },
  {XtNhorizontalLines,       XtCGridLines,      XtRBoolean,  sizeof(Boolean),
   offset(horizontalLines),  XtRImmediate,      (caddr_t)True                },
  {XtNscrollVertical, XtCScroll, XtRBoolean, sizeof(Boolean),
   offset(scrollVertical), XtRImmediate, (caddr_t)True},
  {XtNscrollHorizontal, XtCScroll, XtRBoolean, sizeof(Boolean),
   offset(scrollHorizontal), XtRImmediate, (caddr_t)True},
  {XtNfontFamily,            XtCFontFamily,      XtRString,   sizeof(char *),
   offset(fontFamily),       XtRString,         "schoolbook"                 },
  {XtNfontSize,              XtCFontSize,       XtRInt,      sizeof(int),
   offset(fontSize),         XtRImmediate,      (caddr_t)AtFontNORMAL        },
  {XtNfontFace,              XtCFontFace,       XtRInt,      sizeof(int),
   offset(face),             XtRImmediate,      (caddr_t)AtFontPLAIN         },
  {XtNjustification,         XtCJustification,  XtRShort,    sizeof(short),
   offset(justification),    XtRImmediate,(caddr_t)AtTextJUSTIFY_RIGHT},
  {XtNrowLabelJustification, XtCLabelJustification, XtRShort,sizeof(short),
   offset(rowLabelJustification), XtRImmediate,(caddr_t)AtTextJUSTIFY_RIGHT},
  {XtNcolumnLabelJustification, XtCLabelJustification, XtRShort,sizeof(short),
  offset(columnLabelJustification),XtRImmediate,(caddr_t)AtTextJUSTIFY_CENTER},
  {XtNcolumnWidth,           XtCColumnWidth,    XtRShort,    sizeof(short),
   offset(columnWidth),      XtRImmediate,      (caddr_t)16                  },
  {XtNrowHeight,             XtCRowHeight,      XtRShort,    sizeof(short),
   offset(rowHeight),        XtRImmediate,      (caddr_t)AtSpreadsheetBESTFIT},
  {XtNlineWidth,             XtCLineWidth,      XtRShort,    sizeof(short),
   offset(lineWidth),        XtRImmediate,      (caddr_t)0                   },
  {XtNlineStyle,             XtCLineStyle,      XtRShort,    sizeof(short),
   offset(lineStyle),        XtRImmediate,      (caddr_t)0                   },
  {XtNdashList,              XtCDashList,       XtRShort,    sizeof(short),
   offset(dashList),         XtRImmediate,      (caddr_t)1                   },
  {XtNborderSpace,           XtCBorderSpace,    XtRShort,    sizeof(short),
   offset(borderSpace),      XtRImmediate,      (caddr_t)2                   },
  {XtNmarginHeight,          XtCMarginHeight,    XtRShort,    sizeof(short),
   offset(marginHeight),     XtRImmediate,      (caddr_t)3                   },
  {XtNmarginWidth,           XtCMarginWidth,    XtRShort,    sizeof(short),
   offset(marginWidth),      XtRImmediate,      (caddr_t)3                   },
  {XtNcolumnNameHeight,      XtCRowHeight,       XtRShort,    sizeof(short),
   offset(columnNameHeight), XtRImmediate,      (caddr_t)AtSpreadsheetBESTFIT},
  {XtNrowNameWidth,          XtCRowNameWidth,   XtRShort,      sizeof(short),
   offset(rowNameWidth),     XtRImmediate,      (caddr_t)AtSpreadsheetBESTFIT},
  {XtNrowLabelColor,         XtCForeground,     XtRPixel,      sizeof(Pixel),
   offset(rowLabelColor),    XtRString,         (caddr_t)XtDefaultForeground},
  {XtNcolumnLabelColor,      XtCForeground,     XtRPixel,      sizeof(Pixel),
   offset(columnLabelColor), XtRString,         (caddr_t)XtDefaultForeground },
  {XtNlineColor,             XtCForeground,     XtRPixel,      sizeof(Pixel),
   offset(lineColor),        XtRString,         (caddr_t)XtDefaultForeground },
  {XtNcellColor,             XtCForeground,     XtRPixel,      sizeof(Pixel),
   offset(cellColor),        XtRString,         (caddr_t)XtDefaultForeground },
  {XtNrangeChangedCallback,  XtCRangeChangedCallback, XtRCallback,
       sizeof(XtCallbackList),offset(rangeChangedCallback), XtRCallback, NULL},
  {XtNcursorMovedCallback,   XtCCursorMovedCallback, XtRCallback,
       sizeof(XtCallbackList), offset(cursorMovedCallback), XtRCallback, NULL},
  {XtNrangeSelectedCallback, XtCRangeSelectedCallback, XtRCallback,
       sizeof(XtCallbackList), offset(rangeSelectedCallback),XtRCallback,NULL},
  {XtNscrollCallback, XtCScrollCallback, XtRCallback,
       sizeof(XtCallbackList), offset(scrollCallback),XtRCallback,NULL},
};

#undef offset

/* Entry point declaration */

static void ClassInitialize (Widget);
static void Initialize      (AtSpreadsheetWidget, AtSpreadsheetWidget);
static void Resize          (AtSpreadsheetWidget);
static void Destroy         (AtSpreadsheetWidget);
static void Redisplay       (AtSpreadsheetWidget, XExposeEvent *, Region);
static Boolean SetValues    (AtSpreadsheetWidget,  /*  current */
			     AtSpreadsheetWidget,  /*  request */
			     AtSpreadsheetWidget); /*  new     */
static void Realize(AtSpreadsheetWidget,XtValueMask *,XSetWindowAttributes *);

#ifdef notdef
static void CalcCellPositions(AtSpreadsheetWidget);
#endif

static void DrawGrid(AtSpreadsheetWidget, int, int, int, int);
#define     DrawAllGrid(w)		DrawGrid((w), \
					  -1, -1, \
					  (w)->spreadsheet.rightColumn, \
					  (w)->spreadsheet.bottomRow)
static void DrawRowLabels(AtSpreadsheetWidget, int, int);
#define     DrawAllRowLabels(w)		DrawRowLabels((w), \
					  (w)->spreadsheet.topRow, \
					  (w)->spreadsheet.bottomRow)
static void DrawColumnLabels(AtSpreadsheetWidget, int, int);
#define     DrawAllColumnLabels(w)	DrawColumnLabels((w), \
					  (w)->spreadsheet.leftColumn, \
					  (w)->spreadsheet.rightColumn)
static void DrawCells(AtSpreadsheetWidget, int, int, int, int);
#define     DrawCell(w, x, y)		DrawCells((w), (x), (y), (x), (y))
#define     DrawAllCells(w)		DrawCells((w), \
					  (w)->spreadsheet.leftColumn, \
					  (w)->spreadsheet.topRow, \
					  (w)->spreadsheet.rightColumn, \
					  (w)->spreadsheet.bottomRow)
static void InitColumns(AtSpreadsheetWidget);
static void InitRows(AtSpreadsheetWidget);
static void SetupCursor(AtSpreadsheetWidget);
static void AdjustScrollbars(AtSpreadsheetWidget);
static void MoveCursorTo(AtSpreadsheetWidget, int, int);
static void SetUpperLeftCell(AtSpreadsheetWidget, int, int, Boolean);
static void SetEditedValueInTable(AtSpreadsheetWidget);
static int PixelToCol(AtSpreadsheetWidget, int);
static int PixelToRow(AtSpreadsheetWidget, int);
static void VerticalScroll(Widget, AtSpreadsheetWidget,
			   XmScrollBarCallbackStruct *);
static void HorizontalScroll(Widget, AtSpreadsheetWidget,
			     XmScrollBarCallbackStruct *);
static void MoveCursor(Widget, XEvent *, String *, Cardinal *);
static void StartSelection(AtSpreadsheetWidget, XButtonPressedEvent *);


/* Action Record */
static XtActionsRec actionTable [] = {
{   "start-selection", StartSelection },
};


/*
 * since the cursor text widget always has the keyboard focus,
 * it has to invoke this action on the spreadsheet widget.
 * ClassInitialize registers it.
 * This is kind of a kludge.
 */
static XtActionsRec AtSpreadsheetActions [] = {
  {"AtSpreadsheetMoveCursor",      MoveCursor},
};


#define superclass (&widgetClassRec)

AtSpreadsheetClassRec atSpreadsheetClassRec = {
  {
    /* superclass	  */	(WidgetClass) superclass,
    /* class_name	  */	"AtSpreadsheet",
    /* widget_size	  */	sizeof(AtSpreadsheetRec),
    /* class_initialize   */    ClassInitialize,
    /* chained class init */	NULL,
    /* class_inited       */	FALSE,
    /* initialize	  */	Initialize,
    /* initialize hook    */    NULL,
    /* realize		  */	Realize,
    /* actions		  */	actionTable,
    /* num_actions	  */    XtNumber(actionTable),
    /* resources	  */	resources,
    /* num_resources	  */	XtNumber(resources),
    /* xrm_class	  */	NULLQUARK,
    /* compress_motion	  */	TRUE,
    /* compress_exposure  */	TRUE,
    /* compress enter/exit*/    TRUE,
    /* visible_interest	  */	FALSE,
    /* destroy		  */	Destroy,
    /* resize		  */	Resize,
    /* expose		  */	Redisplay,
    /* set_values	  */	SetValues,
    /* set values hook    */    NULL,
    /* set values almost  */    XtInheritSetValuesAlmost,
    /* get values hook    */    NULL,
    /* accept_focus	  */	NULL,
    /* version            */    XtVersion,
    /* callback offsetlst */    NULL,
    /* default trans      */    defaultTranslations,
    /* query geometry	  */    XtInheritQueryGeometry,
    /* display accelerator*/	NULL,
    /* extension          */    NULL,
  },

};

WidgetClass atSpreadsheetWidgetClass =  (WidgetClass) &atSpreadsheetClassRec;

/* the Class intitialize procedure is called once, the first time
 * an instance of a class is created.  Use it to register converters.
 * You probably won't need it.  If you do, you've got to put a pointer
 * to it in the class record initialization above.
 */
static void ClassInitialize(Widget w)
{
  XtAddActions(AtSpreadsheetActions, XtNumber(AtSpreadsheetActions));
}


/*
 * The initialize procedure does any necessary initialization of the
 * widget.  The resources are all in place, and all the superclasses
 * have already done their initialization.  Make all changes to the
 * widget new.  Treat req as a read-only copy of the widget as it was
 * first requested -- ie.  only the resources are set, no initialization
 * has been done.  You will probably not have to use req at all.
 * This procedure is where you do XtGetGC etc.
 */
static void Initialize(AtSpreadsheetWidget req, AtSpreadsheetWidget new)

{
  AtSpreadsheetPart *ss = &new->spreadsheet;
  XGCValues gcv;
  Arg al[10];
  int ac;
  XFontStruct *font;

  /* go get fonts */
  ss->ff = AtFontFamilyGet(XtDisplay(new), ss->fontFamily);
  font = AtFontFetch(ss->ff, AtFontPLAIN,ss->fontSize);
  /* how big is an "em", and other font statistics */
  ss->emsize = XTextWidth(font , "m", 1);
  ss->ascent = font->ascent;
  ss->descent = font->descent;
  
  if (ss->rowHeight == AtSpreadsheetBESTFIT) {
      ss->rowHeight = ss->ascent + ss->descent;
  };
  if (ss->columnNameHeight == AtSpreadsheetBESTFIT) {
      ss->columnNameHeight = ss->ascent + ss->descent;
  };

  /* get the size of the table */
  AtTableGetSize(ss->table, 
		 &ss->ncols, &ss->nrows);
  InitColumns(new);
  InitRows(new);

  /* create the scrollbars */
  ac = 0;
  XtSetArg(al[ac], XmNminimum, 0); ac++;
  XtSetArg(al[ac], XmNmaximum, ss->nrows); ac++;
  XtSetArg(al[ac], XmNsliderSize, 1); ac++;
  ss->vscroll = XmCreateScrollBar(new, "vscrollbar", al, ac);
  ac = 0;
  XtSetArg(al[ac], XmNminimum, 0); ac++;
  XtSetArg(al[ac], XmNmaximum, ss->ncols); ac++;
  XtSetArg(al[ac], XmNsliderSize, 1); ac++;
  XtSetArg(al[ac], XmNorientation, XmHORIZONTAL); ac++;
  ss->hscroll = XmCreateScrollBar(new, "hscrollbar", al, ac);

  /* hook up the scrollbars */
  XtAddCallback(ss->vscroll, XmNdragCallback, VerticalScroll, new);
  XtAddCallback(ss->vscroll, XmNvalueChangedCallback, VerticalScroll, new);
  XtAddCallback(ss->hscroll, XmNdragCallback, HorizontalScroll, new);
  XtAddCallback(ss->hscroll, XmNvalueChangedCallback, HorizontalScroll, new);
  
  /* get a GC for drawing the grid */
  gcv.foreground = ss->lineColor;
  gcv.line_width = ss->lineWidth;
  gcv.dashes = ss->dashList;
  gcv.line_style = ss->lineStyle;
  ss->gridGC = XtGetGC(new,
		       GCForeground | GCLineWidth | GCDashList | GCLineStyle,
		       &gcv);

  /* create the cursor/input widget */
  ac = 0;
  XtSetArg(al[ac], XmNfontList,
	   XmFontListCreate(AtFontFetch(ss->ff, AtFontPLAIN, ss->fontSize),
			    XmSTRING_DEFAULT_CHARSET)); ac++;
  XtSetArg(al[ac], XtNtranslations,
	   XtParseTranslationTable(cursorTranslations)); ac++;
  XtSetArg(al[ac], XmNmarginWidth, ss->marginWidth - 3); ac++;
  XtSetArg(al[ac], XmNmarginHeight, ss->marginHeight - 3); ac++;
  ss->cursor = XmCreateText(new, "cursor", al, ac);
  XtSetKeyboardFocus(new, ss->cursor);
  XtAddCallback(ss->cursor, XmNactivateCallback, SetEditedValueInTable, NULL);

  /* get initial size and layout the widget */
  if (req->core.width == 0) new->core.width = 0;
  if (req->core.height == 0) new->core.height = 0;
  Resize(new);
}


/*
 * This procedure is called in preparation for destroying a widget.
 * It should free up any memory or other resources associated
 * with the widget.  Notably, it should call XtFreeGC on all GCs.
 * it should not free up any resources of its superclass
 */
static void Destroy(AtSpreadsheetWidget w)
{
  AtSpreadsheetPart *ss = &w->spreadsheet;
  int i;
    
  /* Free Font structures.  */
  AtFontFamilyRelease(ss->ff);

  /* Free col, row labels */
  for(i=0; i < ss->ncols; i++)
    AtTextDestroy(ss->cols[i].label);
  for(i=0; i < ss->nrows; i++)
    AtTextDestroy(ss->rows[i].label);

  /* Free rows and columns */
  XtFree(ss->cols);
  XtFree(ss->rows);
    
  /* free up GCs */
  if (ss->gc != None) XFreeGC(XtDisplay(w), ss->gc);
  XtReleaseGC(w, ss->gridGC);
}

/*
 * This procedure is called when the widget has been resized.
 * It should recalculate any internal information the widget needs
 * to display itself but is not responsible for redisplaying the
 * widget.  w->core.width and w->core.height contain the new size
 * in pixels of the widget.  The x,y, and border_width fields of the
 * widget are also valid.
 */
static void Resize(AtSpreadsheetWidget w)
{
    AtSpreadsheetPart *ss = &w->spreadsheet;
    int scrollwidth, scrollheight;

    scrollwidth = (ss->scrollVertical)?ss->vscroll->core.width:0;
    scrollheight = (ss->scrollHorizontal)?ss->hscroll->core.height:0;
    
    if (w->core.width == 0)	/* usual case when called from initialize */
      {
	w->core.width = ss->x0 + scrollwidth +
	  ss->visibleColumns*(2*ss->marginWidth+ss->columnWidth*ss->emsize);
      }
    else
      {
	ss->visibleColumns = (w->core.width - ss->x0 - scrollwidth) /
	  (2*ss->marginWidth + ss->columnWidth * ss->emsize);
	ss->partialColumn = ((ss->visibleColumns == ss->ncols)
			     ? 0
			     : ((w->core.width - ss->x0 - scrollwidth) %
				(2*ss->marginWidth
				 + ss->columnWidth * ss->emsize)));
      }

    if (ss->visibleColumns > ss->ncols)
      ss->visibleColumns = ss->ncols;
    ss->rightColumn = ss->leftColumn + ss->visibleColumns - 1;
    if (ss->rightColumn >= ss->ncols)
      {
	ss->rightColumn = ss->ncols -1;
	ss->leftColumn = ss->rightColumn - ss->visibleColumns + 1;
      }
    
    if (w->core.height == 0)	/* usual case when called from initialize */
      {
	w->core.height = ss->y0 + scrollheight +
	  ss->visibleRows * (2 * ss->marginHeight + ss->rowHeight);
      }
    else
      {
	ss->visibleRows = (w->core.height - ss->y0 - scrollheight) /
	  (2*ss->marginHeight + ss->rowHeight);
	ss->partialRow = ((ss->visibleRows == ss->nrows)
			  ? 0
			  : ((w->core.height - ss->y0 - scrollheight) %
			     (2*ss->marginHeight + ss->rowHeight)));
      }
    if (ss->visibleRows > ss->nrows) ss->visibleRows = ss->nrows;
    ss->bottomRow = ss->topRow + ss->visibleRows - 1;
    if (ss->bottomRow >= ss->nrows) {
	ss->bottomRow = ss->nrows -1;
	ss->topRow = ss->bottomRow - ss->visibleRows +1;
    }

    /* position the cursor */
    SetupCursor(w);
    
    /* position the scrollbars */
    if (ss->vscroll) 
	XtConfigureWidget(ss->vscroll,
			  w->core.width - scrollwidth, ss->y0,
			  scrollwidth, w->core.height-ss->y0-scrollheight, 0);
    
    if (ss->hscroll)
	XtConfigureWidget(ss->hscroll,
			  ss->x0, w->core.height - scrollheight,
			  w->core.width - ss->x0 - scrollwidth,scrollheight,0);
    
    /* set up scrollbar values */
    AdjustScrollbars(w);
}




/*
 * The redisplay procedure is called when the widget's window
 * gets an expose event.  If the compress_exposure field in the
 * class structure is True, then it is only called once for each
 * series of exposure events, and the region parameter contains the
 * region of the window that needs to be re-drawn.
 * The simple approach is to redraw the entire widget each time.
 * More complicated, but more efficient, is only to redraw the region
 * that needs it.  See sections 7.9.3 and 7.10.1 and 7.10.2
 */
static void Redisplay(AtSpreadsheetWidget w,
		      XExposeEvent *event,
		      Region region)
{
  int x1, y1, x2, y2;
  AtSpreadsheetPart *ss = &w->spreadsheet;

  /*
   * Each time the cursor moves, a single cell needs to be refreshed.
   * This sort of expose event occurs often and is handled by the
   * first special case below
   * XXX This procedure could be more efficient and better structured.
   * Sometimes it doesn't refresh grid lines when it should.
   */
  if (event == NULL)		/* SetUpperLeftCell calls this proc w/NULL */
    {
      DrawAllGrid(w);
      DrawAllRowLabels(w);
      DrawAllColumnLabels(w);
      DrawAllCells(w);
      return;
    }

  x1 = PixelToCol(w, event->x);
  x2 = MIN(ss->ncols - 1, PixelToCol(w, event->x + event->width-1));
  y1 = PixelToRow(w, event->y);
  y2 = MIN(ss->nrows - 1, PixelToRow(w, event->y + event->height-1));

  DrawGrid(w, x1, y1, x2, y2);

  if (x1 == -1)
    {
      x1 = ss->leftColumn;
      DrawRowLabels(w, ((y1 == -1) ? ss->topRow : y1), y2);
    }

  if (y1 == -1)
    {
      y1 = ss->topRow;
      DrawColumnLabels(w, x1, x2);
    }

  DrawCells(w, x1, y1, x2, y2);
}


static void Realize(AtSpreadsheetWidget w, XtValueMask *vm,
		    XSetWindowAttributes *wa)
{
    XGCValues gcv;
    
    (superclass->core_class.realize)(w, vm, wa);
    
    gcv.foreground = w->spreadsheet.cellColor;
    w->spreadsheet.gc = XCreateGC(XtDisplay(w),XtWindow(w),GCForeground,&gcv);

    if (w->spreadsheet.editable) {
	XtRealizeWidget(w->spreadsheet.cursor);
	SetupCursor(w);
    }
    if (w->spreadsheet.scrollVertical) {
	XtRealizeWidget(w->spreadsheet.vscroll);
	XtMapWidget(w->spreadsheet.vscroll);
    }
    if (w->spreadsheet.scrollHorizontal) {
	XtRealizeWidget(w->spreadsheet.hscroll);
	XtMapWidget(w->spreadsheet.hscroll);
    }
}





/*
 * The SetValues procedure is called when the user invokes XtSetValues.
 * it is responsible for checking what resources have been changed and
 * recomputing anything that needs it.  An example is getting an new GC
 * if a font resource changes.
 *
 * The SetValues procedure for all the superclasses is called first,
 * so don't mess with superclass fields unless you want to override them.
 *
 * Make your changes to the widget new.  The widgets current and request
 * should be treated as read-only.  current is the widget before the
 * user called XtSetValues and request is exactly what the user asked
 * for in that call, before any of the superclass SetValues procedures
 * h ad been called.
 *
 * The way to tell if a resource has changed is to compare its value
 * in the new widget with its values in the current widget.
 *
 * Return True if any of the resource changes will require the widget
 * to be re-displayed.  Otherwise return False.
 *
 * See also section 9.7.2.1
 */
static Boolean SetValues (AtSpreadsheetWidget current,
			  AtSpreadsheetWidget request,
			  AtSpreadsheetWidget new)
{
  return False;
}


#define GridLineX(w,i) (w->spreadsheet.cols[i].position - \
		    w->spreadsheet.cols[w->spreadsheet.leftColumn].position + \
		    w->spreadsheet.x0)
#define GridLineY(w,i) (w->spreadsheet.rows[i].position - \
		    w->spreadsheet.rows[w->spreadsheet.topRow].position + \
		    w->spreadsheet.y0)
#define CellLabelX(w,i) (GridLineX(w,i) + w->spreadsheet.marginWidth)
#define CellLabelY(w,j) (GridLineY(w,j) + 1 +w->spreadsheet.marginHeight\
			 + w->spreadsheet.ascent)

static void InitColumns(AtSpreadsheetWidget w)
{
  AtSpreadsheetPart *ss = &w->spreadsheet;
  int i;
  int h;

  ss->cols =(Column *) XtMalloc(ss->ncols * sizeof(Column));
  ss->columnNameHeight = 0;
  for(i=0; i  < ss->ncols; i++) {
    ss->cols[i].width = ss->columnWidth * ss->emsize + 2*ss->marginWidth;
    ss->cols[i].label = AtTextCreate(AtTableGetColumnName(ss->table, i),
				     ss->ff, ss->fontSize);
    /* XXX memory leak ? */
    if (i==0) ss->cols[i].position = 0;
    else ss->cols[i].position = ss->cols[i-1].position + ss->cols[i-1].width;
    
    h = AtTextHeight(ss->cols[i].label);
    if (h > ss->columnNameHeight) ss->columnNameHeight = h;
  }
  ss->y0 = 2*ss->marginWidth + ss->columnNameHeight +
                         ss->lineWidth + ss->borderSpace;
}

static void InitRows(AtSpreadsheetWidget w)
{
  AtSpreadsheetPart *ss = &w->spreadsheet;
  int i;
  int wid;

  ss->rows =(Row *) XtMalloc(ss->nrows * sizeof(Row));
  ss->rowNameWidth = 0;
  for(i=0; i  < ss->nrows; i++) {
    ss->rows[i].height = ss->ascent + ss->descent + 2*ss->marginHeight;
    ss->rows[i].label = AtTextCreate(AtTableGetRowName(ss->table, i),
				     ss->ff, ss->fontSize);
    /* XXX memory leak ? */
    if (i==0) ss->rows[i].position = 0;
    else ss->rows[i].position = ss->rows[i-1].position + ss->rows[i-1].height;
    
    wid = AtTextWidth(ss->rows[i].label);
    if (wid > ss->rowNameWidth) ss->rowNameWidth = wid;
  }
  ss->x0 = 2*ss->marginWidth + ss->rowNameWidth +
      ss->lineWidth + ss->borderSpace;
}


static int PixelToCol(AtSpreadsheetWidget w, int x)
{
  AtSpreadsheetPart *ss = &w->spreadsheet;
  int i, max;

  if (x <= ss->x0) return -1;	/* pixel is in row label area */

  x += (ss->cols[ss->leftColumn].position - ss->x0);
  max = MIN(ss->rightColumn + 1, ss->ncols - 1);
  for(i = ss->leftColumn; i <= max; i++)
    if (x <= ss->cols[i].position) break;
  return i-1;
}

static int PixelToRow(AtSpreadsheetWidget w, int y)
{
  AtSpreadsheetPart *ss = &w->spreadsheet;
  int i, max;

  if (y <= ss->y0) return -1;	/* pixel is in col label area */

  y += (ss->rows[ss->topRow].position - ss->y0);
  max = MIN(ss->bottomRow + 1, ss->nrows - 1);
  for(i = ss->topRow; i <= max; i++)
    if (y < ss->rows[i].position) break;
  return i-1;
}


static void DrawCells(AtSpreadsheetWidget w, int x1, int y1, int x2, int y2)
{
  int i,j;
  XRectangle cliprect;
  AtSpreadsheetPart *ss = &w->spreadsheet;
  AtText *at_text;

  cliprect.x = cliprect.y = 0;
  XSetForeground(XtDisplay(w), ss->gc, ss->cellColor);
  XSetFont(XtDisplay(w), ss->gc,
	   AtFontFetch(ss->ff, AtFontPLAIN, ss->fontSize)->fid);

  if (x2 == ss->rightColumn  &&  x2 != ss->ncols - 1  &&  ss->partialColumn)
    x2++;
  if (y2 == ss->bottomRow  &&  y2 != ss->nrows - 1  &&  ss->partialRow)
    y2++;

  for(i = x1; i <= x2; i++)
    {
      cliprect.width = ss->cols[i].width;
      cliprect.height = w->core.height;
      XSetClipRectangles(XtDisplay(w), ss->gc,
			 GridLineX(w,i),0,
			 &cliprect, 1, YXBanded);

      for(j = y1; j <= y2; j++)
	{
	  at_text = AtTextCreate(AtTableGetCellValue(ss->table, i, j),
				 ss->ff, ss->fontSize);
	  AtTextDrawJustified(XtDisplay(w), XtWindow(w), ss->gc,
			      at_text,
			      ss->justification, AtTextJUSTIFY_CENTER,
			      CellLabelX(w,i), GridLineY(w,j) + 1,
			      (int)ss->cols[i].width - 2*ss->marginWidth,
			      (int)ss->rows[j].height /*columnNameHeight*/);
	}
    }
  XSetClipMask(XtDisplay(w), ss->gc, None); /* turn off clipping */
}


static void DrawRowLabels(AtSpreadsheetWidget w, int from, int to)
{
  AtSpreadsheetPart *ss = &w->spreadsheet;
  int i;
  XRectangle cliprect;

  cliprect.x = cliprect.y = 0;
  XSetForeground(XtDisplay(w), ss->gc, ss->rowLabelColor);

  if (to == ss->bottomRow  &&  to != ss->nrows - 1 &&  ss->partialRow)
    to++;

  for(i = from; i <= to; i++)
    {
      cliprect.width = w->core.width;
      cliprect.height = ss->rows[i].height;
      XSetClipRectangles(XtDisplay(w), ss->gc,
			 0, GridLineY(w,i),
			 &cliprect, 1, YXBanded);

      AtTextDrawJustified(XtDisplay(w), XtWindow(w), ss->gc,
			  ss->rows[i].label, 
			  ss->rowLabelJustification, AtTextJUSTIFY_CENTER,
			  (int)ss->marginWidth, GridLineY(w,i) + 1,
			  (int)ss->rowNameWidth, (int)ss->rows[i].height);
    }
  XSetClipMask(XtDisplay(w), ss->gc, None); /* turn off clipping */
}

static void DrawColumnLabels(AtSpreadsheetWidget w, int from, int to)
{
  AtSpreadsheetPart *ss = &w->spreadsheet;
  int i;
  XRectangle cliprect;

  cliprect.x = cliprect.y = 0;
  XSetForeground(XtDisplay(w), ss->gc, ss->columnLabelColor);

  if (to == ss->rightColumn  &&  to != ss->ncols - 1  &&  ss->partialColumn)
    to++;

  for(i = from; i <= to; i++)
    {
      cliprect.width = ss->cols[i].width;
      cliprect.height = w->core.height;
      XSetClipRectangles(XtDisplay(w), ss->gc,
			 GridLineX(w,i),0,
			 &cliprect, 1, YXBanded);

      AtTextDrawJustified(XtDisplay(w), XtWindow(w), ss->gc,
			  ss->cols[i].label, 
			  ss->columnLabelJustification, AtTextJUSTIFY_CENTER,
			  CellLabelX(w,i), (int)ss->marginHeight,
			  (int)ss->cols[i].width - 2*ss->marginWidth,
			  (int)ss->columnNameHeight);
    }
  XSetClipMask(XtDisplay(w), ss->gc, None); /* turn off clipping */
}

static void DrawGrid(AtSpreadsheetWidget w, int x1, int y1, int x2, int y2)
{
  AtSpreadsheetPart *ss = &w->spreadsheet;
  int i, tmp, max_x, max_y;
  int min_x = 0, min_y = 0;

  if (x2 == ss->rightColumn  &&  x2 != ss->ncols - 1  &&  ss->partialColumn)
    x2++;
  if (y2 == ss->bottomRow  &&  y2 != ss->nrows - 1  &&  ss->partialRow)
    y2++;

  tmp = ss->ncols - 1;
  max_x = MIN(MIN(GridLineX(w, tmp) + ss->cols[tmp].width,
		  GridLineX(w, x2) + ss->cols[x2].width),
	      w->core.width);
  tmp = ss->nrows - 1;
  max_y = MIN(MIN(GridLineY(w, tmp) + ss->rows[tmp].height,
		  GridLineY(w, y2) + ss->rows[y2].height),
	      w->core.height);

  if (x1 != -1)
    min_x = GridLineX(w,x1);

  if (y1 != -1)
    min_y = GridLineY(w,y1);

  if (x1 == -1)
    {
      XDrawLine(XtDisplay(w), XtWindow(w), ss->gridGC,
		ss->x0, min_y, ss->x0, w->core.height);
      XDrawLine(XtDisplay(w), XtWindow(w), ss->gridGC,
		ss->x0 - ss->lineWidth - ss->borderSpace, min_y,
		ss->x0 - ss->lineWidth - ss->borderSpace, w->core.height);
      x1 = ss->leftColumn;
    }

  if (y1 == -1)
    {
      XDrawLine(XtDisplay(w), XtWindow(w), ss->gridGC,
		min_x, ss->y0, w->core.width, ss->y0);
      XDrawLine(XtDisplay(w), XtWindow(w), ss->gridGC,
		min_x, ss->y0 - ss->lineWidth - ss->borderSpace,
		w->core.width, ss->y0 - ss->lineWidth - ss->borderSpace);
      y1 = ss->topRow;
    }

  printf("min(x,y)= %d, %d\tmax(x,y)= %d, %d\tx1,y1,x2,y2= %d,%d,%d,%d\n",
	 min_x, min_y, max_x, max_y,
	 x1, y1, x2, y2);

  if (ss->verticalLines)
    for(i = x1; i <= x2; i++)
      {
	tmp = GridLineX(w,i) + ss->cols[i].width;
	XDrawLine(XtDisplay(w), XtWindow(w), ss->gridGC,
		  tmp, min_y, tmp, max_y);
      }

  if (ss->horizontalLines)
    for(i = y1; i <= y2; i++)
      {
	tmp = GridLineY(w,i) + ss->rows[i].height;
	XDrawLine(XtDisplay(w), XtWindow(w), ss->gridGC,
		  min_x, tmp, max_x, tmp);
      }
}
  

static void SetupCursor(AtSpreadsheetWidget w)
{
    AtSpreadsheetPart *ss = &w->spreadsheet;
    int i = ss->cursorColumn;
    int j = ss->cursorRow;
    char *value;

    if (!XtIsRealized(ss->cursor)) return;

    /* XXX
     * moving the cursor like this causes an expose event for the
     * spreadsheet, over the cell the cursor was obscuring.
     * currently the expose routine does a complete redraw,
     * which is mongo inefficient...
     */
    
    if 	((i < ss->leftColumn) || (i > ss->rightColumn) ||
	 (j < ss->topRow) || (j > ss->bottomRow))
	XtUnmapWidget(ss->cursor);
    else 
	XtMapWidget(ss->cursor);

    XtConfigureWidget(ss->cursor,
		      GridLineX(w,i)+1,
		      GridLineY(w,j)+1,
		      ss->cols[i].width-1,
		      ss->rows[j].height-1,
		      ss->lineWidth);

    value = AtTableGetCellValue(ss->table,i,j);
    XmTextSetString(ss->cursor, value);
    XmTextSetInsertionPosition(ss->cursor, strlen(value));
}

static void AdjustScrollbars(AtSpreadsheetWidget w)
{
    AtSpreadsheetPart *ss = &w->spreadsheet;

    XmScrollBarSetValues(ss->vscroll, ss->topRow, ss->visibleRows,
			 1, ss->visibleRows, False);
    XmScrollBarSetValues(ss->hscroll,ss->leftColumn,ss->visibleColumns,
			 1, ss->visibleColumns, False);
}

static void MoveCursorTo(AtSpreadsheetWidget w, int i, int j)
{
    AtSpreadsheetPart *ss = &w->spreadsheet;
       
    if ((i != ss->cursorColumn) || (j != ss->cursorRow))  {
	ss->cursorColumn = i;
	ss->cursorRow = j;
	SetupCursor(w);
    }
}

static void SetUpperLeftCell(AtSpreadsheetWidget w, int col, int row,
			     Boolean adjust)
{
  AtSpreadsheetPart *ss = &w->spreadsheet;

  if (col < 0)
    col = 0;
  else if (col > ss->ncols - 1)
    col = ss->ncols - 1;
  if (row < 0)
    row = 0;
  else if (row > ss->nrows - 1)
    row = ss->nrows-1;

  if ((col != ss->leftColumn) && (row != ss->topRow))
    {
      XClearWindow(XtDisplay(w), XtWindow(w));
    }

  if (col != ss->leftColumn)
    {
      if (col < ss->leftColumn)
	if (ss->leftColumn - col < ss->visibleColumns)
	  {
	  }
      if (col > ss->rightColumn)
	if (col - ss->rightColumn < ss->visibleColumns)
	  {
	  }
      XClearArea(XtDisplay(w), XtWindow(w),
		 ss->x0 + ss->lineWidth + 1, 0,
		 w->core.width, w->core.height, False);
    }

  if (row != ss->topRow)
    {
      XClearArea(XtDisplay(w), XtWindow(w),
		 0, ss->y0 + ss->lineWidth + 1,
		 w->core.width, w->core.height, False);
    }

  if ((col != ss->leftColumn) || (row != ss->topRow))
    {
      ss->leftColumn = col;
      ss->rightColumn = col + ss->visibleColumns - 1;
      ss->topRow = row;
      ss->bottomRow = row + ss->visibleRows - 1;

      if (adjust) AdjustScrollbars(w);
      SetupCursor(w);
      Redisplay(w, NULL, NULL);
    }
}
	

static void SetEditedValueInTable(AtSpreadsheetWidget w)
{
    AtSpreadsheetPart *ss = &w->spreadsheet;
    char *currentval, *newval;

    currentval = AtTableGetCellValue(ss->table,ss->cursorColumn,ss->cursorRow);
    newval = XmTextGetString(ss->cursor);
    if (strcmp(currentval, newval) != 0)
	AtTableSetCellValue(ss->table, ss->cursorColumn,ss->cursorRow, newval);
    XtFree(newval);
}

static void VerticalScroll(Widget sb, AtSpreadsheetWidget w,
			   XmScrollBarCallbackStruct *data)
{
    SetUpperLeftCell(w, w->spreadsheet.leftColumn, data->value, False);
}

static void HorizontalScroll(Widget sb, AtSpreadsheetWidget w,
			     XmScrollBarCallbackStruct *data)
{
    SetUpperLeftCell(w, data->value, w->spreadsheet.topRow, False);
}



/*
 * this action is invoked by the cursor text widget
 */
static void MoveCursor(Widget cursor, XEvent *event,
			String *params, Cardinal *numparams)
{
    AtSpreadsheetWidget w = (AtSpreadsheetWidget)XtParent(cursor);
    AtSpreadsheetPart *ss = &w->spreadsheet;
    int i,j;
    
    if (!(*numparams))
	return;

    SetEditedValueInTable(w);
    
    i = ss->cursorColumn;
    j = ss->cursorRow;
    
    switch (**params)
      {
      case 'u' :
	if (j > 0) j--;
	break;
      case 'd':
	if (j < ss->nrows-1) j++;
	break;
      case 'l':
	if (i > 0) i--;
	break;
      case 'r':
	if (i < ss->ncols-1) i++;
	break;
/*
      case 'U':
	if (j >= ss->visibleRows)
	  j -= ss->visibleRows;
	else
	  j = 0;
	if (ss->topRow >= ss->visibleRows)
	  ss->topRow -= ss->visibleRows;
	break;
      case 'D':
	j += ss->visibleRows;
	break;
      case 'L':
	i -= ss->visibleColumns;
	break;
      case 'R':
	i += ss->visibleColumns;
	break;
*/
      case 't':
	j = 0;
	break;
      case 'b':
	j = ss->nrows-1;
	break;
      case '<':
	i = 0;
	break;
      case '>':
	i = ss->ncols-1;
	break;
      }

    if (j < ss->topRow)
      SetUpperLeftCell(w, ss->leftColumn, j, True);
    else if (j > ss->bottomRow)
      SetUpperLeftCell(w, ss->leftColumn, j - ss->visibleRows+1, True);
    
    if (i < ss->leftColumn)
      SetUpperLeftCell(w, i, ss->topRow, True);
    else if (i > ss->rightColumn)
      SetUpperLeftCell(w, i-ss->visibleColumns+1, ss->topRow, True);
    MoveCursorTo(w, i, j);
}

static void StartSelection(AtSpreadsheetWidget w, XButtonPressedEvent *event)
{
  AtSpreadsheetPart *ss = &w->spreadsheet;
  int i,j;

  i = PixelToCol(w, event->x);
  j = PixelToRow(w, event->y);
    
  if ((i == -1) && (j == -1))	/* HOME -- upper left cell. */
    {
      SetEditedValueInTable(w);
      SetUpperLeftCell(w, 0, 0, True);
      MoveCursorTo(w, 0, 0);
      return;
    }

  if (i == -1)			/* Left side of row. */
    {
      i = ss->topRow;
      if (j > ss->bottomRow)
	i++;
      SetEditedValueInTable(w);
      SetUpperLeftCell(w, 0, i, True);
      MoveCursorTo(w, 0, j);
      return;
    }

  if (j == -1)			/* Top of column. */
    {
      j = ss->leftColumn;
      if (i > ss->rightColumn)
	j++;
      SetEditedValueInTable(w);
      SetUpperLeftCell(w, j, 0, True);
      MoveCursorTo(w, i, 0);
      return;
    }

  SetEditedValueInTable(w);
  if (j < ss->topRow)
    SetUpperLeftCell(w, ss->leftColumn, j,True);
  else if (j > ss->bottomRow)
    SetUpperLeftCell(w, ss->leftColumn, j - ss->visibleRows+1, True);

  if (i < ss->leftColumn)
    SetUpperLeftCell(w, i, ss->topRow, True);
  else if (i > ss->rightColumn)
    SetUpperLeftCell(w, i-ss->visibleColumns+1, ss->topRow, True);
  MoveCursorTo(w, i, j);
}
    
    


		     
/* implement all the public functions below here */

AtTableObject AtSpreadsheetGetTable(AtSpreadsheetWidget w)

{
  return(w->spreadsheet.table);
}

short int AtSpreadsheetGetCursorRow(AtSpreadsheetWidget w)

{
  return(w->spreadsheet.cursorRow);
}

short int AtSpreadsheetGetCursorColumn(AtSpreadsheetWidget w)

{
  return(w->spreadsheet.cursorColumn);
}

short int AtSpreadsheetGetTopRow(AtSpreadsheetWidget w)

{
  return(w->spreadsheet.topRow);
}

short int AtSpreadsheetGetLeftColumn(AtSpreadsheetWidget w)

{
  return(w->spreadsheet.leftColumn);
}

short int AtSpreadsheetGetBottomRow(AtSpreadsheetWidget w)

{
  return(w->spreadsheet.bottomRow);
}

short int AtSpreadsheetGetRightColumn(AtSpreadsheetWidget w)

{
  return(w->spreadsheet.rightColumn);
}



