/*
 * Copyright(c) 1992 Bell Communications Research, Inc. (Bellcore)
 *                        All rights reserved
 * Permission to use, copy, modify and distribute this material for
 * any purpose and without fee is hereby granted, provided that the
 * above copyright notice and this permission notice appear in all
 * copies, and that the name of Bellcore not be used in advertising
 * or publicity pertaining to this material without the specific,
 * prior written permission of an authorized representative of
 * Bellcore.
 *
 * BELLCORE MAKES NO REPRESENTATIONS AND EXTENDS NO WARRANTIES, EX-
 * PRESS OR IMPLIED, WITH RESPECT TO THE SOFTWARE, INCLUDING, BUT
 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR ANY PARTICULAR PURPOSE, AND THE WARRANTY AGAINST IN-
 * FRINGEMENT OF PATENTS OR OTHER INTELLECTUAL PROPERTY RIGHTS.  THE
 * SOFTWARE IS PROVIDED "AS IS", AND IN NO EVENT SHALL BELLCORE OR
 * ANY OF ITS AFFILIATES BE LIABLE FOR ANY DAMAGES, INCLUDING ANY
 * LOST PROFITS OR OTHER INCIDENTAL OR CONSEQUENTIAL DAMAGES RELAT-
 * ING TO THE SOFTWARE.
 *
 * MatrixWidget Author: Andrew Wason, Bellcore, aw@bae.bellcore.com
 *
 * Revisions:
 *	?/93:  Fixed several major bugs..., internal 3.9
 *	1/93:  Added textBackground resource (create/set/get).
 *	1/93:  Added public function XbaeMatrixGetEventRowColumn().
 *	2/93:  Added verticalScrollBar and horizontalScrollBar resources (get only).
 *	2/93:  Added LosingFocusCB for text-field to commit edit.
 *	8/94:  Added XbaeMatrixGetCurrentCell().
 *	9/94:  Added prev_text to XbaeMatrixModifyVerifyCallbackStruct.
 *	9/94:  Added selectScrollVisible resource.
 *	9/94:  Added oddRowBackground & evenRowBackground
 *	2/95:  Added XbaeMatrixRefresh()
 */

/*
 * Matrix.c - row/column editable matrix widget
 */

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <X11/Xos.h>
#include <X11/StringDefs.h>
#ifndef NO_XFUNCS
# include <X11/Xfuncs.h>
#endif
#include <Xm/XmP.h>
#include <Xm/AtomMgr.h>
#include <Xm/ScrollBar.h>
#include <Xm/Text.h>
#include <Xm/TextF.h>
#include "clip.h"
#include "matrixp.h"
#if XmVersion == 1002
# include <Xm/DrawP.h>
#endif

#if !defined(SABER) && !defined(lint)
static char sccsid[] = "@(#)Matrix.c	4.1 3/19/95";
#endif

#ifndef XlibSpecificationRelease
#define XrmPermStringToQuark XrmStringToQuark
#endif

#if XmVersion <= 1001
#	define DRAW_SHADOW(dpy, draw, tgc, bgc, sz, x, y, w, h)	\
		_XmDrawShadow(dpy, draw, tgc, bgc, sz, x, y, w, h)
#else
#	define DRAW_SHADOW(dpy, draw, tgc, bgc, sz, x, y, w, h)	\
		_XmDrawShadows(dpy, draw, tgc, bgc, x, y, w, h, sz, XmSHADOW_OUT)
#endif

#define CLIENT(t,v)     t v=(t)client
#define CALL(t,v)       t v=(t)call

#if (__STDC__ > 0) || defined(__cplusplus) || defined(c_plusplus) || \
    defined(FUNCPROTO) || (NeedFunctionPrototypes != 0)
/* ANSI stype function args and prototypes */
#  define _			,
#  define F_ARGS(fal, fat)	( fat )
#  define P_ARGS(a)		a
#else
/* Original K&R stype function args and prototypes */
#  define _			;
#  define __			,
#  define F_ARGS(fal, fat)	( fal ) fat ;
#  define P_ARGS(a)
#endif

#if !defined(XlibSpecificationRelease) && !defined(bcopy) && !defined(XBAE_NEED_BCOPY)
# define bcopy(src, dest, n)	memmove(dest, src, n)
#endif

#ifdef NEED_WCHAR
# define TWO_BYTE_FONT(mw)	(mw->matrix.font->max_byte1 != 0)
#endif

#ifdef NEED_24BIT_VISUAL
# define GC_PARENT_WINDOW(w)	XtWindow(get_shell_ancestor((Widget)w))
#else
# define GC_PARENT_WINDOW(w)	RootWindowOfScreen(XtScreen(w))
#endif

/*
 * Translations for Matrix (these will also be used by the Clip child).
 */
static char defaultTranslations[] =
	":<Btn1Down>:		EditCell(Pointer)";

/*
 * Default translations for XmNtextTranslations resource
 */
#define TEXT_TRANSLATIONS \
    "#override\n\
     Shift ~Ctrl ~Meta ~Alt <Key>Tab:	EditCell(Left)\n\
     ~Ctrl ~Meta ~Alt <Key>Tab:		EditCell(Right)\n\
     <Key>osfUp:			EditCell(Up)\n\
     <Key>osfDown:			EditCell(Down)\n\
     <Key>osfActivate:			CommitEdit(False)\n\
     ~Shift ~Meta ~Alt <Key>Return:	CommitEdit(False)\n\
     <Key>osfCancel:			CancelEdit(False)\n\
     Shift Ctrl ~Meta ~Alt <Key>Tab:	TraversePrev()\n\
     Ctrl ~Meta ~Alt <Key>Tab:		TraverseNext()"

void CopyBackground P_ARGS((Widget widget, int offset, XrmValue *value));
void CopyForeground P_ARGS((Widget widget, int offset, XrmValue *value));

#define offset(field)	XtOffsetOf(XbaeMatrixRec, field)

static XtResource resources[] =
{
  {XmNdrawCellCallback, XmCCallback, XmRCallback, sizeof(XtCallbackList),
   offset(matrix.draw_cell_callback), XmRCallback, NULL},
  {XmNaltRowCount, XmCAltRowCount, XmRInt, sizeof(int),
   offset(matrix.alt_row_count), XmRImmediate, (XtPointer) 1},

  {XmNboldLabels, XmCBoldLabels, XmRBoolean, sizeof(Boolean),
   offset(matrix.bold_labels), XmRImmediate, (XtPointer) False},
  {XmNcellHighlightThickness, XmCHighlightThickness, XmRHorizontalDimension,
   sizeof(Dimension), offset(matrix.cell_highlight_thickness),
   XmRImmediate, (XtPointer) 2},
  {XmNcellMarginHeight, XmCMarginHeight, XmRVerticalDimension,
   sizeof(Dimension), offset(matrix.cell_margin_height),
   XmRImmediate, (XtPointer) 5},
  {XmNcellMarginWidth, XmCMarginWidth, XmRHorizontalDimension,
   sizeof(Dimension), offset(matrix.cell_margin_width),
   XmRImmediate, (XtPointer) 5},
  {XmNcells, XmCCells, XmRStringTable, sizeof(String **),
   offset(matrix.cells), XmRImmediate, NULL},
  {XmNcellShadowThickness, XmCShadowThickness, XmRHorizontalDimension,
   sizeof(Dimension), offset(matrix.cell_shadow_thickness),
   XmRImmediate, (XtPointer) 2},
  {XmNcolors, XmCColors, XmRPixelTable, sizeof(Pixel **),
   offset(matrix.colors), XmRImmediate, (XtPointer) NULL},
  {XmNcolumnAlignments, XmCAlignments, XmRAlignmentArray,
   sizeof(unsigned char *), offset(matrix.column_alignments),
   XmRImmediate, (XtPointer) NULL},
  {XmNcolumnLabelAlignments, XmCAlignments, XmRAlignmentArray,
   sizeof(unsigned char *), offset(matrix.column_label_alignments),
   XmRImmediate, (XtPointer) NULL},
  {XmNcolumnLabels, XmCLabels, XmRStringArray, sizeof(String *),
   offset(matrix.column_labels), XmRImmediate, NULL},
  {XmNcolumnMaxLengths, XmCColumnMaxLengths, XmRMaxLengthArray,
   sizeof(int *), offset(matrix.column_max_lengths),
   XmRImmediate, NULL},
  {XmNcolumnWidths, XmCColumnWidths, XmRWidthArray, sizeof(short *),
   offset(matrix.column_widths), XmRImmediate, NULL},
  {XmNcolumns, XmCColumns, XmRInt, sizeof(int),
   offset(matrix.columns), XmRImmediate, (XtPointer) 0},
  {XmNenterCellCallback, XmCCallback, XmRCallback, sizeof(XtCallbackList),
   offset(matrix.enter_cell_callback), XmRCallback, NULL},
  { XmNevenRowBackground, XmCBackground, XmRPixel, sizeof(Pixel),
      offset(matrix.even_row_background), XmRCallProc,
        (XtPointer)CopyBackground},
  {XmNfixedColumns, XmCFixedColumns, XmRDimension, sizeof(Dimension),
   offset(matrix.fixed_columns), XmRImmediate, (XtPointer) 0},
  {XmNfixedRows, XmCFixedRows, XmRDimension, sizeof(Dimension),
   offset(matrix.fixed_rows), XmRImmediate, (XtPointer) 0},
  {XmNhorizontalScrollBar, XmCHorizontalScrollBar, XmRWindow, sizeof(Widget),
   offset(matrix.horizontal_sb), XmRImmediate, NULL},
  {XmNfontList, XmCFontList, XmRFontList, sizeof(XmFontList),
   offset(matrix.font_list), XmRString, (XtPointer) "fixed"},
  {XmNleaveCellCallback, XmCCallback, XmRCallback, sizeof(XtCallbackList),
   offset(matrix.leave_cell_callback), XmRCallback, NULL},
  {XmNmodifyVerifyCallback, XmCCallback, XmRCallback, sizeof(XtCallbackList),
   offset(matrix.modify_verify_callback), XmRCallback, NULL},
  { XmNoddRowBackground, XmCBackground, XmRPixel, sizeof(Pixel),
      offset(matrix.odd_row_background), XmRCallProc,
        (XtPointer)CopyBackground},
  {XmNrowLabelAlignment, XmCAlignment, XmRAlignment, sizeof(unsigned char),
   offset(matrix.row_label_alignment),
   XmRImmediate, (XtPointer) XmALIGNMENT_END},
  {XmNrowLabelWidth, XmCRowLabelWidth, XmRShort, sizeof(short),
   offset(matrix.row_label_width), XmRImmediate, (XtPointer) 0},
  {XmNrowLabels, XmCLabels, XmRStringArray, sizeof(String *),
   offset(matrix.row_labels), XmRImmediate, NULL},
  {XmNrows, XmCRows, XmRInt, sizeof(int),
   offset(matrix.rows), XmRImmediate, (XtPointer) 0},
  {XmNselectCellCallback, XmCCallback, XmRCallback,
   sizeof(XtCallbackList), offset(matrix.select_cell_callback),
   XmRCallback, NULL},
  {XmNselectedCells, XmCSelectedCells, XmRBooleanTable, sizeof(Boolean **),
   offset(matrix.selected_cells), XmRImmediate, (XtPointer) NULL},
  {XmNselectScrollVisible, XmCSelectScrollVisible, XmRBoolean, sizeof(Boolean),
   offset(matrix.scroll_select), XmRImmediate, (XtPointer) True},
  {XmNspace, XmCSpace, XmRHorizontalDimension, sizeof(Dimension),
   offset(matrix.space), XmRImmediate, (XtPointer) 6},
  {XmNtextTranslations, XmCTranslations, XmRTranslationTable,
   sizeof(XtTranslations), offset(matrix.text_translations),
   XmRString, (XtPointer) TEXT_TRANSLATIONS},
  {XmNtextBackground, XmCTextBackground, XmRPixel, sizeof(Pixel),
   offset(matrix.text_background), XmRCallProc,
        (XtPointer)CopyBackground},
  {XmNtopRow, XmCTopRow, XmRInt, sizeof(int),
   offset(matrix.top_row), XmRImmediate, (XtPointer) 0},
  {XmNtraverseCellCallback, XmCCallback, XmRCallback, sizeof(XtCallbackList),
   offset(matrix.traverse_cell_callback), XmRCallback, NULL},
  {XmNverticalScrollBar, XmCVerticalScrollBar, XmRWindow, sizeof(Widget),
   offset(matrix.vertical_sb), XmRImmediate, NULL},
  {XmNvisibleColumns, XmCVisibleColumns, XmRDimension, sizeof(Dimension),
   offset(matrix.visible_columns), XmRImmediate, (XtPointer) 0},
  {XmNvisibleRows, XmCVisibleRows, XmRDimension, sizeof(Dimension),
   offset(matrix.visible_rows), XmRImmediate, (XtPointer) 0},

/* Override Manager default */
  {XmNshadowThickness, XmCShadowThickness, XmRHorizontalDimension,
   sizeof(Dimension),
   XtOffsetOf(XmManagerRec, manager.shadow_thickness),
   XmRImmediate, (XtPointer) 2},
};

static XmSyntheticResource syn_resources[] =
{
  {XmNcellHighlightThickness, sizeof(Dimension), offset(matrix.cell_highlight_thickness),
   _XmFromHorizontalPixels, _XmToHorizontalPixels},
  {XmNcellMarginHeight, sizeof(Dimension), offset(matrix.cell_margin_height),
   _XmFromVerticalPixels, _XmToVerticalPixels},
  {XmNcellMarginWidth, sizeof(Dimension), offset(matrix.cell_margin_width),
   _XmFromHorizontalPixels, _XmToHorizontalPixels},
  {XmNcellShadowThickness, sizeof(Dimension), offset(matrix.cell_shadow_thickness),
   _XmFromHorizontalPixels, _XmToHorizontalPixels},
  {XmNspace, sizeof(Dimension), offset(matrix.space),
   _XmFromHorizontalPixels, _XmToHorizontalPixels},
};

/*
 * End of array flags for the array type converters
 */
#define BAD_WIDTH	0
#define BAD_MAXLENGTH	0
#define BAD_ALIGNMENT	3	/* see Xm.h */

/*
 * Macros to retrieve our children.  Children must be created in this order.
 */
#define HorizScrollChild(mw)	(mw->composite.children[0])
#define VertScrollChild(mw)	(mw->composite.children[1])
#define ClipChild(mw)		(mw->composite.children[2])
#define TextChild(mw)		(mw->composite.children[3])
/*
 * Macros
 */
#define FONT_WIDTH(mw)		(mw->matrix.font->max_bounds.width)
#define TEXT_WIDTH_OFFSET(mw)	(mw->matrix.cell_margin_width +\
				 mw->matrix.cell_shadow_thickness +\
				 mw->matrix.cell_highlight_thickness)
#define TEXT_HEIGHT_OFFSET(mw)	(mw->matrix.cell_margin_height +\
				 mw->matrix.cell_shadow_thickness +\
				 mw->matrix.cell_highlight_thickness)
#define COLUMN_WIDTH(mw, col)	((mw->matrix.column_widths[col] * \
				  FONT_WIDTH(mw)) + \
				 ((int)TEXT_WIDTH_OFFSET(mw) * 2))
#define TEXT_HEIGHT(mw)		(mw->matrix.font->descent + \
				 mw->matrix.font->ascent)
#define ROW_HEIGHT(mw)		(int)((TEXT_HEIGHT_OFFSET(mw) * 2) + \
    					TEXT_HEIGHT(mw))

#define TEXT_X_OFFSET(mw)	(int)(TEXT_WIDTH_OFFSET(mw))
#define TEXT_Y_OFFSET(mw)	(int)(mw->matrix.text_baseline)

#define ROW_LABEL_WIDTH(mw)	(mw->matrix.row_labels \
				 ? (mw->matrix.row_label_width * \
				    FONT_WIDTH(mw)) + (int)TEXT_WIDTH_OFFSET(mw) * 2 \
				 : 0)
#define COLUMN_LABEL_HEIGHT(mw) (mw->matrix.column_labels \
				 ? (int)TEXT_HEIGHT_OFFSET(mw) * 2 + \
				   mw->matrix.column_label_maxlines * \
				   TEXT_HEIGHT(mw) \
				 : 0)

#define FIXED_COLUMN_WIDTH(mw)	mw->matrix.column_positions \
					[mw->matrix.fixed_columns]
#define COLUMN_LABEL_OFFSET(mw)	(ROW_LABEL_WIDTH(mw) + \
				 mw->manager.shadow_thickness)
#define FIXED_COLUMN_LABEL_OFFSET(mw) (COLUMN_LABEL_OFFSET(mw) + \
				       FIXED_COLUMN_WIDTH(mw))

#define FIXED_ROW_HEIGHT(mw)	(mw->matrix.fixed_rows * ROW_HEIGHT(mw))
#define ROW_LABEL_OFFSET(mw)	(COLUMN_LABEL_HEIGHT(mw) + \
				 mw->manager.shadow_thickness)
#define FIXED_ROW_LABEL_OFFSET(mw) (ROW_LABEL_OFFSET(mw) + \
				    FIXED_ROW_HEIGHT(mw))

#define CELL_TOTAL_WIDTH(mw)	mw->matrix.cell_total_width
#define CELL_TOTAL_HEIGHT(mw)	((mw->matrix.rows - \
				  (int) mw->matrix.fixed_rows) \
				 * ROW_HEIGHT(mw))

#define VISIBLE_WIDTH(mw)	((int)ClipChild(mw)->core.width)
#define VISIBLE_HEIGHT(mw)	((int)ClipChild(mw)->core.height)

#define VERT_ORIGIN(mw)		(mw->matrix.top_row)
#define HORIZ_ORIGIN(mw)	(mw->matrix.horiz_origin)

#define VSCROLL_WIDTH(mw)	(VertScrollChild(mw)->core.width + \
				 2 * VertScrollChild(mw)->core.border_width +\
				 mw->matrix.space)
#define VSCROLL_HEIGHT(mw)	(VertScrollChild(mw)->core.height + \
				 2 * VertScrollChild(mw)->core.border_width)
#define HSCROLL_WIDTH(mw)	(HorizScrollChild(mw)->core.width + \
				 2 * HorizScrollChild(mw)->core.border_width)
#define HSCROLL_HEIGHT(mw)	(HorizScrollChild(mw)->core.height + \
				 2 * HorizScrollChild(mw)->core.border_width+\
				 mw->matrix.space)

#define IS_FIXED(mw, row, column) (row < (int)mw->matrix.fixed_rows || \
				   column < (int)mw->matrix.fixed_columns)
#define CELL_WINDOW(mw, row, column) \
    (IS_FIXED(mw, row, column) \
     ? XtWindow(mw) \
     : XtWindow(ClipChild(mw)))

/* Inline functions */
#define FreeColumnWidths(mw)		XtFree((XtPointer) \
					       mw->matrix.column_widths)
#define FreeColumnMaxLengths(mw)	XtFree((XtPointer) \
					       mw->matrix.column_max_lengths)
#define FreeColumnPositions(mw)		XtFree((XtPointer) \
					       mw->matrix.column_positions)
#define FreeColumnAlignments(mw)	XtFree((XtPointer) \
					       mw->matrix.column_alignments)
#define FreeColumnLabelAlignments(mw)	XtFree((XtPointer) \
					       mw->matrix.\
					       column_label_alignments)
#define CreateColumnPositions(mw)	(int *) XtMalloc(mw->matrix.columns * \
							 sizeof(int))
#define YtoRow(mw, y)			((y) / (ROW_HEIGHT(mw)))


/*
 * Macros used for Rectangle calculations.  A Rectangle is defined by it's
 * upper left and lower right corners.
 */

/*
 * Set a Rectangle. (x1,y1) is upper left corner, (x2,y2) is lower right corner
 */
#define SETRECT(r, X1, Y1, X2, Y2) { (r).x1 = X1; (r).y1 = Y1; \
					 (r).x2 = X2; (r).y2 = Y2; }

/*
 * Evaluates to 1 if two Rectangles overlap, 0 if no overlap
 */
#define OVERLAP(r1, r2) \
    ((r1).x2 > (r2).x1 && \
     (r1).x1 < (r2).x2 && \
     (r1).y2 > (r2).y1 && \
     (r1).y1 < (r2).y2)

/*
 * Intersect rectangles r1 and r2, place the result in res.
 * Result will be in r1's coord system.
 * Max and Min are defined in Xm/XmP.h in 1.1, they are deprecated in 1.2
 */
#if XmVersion >= 1002
#define Max(x, y)       (((x) > (y)) ? (x) : (y))
#define Min(x, y)       (((x) < (y)) ? (x) : (y))
#endif
#define X_INTERSECT(r1, r2, res) { (res).x1 = Max((r1).x1, (r2).x1) - (r1).x1;\
				   (res).x2 = Min((r1).x2, (r2).x2) - (r1).x1;}
#define Y_INTERSECT(r1, r2, res) { (res).y1 = Max((r1).y1, (r2).y1) - (r1).y1;\
				   (res).y2 = Min((r1).y2, (r2).y2) - (r1).y1;}
#define INTERSECT(r1, r2, res)	{ X_INTERSECT(r1, r2, res); \
				      Y_INTERSECT(r1, r2, res); }

/*
 * Evaluates to 1 if the point is in the Rectangle, 0 if not
 */
#define INBOX(r, x, y) \
      ( ( ((r).x2 >= x)) && \
	( ((r).x1 <= x)) && \
	( ((r).y2 >= y)) && \
	( ((r).y1 <= y)) )

/*
 * Rectangle struct used for internal calculations.  (x1,y1) are the upper
 * left corner, (x2,y2) are the lower right.
 */
typedef struct _Rectangle
{
  int x1, y1;
  int x2, y2;
} Rectangle;

/*
 * Enumeration for type of a cell
 */
typedef enum { FixedCell, NonFixedCell } CellType;

/*
 * Declaration of methods
 */
static void ClassInitialize();
static void ClassPartInitialize();
static void Initialize();
static void Realize();
static void InsertChild();
static void Redisplay();
static Boolean SetValues();
static void SetValuesAlmost();
static void Destroy();
static void Resize();
static XtGeometryResult GeometryManager();
static XtGeometryResult QueryGeometry();

/*
 * Redraw function for clip widget
 */
static void ClipRedisplay();

/*
 * New Matrix methods
 */
static void SetCell(XbaeMatrixWidget, int, int, _Xconst String, Boolean);
static void EditCell();
static void SelectCell();
static void SelectRow();
static void SelectColumn();
static void DeselectAll();
static void DeselectCell();
static void DeselectRow();
static void DeselectColumn();
static String GetCell();
static Boolean CommitEdit(XbaeMatrixWidget, Boolean);
static void CancelEdit(XbaeMatrixWidget, Boolean);
static void AddRows();
static void DeleteRows();
static void AddColumns();
static void DeleteColumns();
static void SetRowColors();
static void SetColumnColors();
static void SetCellColor();

/*
 * Private functions unique to Matrix
 */
static void CreateDrawGC P_ARGS((XbaeMatrixWidget mw));
static void GetInverseGC P_ARGS((XbaeMatrixWidget mw));
static void CreateDrawClipGC P_ARGS((XbaeMatrixWidget mw));
static void CreateInverseClipGC P_ARGS((XbaeMatrixWidget mw));
static void CreateTopShadowClipGC P_ARGS((XbaeMatrixWidget mw));
static void CreateBottomShadowClipGC P_ARGS((XbaeMatrixWidget mw));
static void SetClipMask P_ARGS((XbaeMatrixWidget mw));
static void NewFont P_ARGS((XbaeMatrixWidget mw));
static void GetCellTotalWidth P_ARGS((XbaeMatrixWidget mw));
static void GetColumnPositions P_ARGS((XbaeMatrixWidget mw));
static void DrawString P_ARGS((XbaeMatrixWidget mw, Window win, String string, int length, int x, int y, int maxlen, unsigned char alignment, Boolean highlight, Boolean bold, Boolean clip, Pixel color));
static void DrawColumnLabel P_ARGS((XbaeMatrixWidget mw _ int column));
static void DrawRowLabel P_ARGS((XbaeMatrixWidget mw _ int row));

static void DrawCellPixmap P_ARGS((XbaeMatrixWidget mw _ int row _ int column _ Pixmap pixmap));
static void RedrawCells P_ARGS((XbaeMatrixWidget mw _ Rectangle *expose));
static void RedrawLabelsAndFixed P_ARGS((XbaeMatrixWidget mw _ Rectangle *expose));
static void ComputeSize P_ARGS((XbaeMatrixWidget mw _ Boolean compute_width _ Boolean compute_height));
static void FreeCells P_ARGS((XbaeMatrixWidget mw));
static void FreeRowLabels P_ARGS((XbaeMatrixWidget mw));
static void FreeColumnLabels P_ARGS((XbaeMatrixWidget mw));
static void FreeColors P_ARGS((XbaeMatrixWidget mw));
static void FreeSelectedCells P_ARGS((XbaeMatrixWidget mw));
static void CreateColors P_ARGS((XbaeMatrixWidget mw));
static void CopySelectedCells P_ARGS((XbaeMatrixWidget mw));
static void ResizeCells P_ARGS((XbaeMatrixWidget current _ XbaeMatrixWidget new));
static void ResizeSelectedCells P_ARGS((XbaeMatrixWidget current _ XbaeMatrixWidget new));
static void ResizeColors P_ARGS((XbaeMatrixWidget current _ XbaeMatrixWidget new));
static void RowColToXY P_ARGS((XbaeMatrixWidget mw _ int row _ int column _ int *x _ int *y));

static void AddRowsToTable P_ARGS((XbaeMatrixWidget mw _ int position _ String *rows _ String *labels _ Pixel *colors _ int num_rows));
static void DeleteRowsFromTable P_ARGS((XbaeMatrixWidget mw _ int position _ int num_rows));

static void AddColumnsToTable();
static void DeleteColumnsFromTable();
static void ClearCell();
static void GetVisibleRows();
static void GetVisibleColumns();
static void GetVisibleCells();
static void MakeRowVisible();
static void MakeColumnVisible();
static void MakeCellVisible();
static void AdjustTopRow();
static void TraverseIn();

#if XmVersion >= 1002
static void TraverseInTimeOut P_ARGS((XtPointer, XtIntervalId *));
#endif /* XmVersion >= 1002 */

static Boolean IsRowVisible(), IsColumnVisible(), IsCellVisible(), XYToRowCol(), DoCommitEdit(),
  EventToXY();
static short MaxRowLabel();
static int XtoCol();
static void CopyRowLabels(), CopyColumnLabels(), CopyCells(), CopyColumnWidths(), CopyColumnMaxLengths(),
  CopyColumnAlignments(), CopyColumnLabelAlignments(), CopyColors();
static void ParseColumnLabel();

/*
 * Scrollbar callbacks
 */
static void ScrollVertCB(), ScrollHorizCB();

/*
 * TextField modifyVerifyCallback
 */
static void ModifyVerifyCB(Widget, XtPointer, XtPointer);
static void LosingFocusCB(Widget, XtPointer, XtPointer);

/*
 * Clip widget focusCallback
 */
static void TraverseInCB();

/*
 * ScrollMgr implementation
 */
static SmScrollMgr SmCreateScrollMgr();
static void SmDestroyScrollMgr(), SmAddScroll(), SmRemoveScroll(), SmScrollEvent();

/*
 * Public convenience functions
 */
void XbaeMatrixEditCell();
void XbaeMatrixSelectCell();
void XbaeMatrixSelectRow();
void XbaeMatrixSelectColumn();
void XbaeMatrixDeselectAll();
void XbaeMatrixDeselectCell();
void XbaeMatrixDeselectRow();
void XbaeMatrixDeselectColumn();
String XbaeMatrixGetCell();
void XbaeMatrixAddRows();
void XbaeMatrixDeleteRows();
void XbaeMatrixAddColumns();
void XbaeMatrixDeleteColumns();
void XbaeMatrixSetRowColors();
void XbaeMatrixSetColumnColors();
void XbaeMatrixSetCellColor();

/*
 * Type converters
 */
static Boolean CvtStringToStringArray();
static void StringArrayDestructor();
static Boolean CvtStringToWidthArray();
static void WidthArrayDestructor();
static Boolean CvtStringToMaxLengthArray();
static void MaxLengthArrayDestructor();
static Boolean StringsAreEqual();
static Boolean CvtStringToAlignmentArray();
static void AlignmentArrayDestructor();

/*
 * Actions
 */
static void EditCellACT(), CancelEditACT(), CommitEditACT(), SelectCellACT(), TraverseNextACT(),
  TraversePrevACT();

/*
 * Matrix actions
 */
static XtActionsRec actions[] =
{
  {"EditCell", EditCellACT},
  {"CancelEdit", CancelEditACT},
  {"CommitEdit", CommitEditACT},
  {"SelectCell", SelectCellACT},
  {"TraverseNext", TraverseNextACT},
  {"TraversePrev", TraversePrevACT},
};


externaldef(xbaematrixclassrec)
XbaeMatrixClassRec xbaeMatrixClassRec =
{
  {
/* core_class fields */
    /* superclass		*/ (WidgetClass) & xmManagerClassRec,
    /* class_name		*/ "XbaeMatrix",
    /* widget_size		*/ sizeof(XbaeMatrixRec),
    /* class_initialize		*/ ClassInitialize,
    /* class_part_initialize	*/ ClassPartInitialize,
    /* class_inited		*/ False,
    /* initialize		*/ Initialize,
    /* initialize_hook		*/ NULL,
    /* realize			*/ Realize,
    /* actions			*/ actions,
    /* num_actions		*/ XtNumber(actions),
    /* resources		*/ resources,
    /* num_resources		*/ XtNumber(resources),
    /* xrm_class		*/ NULLQUARK,
    /* compress_motion		*/ True,
    /* compress_exposure	*/
    		XtExposeCompressSeries | XtExposeGraphicsExpose | XtExposeNoExpose,
    /* compress_enterleave	*/ True,
    /* visible_interest		*/ False,
    /* destroy			*/ Destroy,
    /* resize			*/ Resize,
    /* expose			*/ Redisplay,
    /* set_values		*/ SetValues,
    /* set_values_hook		*/ NULL,
    /* set_values_almost	*/ SetValuesAlmost,
    /* get_values_hook		*/ NULL,
    /* accept_focus		*/ NULL,
    /* version			*/ XtVersion,
    /* callback_private		*/ NULL,
    /* tm_table			*/ defaultTranslations,
    /* query_geometry		*/ QueryGeometry,
    /* display_accelerator	*/ NULL,
    /* extension		*/ NULL
  },

/*
 * XXX I should be able to use XtInheritGeometryManager &
 * XtInheritChangeManaged but Composite defines these as NULL.
 * (Xt seems to allow change_managed to be NULL)
 */
  {
/* composite_class fields */
    /* geometry_manager		*/ GeometryManager,
    /* change_managed		*/ NULL,
    /* insert_child		*/ InsertChild,
    /* delete_child		*/ XtInheritDeleteChild,
    /* extension		*/ NULL,
  },
  {
/* constraint_class fields */
    /* resources		*/ NULL,
    /* num_resources		*/ 0,
    /* constraint_size		*/ 0,
    /* initialize		*/ NULL,
    /* destroy			*/ NULL,
    /* set_values		*/ NULL,
    /* extension		*/ NULL
  },
  {
/* manager_class fields */
    /* translations		*/ XtInheritTranslations,
    /* syn_resources		*/ syn_resources,
    /* num_syn_resources	*/ XtNumber(syn_resources),
    /* syn_constraint_resources	*/ NULL,
    /* num_syn_constraint_resources */ 0,
    /* parent_process		*/ XmInheritParentProcess,
    /* extension		*/ NULL
  },
  {
/* matrix_class fields */
    /* set_cell			*/ SetCell,
    /* get_cell			*/ GetCell,
    /* edit_cell		*/ EditCell,
    /* select_cell		*/ SelectCell,
    /* select_row		*/ SelectRow,
    /* select_column		*/ SelectColumn,
    /* deselect_all		*/ DeselectAll,
    /* deselect_cell		*/ DeselectCell,
    /* deselect_row		*/ DeselectRow,
    /* deselect_column		*/ DeselectColumn,
    /* commit_edit		*/ CommitEdit,
    /* cancel_edit		*/ CancelEdit,
    /* add_rows			*/ AddRows,
    /* delete_rows		*/ DeleteRows,
    /* add_columns		*/ AddColumns,
    /* delete_columns		*/ DeleteColumns,
    /* set_row_colors		*/ SetRowColors,
    /* set_column_colors	*/ SetColumnColors,
    /* set_cell_color		*/ SetCellColor,
    /* extension		*/ NULL,
  }
};

externaldef(xbaematrixwidgetclass)
WidgetClass xbaeMatrixWidgetClass = (WidgetClass) & xbaeMatrixClassRec;

void
CopyBackground F_ARGS(widget __ offset __ value , \
		      Widget widget _ int offset _ XrmValue *value)
{
  value->addr = (XtPointer)&(widget->core.background_pixel);
}


void
CopyForeground F_ARGS(widget __ offset __ value ,\
		      Widget widget _ int offset _ XrmValue *value)
{
  value->addr = (XtPointer)&(((XmManagerWidget)widget)->manager.foreground);
}


static void
ClassInitialize F_ARGS( , void )
{
  /*
   * String to StringArray is used for XmNrowLabels and XmNcolumnLabels
   * We make a private copy of this table, should we cache? XXX
   */
  XtSetTypeConverter(XmRString, XmRStringArray,
		     CvtStringToStringArray, NULL, 0,
		     XtCacheAll | XtCacheRefCount,
		     StringArrayDestructor);

  /*
   * String to ShortArray is used for XmNcolumnWidths resource.
   * We make a private copy of this table, should we cache? XXX
   */
  XtSetTypeConverter(XmRString, XmRWidthArray,
		     CvtStringToWidthArray, NULL, 0,
		     XtCacheAll | XtCacheRefCount,
		     WidthArrayDestructor);

  /*
   * String to IntArray is used for XmNcolumnMaxLengths resource.
   * We make a private copy of this table, should we cache? XXX
   */
  XtSetTypeConverter(XmRString, XmRMaxLengthArray,
		     CvtStringToMaxLengthArray, NULL, 0,
		     XtCacheAll | XtCacheRefCount,
		     MaxLengthArrayDestructor);

  /*
   * String to AlignmentArray is used for XmNcolumnAlignments
   * and XmNcolumnLabelAlignments resources.
   */
  XtSetTypeConverter(XmRString, XmRAlignmentArray,
		     CvtStringToAlignmentArray, NULL, 0,
		     XtCacheAll | XtCacheRefCount,
		     AlignmentArrayDestructor);
}


static void
ClassPartInitialize F_ARGS(mwc , XbaeMatrixWidgetClass mwc)
{
  register XbaeMatrixWidgetClass super =
  (XbaeMatrixWidgetClass) mwc->core_class.superclass;

  /*
   * Allow subclasses to inherit new Matrix methods
   */
  if (mwc->matrix_class.set_cell == XbaeInheritSetCell)
    mwc->matrix_class.set_cell = super->matrix_class.set_cell;
  if (mwc->matrix_class.get_cell == XbaeInheritGetCell)
    mwc->matrix_class.get_cell = super->matrix_class.get_cell;
  if (mwc->matrix_class.edit_cell == XbaeInheritEditCell)
    mwc->matrix_class.edit_cell = super->matrix_class.edit_cell;
  if (mwc->matrix_class.select_cell == XbaeInheritSelectCell)
    mwc->matrix_class.select_cell = super->matrix_class.select_cell;
  if (mwc->matrix_class.select_row == XbaeInheritSelectRow)
    mwc->matrix_class.select_row = super->matrix_class.select_row;
  if (mwc->matrix_class.select_column == XbaeInheritSelectColumn)
    mwc->matrix_class.select_column = super->matrix_class.select_column;
  if (mwc->matrix_class.deselect_all == XbaeInheritDeselectAll)
    mwc->matrix_class.deselect_all = super->matrix_class.deselect_all;
  if (mwc->matrix_class.deselect_cell == XbaeInheritDeselectCell)
    mwc->matrix_class.deselect_cell = super->matrix_class.deselect_cell;
  if (mwc->matrix_class.deselect_row == XbaeInheritDeselectRow)
    mwc->matrix_class.deselect_row = super->matrix_class.deselect_row;
  if (mwc->matrix_class.deselect_column == XbaeInheritDeselectColumn)
    mwc->matrix_class.deselect_column =
      super->matrix_class.deselect_column;
  if (mwc->matrix_class.commit_edit == XbaeInheritCommitEdit)
    mwc->matrix_class.commit_edit = super->matrix_class.commit_edit;
  if (mwc->matrix_class.cancel_edit == XbaeInheritCancelEdit)
    mwc->matrix_class.cancel_edit = super->matrix_class.cancel_edit;
  if (mwc->matrix_class.add_rows == XbaeInheritAddRows)
    mwc->matrix_class.add_rows = super->matrix_class.add_rows;
  if (mwc->matrix_class.delete_rows == XbaeInheritDeleteRows)
    mwc->matrix_class.delete_rows = super->matrix_class.delete_rows;
  if (mwc->matrix_class.add_columns == XbaeInheritAddColumns)
    mwc->matrix_class.add_columns = super->matrix_class.add_columns;
  if (mwc->matrix_class.delete_columns == XbaeInheritDeleteColumns)
    mwc->matrix_class.delete_columns = super->matrix_class.delete_columns;
  if (mwc->matrix_class.set_row_colors == XbaeInheritSetRowColors)
    mwc->matrix_class.set_row_colors = super->matrix_class.set_row_colors;
  if (mwc->matrix_class.set_column_colors == XbaeInheritSetColumnColors)
    mwc->matrix_class.set_column_colors =
      super->matrix_class.set_column_colors;
  if (mwc->matrix_class.set_cell_color == XbaeInheritSetCellColor)
    mwc->matrix_class.set_cell_color = super->matrix_class.set_cell_color;
}


#ifdef NEED_24BIT_VISUAL
static Widget
get_shell_ancestor F_ARGS(w , Widget w)
{
	Widget sh;

	for (sh=w ; !XtIsShell(sh) ; sh=XtParent(sh))
		;
}
#endif


static void
CreateDrawGC F_ARGS(mw , XbaeMatrixWidget mw)
{
  XGCValues values;
  unsigned long mask = GCForeground | GCFont;

  /*
   * GC for drawing cells/labels. We create it instead of using a cached one,
   * since the foreground may change frequently.
   */
  values.foreground = mw->manager.foreground;
  values.font = mw->matrix.font->fid;
  mw->matrix.draw_gc = XCreateGC(XtDisplay(mw),
				 GC_PARENT_WINDOW(mw),
				 mask, &values);
}

static void
GetInverseGC F_ARGS(mw , XbaeMatrixWidget mw)
{
  XGCValues values;
  XtGCMask mask = GCForeground | GCFont;

  /*
   * GC for drawing selected cells.
   */
  values.foreground = mw->core.background_pixel;
  values.font = mw->matrix.font->fid;
  mw->matrix.inverse_gc = XtGetGC((Widget) mw, mask, &values);
}

static void
CreateDrawClipGC F_ARGS(mw , XbaeMatrixWidget mw)
{
  XGCValues values;
  unsigned long mask = GCForeground | GCFont;

  /*
   * GC for drawing cells/labels with clipping.
   */
  values.foreground = mw->manager.foreground;
  values.font = mw->matrix.font->fid;
  mw->matrix.draw_clip_gc = XCreateGC(XtDisplay(mw),
				      GC_PARENT_WINDOW(mw),
				      mask, &values);
}

static void
CreateInverseClipGC F_ARGS(mw , XbaeMatrixWidget mw)
{
  XGCValues values;
  XtGCMask mask = GCForeground | GCFont;

  /*
   * GC for drawing selected cells with clipping.
   */
  values.foreground = mw->core.background_pixel;
  values.font = mw->matrix.font->fid;

  mw->matrix.inverse_clip_gc = XCreateGC(XtDisplay(mw),
					 GC_PARENT_WINDOW(mw),
					 mask, &values);
}

static void
CreateTopShadowClipGC F_ARGS(mw , XbaeMatrixWidget mw)
{
  XGCValues values;
  XtGCMask mask = GCForeground | GCBackground;

  /*
   * GC for drawing top shadow inside cells with clipping.
   */
  values.foreground = mw->manager.top_shadow_color;
  values.background = mw->manager.foreground;

  if (mw->manager.top_shadow_pixmap != XmUNSPECIFIED_PIXMAP)
  {
    mask |= GCFillStyle | GCTile;
    values.fill_style = FillTiled;
    values.tile = mw->manager.top_shadow_pixmap;
  }
  mw->matrix.cell_top_shadow_clip_gc =
    XCreateGC(XtDisplay(mw),
	      GC_PARENT_WINDOW(mw),
	      mask, &values);
}

static void
CreateBottomShadowClipGC F_ARGS(mw , XbaeMatrixWidget mw)
{
  XGCValues values;
  XtGCMask mask = GCForeground | GCBackground;

  /*
   * GC for drawing bottom shadow inside cells with clipping.
   */
  values.foreground = mw->manager.bottom_shadow_color;
  values.background = mw->manager.foreground;

  if (mw->manager.bottom_shadow_pixmap != XmUNSPECIFIED_PIXMAP)
  {
    mask |= GCFillStyle | GCTile;
    values.fill_style = FillTiled;
    values.tile = mw->manager.bottom_shadow_pixmap;
  }
  mw->matrix.cell_bottom_shadow_clip_gc =
    XCreateGC(XtDisplay(mw),
	      GC_PARENT_WINDOW(mw),
	      mask, &values);
}

/*
 * Set the clip_mask in our draw and shadow GCs.  This is necessary for
 * drawing non-fixed column labels and fixed rows.
 */
static void
SetClipMask F_ARGS(mw , XbaeMatrixWidget mw)
{
  XRectangle r;

  /*
   * XRectangle enclosing column labels and fixed rows
   */
  r.x = FIXED_COLUMN_LABEL_OFFSET(mw);
  r.y = 0;
  r.width = ClipChild(mw)->core.width;
  r.height = FIXED_ROW_LABEL_OFFSET(mw);

  /*
   * Reset the clip_mask in our clipping GCs
   */
  XSetClipRectangles(XtDisplay(mw), mw->matrix.draw_clip_gc,
		     0, 0, &r, 1, Unsorted);
  XSetClipRectangles(XtDisplay(mw), mw->matrix.inverse_clip_gc,
		     0, 0, &r, 1, Unsorted);
  XSetClipRectangles(XtDisplay(mw), mw->matrix.cell_top_shadow_clip_gc,
		     0, 0, &r, 1, Unsorted);
  XSetClipRectangles(XtDisplay(mw), mw->matrix.cell_bottom_shadow_clip_gc,
		     0, 0, &r, 1, Unsorted);
}

static void
NewFont F_ARGS(mw , XbaeMatrixWidget mw)
{
  XmFontContext context;
  XmStringCharSet charset;
  XFontStruct *font;

  /*
   * Make a private copy of the FontList
   */
  mw->matrix.font_list = XmFontListCopy(mw->matrix.font_list);

  /*
   * Get XFontStruct from FontList
   */
  if (!XmFontListInitFontContext(&context, mw->matrix.font_list))
    XtAppErrorMsg(XtWidgetToApplicationContext((Widget) mw),
		  "newFont", "badFont", "XbaeMatrix",
		  "XbaeMatrix: XmFontListInitFontContext failed, bad fontList",
		  NULL, 0);

  if (!XmFontListGetNextFont(context, &charset, &font))
    XtAppErrorMsg(XtWidgetToApplicationContext((Widget) mw),
		  "newFont", "badFont", "XbaeMatrix",
	      "XbaeMatrix: XmFontListGetNextFont failed, cannot get font from fontList",
		  NULL, 0);

  XtFree(charset);
  XmFontListFreeFontContext(context);

  mw->matrix.font = font;
}

/*
 * Return the length of the longest row label
 */
static short
MaxRowLabel(mw)
     XbaeMatrixWidget mw;
{
  int i;
  short max = 0, len;

  /*
   * Determine the length of the longest row label
   */
  for (i = 0; i < mw->matrix.rows; i++)
  {
    len = strlen(mw->matrix.row_labels[i]);
    if (len > max)
      max = len;
  }
  return max;
}

/*
 * Get the total pixel width of the non-fixed cell area
 */
static void
GetCellTotalWidth F_ARGS(mw , XbaeMatrixWidget mw)
{
  int i;

  /*
   * Calculate width of non-fixed cell area.
   */
  for (i = mw->matrix.fixed_columns, mw->matrix.cell_total_width = 0;
       i < mw->matrix.columns;
       i++)
    mw->matrix.cell_total_width += COLUMN_WIDTH(mw, i);
}

/*
 * Cache the pixel position of each column
 */
static void
GetColumnPositions F_ARGS(mw , XbaeMatrixWidget mw)
{
  int i, x;

  for (i = 0, x = 0;
       i < mw->matrix.columns;
       x += COLUMN_WIDTH(mw, i), i++)
    mw->matrix.column_positions[i] = x;
}


/*
 * Callbacks for our scrollbars.
 */
static XtCallbackRec VSCallback[] =
{
  {(XtCallbackProc) ScrollVertCB, (XtPointer) NULL},
  {(XtCallbackProc) NULL, NULL}
};
static XtCallbackRec HSCallback[] =
{
  {(XtCallbackProc) ScrollHorizCB, (XtPointer) NULL},
  {(XtCallbackProc) NULL, NULL}
};

/* ARGSUSED */
static void
Initialize(request, new, args, num_args)
     XbaeMatrixWidget request, new;
     ArgList args;
     Cardinal *num_args;
{
  /*
   * Check rows/cols set by resources for consistency/validity
   */
  if (new->matrix.rows == 0 || new->matrix.columns == 0)
  {
    XtAppWarningMsg(XtWidgetToApplicationContext((Widget) new),
		    "initialize", "badSize", "XbaeMatrix",
		    "XbaeMatrix: Number of rows or columns is zero",
		    (String *) NULL, (Cardinal *) NULL);
    if (!new->matrix.rows)
      new->matrix.rows = 1;
    if (!new->matrix.columns)
      new->matrix.columns = 1;
  }

  /*
   * Make sure column_widths were specified
   */
  if (new->matrix.column_widths == NULL)
    XtAppErrorMsg(XtWidgetToApplicationContext((Widget) new),
		  "initialize", "noColumnWidths", "XbaeMatrix",
		  "XbaeMatrix: No columnWidths specified",
		  (String *) NULL, (Cardinal *) NULL);

  /*
   * Only one of fixed_rows/fixed_columns may be specified
   */
  if (new->matrix.fixed_rows && new->matrix.fixed_columns)
  {
    XtAppWarningMsg(XtWidgetToApplicationContext((Widget) new),
		    "initialize", "bothFixed", "XbaeMatrix",
		    "XbaeMatrix: Cannot specify both fixedRows and fixedColumns",
		    (String *) NULL, (Cardinal *) NULL);
    new->matrix.fixed_rows = 0;
    new->matrix.fixed_columns = 0;
  }


  /*
   * We must have at least one non-fixed row/column
   */
  if ((new->matrix.fixed_rows &&
       (int)new->matrix.fixed_rows >= new->matrix.rows) ||
      (new->matrix.fixed_columns &&
       (int)new->matrix.fixed_columns >= new->matrix.columns))
  {
    String params[1];
    Cardinal num_params = 1;

    params[0] = new->matrix.fixed_rows ? "row" : "column";
    XtAppWarningMsg(XtWidgetToApplicationContext((Widget) new),
		    "initialize", "tooManyFixed", "XbaeMatrix",
		    "XbaeMatrix: At least one %s must not be fixed",
		    (String *) params, (Cardinal *) & num_params);
    if (new->matrix.fixed_rows)
      new->matrix.fixed_rows = 0;
    else if (new->matrix.fixed_columns)
      new->matrix.fixed_columns = 0;
  }

  /*
   * We can't have too many visible columns
   */
  if ((int)new->matrix.visible_columns >
      new->matrix.columns - (int)new->matrix.fixed_columns)
  {
    XtAppWarningMsg(XtWidgetToApplicationContext((Widget) new),
		    "initialize", "tooManyVisibleColumns", "XbaeMatrix",
		    "XbaeMatrix: Too many visible columns",
		    (String *) NULL, (Cardinal *) NULL);
    new->matrix.visible_columns = 0;
  }

  /*
   * Copy the pointed to resources.
   * If cells is NULL, we create an array of "" strings.
   */

  CopyCells(new);

  if (new->matrix.row_labels)
    CopyRowLabels(new);
  if (new->matrix.column_labels)
    CopyColumnLabels(new);
  else
  {
    new->matrix.column_label_lines = NULL;
    new->matrix.column_label_maxlines = 0;
  }

  CopyColumnWidths(new);

  if (new->matrix.column_max_lengths)
    CopyColumnMaxLengths(new);

  if (new->matrix.column_alignments)
    CopyColumnAlignments(new);
  if (new->matrix.column_label_alignments)
    CopyColumnLabelAlignments(new);

  if (new->matrix.colors)
    CopyColors(new);

  CopySelectedCells(new);

  /*
   * If user didn't specify a rowLabelWidth, then calculate one based on
   * the widest label
   */
  if (new->matrix.row_label_width == 0 && new->matrix.row_labels)
    new->matrix.row_label_width = MaxRowLabel(new);

  /*
   * Copy the fontList. Get fontStruct from fontList.
   */
  NewFont(new);

  /*
   * Create our 4 children (SBs and textField are unmanaged for now)
   * they must be created in this order so our macros work
   * (horiz scroll, vert scroll and then clip and textField).
   * We scroll horizontally by pixels, vertically by rows.
   */
  new->matrix.horizontal_sb = XtVaCreateWidget("horizScroll",
			  xmScrollBarWidgetClass, (Widget) new,
			  XmNorientation, XmHORIZONTAL,
			  XmNdragCallback, HSCallback,
			  XmNvalueChangedCallback, HSCallback,
			  XmNincrement, FONT_WIDTH(new),
			  XmNsliderSize, 1,
			  XmNbackground, new->core.background_pixel,
			  XmNforeground, new->manager.foreground,
			  XmNbottomShadowColor,
			  new->manager.bottom_shadow_color,
			  XmNbottomShadowPixmap,
			  new->manager.bottom_shadow_pixmap,
			  XmNhighlightColor, new->manager.highlight_color,
			  XmNhighlightPixmap, new->manager.highlight_pixmap,
/* KPB
			  XmNshadowThickness, new->manager.shadow_thickness,
*/
			  XmNtopShadowColor, new->manager.top_shadow_color,
			  XmNtopShadowPixmap, new->manager.top_shadow_pixmap,
			  NULL);
  HORIZ_ORIGIN(new) = 0;

  new->matrix.vertical_sb = XtVaCreateWidget("vertScroll",
			  xmScrollBarWidgetClass, (Widget) new,
			  XmNorientation, XmVERTICAL,
			  XmNdragCallback, VSCallback,
			  XmNvalueChangedCallback, VSCallback,
			  XmNincrement, 1,
			  XmNminimum, 0,
			  XmNmaximum, (new->matrix.rows -
				       (int) new->matrix.fixed_rows),
			  XmNsliderSize, 1,
			  XmNbackground, new->core.background_pixel,
			  XmNforeground, new->manager.foreground,
			  XmNbottomShadowColor,
			  new->manager.bottom_shadow_color,
			  XmNbottomShadowPixmap,
			  new->manager.bottom_shadow_pixmap,
			  XmNhighlightColor, new->manager.highlight_color,
			  XmNhighlightPixmap, new->manager.highlight_pixmap,
/* KPB
			  XmNshadowThickness, new->manager.shadow_thickness,
*/
			  XmNtopShadowColor, new->manager.top_shadow_color,
			  XmNtopShadowPixmap, new->manager.top_shadow_pixmap,
			  NULL);

  /*
   * Create the Clip widget managed so we can use it for traversal
   */
  (void) XtVaCreateManagedWidget("clip",
				 xbaeClipWidgetClass, (Widget) new,
				 XmNexposeProc, ClipRedisplay,
				 XmNtraversalOn,
				 new->manager.traversal_on,
				 XmNbackground,
				 new->core.background_pixel,
				 NULL);

  /*
   * Add a callback to the Clip widget so we know when it gets the focus
   * and can use it in traversal.
   */
  XtAddCallback(ClipChild(new), XmNfocusCallback, TraverseInCB,
		(XtPointer) new);

  /*
   * Create text field (unmanaged for now) - its window will be reparented
   * in Realize to be a subwindow of Clip
   */
  (void) XtVaCreateWidget("textField",
			  xmTextFieldWidgetClass, (Widget) new,
			  XmNmarginWidth, new->matrix.cell_margin_width,
			  XmNmarginHeight, new->matrix.cell_margin_height,
			  XmNtranslations, new->matrix.text_translations,
			  XmNfontList, new->matrix.font_list,
			  XmNshadowThickness,
			  new->matrix.cell_shadow_thickness,
			  XmNhighlightThickness,
			  new->matrix.cell_highlight_thickness,
/* KPB
			  XmNbackground, new->core.background_pixel,
			  XmNbackground, new->matrix.text_background,
*/
			  XmNforeground, new->manager.foreground,
			  XmNbottomShadowColor,
			  new->manager.bottom_shadow_color,
			  XmNbottomShadowPixmap,
			  new->manager.bottom_shadow_pixmap,
			  XmNhighlightColor, new->manager.highlight_color,
			  XmNhighlightPixmap, new->manager.highlight_pixmap,
			  XmNtopShadowColor, new->manager.top_shadow_color,
			  XmNtopShadowPixmap, new->manager.top_shadow_pixmap,
			  NULL);

  /*
   * We need a hook onto textField for the modifyVerifyCallback
   * XXX we always have a modifyVerifyCallback even if the user
   * doesn't want one - how else can we tell when we need to
   * reinstall callbacks on the textField?
   */
  XtAddCallback(TextChild(new), XmNmodifyVerifyCallback, ModifyVerifyCB,
		(XtPointer) new);

  /*
   * Add a callback on losingFocus so we can attempt to commit
   * the edit of the cell we in if the user does not "activate"
   * the cell, but pushes a button somewhere else on the screen.
   */
  XtAddCallback(TextChild(new), XmNlosingFocusCallback, LosingFocusCB, (XtPointer) new);

  /*
   * Compute cell text baseline based on TextField widget
   */
  new->matrix.text_baseline = XmTextFieldGetBaseline(TextChild(new));

  /*
   * Calculate total pixel width of cell area
   */
  GetCellTotalWidth(new);

  /*
   * Cache the pixel position of each column
   */
  new->matrix.column_positions = CreateColumnPositions(new);
  GetColumnPositions(new);

  /*
   * Now we can set the VSB maximum (relies on data from
   * GetCellTotalWidth above)
   */
  XtVaSetValues(HorizScrollChild(new),
		XmNmaximum, CELL_TOTAL_WIDTH(new),
		NULL);

  /*
   * Current position starts at the top left editable cell.
   */
  new->matrix.current_row = new->matrix.fixed_rows;
  new->matrix.current_column = new->matrix.fixed_columns;

  /*
   * We aren't trying to traverse out
   */
  new->matrix.traversing = NOT_TRAVERSING;

#ifndef NEED_24BIT_VISUAL
  /*
   * Get/create our GCs
   */
  CreateDrawGC(new);
  GetInverseGC(new);
  CreateDrawClipGC(new);
  CreateInverseClipGC(new);
  CreateTopShadowClipGC(new);
  CreateBottomShadowClipGC(new);
#endif

  /*
   * Create ScrollMgrs to manage scrolling events
   */
  new->matrix.matrix_scroll_mgr = SmCreateScrollMgr();
  new->matrix.clip_scroll_mgr = SmCreateScrollMgr();

  /*
   * Compute our size.  If either dimension was explicitly set to 0,
   * then that dimension is computed.
   * Use request because superclasses modify width/height.
   */
  if (request->core.width == 0 || request->core.height == 0)
    ComputeSize(new, request->core.width == 0, request->core.height == 0);

  /*
   * Make sure top_row is sensible before we call Resize
   */
  if (VERT_ORIGIN(new) < 0)
    VERT_ORIGIN(new) = 0;
  else if (VERT_ORIGIN(new) > new->matrix.rows)
    VERT_ORIGIN(new) = new->matrix.rows;

  /*
   * Tweak top_row to make sure it is valid before calling Resize
   */
  if (VERT_ORIGIN(new))
    AdjustTopRow(new);

  /*
   * Layout the scrollbars and clip widget based on our size
   */
  Resize(new);
}

static void
Realize(mw, valueMask, attributes)
     XbaeMatrixWidget mw;
     XtValueMask *valueMask;
     XSetWindowAttributes *attributes;
{
  *valueMask |= CWDontPropagate;
  attributes->do_not_propagate_mask =
    ButtonPressMask | ButtonReleaseMask |
    KeyPressMask | KeyReleaseMask | PointerMotionMask;

  /*
   * Don't call our superclasses realize method, because Manager sets
   * bit_gravity
   */
  XtCreateWindow((Widget) mw, InputOutput, CopyFromParent,
		 *valueMask, attributes);

#ifdef NEED_24BIT_VISUAL
  /*
   * Now that we have a window...
   * Get/create our GCs
   */
  CreateDrawGC(mw);
  GetInverseGC(mw);
  CreateDrawClipGC(mw);
  CreateInverseClipGC(mw);
  CreateTopShadowClipGC(mw);
  CreateBottomShadowClipGC(mw);
#endif

  /*
   * Reparent the textFields window to be a subwindow of Clip widget
   * (we need to realize them first)
   */
  XtRealizeWidget(TextChild(mw));
  XtRealizeWidget(ClipChild(mw));
  XReparentWindow(XtDisplay(mw), XtWindow(TextChild(mw)),
		  XtWindow(ClipChild(mw)),
		  TextChild(mw)->core.x, TextChild(mw)->core.y);
}

static void
InsertChild(w)
     Widget w;
{
  if (((CompositeWidget) XtParent(w))->composite.num_children > 3)
  {
    String params[1];
    Cardinal num_params = 1;

    params[0] = XtClass(XtParent(w))->core_class.class_name;
    XtAppWarningMsg(XtWidgetToApplicationContext(w),
		    "insertChild", "badChild", "XbaeMatrix",
		    "XbaeMatrix: Applications cannot add children to %s widgets",
		    params, &num_params);
    return;
  }

  (*((XmManagerWidgetClass)
     (xbaeMatrixWidgetClass->core_class.superclass))->composite_class.
   insert_child) (w);
}

/*
 * Convert a pixel position to the column it is contained in.
 */
static int
XtoCol(mw, x)
     XbaeMatrixWidget mw;
     int x;
{
  int i;

  for (i = 1; i < mw->matrix.columns; i++)
    if (mw->matrix.column_positions[i] > x)
      return i - 1;

  return i - 1;
}

/*
 * Convert a row/column cell position to the x/y of its upper left corner
 * wrt the window it will be drawn in (either the matrix window for
 * fixed cells, or the clip window for non-fixed).
 */
static void
RowColToXY F_ARGS(mw __ row __ column __ x __ y , XbaeMatrixWidget mw _ int row _ int column _ int *x _ int *y)
{
  /*
   * If we are in a fixed cell, calculate x/y relative to Matrixs
   * window (take into account labels etc)
   */
  if (column < (int)mw->matrix.fixed_columns ||
      row < (int)mw->matrix.fixed_rows)
  {
    /*
     * Ignore horiz_origin if we are in a fixed column
     */
    if (column < (int)mw->matrix.fixed_columns)
      *x = COLUMN_LABEL_OFFSET(mw) + mw->matrix.column_positions[column];
    else
      *x = COLUMN_LABEL_OFFSET(mw) +
	mw->matrix.column_positions[column] - HORIZ_ORIGIN(mw);

    /*
     * Ignore vert_origin if we are in a fixed row
     */
    if (row < (int)mw->matrix.fixed_rows)
      *y = ROW_LABEL_OFFSET(mw) + ROW_HEIGHT(mw) * row;
    else
      *y = ROW_LABEL_OFFSET(mw) + ROW_HEIGHT(mw) *
	(row - VERT_ORIGIN(mw));
  }

  /*
   * If we are not fixed we must account for fixed rows/columns
   * and scrolling origins.
   */
  else
  {
    *x = (mw->matrix.column_positions[column] -
	  mw->matrix.column_positions[mw->matrix.fixed_columns]) -
      HORIZ_ORIGIN(mw);
    *y = ROW_HEIGHT(mw) * ((row - (int) mw->matrix.fixed_rows) -
			   VERT_ORIGIN(mw));
  }
}

/*
 * Convert an x/y pixel position to the row/column cell position it picks.
 * 'cell' specifies whether the x/y coord is relative to the fixed cells
 * window or the non-fixed cells window.
 * The coords x,y are adjusted so they are relative to the origin of the
 * picked cell.
 * If we are "out of bounds" on the ``low'' side, go ahead and return False
 * to show an error, but also set the row/column to -1 to indicate this could
 * be a row/column header.  This is currently undocumented behaviour, but
 * may be useful nevertheless.
 */
static Boolean
XYToRowCol(mw, x, y, row, column, cell)
     XbaeMatrixWidget mw;
     int *x, *y;
     int *row, *column;
     CellType cell;
{
  Rectangle rect;

  switch (cell)
	{

	  case FixedCell:

	    /*
             * Get the Rectangle enclosing the cells in fixed rows or columns.
             * If we don't have fixed rows or columns, then we didn't hit a cell.
             */
	    if (mw->matrix.fixed_columns)
	    {
	      SETRECT(rect,
		      COLUMN_LABEL_OFFSET(mw), ROW_LABEL_OFFSET(mw),
		      FIXED_COLUMN_LABEL_OFFSET(mw) - 1,
		      ROW_LABEL_OFFSET(mw) + VISIBLE_HEIGHT(mw) - 1);
	    }
	    else if (mw->matrix.fixed_rows)
	    {
	      SETRECT(rect,
		      COLUMN_LABEL_OFFSET(mw), ROW_LABEL_OFFSET(mw),
		      COLUMN_LABEL_OFFSET(mw) + VISIBLE_WIDTH(mw) - 1,
		      FIXED_ROW_LABEL_OFFSET(mw) - 1);
	    }
	    else
            {
              if (*x < ROW_LABEL_WIDTH(mw))
                *row = -1;
              else
                *column = XtoCol(mw, *x + (mw->matrix.fixed_columns
                                           ? 0
                                           : HORIZ_ORIGIN(mw)));
              if (*y < COLUMN_LABEL_HEIGHT(mw))
                *column = -1;
              else
                *row = YtoRow(mw, *y) + (mw->matrix.fixed_rows
                                         ? 0
                                         : VERT_ORIGIN(mw));
              return False;
            }

	    /*
             * If the point is in this rectangle, calculate the row/column
             * it hits. Otherwise we didn't hit a cell.
             */
	    if (INBOX(rect, *x, *y))
	    {

	      /*
               * Translate the point to rect's coord system
               */
	      *x -= rect.x1;
	      *y -= rect.y1;

	      /*
               * Convert this point to a row/column. We only take into
               * account the scrolling origins if we are not fixed in that
               * dimension.
               */
	      *row = YtoRow(mw, *y) + (mw->matrix.fixed_rows
				       ? 0
				       : VERT_ORIGIN(mw));
	      *column = XtoCol(mw, *x + (mw->matrix.fixed_columns
					 ? 0
					 : HORIZ_ORIGIN(mw)));

	      /*
               * Sanity check the result
               */
	      if ((mw->matrix.fixed_rows && *row >= (int)mw->matrix.fixed_rows) ||
		  (mw->matrix.fixed_columns &&
                   *column >= (int)mw->matrix.fixed_columns) ||
		  *row < 0 || *column < 0)
		return False;

	      /*
               * Adjust x,y so they are relative to this cells origin.
               */
	      *x -= mw->matrix.column_positions[*column] -
		(mw->matrix.fixed_columns
		 ? 0
		 : HORIZ_ORIGIN(mw));
	      *y %= ROW_HEIGHT(mw);

	      return True;
	    }
	    else
	      return False;

	    /* NOTREACHED */
	    break;

	  case NonFixedCell:

	    /*
             * Translate the point to take into account fixed rows or columns.
             */
	    *x += FIXED_COLUMN_WIDTH(mw);
	    *y += FIXED_ROW_HEIGHT(mw);

	    /*
             * Convert the new point to a row/column position
             */
	    *row = YtoRow(mw, *y) + VERT_ORIGIN(mw);
	    *column = XtoCol(mw, *x + HORIZ_ORIGIN(mw));

	    /*
             * Sanity check the result
             */
	    if (*row >= mw->matrix.rows || *column >= mw->matrix.columns ||
		*row < 0 || *column < 0)
	      return False;

	    /*
             * Adjust x,y so they are relative to this cells origin.
             */
	    *x -= mw->matrix.column_positions[*column] - HORIZ_ORIGIN(mw);
	    *y %= ROW_HEIGHT(mw);

	    return True;

	    /* NOTREACHED */
	    break;

	  default:
	    return False;
	}
}

/*
 * Width in pixels of a character in a given font
 */
#define charWidth(fs,c) ((fs)->per_char\
                         ? (fs)->per_char[(unsigned char)(c) -\
                                          (fs)->min_char_or_byte2].width\
                         : (fs)->min_bounds.width)


/*
 * Draw a string with specified attributes. We want to avoid having to
 * use a GC clip_mask, so we clip by characters. This complicates the code.
 */
static void
DrawString F_ARGS(mw __ win __ string __ length __ x __ y __ maxlen __ alignment __ highlight __ bold __ clip __ color , \
     XbaeMatrixWidget mw _ Window win _ String string _ int length _ int x _ int y _ int maxlen _ unsigned char alignment _ Boolean highlight _ Boolean bold _ Boolean clip _ Pixel color)
{
  int start, width, maxwidth;
  GC gc;

  /*
   * Initialize starting character in string
   */
  start = 0;

  /*
   * Calculate max width in pixels and actual pixel width of string.
   * XXX XTextWidth only sums the width of each char.	 A char could
   * have an rbearing greater than the width.	 So we might not clip
   * a char which really should be.
   */
  maxwidth = maxlen * FONT_WIDTH(mw);
  width = XTextWidth(mw->matrix.font, string, length);

  /*
   * If the width of the string is greater than the width of this cell,
   * we need to clip. We don't want to use the server to clip because
   * it is slow, so we truncate characters if we exceed a cells pixel width.
   */
  if (width > maxwidth)
  {

    switch (alignment)
	  {

	    case XmALIGNMENT_CENTER:
	      {
		int startx = x;
		int endx = x + maxwidth - 1;
		int newendx;

		/*
                 * Figure out our x for the centered string.  Then loop and chop
                 * characters off the front until we are within the cell.
                 * Adjust x, the starting character and the length of the string
                 * for each char.
                 */
		x += maxwidth / 2 - width / 2;
		while (x < startx)
		{
		  int cw = charWidth(mw->matrix.font, string[start]);

		  x += cw;
		  width -= cw;
		  length--;
		  start++;
		}

		/*
                 * Now figure out the end x of the string.  Then loop and chop
                 * characters off the end until we are within the cell.
                 */
		newendx = x + width - 1;
		while (newendx > endx)
		{
		  newendx -= charWidth(mw->matrix.font,
				       string[start + length - 1]);
		  length--;
		}

		break;
	      }

	    case XmALIGNMENT_END:
	      {

		/*
                 * Figure out our x for the right justified string.
                 * Then loop and chop characters off the front until we fit.
                 * Adjust x for each char lopped off. Also adjust the starting
                 * character and length of the string for each char.
                 */
		x += maxwidth - width;
		while (width > maxwidth)
		{
		  int cw = charWidth(mw->matrix.font, string[start]);

		  width -= cw;
		  x += cw;
		  length--;
		  start++;
		}
		break;
	      }

	    case XmALIGNMENT_BEGINNING:
	    default:
	      /*
               * Leave x alone, but chop characters off the end until we fit
               */
	      while (width > maxwidth)
	      {
		width -= charWidth(mw->matrix.font, string[length - 1]);
		length--;
	      }
	      break;
	  }
  }

  /*
   * We fit inside our cell, so just compute the x of the start of our string
   */
  else
  {
    switch (alignment)
	  {

	    case XmALIGNMENT_CENTER:
	      x += maxwidth / 2 - width / 2;
	      break;

	    case XmALIGNMENT_END:
	      x += maxwidth - width;
	      break;

	    case XmALIGNMENT_BEGINNING:
	    default:
	      /*
               * Leave x alone
               */
	      break;
	  }
  }

  /*
   * Figure out which GC to use
   */
  if (highlight)
  {
    if (clip)
      gc = mw->matrix.inverse_clip_gc;
    else
      gc = mw->matrix.inverse_gc;
  }
  else
  {
    if (clip)
      gc = mw->matrix.draw_clip_gc;
    else
      gc = mw->matrix.draw_gc;
  }

  /*
   * Don't worry, XSetForeground is smart about avoiding unnecessary
   * protocol requests.
   */
  if (!highlight)
    XSetForeground(XtDisplay(mw), gc, color);

  /*
   * Now draw the string at x starting at char 'start' and of length 'length'
   */
#ifdef NEED_WCHAR
  if (TWO_BYTE_FONT(mw))
    XDrawString16(XtDisplay(mw), win, gc, x, y, &string[start], length);
  else
#endif
    XDrawString(XtDisplay(mw), win, gc, x, y, &string[start], length);

  /*
   * If bold is on, draw the string again offset by 1 pixel (overstrike)
   */
  if (bold)
#ifdef NEED_WCHAR
    if (TWO_BYTE_FONT(mw))
      XDrawString16(XtDisplay(mw), win, gc, x - 1, y, &string[start], length);
    else
#endif
      XDrawString(XtDisplay(mw), win, gc, x - 1, y, &string[start], length);
}

/*
 * Draw the column label for the specified column.  Handles labels in
 * fixed and non-fixed columns.
 */
static void
DrawColumnLabel F_ARGS(mw __ column , XbaeMatrixWidget mw _ int column)
{
  String label;
  int x, y, i;

  if (mw->matrix.column_labels[column][0] == '\0')
    return;

  /*
   * If the column label is in a fixed column, we don't need to account
   * for the horiz_origin
   */
  if (column < (int)mw->matrix.fixed_columns)
    x = COLUMN_LABEL_OFFSET(mw) + mw->matrix.column_positions[column] +
      TEXT_X_OFFSET(mw);
  else
    x = COLUMN_LABEL_OFFSET(mw) + (mw->matrix.column_positions[column] -
				   HORIZ_ORIGIN(mw)) + TEXT_X_OFFSET(mw);

  /*
   * Set our y to the baseline of the first line in this column
   */
  y = TEXT_Y_OFFSET(mw) + (mw->matrix.column_label_maxlines -
			   mw->matrix.column_label_lines[column].lines) *
    TEXT_HEIGHT(mw);

  label = mw->matrix.column_labels[column];
  for (i = 0; i < mw->matrix.column_label_lines[column].lines; i++)
  {
    DrawString(mw, XtWindow(mw),
	       label, mw->matrix.column_label_lines[column].lengths[i],
	       x, y,
	       mw->matrix.column_widths[column],
	       mw->matrix.column_label_alignments
	       ? mw->matrix.column_label_alignments[column]
	       : XmALIGNMENT_BEGINNING,
	       False,
	       mw->matrix.bold_labels,
	       column >= (int)mw->matrix.fixed_columns,
	       mw->manager.foreground);
    y += TEXT_HEIGHT(mw);
    label += mw->matrix.column_label_lines[column].lengths[i] + 1;
  }
}

/*
 * Draw the row label for the specified row. Handles labels in fixed and
 * non-fixed rows.
 */
static void
DrawRowLabel F_ARGS(mw __ row , XbaeMatrixWidget mw _ int row)
{
  int y;

  if (mw->matrix.row_labels[row][0] == '\0')
    return;

  /*
   * If the row label is in a fixed row we don't need to account
   * for the vert_origin
   */
  if (row < (int)mw->matrix.fixed_rows)
    y = ROW_LABEL_OFFSET(mw) + ROW_HEIGHT(mw) * row + TEXT_Y_OFFSET(mw);
  else
    y = ROW_LABEL_OFFSET(mw) + ROW_HEIGHT(mw) * (row - VERT_ORIGIN(mw)) +
      TEXT_Y_OFFSET(mw);

  DrawString(mw, XtWindow(mw),
	     mw->matrix.row_labels[row],
	     strlen(mw->matrix.row_labels[row]),
	     TEXT_X_OFFSET(mw), y,
	     mw->matrix.row_label_width,
	     mw->matrix.row_label_alignment,
	     False,
	     mw->matrix.bold_labels,
	     False,
	     mw->manager.foreground);
}

/*
 * Draw a fixed or non-fixed cell. The coordinates are calculated relative
 * to the correct window and pixmap is copied to that window.
 */
static void
DrawCellPixmap F_ARGS(mw __ row __ column __ pixmap , \
	XbaeMatrixWidget mw _ int row _ int column _ Pixmap pixmap)
{
  int x, y;
  Window win = CELL_WINDOW(mw, row, column);
  Boolean clipped = row < (int)mw->matrix.fixed_rows;
  GC gc;
  XtGCMask gc_mask;
  XGCValues gc_values;

  if (!win)
    return;

  /*
   * Convert the row/column to the coordinates relative to the correct
   * window
   */
  RowColToXY(mw, row, column, &x, &y);

  /*
   * Draw the pixmap.
  gc_mask.background = None;
  gc_mask.function
  gc_mask.planemask
  gc_mask.subwindow_mode
  gc_mask.graphics_exposeures
  gc_mask.clip_x_origin
  gc_mask.clip_y_origin
  gc_mask.clip_mask
  gc = XtGetGC((clipped ? ClipChild(mw) : (Widget)mw), gc_mask, &gc_values);
  XtReleaseGC((clipped ? ClipChild(mw) : (Widget)mw), gc);
   */

  XCopyArea(XtDisplay(mw), pixmap, win, mw->matrix.draw_gc, 0, 0, COLUMN_WIDTH(mw, column), ROW_HEIGHT(mw), 0, 0);
/*
DefaultGC(dpy, DefaultScreen(dpy))
*/
	      

  /* XXX Do I really want to do this?
   * Surround the cell with a shadow.
  if (mw->matrix.cell_shadow_thickness)
  {
    if (clipped)
      DRAW_SHADOW(XtDisplay(mw), win,
		    mw->matrix.cell_bottom_shadow_clip_gc,
		    mw->matrix.cell_top_shadow_clip_gc,
		    mw->matrix.cell_shadow_thickness,
		    x + (int) mw->matrix.cell_highlight_thickness,
		    y + (int) mw->matrix.cell_highlight_thickness,
		    COLUMN_WIDTH(mw, column) -
		    2 * mw->matrix.cell_highlight_thickness,
		    ROW_HEIGHT(mw) -
		    2 * mw->matrix.cell_highlight_thickness);
    else
      DRAW_SHADOW(XtDisplay(mw), win,
		    mw->manager.bottom_shadow_GC,
		    mw->manager.top_shadow_GC,
		    mw->matrix.cell_shadow_thickness,
		    x + (int) mw->matrix.cell_highlight_thickness,
		    y + (int) mw->matrix.cell_highlight_thickness,
		    COLUMN_WIDTH(mw, column) -
		    2 * mw->matrix.cell_highlight_thickness,
		    ROW_HEIGHT(mw) -
		    2 * mw->matrix.cell_highlight_thickness);
  }
   */

}

/*
 * Draw a fixed or non-fixed cell. The coordinates are calculated relative
 * to the correct window and the cell is drawn in that window.
 */
static void
DrawCellString(mw, row, column, string)
     XbaeMatrixWidget mw;
     int row, column;
     String string;
{
  int x, y;
  Pixel color;
  Window win = CELL_WINDOW(mw, row, column);
  Boolean clipped = row < (int)mw->matrix.fixed_rows;
  GC gc;
  Boolean alt;

  if (!win)
    return;

  /*
   * Convert the row/column to the coordinates relative to the correct
   * window
   */
  RowColToXY(mw, row, column, &x, &y);
  alt = (mw->matrix.alt_row_count && row >= (int)mw->matrix.fixed_rows)
        ? (((row - (int)mw->matrix.fixed_rows) / mw->matrix.alt_row_count) % 2)
        : False;

  /* //// get the gc */
  gc = (clipped ? mw->matrix.draw_clip_gc : mw->matrix.draw_gc);

  /* //// set the background */
  /* NEW
   * If this cell is selected, fill the cell with the selected foreground
   * color
   */
  if (mw->matrix.selected_cells[row][column])
  {
    if (mw->matrix.colors)
      color = mw->matrix.colors[row][column]; /* OLD */
    else
      color = mw->manager.foreground;
  }
  else if (XtIsManaged(TextChild(mw)) &&
           row == mw->matrix.current_row &&
	   column == mw->matrix.current_column)
  {
    color = mw->matrix.text_background;
  }
    /* NEW color = mw->matrix.selected_background; */
/* later
  else if (mw->matrix.cell_background && 
    mw->matrix.cell_background[row][column] != 
    XbaeMatrixDefaultBackground)
      color = mw->matrix.cell_background[row][column];
*/
  else
  {
      if (alt)
        color = mw->matrix.odd_row_background;
      else
        color = mw->matrix.even_row_background;
  }
/* NEW - end */

  /* //// fill the cell */
  /*
   * Don't worry, XSetForeground is smart about avoiding
   * unnecessary protocol requests.
   */
  XSetForeground(XtDisplay(mw), gc, color);
  XFillRectangle(XtDisplay(mw), win, gc,
		x, y, COLUMN_WIDTH(mw, column), ROW_HEIGHT(mw));
/*
                 x + (mw->matrix.cell_highlight_thickness + mw->matrix.cell_shadow_thickness),
                 y + (mw->matrix.cell_highlight_thickness + mw->matrix.cell_shadow_thickness),
                 COLUMN_WIDTH(mw, column) -
                 (mw->matrix.cell_highlight_thickness +
                  mw->matrix.cell_shadow_thickness) * 2,
                 ROW_HEIGHT(mw) -
                 (mw->matrix.cell_highlight_thickness +
                  mw->matrix.cell_shadow_thickness) * 2);
*/

  /* //// set the foreground */
  /*
   * Figure out what color this cell is.
   */
  if (mw->matrix.colors)
    color = mw->matrix.colors[row][column];
  else
    color = mw->manager.foreground;

  /* //// draw the string */
  /*
   * Draw the string in the cell.
   */
  if (*string != '\0')
  {
    DrawString(mw, win, string, strlen(string),
	       x + TEXT_X_OFFSET(mw),
	       y + TEXT_Y_OFFSET(mw),
	       mw->matrix.column_widths[column],
	       mw->matrix.column_alignments
	        ? mw->matrix.column_alignments[column]
	        : XmALIGNMENT_BEGINNING,
	       mw->matrix.selected_cells[row][column],
	       False, clipped, color);
  }

  /*
   * Surround the cell with a shadow.
   */
  if (mw->matrix.cell_shadow_thickness)
  {
    if (clipped)
      DRAW_SHADOW(XtDisplay(mw), win,
		    mw->matrix.cell_bottom_shadow_clip_gc,
		    mw->matrix.cell_top_shadow_clip_gc,
		    mw->matrix.cell_shadow_thickness,
		    x + (int) mw->matrix.cell_highlight_thickness,
		    y + (int) mw->matrix.cell_highlight_thickness,
		    COLUMN_WIDTH(mw, column) -
		    2 * mw->matrix.cell_highlight_thickness,
		    ROW_HEIGHT(mw) -
		    2 * mw->matrix.cell_highlight_thickness);
    else
      DRAW_SHADOW(XtDisplay(mw), win,
		    mw->manager.bottom_shadow_GC,
		    mw->manager.top_shadow_GC,
		    mw->matrix.cell_shadow_thickness,
		    x + (int) mw->matrix.cell_highlight_thickness,
		    y + (int) mw->matrix.cell_highlight_thickness,
		    COLUMN_WIDTH(mw, column) -
		    2 * mw->matrix.cell_highlight_thickness,
		    ROW_HEIGHT(mw) -
		    2 * mw->matrix.cell_highlight_thickness);
  }
}


static XbaeCellType
get_draw_cell_value F_ARGS(mw __ row __ column __ string __ pixmap , \
		   XbaeMatrixWidget mw _ int row _ int column _ String *string _ Pixmap *pixmap)
{
    XbaeMatrixDrawCellCallbackStruct cbd;

    cbd.reason = XbaeDrawCellReason;
    cbd.row = row;
    cbd.column = column;
    cbd.width = COLUMN_WIDTH(mw, column);
    cbd.height = ROW_HEIGHT(mw);
    cbd.type = XbaeString;
    cbd.string = "";
    cbd.pixmap = (Pixmap)NULL;
    XtCallCallbackList((Widget)mw, mw->matrix.draw_cell_callback, (XtPointer) &cbd);

    /*
     * Try a "little" error checking, there's not much we can do.
    if ((cbd.type == XbaePixmap) &&
	((cbd.pixmap == XmUNSPECIFIED_PIXMAP) || (cbd.pixmap == BadPixmap)))
    {
      cbd.string = "";
      cbd.type = XbaeString;
    }
    *pixmap = cbd.pixmap;
    *string = cbd.string;
     */

    /* temporarily turn pixmaps off */
    if (cbd.type == XbaePixmap)
    {
      cbd.string = "";
      cbd.type = XbaeString;
    }

    return (cbd.type);
}


static void
DrawCell(mw, row, column)
     XbaeMatrixWidget mw;
     int row, column;
{
  if (mw->matrix.draw_cell_callback == NULL)
    DrawCellString(mw, row, column, mw->matrix.cells[row][column]);
  else
  {
    String string;
    Pixmap pixmap;

    if (get_draw_cell_value(mw, row, column, &string, &pixmap) == XbaeString)
      DrawCellString(mw, row, column, string);
    else
      DrawCellPixmap(mw, row, column, pixmap);
  }
}

/*
 * Redraw the row and column labels and the cells in fixed rows/columns
 * that are overlapped by the Rectangle argument.
 */
static void
RedrawLabelsAndFixed F_ARGS(mw __ expose , XbaeMatrixWidget mw _ Rectangle *expose)
{
  /*
   * Handle the row labels that are in fixed rows
   */
  if (mw->matrix.fixed_rows && mw->matrix.row_labels)
  {
    Rectangle rect;

    /*
     * Get the Rectangle enclosing the fixed row labels
     */
    SETRECT(rect,
	    0, ROW_LABEL_OFFSET(mw),
	    ROW_LABEL_WIDTH(mw) - 1, FIXED_ROW_LABEL_OFFSET(mw) - 1);

    /*
     * If the expose Rectangle overlaps, then some labels must be redrawn
     */
    if (OVERLAP(*expose, rect))
    {
      Rectangle intersect;
      int endRow, i;

      /*
       * Intersect the fixed-row-labels Rectangle with the expose
       * Rectangle along the Y axis.  The resulting Rectangle will
       * be in 'rect's coordinate system.
       */
      Y_INTERSECT(rect, *expose, intersect);

      /*
       * Redraw each label that was intersected
       */
      endRow = YtoRow(mw, intersect.y2);
      for (i = YtoRow(mw, intersect.y1); i <= endRow; i++)
	DrawRowLabel(mw, i);
    }
  }

  /*
   * Handle row labels that aren't in fixed rows
   */
  if (mw->matrix.row_labels)
  {
    Rectangle rect;

    /*
     * Get the Rectangle enclosing the non-fixed row labels
     */
    SETRECT(rect,
	    0, FIXED_ROW_LABEL_OFFSET(mw),
	    ROW_LABEL_WIDTH(mw) - 1,
	    FIXED_ROW_LABEL_OFFSET(mw) + VISIBLE_HEIGHT(mw) - 1);

    /*
     * If the expose Rectangle overlaps, then some labels must be redrawn
     */
    if (OVERLAP(*expose, rect))
    {
      Rectangle intersect;
      int endRow, i;

      /*
       * Intersect the fixed-row-labels Rectangle with the expose
       * Rectangle along the Y axis.  The resulting Rectangle will
       * be in 'rect's coordinate system.
       */
      Y_INTERSECT(rect, *expose, intersect);

      /*
       * Translate 'intersect' to take into account any fixed rows.
       * This gets it back into the coord system expected by YtoRow().
       */
      intersect.y1 += FIXED_ROW_HEIGHT(mw);
      intersect.y2 += FIXED_ROW_HEIGHT(mw);

      /*
       * Redraw each label that was intersected
       */
      endRow = YtoRow(mw, intersect.y2) + VERT_ORIGIN(mw);
      for (i = YtoRow(mw, intersect.y1) + VERT_ORIGIN(mw);
	   i <= endRow;
	   i++)
	DrawRowLabel(mw, i);
    }
  }

  /*
   * Handle the column labels that are in fixed columns
   */
  if (mw->matrix.fixed_columns && mw->matrix.column_labels)
  {
    Rectangle rect;

    /*
     * Get the Rectangle enclosing the portion of the column labels
     * that are in fixed columns
     */
    SETRECT(rect,
	    COLUMN_LABEL_OFFSET(mw), 0,
	    FIXED_COLUMN_LABEL_OFFSET(mw) - 1,
	    COLUMN_LABEL_HEIGHT(mw) - 1);

    /*
     * If the expose Rectangle overlaps, then some labels must be redrawn
     */
    if (OVERLAP(*expose, rect))
    {
      Rectangle intersect;
      int endCol, i;

      /*
       * Intersect the fixed-column-labels Rectangle with the expose
       * Rectangle along the X axis.  The resulting Rectangle will
       * be in 'rect's coordinate system.
       */
      X_INTERSECT(rect, *expose, intersect);

      /*
       * Redraw each label that was intersected
       */
      endCol = XtoCol(mw, intersect.x2);
      for (i = XtoCol(mw, intersect.x1); i <= endCol; i++)
	DrawColumnLabel(mw, i);
    }
  }

  /*
   * Handle column labels that aren't in fixed columns
   */
  if (mw->matrix.column_labels)
  {
    Rectangle rect;

    /*
     * Get the Rectangle enclosing the non-fixed column labels
     */
    SETRECT(rect,
	    FIXED_COLUMN_LABEL_OFFSET(mw), 0,
	    FIXED_COLUMN_LABEL_OFFSET(mw) + VISIBLE_WIDTH(mw) - 1,
	    COLUMN_LABEL_HEIGHT(mw) - 1);

    /*
     * If the expose Rectangle overlaps, then some labels must be redrawn
     */
    if (OVERLAP(*expose, rect))
    {
      Rectangle intersect;
      int endCol, i;

      /*
       * Intersect the non-fixed-column-labels Rectangle with the expose
       * Rectangle along the X axis.  The resulting Rectangle will
       * be in 'rect's coordinate system.
       */
      X_INTERSECT(rect, *expose, intersect);

      /*
       * Translate 'intersect' to take into account any fixed columns.
       * This gets it back into the coord system expected by XtoCol().
       */
      intersect.x1 += FIXED_COLUMN_WIDTH(mw);
      intersect.x2 += FIXED_COLUMN_WIDTH(mw);

      /*
       * Redraw each label that was intersected
       */
      endCol = XtoCol(mw, intersect.x2 + HORIZ_ORIGIN(mw));
      for (i = XtoCol(mw, intersect.x1 + HORIZ_ORIGIN(mw));
	   i <= endCol;
	   i++)
	DrawColumnLabel(mw, i);
    }
  }

  /*
   * Handle cells in fixed rows or fixed columns
   */
  if (mw->matrix.fixed_rows || mw->matrix.fixed_columns)
  {
    Rectangle rect;

    /*
     * Get the Rectangle enclosing the cells in fixed rows or columns
     */
    if (mw->matrix.fixed_columns)
    {
      SETRECT(rect,
	      COLUMN_LABEL_OFFSET(mw), ROW_LABEL_OFFSET(mw),
	      FIXED_COLUMN_LABEL_OFFSET(mw) - 1,
	      ROW_LABEL_OFFSET(mw) + VISIBLE_HEIGHT(mw) - 1);
    }
    else if (mw->matrix.fixed_rows)
    {
      SETRECT(rect,
	      COLUMN_LABEL_OFFSET(mw), ROW_LABEL_OFFSET(mw),
	      COLUMN_LABEL_OFFSET(mw) + VISIBLE_WIDTH(mw) - 1,
	      FIXED_ROW_LABEL_OFFSET(mw) - 1);
    }

    /*
     * If the expose Rectangle overlaps, then some cells must be redrawn
     */
    if (OVERLAP(*expose, rect))
    {
      Rectangle intersect;
      int startCol, endCol, startRow, endRow, i, j;

      /*
       * Intersect the fixed-cells Rectangle with the expose
       * Rectangle along the X and Y axis.  The resulting Rectangle will
       * be in 'rect's coordinate system.
       */
      INTERSECT(rect, *expose, intersect);

      /*
       * Get starting and ending rows/columns.  We only take into
       * account the scrolling origins if we are not fixed in that
       * dimension.
       */
      startCol = XtoCol(mw, intersect.x1 +
			(mw->matrix.fixed_columns
			 ? 0
			 : HORIZ_ORIGIN(mw)));
      endCol = XtoCol(mw, intersect.x2 +
		      (mw->matrix.fixed_columns
		       ? 0
		       : HORIZ_ORIGIN(mw)));
      startRow = YtoRow(mw, intersect.y1) +
	(mw->matrix.fixed_rows
	 ? 0
	 : VERT_ORIGIN(mw));
      endRow = YtoRow(mw, intersect.y2) +
	(mw->matrix.fixed_rows
	 ? 0
	 : VERT_ORIGIN(mw));

      /*
	     * Redraw each cell that was intersected
	     */
      for (i = startRow; i <= endRow; i++)
	for (j = startCol; j <= endCol; j++)
	  DrawCell(mw, i, j);
    }
  }

  /*
   * Draw a shadow just inside row/column labels and around outer edge
   * of clip widget.	We can't use height of clip widget because it is
   * truncated to nearest row.  We use cell_visible_height instead.
   */
  if (mw->manager.shadow_thickness)
    DRAW_SHADOW(XtDisplay(mw), XtWindow(mw),
		  mw->manager.bottom_shadow_GC,
		  mw->manager.top_shadow_GC,
		  mw->manager.shadow_thickness,
		  ROW_LABEL_WIDTH(mw),
		  COLUMN_LABEL_HEIGHT(mw),
		  ClipChild(mw)->core.width + FIXED_COLUMN_WIDTH(mw) +
		  2 * mw->manager.shadow_thickness,
		  mw->matrix.cell_visible_height + FIXED_ROW_HEIGHT(mw) +
		  2 * mw->manager.shadow_thickness);
}

/*
 * This is the expose method for the Matrix widget.
 * It redraws the row and column labels, the cells in fixed rows and columns
 * and the clip window shadow.
 */
/* ARGSUSED */
static void
Redisplay(mw, event, region)
     XbaeMatrixWidget mw;
     XEvent *event;
     Region region;
{
  Rectangle expose;

  /*
   * Send our events to the mw ScrollMgr to be adjusted.
   */
  switch (event->type)
	{

	  case Expose:
	    /*
             * The Expose event will be translated into our scrolled
             * coordinate system.  Then it is put in a Rectangle.
             */
	    SmScrollEvent(mw->matrix.matrix_scroll_mgr, event);
	    SETRECT(expose,
		    event->xexpose.x, event->xexpose.y,
		    event->xexpose.x + event->xexpose.width,
		    event->xexpose.y + event->xexpose.height);
	    break;

	  case GraphicsExpose:
	    /*
             * The GraphicsExpose event will cause a scroll to be removed
             * from the managers queue, then the event will be translated
             * into our scrolled coordinate system.	 Then it is put in a Rectangle.
             */
	    SmScrollEvent(mw->matrix.matrix_scroll_mgr, event);
	    SETRECT(expose,
		    event->xgraphicsexpose.x, event->xgraphicsexpose.y,
		    event->xgraphicsexpose.x + event->xgraphicsexpose.width,
		    event->xgraphicsexpose.y + event->xgraphicsexpose.height);
	    break;

	  case NoExpose:
	    /*
             * The NoExpose event means we won't be getting any GraphicsExpose
             * events, so the scroll well be removed from the queue and
             * we are done.
             */
	    SmScrollEvent(mw->matrix.matrix_scroll_mgr, event);
	    return;

	  default:
	    return;
	}

  /*
   * Redraw the row/column labels and fixed rows/columns which are
   * overlapped by the expose Rectangle.
   */
  RedrawLabelsAndFixed(mw, &expose);
}

/*
 * Redraw all cells in the clip widget damaged by the passed Rectangle.
 * The Rectangle must be within the bounds of the cells. These are the
 * non-fixed cells.
 */
static void
RedrawCells F_ARGS(mw __ expose , XbaeMatrixWidget mw _ Rectangle *expose)
{
  int startCol, endCol, startRow, endRow, i, j;
  Rectangle rect;

  /*
   * Translate the 'expose' Rectangle to take into account the
   * fixed rows or columns.
   */
  SETRECT(rect,
	  expose->x1 + FIXED_COLUMN_WIDTH(mw),
	  expose->y1 + FIXED_ROW_HEIGHT(mw),
	  expose->x2 + FIXED_COLUMN_WIDTH(mw),
	  expose->y2 + FIXED_ROW_HEIGHT(mw));

  /*
   * Calculate the starting and ending rows/columns of the cells
   * which must be redrawn.
   */
  startCol = XtoCol(mw, rect.x1 + HORIZ_ORIGIN(mw));
  endCol = XtoCol(mw, rect.x2 + HORIZ_ORIGIN(mw));
  startRow = YtoRow(mw, rect.y1) + VERT_ORIGIN(mw);
  endRow = YtoRow(mw, rect.y2) + VERT_ORIGIN(mw);

  /*
   * Redraw all cells which were exposed.
   */
  for (i = startRow; i <= endRow; i++)
    for (j = startCol; j <= endCol; j++)
      DrawCell(mw, i, j);
}

/*
 * This is the exposeProc function for the Clip widget.
 * It handles expose events for the Clip widget by redrawing those
 * non-fixed cells which were damaged.
 * It recieves Expose, GraphicsExpose and NoExpose events.
 */
/* ARGSUSED */
static void
ClipRedisplay(w, event, region)
     Widget w;
     XEvent *event;
     Region region;
{
  XbaeMatrixWidget mw = (XbaeMatrixWidget) XtParent(w);
  Rectangle expose, clip, intersect;

  /*
   * Send our events to the clip ScrollMgr to be adjusted.
   */
  switch (event->type)
	{

	  case Expose:
	    /*
             * The Expose event will be translated into our scrolled
             * coordinate system.  Then it is put in a Rectangle.
             */
	    SmScrollEvent(mw->matrix.clip_scroll_mgr, event);
	    SETRECT(expose,
		    event->xexpose.x, event->xexpose.y,
		    event->xexpose.x + event->xexpose.width - 1,
		    event->xexpose.y + event->xexpose.height - 1);
	    break;

	  case GraphicsExpose:
	    /*
             * The GraphicsExpose event will cause a scroll to be removed
             * from the managers queue, then the event will be translated
             * into our scrolled coordinate system.	 Then it is put in a Rectangle.
             */
	    SmScrollEvent(mw->matrix.clip_scroll_mgr, event);
	    SETRECT(expose,
		    event->xgraphicsexpose.x, event->xgraphicsexpose.y,
		    event->xgraphicsexpose.x + event->xgraphicsexpose.width - 1,
		    event->xgraphicsexpose.y + event->xgraphicsexpose.height - 1);
	    break;

	  case NoExpose:
	    /*
             * The NoExpose event means we won't be getting any GraphicsExpose
             * events, so the scroll well be removed from the queue and
             * we are done.
             */
	    SmScrollEvent(mw->matrix.clip_scroll_mgr, event);
	    return;

	  default:
	    return;
	}

  /*
   * We may get an expose event larger than the size of the Clip widget.
   * This is because in set_values we may clear the Clip widget
   * before it gets resized smaller (maybe in set_values_almost).
   * So here we intersect the expose event with the clip widget
   * to ensure the expose Rectangle is not larger than the Clip widget.
   */
  SETRECT(clip,
	  0, 0,
	  w->core.width - 1, w->core.height - 1);
  INTERSECT(clip, expose, intersect);

  /*
   * Redraw those cells which overlap the intersect Rectangle.
   */
  RedrawCells(mw, &intersect);
}


/* ARGSUSED */
static Boolean
SetValues(current, request, new, args, num_args)
     XbaeMatrixWidget current, request, new;
     ArgList args;
     Cardinal *num_args;
{
  Boolean redisplay = False;	/* need to redraw */
  Boolean relayout = False;	/* need to layout, but same size */
  Boolean new_column_widths = False;	/* column widths changed */
  Boolean new_cells = False;	/* cells changed */
  Boolean do_top_row = False;	/* reset top_row */
  int n;
  Arg wargs[9];

#define NE(field)	(current->field != new->field)
#define EQ(field)	(current->field == new->field)

  /*
   * We cannot re-set either of the scrollbars.
   */
  if (NE(matrix.vertical_sb) || NE(matrix.horizontal_sb))
  {
    XtAppWarningMsg(XtWidgetToApplicationContext((Widget) new),
		    "setValues", "set scroll bars", "XbaeMatrix",
		    "XbaeMatrix: Cannot set the horizontal or vertical scrollbars",
		    NULL, 0);
    new->matrix.vertical_sb = current->matrix.vertical_sb;
    new->matrix.horizontal_sb = current->matrix.horizontal_sb;
  }

  /*
   * If rows changed, then:
   *	row_labels must change or be NULL
   */
  if (NE(matrix.rows) &&
      (new->matrix.row_labels && EQ(matrix.row_labels)))
  {
    XtAppWarningMsg(XtWidgetToApplicationContext((Widget) new),
		    "setValues", "rows",
		    "XbaeMatrix",
		    "XbaeMatrix: Number of rows changed but dependent resources did not",
		    NULL, 0);
    new->matrix.rows = current->matrix.rows;
    new->matrix.row_labels = current->matrix.row_labels;
  }

  /*
   * If columns changed, then:
   *	column_widths must change
   *	column_max_lengths must change or be NULL
   *	column_labels must change or be NULL
   *	column_alignments must change or be NULL
   *	column_label_alignments must change or be NULL
   */
  if (NE(matrix.columns) &&
      ((new->matrix.column_labels && EQ(matrix.column_labels)) ||
       (new->matrix.column_max_lengths && EQ(matrix.column_max_lengths)) ||
       (new->matrix.column_alignments && EQ(matrix.column_alignments)) ||
       (new->matrix.column_label_alignments &&
	EQ(matrix.column_label_alignments)) ||
       EQ(matrix.column_widths)))
  {
    XtAppWarningMsg(XtWidgetToApplicationContext((Widget) new),
		    "setValues", "columns", "XbaeMatrix",
		"XbaeMatrix: Number of columns changed but dependent resources did not",
		    NULL, 0);
    new->matrix.columns = current->matrix.columns;
    new->matrix.column_widths = current->matrix.column_widths;
    new->matrix.column_max_lengths = current->matrix.column_max_lengths;
    new->matrix.column_labels = current->matrix.column_labels;
    new->matrix.column_alignments = current->matrix.column_alignments;
    new->matrix.column_label_alignments =
      current->matrix.column_label_alignments;
  }

  /*
   * Make sure we have at least one row/column.
   */
  if (new->matrix.columns <= 0 || new->matrix.rows <= 0)
  {
    XtAppWarningMsg(XtWidgetToApplicationContext((Widget) new),
		    "setValues", "size", "XbaeMatrix",
		    "XbaeMatrix: Must have at least one row and column",
		    NULL, 0);
    if (new->matrix.columns <= 0)
      new->matrix.columns = current->matrix.columns;
    if (new->matrix.rows <= 0)
      new->matrix.rows = current->matrix.rows;
  }

  /*
   * We cannot have both fixed rows and fixed columns
   */
  if (new->matrix.fixed_rows && new->matrix.fixed_columns)
  {
    XtAppWarningMsg(XtWidgetToApplicationContext((Widget) new),
		    "setValues", "bothFixed", "XbaeMatrix",
		    "XbaeMatrix: Cannot have both fixed rows and columns",
		    NULL, 0);
    if (NE(matrix.fixed_rows))
      new->matrix.fixed_rows = current->matrix.fixed_rows;
    if (NE(matrix.fixed_columns))
      new->matrix.fixed_columns = current->matrix.fixed_columns;
  }

  /*
   * We must have at least one non-fixed row/column.
   * This could be caused by fixed_rows/columns or rows/columns changing.
   */
  if ((new->matrix.fixed_rows &&
       (int)new->matrix.fixed_rows >= new->matrix.rows) ||
      (new->matrix.fixed_columns &&
       (int)new->matrix.fixed_columns >= new->matrix.columns))
  {
    String params[1];
    Cardinal num_params = 1;

    params[0] = new->matrix.fixed_rows ? "row" : "column";
    XtAppWarningMsg(XtWidgetToApplicationContext((Widget) new),
		    "setValues", "tooManyFixed", "XbaeMatrix",
		    "XbaeMatrix: At least one %s must not be fixed",
		    (String *) params, (Cardinal *) & num_params);
    if (new->matrix.fixed_rows)
    {
      if (NE(matrix.fixed_rows))
	new->matrix.fixed_rows = current->matrix.fixed_rows;
      if (NE(matrix.rows))
	new->matrix.rows = current->matrix.rows;
    }
    else if (new->matrix.fixed_columns)
    {
      if (NE(matrix.fixed_columns))
	new->matrix.fixed_columns = current->matrix.fixed_columns;
      if (NE(matrix.columns))
	new->matrix.columns = current->matrix.columns;
    }
  }

  /*
   * We can't have too many visible columns.
   * This could be caused by visible_columns or columns or fixed_columns
   * changing.
   */
  if ((int)new->matrix.visible_columns >
      new->matrix.columns - (int)new->matrix.fixed_columns)
  {
    XtAppWarningMsg(XtWidgetToApplicationContext((Widget) new),
		    "setValues", "tooManyVisibleColumns", "XbaeMatrix",
	 "XbaeMatrix: visibleColumns must not be greater than (columns - fixedColumns)",
		    (String *) NULL, (Cardinal *) NULL);
    if (NE(matrix.visible_columns))
      new->matrix.visible_columns = current->matrix.visible_columns;
    if (NE(matrix.columns))
      new->matrix.columns = current->matrix.columns;
    if (NE(matrix.fixed_columns))
      new->matrix.fixed_columns = current->matrix.fixed_columns;
  }

  /*
   * Make sure we have column_widths
   */
  if (new->matrix.column_widths == NULL)
  {
    XtAppWarningMsg(XtWidgetToApplicationContext((Widget) new),
		    "setValues", "columnWidths", "XbaeMatrix",
		    "XbaeMatrix: Must specify columnWidths",
		    NULL, 0);
    new->matrix.column_widths = current->matrix.column_widths;
  }


  /*
   * If rows or columns or fixed rows/columns changed,
   * then we need to relayout.
   */
  if (NE(matrix.rows) || NE(matrix.fixed_rows))
  {
    /*
     * Reset VSB maximum. sliderSize will be reset later in Resize.
     */
    XtVaSetValues(VertScrollChild(new),
		  XmNmaximum, (new->matrix.rows -
			       (int) new->matrix.fixed_rows),
		  XmNvalue, 0,
		  XmNsliderSize, 1,
		  NULL);
    do_top_row = True;
    relayout = True;
  }
  if (NE(matrix.columns) || NE(matrix.fixed_columns))
    relayout = True;

  /*
   * Copy any pointed to resources if they changed
   */

  if (NE(matrix.cells))
  {
    FreeCells(current);
    CopyCells(new);
    redisplay = True;
    new_cells = True;
  }
  else if (NE(matrix.rows) || NE(matrix.columns))
    ResizeCells(current, new);
  if (NE(matrix.row_labels))
  {
    /*
     * If we added or deleted row_labels, we need to layout.
     */
    if (!current->matrix.row_labels || !new->matrix.row_labels)
      relayout = True;
    else
      redisplay = True;

    FreeRowLabels(current);
    if (new->matrix.row_labels)
      CopyRowLabels(new);
  }
  if (NE(matrix.column_labels))
  {
    FreeColumnLabels(current);
    if (new->matrix.column_labels)
      CopyColumnLabels(new);
    else
    {
      new->matrix.column_label_lines = NULL;
      new->matrix.column_label_maxlines = 0;
    }

    /*
     * If the number of lines in column labels changed, we need to relayout
     */
    if (current->matrix.column_label_maxlines !=
	new->matrix.column_label_maxlines)
      relayout = True;
    else
      redisplay = True;
  }
  if (NE(matrix.column_max_lengths))
  {
    FreeColumnMaxLengths(current);
    if (new->matrix.column_max_lengths)
      CopyColumnMaxLengths(new);
    redisplay = True;
  }
  if (NE(matrix.column_alignments))
  {
    FreeColumnAlignments(current);
    if (new->matrix.column_alignments)
      CopyColumnAlignments(new);
    redisplay = True;
  }
  if (NE(matrix.column_label_alignments))
  {
    FreeColumnLabelAlignments(current);
    if (new->matrix.column_label_alignments)
      CopyColumnLabelAlignments(new);
    redisplay = True;
  }
  if (NE(matrix.colors))
  {
    FreeColors(current);
    if (new->matrix.colors)
      CopyColors(new);
    redisplay = True;
  }
  else if (new->matrix.colors && (NE(matrix.rows) || NE(matrix.columns)))
    ResizeColors(current, new);
  if (NE(matrix.column_widths))
  {
    FreeColumnWidths(current);
    CopyColumnWidths(new);
    relayout = True;
    new_column_widths = True;
  }
  if (NE(matrix.selected_cells))
  {
    FreeSelectedCells(current);
    CopySelectedCells(new);
    redisplay = True;
  }
  else if (NE(matrix.rows) || NE(matrix.columns))
    ResizeSelectedCells(current, new);

  /*
   * If traversal changes, pass through to Clip and textField children.
   */
  if (NE(manager.traversal_on))
  {
    XtVaSetValues(ClipChild(new),
		  XmNtraversalOn, new->manager.traversal_on,
		  NULL);
  }

  if (EQ(matrix.odd_row_background) &&
      (current->matrix.odd_row_background == current->core.background_pixel))
  {
    new->matrix.odd_row_background = new->core.background_pixel;
    redisplay = True;
  }
  if (EQ(matrix.even_row_background) &&
      (current->matrix.even_row_background == current->core.background_pixel))
  {
    new->matrix.even_row_background = new->core.background_pixel;
    redisplay = True;
  }
  if (NE(matrix.even_row_background) || NE(matrix.odd_row_background))
    redisplay = True;

  /*
   * Pass through primitive/manager resources to our children
   */
  n = 0;
  if (NE(core.background_pixel))
  {
    XtVaSetValues(ClipChild(new),
		  XmNbackground, new->core.background_pixel,
		  NULL);
    XtSetArg(wargs[n], XmNbackground, new->core.background_pixel); n++;
  }
  if (NE(manager.foreground))
  {
    XtSetArg(wargs[n], XmNforeground, new->manager.foreground); n++;
  }
  if (NE(manager.bottom_shadow_color))
  {
    XtSetArg(wargs[n], XmNbottomShadowColor,
             new->manager.bottom_shadow_color); n++;
  }
  if (NE(manager.bottom_shadow_pixmap))
  {
    XtSetArg(wargs[n], XmNbottomShadowPixmap,
	     new->manager.bottom_shadow_pixmap); n++;
  }
  if (NE(manager.highlight_color))
  {
    XtSetArg(wargs[n], XmNhighlightColor,
	     new->manager.highlight_color); n++;
  }
  if (NE(manager.highlight_pixmap))
  {
    XtSetArg(wargs[n], XmNhighlightPixmap,
	     new->manager.highlight_pixmap); n++;
  }
  if (NE(manager.top_shadow_color))
  {
    XtSetArg(wargs[n], XmNtopShadowColor,
	     new->manager.top_shadow_color); n++;
  }
  if (NE(manager.top_shadow_pixmap))
  {
    XtSetArg(wargs[n], XmNtopShadowPixmap,
	     new->manager.top_shadow_pixmap); n++;
  }
  if (n)
  {
    XtSetValues(VertScrollChild(new), wargs, n);
    XtSetValues(HorizScrollChild(new), wargs, n);
    XtSetValues(TextChild(new), wargs, n);
  }

  /*
   * Would really like to do this in the above,
   * but we need to override background_pixel.
   */
  if (EQ(matrix.text_background) &&
      (current->matrix.text_background == current->core.background_pixel))
    new->matrix.text_background = new->core.background_pixel;
  if (NE(matrix.text_background) || NE(core.background_pixel))
  {
    XtVaSetValues(TextChild(new),
		  XmNbackground, new->matrix.text_background,
		  NULL);
    redisplay = True;
  }

  /*
   * Get a new XFontStruct and copy the fontList if it changed
   * and pass it to the textField.
   * Reset the HSB increment.
   * redisplay and relayout will be set below.
   */
  if (NE(matrix.font_list))
  {
    XmFontListFree(current->matrix.font_list);
    NewFont(new);
    XtVaSetValues(TextChild(new),
		  XmNfontList, new->matrix.font_list,
		  NULL);
    XtVaSetValues(HorizScrollChild(new),
		  XmNincrement, FONT_WIDTH(new),
		  NULL);
  }

  /*
   * Pass the cell resources on to the textField.
   * Both redisplay and relayout will be set below.
   * XXX XmTextField has a bug where it will not recompute it's size when
   * XmNshadowThickness changes.
   */
  if (NE(matrix.cell_margin_width) ||
      NE(matrix.cell_margin_height) ||
      NE(matrix.cell_shadow_thickness) ||
      NE(matrix.cell_highlight_thickness))
  {
    XtVaSetValues(TextChild(new),
		  XmNmarginWidth, new->matrix.cell_margin_width,
		  XmNmarginHeight, new->matrix.cell_margin_height,
		  XmNshadowThickness, new->matrix.cell_shadow_thickness,
		  XmNhighlightThickness, new->matrix.cell_highlight_thickness,
		  NULL);
  }

  /*
   * If anything changed which affects text offsets, recalc them
   */
  if (NE(matrix.font->fid) || NE(matrix.cell_margin_width) ||
      NE(matrix.cell_margin_height) ||
      NE(matrix.cell_shadow_thickness) ||
      NE(matrix.cell_highlight_thickness))
  {

    new->matrix.text_baseline =
      XmTextFieldGetBaseline(TextChild(new));
    relayout = True;
  }

  /*
   * If anything changed to affect cell total width or column positions,
   * recalc them
   */
  if (new_cells || NE(matrix.font->fid) ||
      NE(matrix.cell_margin_width) ||
      NE(matrix.cell_margin_height) ||
      NE(matrix.cell_shadow_thickness) ||
      NE(matrix.cell_highlight_thickness) ||
      NE(matrix.fixed_columns) ||
      new_column_widths)
  {

    GetCellTotalWidth(new);

    /*
     * Reset the HSB maximum.  sliderSize will be reset later in Resize.
     */
    XtVaSetValues(HorizScrollChild(new),
		  XmNmaximum, CELL_TOTAL_WIDTH(new),
		  XmNvalue, 0,
		  XmNsliderSize, 1,
		  NULL);
    HORIZ_ORIGIN(new) = 0;

    /*
     * If the number of columns changed, we need to allocate a new array.
     */
    if (NE(matrix.columns))
    {
      FreeColumnPositions(current);
      new->matrix.column_positions = CreateColumnPositions(new);
    }

    /*
     * If anything but fixed_columns changed, we need to recalc
     * column positions.
     */
    if (new_cells || NE(matrix.font->fid) ||
	NE(matrix.cell_margin_width) ||
	NE(matrix.cell_margin_height) ||
	NE(matrix.cell_shadow_thickness) ||
	NE(matrix.cell_highlight_thickness) ||
	new_column_widths)
      GetColumnPositions(new);

    relayout = True;
  }

  /*
   * Install text_translations on textField
   */
  if (NE(matrix.text_translations))
    XtVaSetValues(TextChild(new),
		  XmNtranslations, new->matrix.text_translations,
		  NULL);

  /*
   * If row_label_width was set to 0, calculate it.
   * Otherwise if it was changed, set flags.
   */
  if (new->matrix.row_label_width == 0 && new->matrix.row_labels)
  {
    new->matrix.row_label_width = MaxRowLabel(new);
    relayout = True;
  }
  else if (NE(matrix.row_label_width))
    relayout = True;


  /*
   * Get new cached GC if needed
   */
  if (NE(core.background_pixel) || NE(matrix.font->fid))
  {
    XtReleaseGC((Widget) new, new->matrix.inverse_gc);
    GetInverseGC(new);
    redisplay = True;
  }

  /*
   * Change created GCs if needed
   */

  if (NE(manager.foreground))
  {
    /*
     * We don't need to put the new foreground in draw_gc or
     * draw_clip_gc because they get a new foreground when they are used.
     */
    XSetForeground(XtDisplay(new), new->matrix.cell_top_shadow_clip_gc,
		   new->manager.foreground);
    XSetBackground(XtDisplay(new), new->matrix.cell_bottom_shadow_clip_gc,
		   new->manager.foreground);
    redisplay = True;
  }
  if (NE(core.background_pixel))
  {
    XSetForeground(XtDisplay(new), new->matrix.inverse_clip_gc,
		   new->core.background_pixel);
    redisplay = True;
  }
  if (NE(manager.top_shadow_color))
  {
    XSetForeground(XtDisplay(new), new->matrix.cell_top_shadow_clip_gc,
		   new->manager.top_shadow_color);
    redisplay = True;
  }
  if (NE(manager.top_shadow_pixmap))
  {
    XSetTile(XtDisplay(new), new->matrix.cell_top_shadow_clip_gc,
	     new->manager.top_shadow_pixmap);
    redisplay = True;
  }
  if (NE(manager.bottom_shadow_color))
  {
    XSetForeground(XtDisplay(new), new->matrix.cell_bottom_shadow_clip_gc,
		   new->manager.bottom_shadow_color);
    redisplay = True;
  }
  if (NE(manager.bottom_shadow_pixmap))
  {
    XSetTile(XtDisplay(new), new->matrix.cell_bottom_shadow_clip_gc,
	     new->manager.bottom_shadow_pixmap);
    redisplay = True;
  }
  if (NE(matrix.font->fid))
  {
    XSetFont(XtDisplay(new), new->matrix.draw_gc, new->matrix.font->fid);
    XSetFont(XtDisplay(new), new->matrix.draw_clip_gc,
	     new->matrix.font->fid);
    XSetFont(XtDisplay(new), new->matrix.inverse_clip_gc,
	     new->matrix.font->fid);
    redisplay = True;
  }

  /*
   * See if any other resources changed which will require a relayout
   */
  if (NE(matrix.space) || NE(matrix.cell_shadow_thickness) ||
      NE(manager.shadow_thickness))
    relayout = True;

  /*
   * If bold_labels changed, and we have labels, we must redisplay
   */
  if (NE(matrix.bold_labels) &&
      (new->matrix.row_labels || new->matrix.column_labels))
    redisplay = True;

  /*
   * Compute a new size if:
   *	 visible_rows or visible_columns changed.
   *	 user set our width or height to zero.
   */
  if (NE(matrix.visible_rows) || NE(matrix.visible_columns) ||
      request->core.height == 0 || request->core.width == 0)
    ComputeSize(new, request->core.width == 0, request->core.height == 0);

  /*
   * If our size didn't change, but we need to layout, call Resize.
   * If our size did change, then Xt will call our Resize method for us.
   * If our size did change, but the new size is later refused,
   *	 then SetValuesAlmost will call Resize to layout.
   */
  if (EQ(core.width) && EQ(core.height) && relayout)
    Resize(new);

  /*
   * The user forced a new top_row or something changed to force
   * us to recheck the current top_row.
   */
  if (NE(matrix.top_row) || do_top_row)
  {
    AdjustTopRow(new);
    XtVaSetValues(VertScrollChild(new),
		  XmNvalue, VERT_ORIGIN(new),
		  NULL);
    redisplay = True;
  }

  /*
   * Cancel any current edit.
   * Force the Clip widget to redisplay.  Note: this may generate an
   * expose event for the current size of the Clip widget, and the Clip
   * widget may be sized smaller in set_values_almost.  The ClipRedisplay
   * function can handle this case.
   * XXX If the Clip widget has been/will be resized, then we don't need
   * to force a redraw.  But how can set_values_almost for XtGeometryAlmost
   * determine if Resize will really resize the Clip widget?
   */
  if (redisplay || relayout)
  {
    (*((XbaeMatrixWidgetClass) XtClass(new))->matrix_class.cancel_edit)
      (new, True);
    XtVaSetValues(TextChild(new),
		  XmNbackground, new->matrix.text_background,
		  NULL);

    XbaeClipRedraw(ClipChild(new));
  }

  /*
   * We want to return True when we need to redisplay or relayout.
   */
  return redisplay || relayout;

#undef NE
#undef EQ
}

/* ARGSUSED */
static void
SetValuesAlmost(old, new, request, reply)
     XbaeMatrixWidget old;
     XbaeMatrixWidget new;
     XtWidgetGeometry *request;
     XtWidgetGeometry *reply;
{
  /*
   * If XtGeometryAlmost, accept compromize - Resize will take care of it
   */
  if (reply->request_mode)
  {
    *request = *reply;

#if XtSpecificationRelease > 4
    /*
     * In R5, XtSetValues changed so that when a widgets parent
     * returns XtGeometryAlmost, Xt will only call the widgets resize
     * method if the widgets size actually changed.  It turns out that
     * some manager widgets (old Wcl XmpTable and 1.1.x XmForm) return
     * XtGeometryAlmost with a compromise size which is the widgets
     * original size (not much of a compromise)!  This means as of R5,
     * Matrix's resize method won't get called in that case.
     *
     * So, for R5 we explicitly call our resize method here for the
     * case of XtGeometryAlmost where our size did not change.
     */
    if ((reply->request_mode & CWWidth ||
	 reply->request_mode & CWHeight)
	&&
	(old->core.width == new->core.width &&
	 old->core.height == new->core.height))
      Resize(new);
#endif
  }

  /*
   * If XtGeometryNo, call Resize to relayout if it was a size change
   * that was denied.
   * Accept the original geometry.
   * (we need to call Resize even though the size
   * didn't change to force a relayout - set_values relies on this)
   */
  else
  {
    if ((request->request_mode & CWWidth ||
	 request->request_mode & CWHeight))
      Resize(new);

    request->request_mode = 0;
  }
}

static void
ComputeSize F_ARGS(mw __ compute_width __ compute_height , XbaeMatrixWidget mw _ Boolean compute_width _ Boolean compute_height)
{
  unsigned long full_width = CELL_TOTAL_WIDTH(mw) + FIXED_COLUMN_WIDTH(mw) +
  ROW_LABEL_WIDTH(mw) + 2 * mw->manager.shadow_thickness;
  unsigned long full_height = CELL_TOTAL_HEIGHT(mw) + FIXED_ROW_HEIGHT(mw) +
  COLUMN_LABEL_HEIGHT(mw) + 2 * mw->manager.shadow_thickness;
  unsigned long width, height;

  /*
   * Calculate our width.
   * If visible_columns is set, then base it on that.
   * Otherwise, if the compute_width flag is set, then we are full width.
   * Otherwise we keep whatever width we are.
   */
  if (mw->matrix.visible_columns)
    width = ROW_LABEL_WIDTH(mw) + 2 * mw->manager.shadow_thickness +
      COLUMN_WIDTH(mw, (mw->matrix.visible_columns + mw->matrix.fixed_columns) - 1) +
      mw->matrix.column_positions[mw->matrix.fixed_columns + mw->matrix.visible_columns - 1];
  else if (compute_width)
    width = full_width;
  else
    width = mw->core.width;

  /*
   * Calculate our height.
   * If visible_rows is set, then base it on that.
   * Otherwise, if the compute_height flag is set, then we are full height.
   * Otherwise we keep whatever height we are.
   */
  if (mw->matrix.visible_rows)
    height = mw->matrix.visible_rows * ROW_HEIGHT(mw) +
      FIXED_ROW_HEIGHT(mw) + COLUMN_LABEL_HEIGHT(mw) +
      2 * mw->manager.shadow_thickness;
  else if (compute_height)
    height = full_height;
  else
    height = mw->core.height;

  /*
   * Store our calculated size.
   */
  mw->core.width = width;
  mw->core.height = height;

  /*
   * If we are less than full width, then we need an HSB, so increment
   * our height by the size of the HSB (if we are allowed to modify our
   * height).
   */
  if (width < full_width)
    if (compute_height || mw->matrix.visible_rows)
      mw->core.height += HSCROLL_HEIGHT(mw);

  /*
   * If we are less than full height, then we need a VSB, so increment
   * our width by the size of the VSB (if we are allowed to modify our
   * width).
   */
  if (height < full_height)
    if (compute_width || mw->matrix.visible_columns)
      mw->core.width += VSCROLL_WIDTH(mw);

  /*
   * Save our calculated size for use in our query_geometry method.
   * This is the size we really want to be (not necessarily the size
   * we will end up being).
   */
  mw->matrix.desired_width = mw->core.width;
  mw->matrix.desired_height = mw->core.height;
}


static void
Destroy(mw)
     XbaeMatrixWidget mw;
{
  XtReleaseGC((Widget) mw, mw->matrix.inverse_gc);

  XFreeGC(XtDisplay(mw), mw->matrix.draw_gc);
  XFreeGC(XtDisplay(mw), mw->matrix.draw_clip_gc);
  XFreeGC(XtDisplay(mw), mw->matrix.inverse_clip_gc);
  XFreeGC(XtDisplay(mw), mw->matrix.cell_top_shadow_clip_gc);
  XFreeGC(XtDisplay(mw), mw->matrix.cell_bottom_shadow_clip_gc);

  FreeCells(mw);
  FreeRowLabels(mw);
  FreeColumnLabels(mw);
  FreeColumnWidths(mw);
  FreeColumnMaxLengths(mw);
  FreeColumnPositions(mw);
  FreeColumnAlignments(mw);
  FreeColumnLabelAlignments(mw);
  FreeColors(mw);
  FreeSelectedCells(mw);

  XmFontListFree(mw->matrix.font_list);

  SmDestroyScrollMgr(mw->matrix.matrix_scroll_mgr);
  SmDestroyScrollMgr(mw->matrix.clip_scroll_mgr);
}


/*
 * Position and size the scrollbars and clip widget for our new size.
 */
static void
Resize(mw)
     XbaeMatrixWidget mw;
{
  int cell_width, cell_height, rows_visible;
  Boolean has_horiz, has_vert;
  int width = mw->core.width;
  int height = mw->core.height;

  /*
   * Full size of widget (no SBs needed) - may be very large
   */
  long int full_width = CELL_TOTAL_WIDTH(mw) + FIXED_COLUMN_WIDTH(mw) +
  ROW_LABEL_WIDTH(mw) + 2 * mw->manager.shadow_thickness;
  long int full_height = CELL_TOTAL_HEIGHT(mw) + FIXED_ROW_HEIGHT(mw) +
  COLUMN_LABEL_HEIGHT(mw) + 2 * mw->manager.shadow_thickness;

  /*
   * Portion of cells which are visible in clip widget
   */
  int horiz_visible = CELL_TOTAL_WIDTH(mw) - HORIZ_ORIGIN(mw);
  int vert_visible = CELL_TOTAL_HEIGHT(mw) -
  VERT_ORIGIN(mw) * ROW_HEIGHT(mw);


  /*
   * If matrix is bigger in both dimensions, then we don't need either
   * SB. If both dimensions are smaller than matrix, then we need both
   * SBs. Otherwise, only one or the other dimension must be too small:
   *	- Get the dimensions assuming both SBs are mapped (subtract size
   *	  of SBs)
   *	- Flag both SBs as mapped
   *	- If the full_width is small enough that we won't need a HSB even
   *	  if the VSB is mapped, then unflag the HSB and reset the height
   *	- Next compare the full_height to the height just computed and decide
   *	  if we need a VSB (if we didn't need a HSB above, then we had reset
   *	  the height to not count the HSB)
   *
   * The whole point is, if we map one SB then we may need to map the
   * other one because of the space taken by the first one
   */
  if ((long)mw->core.width >= full_width && (long)mw->core.height >= full_height)
    has_horiz = has_vert = False;
  else if ((long)mw->core.width < full_width && (long)mw->core.height < full_height)
    has_horiz = has_vert = True;
  else
  {
    height -= HSCROLL_HEIGHT(mw);
    width -= VSCROLL_WIDTH(mw);

    has_horiz = has_vert = True;
    if (full_width <= width)
    {
      height = mw->core.height;
      has_horiz = False;
    }
    if (full_height <= height)
      has_vert = False;
  }


  /*
   * If widget is smaller than full size, move/resize the scrollbar and
   * set sliderSize, also if cell_width/cell_height is greater than
   * the amount of cell area visible, then we need to drag the cells
   * back into the visible part of the clip widget and set the
   * scrollbar value.
   *
   * Otherwise, the widget is larger than full size, so set
   * cell_width/cell_height to size of cells and set origin to 0
   * to force full cell area to be displayed
   *
   * We also need to move the textField correspondingly
   */

  /*
   * We were resized smaller than our max width.
   */
  if (width < full_width)
  {

    /*
     * Calculate the width of the non-fixed visible cells.
     */
    cell_width = mw->core.width -
      (FIXED_COLUMN_WIDTH(mw) + ROW_LABEL_WIDTH(mw) +
       2 * mw->manager.shadow_thickness);

    /*
     * Subtract the VSB if we have one.
     */
    if (has_vert)
      cell_width -= VSCROLL_WIDTH(mw);

    if (cell_width <= 0)
      cell_width = 1;

    /*
     * If the window is not full height, then place the HSB at the edge
     * of the window.  Is the window is larger than full height, then
     * place the HSB immediately below the cell region.
     */
    XtConfigureWidget(HorizScrollChild(mw),
		      FIXED_COLUMN_LABEL_OFFSET(mw),
		      height < full_height
		      ? (Position) (mw->core.height -
				    (HorizScrollChild(mw)->core.height +
				     2 * HorizScrollChild(mw)->
				     core.border_width))
		      : (Position) (full_height + mw->matrix.space),
		      cell_width,
		      HorizScrollChild(mw)->core.height,
		      HorizScrollChild(mw)->core.border_width);

    /*
     * If the cells are scrolled off to the left, then drag them
     * back onto the screen.
     */
    if (cell_width > horiz_visible)
    {
      HORIZ_ORIGIN(mw) -= (cell_width - horiz_visible);

      if (XtIsManaged(TextChild(mw)))
	XtMoveWidget(TextChild(mw),
		     TextChild(mw)->core.x +
		     (cell_width - horiz_visible),
		     TextChild(mw)->core.y);
    }

    /*
     * Setup the HSB to reflect our new size.
     */
    XtVaSetValues(HorizScrollChild(mw),
		  XmNpageIncrement, cell_width,
		  XmNsliderSize, cell_width,
		  XmNvalue, HORIZ_ORIGIN(mw),
		  NULL);
  }

  /*
   * We were resized larger than the our max width.  Drag the cells back
   * onto the screen if they were scrolled off to the left.
   */
  else
  {
    if (XtIsManaged(TextChild(mw)))
      XtMoveWidget(TextChild(mw),
		   TextChild(mw)->core.x + HORIZ_ORIGIN(mw),
		   TextChild(mw)->core.y);

    cell_width = CELL_TOTAL_WIDTH(mw);
    HORIZ_ORIGIN(mw) = 0;
  }

  /*
   * We were resized smaller than our max height.
   */
  if (height < full_height)
  {

    /*
     * Calculate the height of the non-fixed visible cells.
     */
    cell_height = mw->core.height -
      (FIXED_ROW_HEIGHT(mw) + COLUMN_LABEL_HEIGHT(mw) +
       2 * mw->manager.shadow_thickness);

    /*
     * Subtract the HSB if we have one.
     */
    if (has_horiz)
      cell_height -= HSCROLL_HEIGHT(mw);

    if (cell_height <= 0)
      cell_height = 1;

    /*
     * If the window is not full width, then place the VSB at the edge
     * of the window.  Is the window is larger than full width, then
     * place the VSB immediately to the right of the cell region.
     */
    XtConfigureWidget(VertScrollChild(mw),
		      width < full_width
		      ? (Position) (mw->core.width -
				    (VertScrollChild(mw)->core.width +
				     2 * VertScrollChild(mw)->
				     core.border_width))
		      : (Position) full_width + mw->matrix.space,
		      FIXED_ROW_LABEL_OFFSET(mw),
		      VertScrollChild(mw)->core.width,
		      cell_height,
		      VertScrollChild(mw)->core.border_width);

    /*
     * If the cells are scrolled off the top, then drag them
     * back onto the screen.
     */
    if (cell_height > vert_visible)
    {
      int rows = (cell_height - vert_visible) / ROW_HEIGHT(mw);

      VERT_ORIGIN(mw) -= rows;

      if (XtIsManaged(TextChild(mw)))
	XtMoveWidget(TextChild(mw),
		     TextChild(mw)->core.x,
		     TextChild(mw)->core.y + rows * ROW_HEIGHT(mw));
    }

    /*
     * Setup the VSB to reflect our new size.
     */
    rows_visible = cell_height / ROW_HEIGHT(mw);
    XtVaSetValues(VertScrollChild(mw),
		  XmNpageIncrement, rows_visible <= 0 ? 1 : rows_visible,
		  XmNsliderSize, rows_visible <= 0 ? 1 : rows_visible,
		  XmNvalue, VERT_ORIGIN(mw),
		  NULL);
  }

  /*
   * We were resized larger than the our max height.	Drag the cells back
   * onto the screen if they were scrolled off the top.
   */
  else
  {
    if (XtIsManaged(TextChild(mw)))
      XtMoveWidget(TextChild(mw),
		   TextChild(mw)->core.x,
		   TextChild(mw)->core.y +
		   VERT_ORIGIN(mw) * ROW_HEIGHT(mw));

    cell_height = CELL_TOTAL_HEIGHT(mw);
    rows_visible = mw->matrix.rows - mw->matrix.fixed_rows;
    VERT_ORIGIN(mw) = 0;
  }

  /*
   * Map/unmap scrollbars based on flags set above
   */
  if (has_horiz)
    XtManageChild(HorizScrollChild(mw));
  else
    XtUnmanageChild(HorizScrollChild(mw));

  if (has_vert)
    XtManageChild(VertScrollChild(mw));
  else
    XtUnmanageChild(VertScrollChild(mw));

  /*
   * Now that we have cell_width & cell_height,
   * make the clip widget this size.	Height is truncated to the
   * nearest row.
   */
  XtConfigureWidget(ClipChild(mw),
		    FIXED_COLUMN_LABEL_OFFSET(mw),
		    FIXED_ROW_LABEL_OFFSET(mw),
		    cell_width,
		    rows_visible <= 0
		    ? cell_height
		    : rows_visible * ROW_HEIGHT(mw),
		    0);

  /*
   * Save the non-truncated height.  We need this so we can draw
   * the shadow correctly.
   */
  mw->matrix.cell_visible_height = cell_height;

  /*
   * Set the clip_mask in our clipping GCs.  This function relies on
   * the Clip widget being the correct size (above).
   */
  if (XtIsRealized((Widget)mw))
    SetClipMask(mw);
}

/*
 * Since we totally control our childrens geometry, allow anything.
 */
/* ARGSUSED */
static XtGeometryResult
GeometryManager(w, desired, allowed)
     Widget w;
     XtWidgetGeometry *desired, *allowed;
{
#define Wants(flag) (desired->request_mode & flag)

  if (Wants(XtCWQueryOnly))
    return (XtGeometryYes);

  if (Wants(CWWidth))
    w->core.width = desired->width;
  if (Wants(CWHeight))
    w->core.height = desired->height;
  if (Wants(CWX))
    w->core.x = desired->x;
  if (Wants(CWY))
    w->core.y = desired->y;
  if (Wants(CWBorderWidth))
    w->core.border_width = desired->border_width;

  return (XtGeometryYes);

#undef Wants
}

/*
 * We would prefer to be the size calculated in ComputeSize and saved in
 * desired_width/height
 */
static XtGeometryResult
QueryGeometry(mw, proposed, desired)
     XbaeMatrixWidget mw;
     XtWidgetGeometry *proposed, *desired;
{
#define Set(bit) (proposed->request_mode & bit)

  desired->width = mw->matrix.desired_width;
  desired->height = mw->matrix.desired_height;
  desired->request_mode = CWWidth | CWHeight;

  if (Set(CWWidth) && proposed->width == desired->width &&
      Set(CWHeight) && proposed->height == desired->height)
    return (XtGeometryYes);

  if (desired->width == mw->core.width && desired->height == mw->core.height)
    return (XtGeometryNo);

  return (XtGeometryAlmost);

#undef Set
}

/*
 * Callback for vertical scrollbar
 */
/* ARGSUSED */
static void
ScrollVertCB(w, client_data, call_data)
     Widget w;
     XtPointer client_data;
     XmScrollBarCallbackStruct *call_data;
{
  XbaeMatrixWidget mw = (XbaeMatrixWidget) XtParent(w);
  int src_y, dest_y, height;

  /*
   * Didn't scroll
   */
  if (call_data->value == VERT_ORIGIN(mw))
    return;

  /*
   * Scrolled forward. We want to copy a chunk starting at src_y up
   * to the top (dest_y=0)
   */
  else if (call_data->value > VERT_ORIGIN(mw))
  {
    dest_y = 0;
    src_y = (call_data->value - VERT_ORIGIN(mw)) * ROW_HEIGHT(mw);
    height = ClipChild(mw)->core.height - src_y;
  }

  /*
   * Scrolled backward. We want to copy a chunk starting at the top
   * (src_y=0) down to dest_y.
   */
  else
  {
    dest_y = (VERT_ORIGIN(mw) - call_data->value) * ROW_HEIGHT(mw);
    src_y = 0;
    height = ClipChild(mw)->core.height - dest_y;
  }

  /*
   * The textField needs to scroll along with the cells.
   */
  if (XtIsManaged(TextChild(mw)))
    XtMoveWidget(TextChild(mw),
		 TextChild(mw)->core.x,
		 TextChild(mw)->core.y +
		 (VERT_ORIGIN(mw) - call_data->value) * ROW_HEIGHT(mw));

  /*
   * Now we can adjust our vertical origin
   */
  VERT_ORIGIN(mw) = call_data->value;

  if (!XtIsRealized((Widget)mw))
    return;

  /*
   * If we scrolled more than a screenful, just clear and
   * redraw the whole thing
   */
  if (height <= 0)
  {
    Rectangle rect;

    /*
     * Clear the whole clip window.
     */
    XClearArea(XtDisplay(mw), XtWindow(ClipChild(mw)),
	       0, 0,
	       0 /*Full Width*/ , 0 /*Full Height*/ ,
	       False);

    /*
     * Redraw all the non-fixed cells in the clip window
     */
    SETRECT(rect,
	    0, 0,
	    ClipChild(mw)->core.width - 1,
	    ClipChild(mw)->core.height - 1);
    RedrawCells(mw, &rect);

    /*
     * Clear the non-fixed row labels and the cells in fixed columns
     */
    XClearArea(XtDisplay(mw), XtWindow(mw),
	       0, FIXED_ROW_LABEL_OFFSET(mw),
	       FIXED_COLUMN_LABEL_OFFSET(mw), 0 /*Full Height*/ ,
	       False);

    /*
     * Redraw non-fixed row labels and cells in fixed columns
     */
    SETRECT(rect,
	    0, FIXED_ROW_LABEL_OFFSET(mw),
	    FIXED_COLUMN_LABEL_OFFSET(mw), mw->core.height);
    RedrawLabelsAndFixed(mw, &rect);
  }

  /*
   * If we scrolled less than a screenful, we want to copy as many
   * pixels as we can and then clear and redraw the newly scrolled data.
   */
  else
  {
    Rectangle rect;
    int y_clear = src_y > dest_y ? height : 0;

    /*
     * Queue this scroll with the ScrollMgr
     */
    SmAddScroll(mw->matrix.clip_scroll_mgr, 0, dest_y - src_y);

    /*
     * Copy the non-fixed cells in the clip widget
     */
    XCopyArea(XtDisplay(mw),
	      XtWindow(ClipChild(mw)), XtWindow(ClipChild(mw)),
	      mw->matrix.draw_gc,
	      0, src_y,
	      ClipChild(mw)->core.width, height,
	      0, dest_y);

    /*
     * Clear the newly scrolled chunk of the clip widget
     */
    XClearArea(XtDisplay(mw), XtWindow(ClipChild(mw)),
	       0, y_clear,
	       0 /*Full Width*/ , ClipChild(mw)->core.height - height,
	       False);

    /*
     * Redraw the non-fixed cells into the new chunk
     */
    SETRECT(rect,
	    0, y_clear,
	    ClipChild(mw)->core.width - 1,
	    (y_clear + (ClipChild(mw)->core.height - height)) - 1);
    RedrawCells(mw, &rect);

    /*
     * Translate coordinates for fixed columns and row labels.
     */
    src_y += FIXED_ROW_LABEL_OFFSET(mw);
    dest_y += FIXED_ROW_LABEL_OFFSET(mw);
    y_clear += FIXED_ROW_LABEL_OFFSET(mw);

    /*
     * Queue this scroll with the ScrollMgr
     */
    SmAddScroll(mw->matrix.matrix_scroll_mgr, 0, dest_y - src_y);

    /*
     * Copy the fixed columns and row labels
     */
    XCopyArea(XtDisplay(mw),
	      XtWindow(mw), XtWindow(mw),
	      mw->matrix.draw_gc,
	      0, src_y,
	      FIXED_COLUMN_LABEL_OFFSET(mw), height,
	      0, dest_y);

    /*
     * Clear the newly scrolled chunk of fixed columns and row labels
     */
    XClearArea(XtDisplay(mw), XtWindow(mw),
	       0, y_clear,
	       FIXED_COLUMN_LABEL_OFFSET(mw),
	       ClipChild(mw)->core.height - height,
	       False);

    /*
     * Redraw the new chunk of fixed columns and row labels
     */
    SETRECT(rect,
	    0, y_clear,
	    FIXED_COLUMN_LABEL_OFFSET(mw) - 1,
	    (y_clear + (ClipChild(mw)->core.height - height)) - 1);
    RedrawLabelsAndFixed(mw, &rect);
  }
}

/*
 * Callback for horizontal scrollbar
 */
/* ARGSUSED */
static void
ScrollHorizCB(w, client_data, call_data)
     Widget w;
     XtPointer client_data;
     XmScrollBarCallbackStruct *call_data;
{
  XbaeMatrixWidget mw = (XbaeMatrixWidget) XtParent(w);
  int src_x, dest_x, width;

  /*
   * Didn't scroll
   */
  if (call_data->value == HORIZ_ORIGIN(mw))
    return;

  /*
   * Scrolled right. We want to copy a chunk starting at src_x over to
   * the left (dest_x=0)
   */
  else if (call_data->value > HORIZ_ORIGIN(mw))
  {
    dest_x = 0;
    src_x = call_data->value - HORIZ_ORIGIN(mw);
    width = ClipChild(mw)->core.width - src_x;
  }

  /*
   * Scrolled left. We want to copy a chunk starting at the left (src_x=0)
   * over to the right to dest_x
   */
  else
  {
    dest_x = HORIZ_ORIGIN(mw) - call_data->value;
    src_x = 0;
    width = ClipChild(mw)->core.width - dest_x;
  }

  /*
   * The textField needs to scroll along with the cells.
   */
  if (XtIsManaged(TextChild(mw)))
    XtMoveWidget(TextChild(mw),
		 TextChild(mw)->core.x + (HORIZ_ORIGIN(mw) -
					  call_data->value),
		 TextChild(mw)->core.y);

  /*
   * Now we can adjust our horizontal origin
   */
  HORIZ_ORIGIN(mw) = call_data->value;

  if (!XtIsRealized((Widget)mw))
    return;

  /*
   * If we scrolled more than a screenful, just clear and
   * redraw the whole thing
   */
  if (width <= 0)
  {
    Rectangle rect;

    /*
     * Clear the whole clip window
     */
    XClearArea(XtDisplay(mw), XtWindow(ClipChild(mw)),
	       0, 0,
	       0 /*Full Width*/ , 0 /*Full Height*/ ,
	       False);

    /*
     * Redraw all the non-fixed cells in the clip window
     */
    SETRECT(rect,
	    0, 0,
	    ClipChild(mw)->core.width - 1,
	    ClipChild(mw)->core.height - 1);
    RedrawCells(mw, &rect);

    /*
     * Clear the non-fixed column labels and the cells in fixed rows
     */
    XClearArea(XtDisplay(mw), XtWindow(mw),
	       FIXED_COLUMN_LABEL_OFFSET(mw), 0,
	       0 /*Full Width*/ , FIXED_ROW_LABEL_OFFSET(mw), False);

    /*
     * Redraw non-fixed column labels and cells in fixed rows
     */
    SETRECT(rect,
	    FIXED_COLUMN_LABEL_OFFSET(mw), 0,
	    mw->core.width, FIXED_ROW_LABEL_OFFSET(mw));
    RedrawLabelsAndFixed(mw, &rect);
  }

  /*
   * If we scrolled less than a screenful, we want to copy as many
   * pixels as we can and then clear and redraw the newly scrolled data.
   */
  else
  {
    Rectangle rect;
    int x_clear = src_x > dest_x ? width : 0;

    /*
     * Queue this scroll with the ScrollMgr
     */
    SmAddScroll(mw->matrix.clip_scroll_mgr, dest_x - src_x, 0);

    /*
     * Copy the non-fixed cells in the clip widget
     */
    XCopyArea(XtDisplay(mw),
	      XtWindow(ClipChild(mw)), XtWindow(ClipChild(mw)),
	      mw->matrix.draw_gc,
	      src_x, 0,
	      width, ClipChild(mw)->core.height,
	      dest_x, 0);

    /*
     * Clear the newly scrolled chunk of the clip widget
     */
    XClearArea(XtDisplay(mw), XtWindow(ClipChild(mw)),
	       x_clear, 0,
	       ClipChild(mw)->core.width - width, 0 /*Full Height*/ ,
	       False);

    /*
     * Redraw the non-fixed cells into the new chunk
     */
    SETRECT(rect,
	    x_clear, 0,
	    (x_clear + (ClipChild(mw)->core.width - width)) - 1,
	    ClipChild(mw)->core.height - 1);
    RedrawCells(mw, &rect);

    /*
     * Translate coordinates for fixed rows and column labels.
     */
    src_x += FIXED_COLUMN_LABEL_OFFSET(mw);
    dest_x += FIXED_COLUMN_LABEL_OFFSET(mw);
    x_clear += FIXED_COLUMN_LABEL_OFFSET(mw);

    /*
     * Queue this scroll with the ScrollMgr
     */
    SmAddScroll(mw->matrix.matrix_scroll_mgr, dest_x - src_x, 0);

    /*
     * Copy the fixed rows and column labels
     */
    XCopyArea(XtDisplay(mw),
	      XtWindow(mw), XtWindow(mw),
	      mw->matrix.draw_gc,
	      src_x, 0,
	      width, FIXED_ROW_LABEL_OFFSET(mw),
	      dest_x, 0);

    /*
     * Clear the newly scrolled chunk of fixed rows and column labels
     */
    XClearArea(XtDisplay(mw), XtWindow(mw),
	       x_clear, 0,
	       ClipChild(mw)->core.width - width,
	       FIXED_ROW_LABEL_OFFSET(mw),
	       False);

    /*
     * Redraw the new chunk of fixed rows and column labels
     */
    SETRECT(rect,
	    x_clear, 0,
	    (x_clear + (ClipChild(mw)->core.width - width)) - 1,
	    FIXED_ROW_LABEL_OFFSET(mw) - 1);
    RedrawLabelsAndFixed(mw, &rect);
  }
}

/*
 * This is the modifyVerifyCallback we added to textField. We need to
 * call Matrixs modifyVerifyCallback list with the textField info
 * and the row/col that is changing.
 */
/* ARGSUSED */
static void
ModifyVerifyCB(w, client, call)
     Widget w;
     XtPointer client;
     XtPointer call;
{
  CLIENT(XbaeMatrixWidget, mw);
  CALL(XmTextVerifyCallbackStruct *, verify);
  XbaeMatrixModifyVerifyCallbackStruct call_data;

  if (mw->matrix.modify_verify_callback == NULL)
    return;

  call_data.reason = XbaeModifyVerifyReason;
  call_data.row = mw->matrix.current_row;
  call_data.column = mw->matrix.current_column;
  call_data.verify = verify;
  /*
   * I could do:  prev_text = ((XmTextFieldRec*)w)->text.value;
   * but I think this is safer.
   */
  call_data.prev_text = XmTextFieldGetString(w);

  XtCallCallbackList((Widget) mw, mw->matrix.modify_verify_callback,
		     (XtPointer) & call_data);

  XtFree((char *)call_data.prev_text);
}

/*
 * This function is called from either the Clip focus CB or a timeout proc.
 * It is called as a result of the Clip getting the focus. We want to give
 * the focus to the textField if a cell is being edited.  If no cells are
 * being edited, force an edit on the top left most visible cell.
 */
static void
TraverseIn(mw)
     XbaeMatrixWidget mw;
{
  /*
   * If the traversing flag is set, then Clip got the focus because
   * textField was trying to traverse out of mw.  We'll help it along.
   * Sickening.
   */
  if (mw->matrix.traversing != NOT_TRAVERSING)
  {
    XmProcessTraversal(ClipChild(mw), mw->matrix.traversing);
    return;
  }

  /*
   * If the textField is managed and not visible, scroll it onto the screen
   * and traverse to it.
   */
  if (XtIsManaged(TextChild(mw)))
  {
    if (!IsCellVisible(mw,
		       mw->matrix.current_row, mw->matrix.current_column))
      MakeCellVisible(mw,
		      mw->matrix.current_row, mw->matrix.current_column);
    XmProcessTraversal(TextChild(mw), XmTRAVERSE_CURRENT);
  }

  /*
   * Otherwise, no cell is being edited.  Force an edit on the top-left
   * most visible cell.
   */
  else
  {
    int column = XtoCol(mw, FIXED_COLUMN_WIDTH(mw) + HORIZ_ORIGIN(mw));
    int row = VERT_ORIGIN(mw) + mw->matrix.fixed_rows;

    /*
     * Call the traverseCellCallback to allow the application to
     * perform custom traversal.
     */
    if (mw->matrix.traverse_cell_callback)
    {
      XbaeMatrixTraverseCellCallbackStruct call_data;

      call_data.reason = XbaeTraverseCellReason;
      call_data.row = 0;
      call_data.column = 0;
      call_data.next_row = row;
      call_data.next_column = column;
      call_data.fixed_rows = mw->matrix.fixed_rows;
      call_data.fixed_columns = mw->matrix.fixed_columns;
      call_data.num_rows = mw->matrix.rows;
      call_data.num_columns = mw->matrix.columns;
      call_data.param = NULL;
      call_data.qparam = NULLQUARK;

      XtCallCallbackList((Widget) mw, mw->matrix.traverse_cell_callback,
			 (XtPointer) & call_data);

      row = call_data.next_row;
      column = call_data.next_column;
    }

    (*((XbaeMatrixWidgetClass) XtClass(mw))->matrix_class.edit_cell)
      (mw, row, column);

    XmProcessTraversal(TextChild(mw), XmTRAVERSE_CURRENT);
  }
}

#if XmVersion >= 1002
/*
 * Under Motif 1.2, TraverseInCB can't call TraverseIn directly, so it
 * adds a zero length timeout and we call it from here.
 */
/* ARGSUSED */
static void
TraverseInTimeOut(mw, timer)
     XtPointer mw;
     XtIntervalId *timer;
{
  TraverseIn((XbaeMatrixWidget)mw);
}

#endif /* XmVersion >= 1002 */

/*
 * This is the Clip widgets focusCallback. We want to give the focus to
 * the textField if a cell is being edited.  If no cells are being edited,
 * force an edit on the top left most visible cell.
 */
/* ARGSUSED */
static void
TraverseInCB(w, mw, call_data)
     Widget w;
     XbaeMatrixWidget mw;
     XtPointer call_data;
{
#if XmVersion < 1002
  TraverseIn(mw);
#else
  /*
   * Under Motif 1.2, we can't call TraverseIn directly because it
   * calls XmProcessTraversal and recursive calls to XmProcessTraversal
   * are disallowed in 1.2 (we may be in this CB as a result of someone
   * calling XmProcessTraversal).  So we add a zero length timeout
   * and call TraverseIn from there.
   */
  XtAppAddTimeOut(XtWidgetToApplicationContext((Widget) w), 0L,
		  TraverseInTimeOut, (XtPointer) mw);
#endif
}

static void
CopyCells(mw)
     XbaeMatrixWidget mw;
{
  String **copy;
  int i, j;

  /*
   * Malloc an array of row pointers
   */
  copy = (String **) XtMalloc(mw->matrix.rows * sizeof(String *));

  /*
   * Malloc an array of Strings for each row pointer
   */
  for (i = 0; i < mw->matrix.rows; i++)
    copy[i] = (String *) XtMalloc(mw->matrix.columns * sizeof(String));

  /*
   * Create a bunch of "" cells if cells was NULL
   */
  if (!mw->matrix.cells)
  {
    for (i = 0; i < mw->matrix.rows; i++)
      for (j = 0; j < mw->matrix.columns; j++)
	copy[i][j] = XtNewString("");
  }

  /*
   * Otherwise copy the table passed in
   */
  else
  {
    for (i = 0; i < mw->matrix.rows; i++)
      for (j = 0; j < mw->matrix.columns; j++)
      {
	if (!mw->matrix.cells[i][j])
	{
	  XtAppWarningMsg(XtWidgetToApplicationContext((Widget) mw),
			  "copyCells", "badValue", "XbaeMatrix",
			  "XbaeMatrix: NULL entry found in cell table",
			  NULL, 0);
	  copy[i][j] = XtNewString("");
	}
	else
	  copy[i][j] = XtNewString(mw->matrix.cells[i][j]);
      }
  }

  mw->matrix.cells = copy;
}

static void
CopyRowLabels(mw)
     XbaeMatrixWidget mw;
{
  String *copy;
  int i;

  copy = (String *) XtMalloc(mw->matrix.rows * sizeof(String));

  for (i = 0; i < mw->matrix.rows; i++)
    if (!mw->matrix.row_labels[i])
    {
      XtAppWarningMsg(XtWidgetToApplicationContext((Widget) mw),
		      "copyRowLabels", "badValue", "XbaeMatrix",
		      "XbaeMatrix: NULL entry found in rowLabels array",
		      NULL, 0);
      copy[i] = XtNewString("");
    }
    else
      copy[i] = XtNewString(mw->matrix.row_labels[i]);

  mw->matrix.row_labels = copy;
}

static void
CopyColumnLabels(mw)
     XbaeMatrixWidget mw;
{
  String *copy;
  int i;

  copy = (String *) XtMalloc(mw->matrix.columns * sizeof(String));

  mw->matrix.column_label_lines = (ColumnLabelLines)
    XtMalloc(mw->matrix.columns * sizeof(ColumnLabelLinesRec));

  for (i = 0; i < mw->matrix.columns; i++)
    if (!mw->matrix.column_labels[i])
    {
      XtAppWarningMsg(XtWidgetToApplicationContext((Widget) mw),
		      "copyColumnLabels", "badValue", "XbaeMatrix",
		      "XbaeMatrix: NULL entry found in columnLabels array",
		      NULL, 0);
      copy[i] = XtNewString("");
      ParseColumnLabel(copy[i], &mw->matrix.column_label_lines[i]);
    }
    else
    {
      copy[i] = XtNewString(mw->matrix.column_labels[i]);
      ParseColumnLabel(mw->matrix.column_labels[i],
		       &mw->matrix.column_label_lines[i]);
    }

  /*
   * Determine max number of lines in column labels
   */
  mw->matrix.column_label_maxlines = mw->matrix.column_label_lines[0].lines;
  for (i = 1; i < mw->matrix.columns; i++)
    if (mw->matrix.column_label_lines[i].lines >
	mw->matrix.column_label_maxlines)
      mw->matrix.column_label_maxlines =
	mw->matrix.column_label_lines[i].lines;

  mw->matrix.column_labels = copy;
}

static void
CopyColumnWidths(mw)
     XbaeMatrixWidget mw;
{
  short *copy;
  int i;
  Boolean bad = False;

  copy = (short *) XtMalloc(mw->matrix.columns * sizeof(short));

  for (i = 0; i < mw->matrix.columns; i++)
  {
    if (!bad && mw->matrix.column_widths[i] == BAD_WIDTH)
    {
      bad = True;
      XtAppWarningMsg(XtWidgetToApplicationContext((Widget) mw),
		      "copyColumnWidths", "tooShort", "XbaeMatrix",
		      "XbaeMatrix: Column widths array is too short",
		      NULL, 0);
      copy[i] = 1;
    }
    else if (bad)
      copy[i] = 1;
    else
      copy[i] = mw->matrix.column_widths[i];
  }

  mw->matrix.column_widths = copy;
}

static void
CopyColumnMaxLengths(mw)
     XbaeMatrixWidget mw;
{
  int *copy;
  int i;
  Boolean bad = False;

  copy = (int *) XtMalloc(mw->matrix.columns * sizeof(int));

  for (i = 0; i < mw->matrix.columns; i++)
  {
    if (!bad && mw->matrix.column_max_lengths[i] == BAD_MAXLENGTH)
    {
      bad = True;
      XtAppWarningMsg(XtWidgetToApplicationContext((Widget) mw),
		      "copyColumnMaxLengths", "tooShort", "XbaeMatrix",
		      "XbaeMatrix: Column max lengths array is too short",
		      NULL, 0);
      copy[i] = 1;
    }
    else if (bad)
      copy[i] = 1;
    else
      copy[i] = mw->matrix.column_max_lengths[i];
  }

  mw->matrix.column_max_lengths = copy;
}

static void
CopyColumnAlignments(mw)
     XbaeMatrixWidget mw;
{
  unsigned char *copy;
  int i;
  Boolean bad = False;

  copy = (unsigned char *) XtMalloc(mw->matrix.columns *
				    sizeof(unsigned char));

  for (i = 0; i < mw->matrix.columns; i++)
  {
    if (!bad && mw->matrix.column_alignments[i] == BAD_ALIGNMENT)
    {
      bad = True;
      XtAppWarningMsg(XtWidgetToApplicationContext((Widget) mw),
		      "copyColumnAlignments", "tooShort", "XbaeMatrix",
		      "XbaeMatrix: Column alignments array is too short",
		      NULL, 0);
      copy[i] = XmALIGNMENT_BEGINNING;
    }
    else if (bad)
      copy[i] = XmALIGNMENT_BEGINNING;
    else
      copy[i] = mw->matrix.column_alignments[i];
  }

  mw->matrix.column_alignments = copy;
}

static void
CopyColumnLabelAlignments(mw)
     XbaeMatrixWidget mw;
{
  unsigned char *copy;
  int i;
  Boolean bad = False;

  copy = (unsigned char *) XtMalloc(mw->matrix.columns *
				    sizeof(unsigned char));

  for (i = 0; i < mw->matrix.columns; i++)
  {
    if (!bad &&
	mw->matrix.column_label_alignments[i] == BAD_ALIGNMENT)
    {
      bad = True;
      XtAppWarningMsg(XtWidgetToApplicationContext((Widget) mw),
		      "copyColumnLabelAlignments", "tooShort",
		      "XbaeMatrix",
		      "XbaeMatrix: Column label alignments array is too short",
		      NULL, 0);
      copy[i] = XmALIGNMENT_BEGINNING;
    }
    else if (bad)
      copy[i] = XmALIGNMENT_BEGINNING;
    else
      copy[i] = mw->matrix.column_label_alignments[i];
  }

  mw->matrix.column_label_alignments = copy;
}

static void
CopyColors(mw)
     XbaeMatrixWidget mw;
{
  Pixel **copy;
  int i, j;

  /*
   * Malloc an array of row pointers
   */
  copy = (Pixel **) XtMalloc(mw->matrix.rows * sizeof(Pixel *));

  /*
   * Malloc an array of Pixels for each row pointer
   */
  for (i = 0; i < mw->matrix.rows; i++)
    copy[i] = (Pixel *) XtMalloc(mw->matrix.columns * sizeof(Pixel));

  for (i = 0; i < mw->matrix.rows; i++)
    for (j = 0; j < mw->matrix.columns; j++)
      copy[i][j] = mw->matrix.colors[i][j];

  mw->matrix.colors = copy;
}

/*
 * Copy the selectedCells resource. Create a 2D array of Booleans to
 * represent selected cells if it is NULL.
 */
static void
CopySelectedCells F_ARGS(mw , XbaeMatrixWidget mw)
{
  Boolean **copy;
  int i, j;

  /*
   * Malloc an array of row pointers
   */
  copy = (Boolean **) XtMalloc(mw->matrix.rows * sizeof(Boolean *));

  /*
   * Malloc an array of Booleans for each row pointer
   */
  for (i = 0; i < mw->matrix.rows; i++)
    copy[i] = (Boolean *) XtCalloc(mw->matrix.columns, sizeof(Boolean));

  /*
   * If selected_cells is not NULL, copy the table passed in
   */
  if (mw->matrix.selected_cells)
    for (i = 0; i < mw->matrix.rows; i++)
      for (j = 0; j < mw->matrix.columns; j++)
	copy[i][j] = mw->matrix.selected_cells[i][j];

  mw->matrix.selected_cells = copy;
}

static void
ParseColumnLabel(label, lines)
     String label;
     ColumnLabelLines lines;
{
  char *nl;

  /*
   * First count the number of lines in the label
   */
  lines->lines = 1;
  nl = label;
  while ((nl = strchr(nl, '\n')) != NULL)
  {
    nl++;
    lines->lines++;
  }

  /*
   * Now malloc a lengths array of the correct size.
   */
  lines->lengths = (int *) XtMalloc(lines->lines * sizeof(int));

  /*
   * An entry in the lengths array is the length of that line (substring).
   */

  /*
   * Handle the case of one line (no strchr() needed)
   */
  if (lines->lines == 1)
    lines->lengths[0] = strlen(label);
  else
  {
    int i;

    nl = label;
    i = 0;
    while ((nl = strchr(nl, '\n')) != NULL)
    {
      lines->lengths[i] = nl - label;
      nl++;
      label = nl;
      i++;
    }
    lines->lengths[i] = strlen(label);
  }
}

static void
FreeCells F_ARGS(mw , XbaeMatrixWidget mw)
{
  int i, j;

  if (!mw->matrix.cells)
    return;

  /*
   * Free each cell in a row, then free the row and go to the next one
   */
  for (i = 0; i < mw->matrix.rows; i++)
  {
    for (j = 0; j < mw->matrix.columns; j++)
      XtFree((XtPointer) mw->matrix.cells[i][j]);
    XtFree((XtPointer) mw->matrix.cells[i]);
  }

  /*
   * Free the array of row pointers
   */
  XtFree((XtPointer) mw->matrix.cells);
}

static void
FreeRowLabels F_ARGS(mw , XbaeMatrixWidget mw)
{
  int i;

  if (!mw->matrix.row_labels)
    return;

  for (i = 0; i < mw->matrix.rows; i++)
    XtFree((XtPointer) mw->matrix.row_labels[i]);

  XtFree((XtPointer) mw->matrix.row_labels);
}

static void
FreeColumnLabels F_ARGS(mw , XbaeMatrixWidget mw)
{
  int i;

  if (!mw->matrix.column_labels)
    return;

  for (i = 0; i < mw->matrix.columns; i++)
  {
    XtFree((XtPointer) mw->matrix.column_labels[i]);
    XtFree((XtPointer) mw->matrix.column_label_lines[i].lengths);
  }

  XtFree((XtPointer) mw->matrix.column_label_lines);
  XtFree((XtPointer) mw->matrix.column_labels);
}


static void
FreeColors F_ARGS(mw , XbaeMatrixWidget mw)
{
  int i;

  if (!mw->matrix.colors)
    return;

  /*
   * Free each row of Pixels
   */
  for (i = 0; i < mw->matrix.rows; i++)
    XtFree((XtPointer) mw->matrix.colors[i]);

  /*
   * Free the array of row pointers
   */
  XtFree((XtPointer) mw->matrix.colors);
}

static void
FreeSelectedCells F_ARGS(mw , XbaeMatrixWidget mw)
{
  int i;

  /*
   * Free each row of Booleans
   */
  for (i = 0; i < mw->matrix.rows; i++)
    XtFree((XtPointer) mw->matrix.selected_cells[i]);

  /*
   * Free the array of row pointers
   */
  XtFree((XtPointer) mw->matrix.selected_cells);
}

/*
 * Create a matrix of Pixels
 */
static void
CreateColors F_ARGS(mw , XbaeMatrixWidget mw)
{
  int i;

  /*
   * Malloc an array of row pointers
   */
  mw->matrix.colors = (Pixel **) XtMalloc(mw->matrix.rows * sizeof(Pixel *));

  /*
   * Malloc an array of Pixels for each row pointer
   */
  for (i = 0; i < mw->matrix.rows; i++)
    mw->matrix.colors[i] = (Pixel *) XtMalloc(mw->matrix.columns *
					      sizeof(Pixel));
}

/*
 * Add rows/columns of cells when set_values changes our rows/columns
 */
static void
ResizeCells F_ARGS(current __ new , XbaeMatrixWidget current _ XbaeMatrixWidget new)
{
  int i, j;
  int safe_rows;

  if (new->matrix.rows == current->matrix.rows)
    safe_rows = new->matrix.rows;

  /*
   * Adding rows
   */
  if (new->matrix.rows > current->matrix.rows)
  {
    /*
     * Realloc a larger array of row pointers
     */
    new->matrix.cells =
      (String **) XtRealloc((char *) new->matrix.cells,
			    new->matrix.rows * sizeof(String *));

    /*
     * Malloc a new row array for each row. Initialize it with
     * NULL Strings. Use the new column size.
     */
    for (i = current->matrix.rows; i < new->matrix.rows; i++)
    {
      new->matrix.cells[i] =
	(String *) XtMalloc(new->matrix.columns * sizeof(String));
      for (j = 0; j < new->matrix.columns; j++)
	new->matrix.cells[i][j] = XtNewString("");
    }

    safe_rows = current->matrix.rows;
  }

  /*
   * Deleting rows
   */
  if (new->matrix.rows < current->matrix.rows)
  {
    /*
     * Free the cells in the rows being deleted and the rows themselves
     */
    for (i = new->matrix.rows; i < current->matrix.rows; i++)
    {
      for (j = 0; j < current->matrix.columns; j++)
	XtFree((XtPointer) new->matrix.cells[i][j]);
      XtFree((XtPointer) new->matrix.cells[i]);
    }

    safe_rows = new->matrix.rows;
  }

  /*
   * Adding columns
   */
  if (new->matrix.columns > current->matrix.columns)
  {
    /*
     * Realloc each row array. Do not touch any rows added/deleted above
     * (use safe_rows)
     */
    for (i = 0; i < safe_rows; i++)
    {
      new->matrix.cells[i] =
	(String *) XtRealloc((char *) new->matrix.cells[i],
			     new->matrix.columns * sizeof(String));
      for (j = current->matrix.columns; j < new->matrix.columns; j++)
	new->matrix.cells[i][j] = XtNewString("");
    }
  }

  /*
   * Deleting columns
   */
  if (new->matrix.columns < current->matrix.columns)
  {
    /*
     * Free all the cells in the deleted columns. Do not touch any
     * rows added/deleted above (use safe_rows).
     * We don't bother to realloc each row, just leave some wasted space.
     * XXX is this a problem?
     */
    for (i = 0; i < safe_rows; i++)
      for (j = new->matrix.columns; j < current->matrix.columns; j++)
	XtFree((XtPointer) new->matrix.cells[i][j]);
  }
}

/*
 * Add rows/columns of selected flags when set_values changes our rows/columns
 */
static void
ResizeSelectedCells F_ARGS(current __ new , XbaeMatrixWidget current _ XbaeMatrixWidget new)
{
  int i;
  int safe_rows;

  if (new->matrix.rows == current->matrix.rows)
    safe_rows = new->matrix.rows;

  /*
   * Adding rows
   */
  if (new->matrix.rows > current->matrix.rows)
  {
    /*
     * Realloc a larger array of row pointers
     */
    new->matrix.selected_cells =
      (Boolean **) XtRealloc((char *) new->matrix.selected_cells,
			     new->matrix.rows * sizeof(Boolean *));

    /*
     * Calloc a new row array for each row. Use the new column size.
     */
    for (i = current->matrix.rows; i < new->matrix.rows; i++)
      new->matrix.selected_cells[i] =
	(Boolean *) XtCalloc(new->matrix.columns, sizeof(Boolean));

    safe_rows = current->matrix.rows;
  }

  /*
   * Deleting rows
   */
  if (new->matrix.rows < current->matrix.rows)
  {
    for (i = new->matrix.rows; i < current->matrix.rows; i++)
      XtFree((XtPointer) new->matrix.selected_cells[i]);
    safe_rows = new->matrix.rows;
  }

  /*
   * Adding columns
   */
  if (new->matrix.columns > current->matrix.columns)
  {
    /*
     * Realloc each row array. Do not touch any rows added/deleted above
     * (use safe_rows)
     */
    for (i = 0; i < safe_rows; i++)
    {
      int j;

      new->matrix.selected_cells[i] =
	(Boolean *) XtRealloc((char *) new->matrix.selected_cells[i],
			      new->matrix.columns * sizeof(Boolean));
      for (j = current->matrix.columns; j < new->matrix.columns; j++)
	new->matrix.selected_cells[i][j] = False;
    }
  }

  /*
   * Deleting columns
   *   if (new->matrix.columns < current->matrix.columns)
   * We don't bother to realloc, just leave some wasted space.
   * XXX is this a problem?
   */
}

/*
 * Add rows/columns of colors when set_values changes our rows/columns
 */
static void
ResizeColors F_ARGS(current __ new , XbaeMatrixWidget current _ XbaeMatrixWidget new)
{
  int i, j;
  int safe_rows;

  if (new->matrix.rows == current->matrix.rows)
    safe_rows = new->matrix.rows;

  /*
   * Adding rows
   */
  if (new->matrix.rows > current->matrix.rows)
  {
    /*
     * Realloc a larger array of row pointers
     */
    new->matrix.colors =
      (Pixel **) XtRealloc((char *) new->matrix.colors,
			   new->matrix.rows * sizeof(Pixel *));

    /*
     * Malloc a new row array for each row. Initialize it with foreground.
     * Use the new column size.
     */
    for (i = current->matrix.rows; i < new->matrix.rows; i++)
    {
      new->matrix.colors[i] =
	(Pixel *) XtMalloc(new->matrix.columns * sizeof(Pixel));
      for (j = 0; j < new->matrix.columns; j++)
	new->matrix.colors[i][j] = new->manager.foreground;
    }

    safe_rows = current->matrix.rows;
  }

  /*
   * Deleting rows
   */
  if (new->matrix.rows < current->matrix.rows)
  {
    for (i = new->matrix.rows; i < current->matrix.rows; i++)
      XtFree((XtPointer) new->matrix.colors[i]);
    safe_rows = new->matrix.rows;
  }

  /*
   * Adding columns
   */
  if (new->matrix.columns > current->matrix.columns)
  {
    /*
     * Realloc each row array. Do not touch any rows added/deleted above
     * (use safe_rows)
     */
    for (i = 0; i < safe_rows; i++)
    {
      int j;

      new->matrix.colors[i] =
	(Pixel *) XtRealloc((char *) new->matrix.colors[i],
			    new->matrix.columns * sizeof(Pixel));
      for (j = current->matrix.columns; j < new->matrix.columns; j++)
	new->matrix.colors[i][j] = new->manager.foreground;
    }
  }

  /*
   * Deleting columns
   *   if (new->matrix.columns < current->matrix.columns)
   * We don't bother to realloc, just leave some wasted space.
   * XXX is this a problem?
   */
}

/*
 * Add rows to the internal cells data structure.
 * If rows or labels is NULL, add empty rows.
 */
static void
AddRowsToTable F_ARGS(mw __ position __ rows __ labels __ colors __ num_rows , XbaeMatrixWidget mw _ int position _ String *rows _ String *labels _ Pixel *colors _ int num_rows)
{
  int i, j;

  /*
   * Realloc a larger array of row pointers and a larger label array
   */
  mw->matrix.cells = (String **) XtRealloc((char *) mw->matrix.cells,
					   (mw->matrix.rows + num_rows) *
					   sizeof(String *));
  if (mw->matrix.row_labels)
    mw->matrix.row_labels =
      (String *) XtRealloc((char *) mw->matrix.row_labels,
			   (mw->matrix.rows + num_rows) *
			   sizeof(String));
  if (mw->matrix.colors)
    mw->matrix.colors = (Pixel **) XtRealloc((char *) mw->matrix.colors,
					     (mw->matrix.rows + num_rows) *
					     sizeof(Pixel *));

  mw->matrix.selected_cells =
    (Boolean **) XtRealloc((char *) mw->matrix.selected_cells,
			   (mw->matrix.rows + num_rows) *
			   sizeof(Boolean *));

  /*
   * If we are inserting rows into the middle, we need to make room.
   * XXX we are assuming bcopy can handle overlapping moves.
   */
  if (position < mw->matrix.rows)
  {
    bcopy(&mw->matrix.cells[position],
	  &mw->matrix.cells[position + num_rows],
	  (mw->matrix.rows - position) * sizeof(String *));
    if (mw->matrix.row_labels)
      bcopy(&mw->matrix.row_labels[position],
	    &mw->matrix.row_labels[position + num_rows],
	    (mw->matrix.rows - position) * sizeof(String));
    if (mw->matrix.colors)
      bcopy(&mw->matrix.colors[position],
	    &mw->matrix.colors[position + num_rows],
	    (mw->matrix.rows - position) * sizeof(Pixel *));
    bcopy(&mw->matrix.selected_cells[position],
	  &mw->matrix.selected_cells[position + num_rows],
	  (mw->matrix.rows - position) * sizeof(Boolean *));
  }

  /*
   * Malloc a new row array for each new row. Copy the label for each row.
   * If no label was passed in, use a NULL String. Malloc a new Pixel
   * and Boolean row array for each new row.
   */
  for (i = 0; i < num_rows; i++)
  {
    mw->matrix.cells[i + position] =
      (String *) XtMalloc(mw->matrix.columns * sizeof(String));
    if (mw->matrix.row_labels)
      mw->matrix.row_labels[i + position] =
	labels ? XtNewString(labels[i]) : XtNewString("");
    if (mw->matrix.colors)
      mw->matrix.colors[i + position] =
	(Pixel *) XtMalloc(mw->matrix.columns * sizeof(Pixel));
    mw->matrix.selected_cells[i + position] =
      (Boolean *) XtMalloc(mw->matrix.columns * sizeof(Boolean));
  }

  /*
   * Copy the rows arrays passed in into each new row, or if NULL
   * was passed in initialize each row to NULL Strings. Copy the colors
   * arrays passed in into each new row, if NULL was passed use foreground.
   */
  for (i = 0; i < num_rows; i++)
    for (j = 0; j < mw->matrix.columns; j++)
    {
      mw->matrix.cells[i + position][j] =
	rows
	? XtNewString(rows[i * mw->matrix.columns + j])
	: XtNewString("");
      if (mw->matrix.colors)
	mw->matrix.colors[i + position][j] =
	  colors
	  ? colors[i]
	  : mw->manager.foreground;
      mw->matrix.selected_cells[i + position][j] = False;
    }

  mw->matrix.rows += num_rows;
}

/*
 * Delete rows from the internal cells data structure.
 */
static void
DeleteRowsFromTable F_ARGS(mw __ position __ num_rows , XbaeMatrixWidget mw _ int position _ int num_rows)
{
  int i, j;

  /*
   * We don't bother to realloc, we will just have some wasted space.
   * XXX is this a problem?
   */

  /*
   * Free all the cells in the rows being deleted and the rows themselves.
   * Also free the String row labels.  Free the color arrays for the rows
   * being deleted.
   */
  for (i = position; i < position + num_rows; i++)
  {
    for (j = 0; j < mw->matrix.columns; j++)
      XtFree((XtPointer) mw->matrix.cells[i][j]);
    XtFree((XtPointer) mw->matrix.cells[i]);
    if (mw->matrix.row_labels)
      XtFree((XtPointer) mw->matrix.row_labels[i]);
    if (mw->matrix.colors)
      XtFree((XtPointer) mw->matrix.colors[i]);
    XtFree((XtPointer) mw->matrix.selected_cells[i]);
  }

  /*
   * Copy those rows which are below the ones deleted, up.
   * (unless we deleted rows from the bottom).
   * XXX we assume bcopy can handle overlapping moves
   */
  if (position + num_rows < mw->matrix.rows)
  {
    bcopy(&mw->matrix.cells[position + num_rows],
	  &mw->matrix.cells[position],
	  (mw->matrix.rows - position - num_rows) * sizeof(String *));
    if (mw->matrix.row_labels)
      bcopy(&mw->matrix.row_labels[position + num_rows],
	    &mw->matrix.row_labels[position],
	    (mw->matrix.rows - position - num_rows) * sizeof(String));
    if (mw->matrix.colors)
      bcopy(&mw->matrix.colors[position + num_rows],
	    &mw->matrix.colors[position],
	    (mw->matrix.rows - position - num_rows) * sizeof(Pixel *));
    bcopy(&mw->matrix.selected_cells[position + num_rows],
	  &mw->matrix.selected_cells[position],
	  (mw->matrix.rows - position - num_rows) * sizeof(Boolean *));
  }

  mw->matrix.rows -= num_rows;
}

/*
 * Add columns to the internal cells data structure.
 * If columns or labels is NULL, add empty columns.
 * If max_lengths is NULL, widths will be used.
 * If alignments is NULL, use XmALIGNMENT_BEGINNING.
 * If label_alignments is NULL, use alignments, or if it is NULL
 *   XmALIGNMENT_BEGINNING.
 * widths must not be NULL.
 */
static void
AddColumnsToTable(mw, position, columns, labels, widths, max_lengths,
		  alignments, label_alignments, colors, num_columns)
     XbaeMatrixWidget mw;
     int position;
     String *columns;
     String *labels;
     short *widths;
     int *max_lengths;
     unsigned char *alignments;
     unsigned char *label_alignments;
     Pixel *colors;
     int num_columns;
{
  int i, j;

  /*
   * Realloc larger cells, widths, max_lengths, alignments,
   * colors, selected_cells, labels and label lines arrays.
   */

  for (i = 0; i < mw->matrix.rows; i++)
  {
    mw->matrix.cells[i] =
      (String *) XtRealloc((char *) mw->matrix.cells[i],
			   (mw->matrix.columns + num_columns) *
			   sizeof(String));
    if (mw->matrix.colors)
      mw->matrix.colors[i] =
	(Pixel *) XtRealloc((char *) mw->matrix.colors[i],
			    (mw->matrix.columns + num_columns) *
			    sizeof(Pixel));
    mw->matrix.selected_cells[i] =
      (Boolean *) XtRealloc((char *) mw->matrix.selected_cells[i],
			    (mw->matrix.columns + num_columns) *
			    sizeof(Boolean));
  }

  mw->matrix.column_widths =
    (short *) XtRealloc((char *) mw->matrix.column_widths,
			(mw->matrix.columns + num_columns) *
			sizeof(short));

  if (mw->matrix.column_max_lengths)
    mw->matrix.column_max_lengths =
      (int *) XtRealloc((char *) mw->matrix.column_max_lengths,
			(mw->matrix.columns + num_columns) *
			sizeof(int));

  if (mw->matrix.column_alignments)
    mw->matrix.column_alignments =
      (unsigned char *)
      XtRealloc((char *) mw->matrix.column_alignments,
		(mw->matrix.columns + num_columns) *
		sizeof(unsigned char));

  if (mw->matrix.column_label_alignments)
    mw->matrix.column_label_alignments =
      (unsigned char *) XtRealloc((char *) mw->matrix.
				  column_label_alignments,
				  (mw->matrix.columns + num_columns) *
				  sizeof(unsigned char));

  if (mw->matrix.column_labels)
  {
    mw->matrix.column_labels =
      (String *) XtRealloc((char *) mw->matrix.column_labels,
			   (mw->matrix.columns + num_columns) *
			   sizeof(String));
    mw->matrix.column_label_lines =
      (ColumnLabelLines) XtRealloc((char *) mw->matrix.column_label_lines,
				   (mw->matrix.columns + num_columns) *
				   sizeof(ColumnLabelLinesRec));
  }

  /*
   * If we are inserting columns into the middle, we need to make room.
   * XXX we are assuming bcopy can handle overlapping moves.
   */
  if (position < mw->matrix.columns)
  {
    bcopy(&mw->matrix.column_widths[position],
	  &mw->matrix.column_widths[position + num_columns],
	  (mw->matrix.columns - position) * sizeof(short));

    if (mw->matrix.column_max_lengths)
      bcopy(&mw->matrix.column_max_lengths[position],
	    &mw->matrix.column_max_lengths[position + num_columns],
	    (mw->matrix.columns - position) * sizeof(int));

    if (mw->matrix.column_alignments)
      bcopy(&mw->matrix.column_alignments[position],
	    &mw->matrix.column_alignments[position + num_columns],
	    (mw->matrix.columns - position) * sizeof(unsigned char));

    if (mw->matrix.column_label_alignments)
      bcopy(&mw->matrix.column_label_alignments[position],
	    &mw->matrix.column_label_alignments[position + num_columns],
	    (mw->matrix.columns - position) * sizeof(unsigned char));

    if (mw->matrix.column_labels)
    {
      bcopy(&mw->matrix.column_labels[position],
	    &mw->matrix.column_labels[position + num_columns],
	    (mw->matrix.columns - position) * sizeof(String));
      bcopy(&mw->matrix.column_label_lines[position],
	    &mw->matrix.column_label_lines[position + num_columns],
	    (mw->matrix.columns - position) *
	    sizeof(ColumnLabelLinesRec));
    }

    /*
     * Shift the columns in each row.
     */
    for (i = 0; i < mw->matrix.rows; i++)
    {
      bcopy(&mw->matrix.cells[i][position],
	    &mw->matrix.cells[i][position + num_columns],
	    (mw->matrix.columns - position) * sizeof(String));
      if (mw->matrix.colors)
	bcopy(&mw->matrix.colors[i][position],
	      &mw->matrix.colors[i][position + num_columns],
	      (mw->matrix.columns - position) * sizeof(Pixel));
      bcopy(&mw->matrix.selected_cells[i][position],
	    &mw->matrix.selected_cells[i][position + num_columns],
	    (mw->matrix.columns - position) * sizeof(Boolean));
    }
  }

  /*
   * Copy all of the passed in info into each new column
   * (except column_positions which will be recalculated below).
   * If columns or labels is NULL, add empty columns.
   * If max_lengths is NULL, widths will be used.
   * If alignments is NULL, use XmALIGNMENT_BEGINNING.
   * If label_alignments is NULL, use XmALIGNMENT_BEGINNING
   * If labels is NULL, use NULL strings.
   * If colors is NULL, use foreground.
   */
  for (j = 0; j < num_columns; j++)
  {
    mw->matrix.column_widths[j + position] = widths[j];

    if (mw->matrix.column_max_lengths)
      mw->matrix.column_max_lengths[j + position] =
	max_lengths ? max_lengths[j] : (int) widths[j];

    if (mw->matrix.column_alignments)
      mw->matrix.column_alignments[j + position] =
	alignments ? alignments[j] : XmALIGNMENT_BEGINNING;

    if (mw->matrix.column_label_alignments)
      mw->matrix.column_label_alignments[j + position] =
	label_alignments ? label_alignments[j] : XmALIGNMENT_BEGINNING;

    if (mw->matrix.column_labels)
    {
      mw->matrix.column_labels[j + position] =
	labels ? XtNewString(labels[j]) : XtNewString("");
      ParseColumnLabel(mw->matrix.column_labels[j + position],
		       &mw->matrix.column_label_lines[j + position]);
    }

    /*
     * Add this new column to each row.
     */
    for (i = 0; i < mw->matrix.rows; i++)
    {
      mw->matrix.cells[i][j + position] =
	columns
	? XtNewString(columns[i * num_columns + j])
	: XtNewString("");
      if (mw->matrix.colors)
	mw->matrix.colors[i][j + position] =
	  colors
	  ? colors[j]
	  : mw->manager.foreground;
      mw->matrix.selected_cells[i][j + position] = False;
    }
  }

  mw->matrix.columns += num_columns;
  GetCellTotalWidth(mw);

  /*
   * See if the max number of column label lines changed
   */
  if (mw->matrix.column_labels)
  {
    int end;

    end = position + num_columns;
    for (i = position; i < end; i++)
      if (mw->matrix.column_label_lines[i].lines >
	  mw->matrix.column_label_maxlines)
	mw->matrix.column_label_maxlines =
	  mw->matrix.column_label_lines[i].lines;
  }

  /*
   * Recalculate the column positions
   */
  FreeColumnPositions(mw);
  mw->matrix.column_positions = CreateColumnPositions(mw);
  GetColumnPositions(mw);
}

/*
 * Delete columns from the internal cells data structure.
 */
static void
DeleteColumnsFromTable(mw, position, num_columns)
     XbaeMatrixWidget mw;
     int position;
     int num_columns;
{
  int i, j;

  /*
   * Free all the cells in the columns being deleted.
   * Also free the String column labels and the associated ColumnLabelLines
   * lengths arrays.
   */
  for (j = position; j < position + num_columns; j++)
  {
    for (i = 0; i < mw->matrix.rows; i++)
      XtFree((XtPointer) mw->matrix.cells[i][j]);
    if (mw->matrix.column_labels)
    {
      XtFree((XtPointer) mw->matrix.column_labels[j]);
      XtFree((XtPointer) mw->matrix.column_label_lines[j].lengths);
    }
  }

  /*
   * Shift those columns after the ones being deleted, left.
   * (unless we deleted columns from the right).
   * XXX we assume bcopy can handle overlapping moves
   */
  if (position + num_columns < mw->matrix.columns)
  {
    bcopy(&mw->matrix.column_widths[position + num_columns],
	  &mw->matrix.column_widths[position],
	  (mw->matrix.columns - position - num_columns) * sizeof(short));

    if (mw->matrix.column_max_lengths)
      bcopy(&mw->matrix.column_max_lengths[position + num_columns],
	    &mw->matrix.column_max_lengths[position],
	    (mw->matrix.columns - position - num_columns) * sizeof(int));

    if (mw->matrix.column_alignments)
      bcopy(&mw->matrix.column_alignments[position + num_columns],
	    &mw->matrix.column_alignments[position],
	    (mw->matrix.columns - position - num_columns) *
	    sizeof(unsigned char));

    if (mw->matrix.column_label_alignments)
      bcopy(&mw->matrix.column_label_alignments[position + num_columns],
	    &mw->matrix.column_label_alignments[position],
	    (mw->matrix.columns - position - num_columns) *
	    sizeof(unsigned char));

    if (mw->matrix.column_labels)
    {
      bcopy(&mw->matrix.column_labels[position + num_columns],
	    &mw->matrix.column_labels[position],
	    (mw->matrix.columns - position - num_columns) *
	    sizeof(String));
      bcopy(&mw->matrix.column_label_lines[position + num_columns],
	    &mw->matrix.column_label_lines[position],
	    (mw->matrix.columns - position - num_columns) *
	    sizeof(ColumnLabelLinesRec));
    }

    /*
     * Shift the columns in each row.
     */
    for (i = 0; i < mw->matrix.rows; i++)
    {
      bcopy(&mw->matrix.cells[i][position + num_columns],
	    &mw->matrix.cells[i][position],
	    (mw->matrix.columns - position - num_columns) *
	    sizeof(String));
      if (mw->matrix.colors)
	bcopy(&mw->matrix.colors[i][position + num_columns],
	      &mw->matrix.colors[i][position],
	      (mw->matrix.columns - position - num_columns) *
	      sizeof(Pixel));
      bcopy(&mw->matrix.selected_cells[i][position + num_columns],
	    &mw->matrix.selected_cells[i][position],
	    (mw->matrix.columns - position - num_columns) *
	    sizeof(Boolean));
    }
  }

  mw->matrix.columns -= num_columns;
  GetCellTotalWidth(mw);

  /*
   * See if the max number of column label lines changed
   */
  if (mw->matrix.column_labels)
  {
    mw->matrix.column_label_maxlines =
      mw->matrix.column_label_lines[0].lines;
    for (i = 1; i < mw->matrix.columns; i++)
      if (mw->matrix.column_label_lines[i].lines >
	  mw->matrix.column_label_maxlines)
	mw->matrix.column_label_maxlines =
	  mw->matrix.column_label_lines[i].lines;
  }

  /*
   * Recalculate the column positions
   */
  FreeColumnPositions(mw);
  mw->matrix.column_positions = CreateColumnPositions(mw);
  GetColumnPositions(mw);
}

/*
 * Try to make the row specified by the topRow resource (VERT_ORIGIN)
 * be the top row. The row is relative to fixed_rows - so 0 would
 * be the first non-fixed row.
 * If we can't make topRow the top row, make it as close as possible.
 */
static void
AdjustTopRow(mw)
     XbaeMatrixWidget mw;
{
  int rows_visible = VISIBLE_HEIGHT(mw) / ROW_HEIGHT(mw);

  /*
   * If we have less than one full row visible, then count it as a full row
   */
  if (rows_visible == 0)
    rows_visible = 1;
  /*
   * rows_visible might be inaccurate since Clip may not have been resized
   */
  else if (rows_visible > mw->matrix.rows)
    rows_visible = mw->matrix.rows;

  if (VERT_ORIGIN(mw) > (mw->matrix.rows - (rows_visible +
					    (int)mw->matrix.fixed_rows)))
    VERT_ORIGIN(mw) = mw->matrix.rows - (rows_visible +
					 mw->matrix.fixed_rows);
  else if (VERT_ORIGIN(mw) < 0)
    VERT_ORIGIN(mw) = 0;
}

/*
 * Return the top and bottom-most visible non-fixed row
 */
static void
GetVisibleRows(mw, top_row, bottom_row)
     XbaeMatrixWidget mw;
     int *top_row, *bottom_row;
{
  *top_row = VERT_ORIGIN(mw) + mw->matrix.fixed_rows;
  *bottom_row = *top_row + VISIBLE_HEIGHT(mw) / ROW_HEIGHT(mw);
}

/*
 * Return the left and right-most visible non-fixed column
 */
static void
GetVisibleColumns(mw, left_column, right_column)
     XbaeMatrixWidget mw;
     int *left_column, *right_column;
{
  *left_column = XtoCol(mw, FIXED_COLUMN_WIDTH(mw) + HORIZ_ORIGIN(mw));
  *right_column = XtoCol(mw, FIXED_COLUMN_WIDTH(mw) + HORIZ_ORIGIN(mw) +
			 VISIBLE_WIDTH(mw));
}

/*
 * Return the top and bottom row and left and right column of
 * the visible non-fixed cells
 */
static void
GetVisibleCells(mw, top_row, bottom_row, left_column, right_column)
     XbaeMatrixWidget mw;
     int *top_row, *bottom_row, *left_column, *right_column;
{
  GetVisibleRows(mw, top_row, bottom_row);
  GetVisibleColumns(mw, left_column, right_column);
}

/*
 * Utility function to clear a cell so we can draw something new in it.
 * Does not generate expose events on the cell.
 * Does not check if the cell is actually visible before clearing it.
 */
static void
ClearCell(mw, row, column)
     XbaeMatrixWidget mw;
     int row, column;
{
  int x, y;
  Window win = CELL_WINDOW(mw, row, column);

  if (!win)
    return;

  RowColToXY(mw, row, column, &x, &y);

  XClearArea(XtDisplay(mw), win,
	     x, y,
	     COLUMN_WIDTH(mw, column),
	     ROW_HEIGHT(mw),
	     False);
}

/*
 * Return True if a row is visible on the screen (not scrolled totally off)
 */
static Boolean
IsRowVisible(mw, row)
     XbaeMatrixWidget mw;
     int row;
{
  /*
   * If we are not in a fixed row, see if we are on the screen vertically
   * (fixed rows are always on the screen)
   */
  if (row >= (int)mw->matrix.fixed_rows)
  {
    row -= mw->matrix.fixed_rows;
    if (row >= VERT_ORIGIN(mw) &&
	row <
	(int)(ClipChild(mw)->core.height) / ROW_HEIGHT(mw) + VERT_ORIGIN(mw))
      return True;
  }
  else
    return True;

  return False;
}

/*
 * Return True if a column is visible on the screen (not scrolled totally off)
 */
static Boolean
IsColumnVisible(mw, column)
     XbaeMatrixWidget mw;
     int column;
{
  /*
   * If we are not in a fixed column, see if we are on the screen
   * horizontally (fixed columns are always on the screen)
   */
  if (column >= (int)mw->matrix.fixed_columns)
  {
    int x;

    /*
     * Calculate the x endpoints of this column
     */
    x = mw->matrix.column_positions[column] -
      mw->matrix.column_positions[mw->matrix.fixed_columns];

    /*
     * Check if we are visible horizontally
     */
    if (x + COLUMN_WIDTH(mw, column) > HORIZ_ORIGIN(mw) &&
	x < (int)(ClipChild(mw)->core.width) + HORIZ_ORIGIN(mw))
      return True;
  }
  else
    return True;

  return False;
}

/*
 * Return True if a cell is visible on the screen (not scrolled totally off)
 */
static Boolean
IsCellVisible(mw, row, column)
     XbaeMatrixWidget mw;
     int row, column;
{
  return IsRowVisible(mw, row) && IsColumnVisible(mw, column);
}

/*
 * Scroll a row so it is visible on the screen.
 */
static void
MakeRowVisible(mw, row)
     XbaeMatrixWidget mw;
     int row;
{
  int rows_visible;
  int value, slider_size, increment, page_increment, vert_value;

  /*
   * If we are in a fixed row, we are already visible.
   */
  if (row < (int)mw->matrix.fixed_rows)
    return;

  /*
   * Take into account fixed_rows.
   * Calculate the number of rows visible. If less than one full
   * row is visible, use one full row.
   */
  row -= mw->matrix.fixed_rows;
  rows_visible = VISIBLE_HEIGHT(mw) / ROW_HEIGHT(mw);
  if (rows_visible == 0)
    rows_visible = 1;

  /*
   * Figure out the new value of the VSB to scroll this cell
   * onto the screen (the VSB uses row coordinates instead of pixels)
   */
  if (row < VERT_ORIGIN(mw))
    vert_value = row;
  else if (row >= rows_visible + VERT_ORIGIN(mw))
    vert_value = row - rows_visible + 1;
  else
    vert_value = VERT_ORIGIN(mw);

  /*
   * Give the VSB the new value and pass a flag to make it call
   * our scroll callbacks
   */
  if (vert_value != VERT_ORIGIN(mw))
  {
    XmScrollBarGetValues(VertScrollChild(mw), &value,
			 &slider_size, &increment, &page_increment);
    XmScrollBarSetValues(VertScrollChild(mw), vert_value,
			 slider_size, increment, page_increment, True);
  }
}

/*
 * Scroll a column so it is visible on the screen.
 */
static void
MakeColumnVisible(mw, column)
     XbaeMatrixWidget mw;
     int column;
{
  int value, slider_size, increment, page_increment, x, horiz_value;

  /*
   * If we are in a fixed column, we are already visible.
   */
  if (column < (int)mw->matrix.fixed_columns)
    return;

  /*
   * Calculate the x position of this column
   */
  x = mw->matrix.column_positions[column] -
    mw->matrix.column_positions[mw->matrix.fixed_columns];

  /*
   * Figure out the new value of the HSB to scroll this cell
   * onto the screen. If the whole cell won't fit, scroll so its
   * left edge is visible.
   */
  if (x < HORIZ_ORIGIN(mw))
    horiz_value = x;
  else if (x + COLUMN_WIDTH(mw, column) >
	   VISIBLE_WIDTH(mw) + HORIZ_ORIGIN(mw))
  {
    int off = (x + COLUMN_WIDTH(mw, column)) - (VISIBLE_WIDTH(mw) +
						HORIZ_ORIGIN(mw));

    if (x - off < HORIZ_ORIGIN(mw))
      horiz_value = x;
    else
      horiz_value = HORIZ_ORIGIN(mw) + off;
  }
  else
    horiz_value = HORIZ_ORIGIN(mw);

  /*
   * Give the HSB the new value and pass a flag to make it
   * call our scroll callbacks
   */
  if (horiz_value != HORIZ_ORIGIN(mw))
  {
    XmScrollBarGetValues(HorizScrollChild(mw), &value,
			 &slider_size, &increment, &page_increment);
    XmScrollBarSetValues(HorizScrollChild(mw), horiz_value,
			 slider_size, increment, page_increment, True);
  }
}

/*
 * Scrolls a fixed or non-fixed cell so it is visible on the screen.
 */
static void
MakeCellVisible(mw, row, column)
     XbaeMatrixWidget mw;
     int row, column;
{
  if (mw->matrix.scroll_select || !IsRowVisible(mw, row ))
    MakeRowVisible(mw, row);
  if (mw->matrix.scroll_select || !IsColumnVisible(mw, column ))
    MakeColumnVisible(mw, column);

  /* old
  MakeRowVisible(mw, row);
  MakeColumnVisible(mw, column);
  */
}

static Boolean
DoCommitEdit(mw)
     XbaeMatrixWidget mw;
{
  String cell;

  if (!XtIsManaged(TextChild(mw)))
    return True;

  /*
   * Get the value the user entered in the textField (this is a copy)
   */
  cell = XmTextFieldGetString(TextChild(mw));

  /*
   * Call the leaveCellCallback to see if we can leave the current cell.
   */
  if (mw->matrix.leave_cell_callback)
  {
    XbaeMatrixLeaveCellCallbackStruct call_data;

    call_data.reason = XbaeLeaveCellReason;
    call_data.row = mw->matrix.current_row;
    call_data.column = mw->matrix.current_column;
    call_data.value = cell;
    call_data.doit = True;

    XtCallCallbackList((Widget) mw, mw->matrix.leave_cell_callback,
		       (XtPointer) & call_data);

    /*
     * Application doesn't want to leave this cell. Make the cell visible
     * and traverse to it so the user can see where they screwed up.
     */
    if (!call_data.doit)
    {
      MakeCellVisible(mw,
		      mw->matrix.current_row, mw->matrix.current_column);
      XmProcessTraversal(TextChild(mw), XmTRAVERSE_CURRENT);
      XtFree((XtPointer) cell);
      return False;
    }

    /*
     * Use the applications value if it is different.
     * If the application modified the string inplace, we will pick that
     * up automatically.
     */
    if (call_data.value != cell)
    {
      XtFree((XtPointer) cell);
      cell = call_data.value;
    }
  }

  /*
   * Call the set_cell method to store the new value in the cell and redraw.
   */
  (*((XbaeMatrixWidgetClass) XtClass(mw))->matrix_class.set_cell)
    (mw, mw->matrix.current_row, mw->matrix.current_column, cell, False);

  XtFree((XtPointer) cell);

  return True;
}

/*
 * Matrix set_cell method
 */
static void
SetCell F_ARGS(mw __ row __ column __ value __ update_text , \
     XbaeMatrixWidget mw _ int row _ int column _ _Xconst String value _ \
     Boolean update_text)
{
  if (row >= mw->matrix.rows || row < 0 ||
      column >= mw->matrix.columns || column < 0)
  {
    XtAppWarningMsg(XtWidgetToApplicationContext((Widget) mw),
		    "setCell", "badIndex", "XbaeMatrix",
		    "XbaeMatrix: Row or column parameter out of bounds for SetCell.",
		    NULL, 0);
    return;
  }

  /*
   * Store the new value in the cell.
   */
  XtFree((XtPointer) mw->matrix.cells[row][column]);
  mw->matrix.cells[row][column] = XtNewString(value);

  /*
   * Draw the cell.
   */
  if (IsCellVisible(mw, row, column))
  {
    ClearCell(mw, row, column);
    DrawCell(mw, row, column);
  }

  /*
   * If we are editing this cell, load the textField too if update_text set.
   * XXX there is a bug in XmTextFieldSetString in Motif 1.1.3
   *     it doesn't handle "" strings well.  Use SetValues instead.
   */
  if (update_text && XtIsManaged(TextChild(mw)) &&
      mw->matrix.current_row == row && mw->matrix.current_column == column)
  {
    if (value[0] == '\0')
      XtVaSetValues(TextChild(mw),
		    XmNvalue, value,
		    NULL);
    else
      XmTextFieldSetString(TextChild(mw), value);
  }
}

/*
 * Public interface to set_cell method
 */
void
XbaeMatrixSetCell F_ARGS(w __ row __ column __ value , \
			 Widget w _ int row _ int column _ _Xconst String value)
{
  /*
   * Make sure w is a Matrix or a subclass
   */
  XtCheckSubclass(w, xbaeMatrixWidgetClass, NULL);

  /*
   * Call the set_cell method
   */
  (*((XbaeMatrixWidgetClass) XtClass(w))->matrix_class.set_cell)
    ((XbaeMatrixWidget) w, row, column, value, True);
}

/*
 * Matrix edit_cell method
 */
static void
EditCell(mw, row, column)
     XbaeMatrixWidget mw;
     int row, column;
{
  Boolean edit = True;
  int x, y;
  Pixel fgcolor, bgcolor;
  Boolean alt;
  String string;
  Pixmap pixmap;

  if (row >= mw->matrix.rows || row < 0 ||
      column >= mw->matrix.columns || column < 0)
  {
    XtAppWarningMsg(XtWidgetToApplicationContext((Widget) mw),
		    "editCell", "badIndex", "XbaeMatrix",
		    "XbaeMatrix: Row or column parameter out of bounds for EditCell.",
		    NULL, 0);
    return;
  }

  /*
   * Attempt to commit the edit in the current cell. Return if we fail.
   */
  if (!DoCommitEdit(mw))
    return;

  /*
   * Scroll the cell onto the screen
   */
  MakeCellVisible(mw, row, column);

  /*
   * Fixed cells are not editable
   */
  if (IS_FIXED(mw, row, column))
    return;

  /*
   * If we have an enterCellCallback, call it to see if the cell is
   * editable.
   */
  if (mw->matrix.enter_cell_callback)
  {
    XbaeMatrixEnterCellCallbackStruct call_data;

    call_data.reason = XbaeEnterCellReason;
    call_data.row = row;
    call_data.column = column;
    call_data.doit = True;
    XtCallCallbackList((Widget) mw, mw->matrix.enter_cell_callback,
		       (XtPointer) & call_data);

    edit = call_data.doit;
  }

  mw->matrix.current_row = row;
  mw->matrix.current_column = column;

  /*
   * Unmap the textField to avoid flashing.
   */
  if (XtIsManaged(TextChild(mw)) && XtIsRealized(TextChild(mw)))
    XtUnmapWidget(TextChild(mw));

  /*
   * Convert the row/column to an xy position and move the textField
   * to this position. (the xy position will be relative to the Clip
   * widget because only non-fixed cells are editable)
   */
  RowColToXY(mw, row, column, &x, &y);
  XtMoveWidget(TextChild(mw), x, y);

  /*
   * If we're doing a drawCell, go ask the app what to put there.
   */
  if (mw->matrix.draw_cell_callback)
  {
    if (get_draw_cell_value(mw, row, column, &string, &pixmap) == XbaePixmap)
    {
      /*
       * If we're showing a pixmap, we don't need the TextField.
      return;
       */

      /* temporarily turn pixmaps off */
      string = "";
    }
  }
  else
    string = mw->matrix.cells[row][column];

#ifndef XBAE_NO_PROTOCOL_BUG
  /*
   * XXX Resetting the XmTextField foreground can cause a protocol error in
   * XmTextField.  I filed a bug report.  Put this code back in when it
   * is fixed.
   */
  /*
   * Figure out what color the textField needs to be
   */
  alt = (mw->matrix.alt_row_count && row >= (int)mw->matrix.fixed_rows)
        ? (((row - (int)mw->matrix.fixed_rows) / mw->matrix.alt_row_count) % 2)
        : False;
  if (mw->matrix.colors)
    fgcolor = mw->matrix.colors[row][column];
  else
    fgcolor = mw->manager.foreground;

  if (mw->matrix.text_background != mw->core.background_pixel)
    bgcolor = mw->matrix.text_background;
  else
  {
    if (alt)  /* (row & 1) */
      bgcolor = mw->matrix.odd_row_background;
    else
      bgcolor = mw->matrix.even_row_background;
  }

  /*
   * Include XmNforeground in the setvalues below
   */
#endif /* XBAE_NO_PROTOCOL_BUG */

  /*
   * Setup the textField for the new cell. If the modifyVerify CB
   * rejects the new value, then it is the applications fault for
   * loading the cell with a bad value to begin with.
   */
  XtVaSetValues(TextChild(mw),
		XmNvalue, string,
		XmNwidth, COLUMN_WIDTH(mw, column),
		XmNheight, ROW_HEIGHT(mw),
		XmNmaxLength, mw->matrix.column_max_lengths
		? mw->matrix.column_max_lengths[column]
		: (int) mw->matrix.column_widths[column],
		XmNeditable, edit,
		XmNcursorPositionVisible, edit,
#ifndef XBAE_NO_PROTOCOL_BUG
		XmNbackground, bgcolor,
		XmNforeground, fgcolor,
#endif /* XBAE_NO_PROTOCOL_BUG */
		NULL);

  /*
   * We need this to work around an XmTextField problem with
   * the I-beam and caret
   */
  if (edit)
    XmTextFieldSetInsertionPosition(TextChild(mw), strlen(string));

  /*
   * Manage and map the textField
   */
  XtManageChild(TextChild(mw));
  if (XtIsRealized(TextChild(mw)))
    XtMapWidget(TextChild(mw));
}

/*
 * Public interface to edit_cell method
 */
void
XbaeMatrixEditCell(w, row, column)
     Widget w;
     int row, column;
{
  /*
   * Make sure w is a Matrix or a subclass
   */
  XtCheckSubclass(w, xbaeMatrixWidgetClass, NULL);

  /*
   * Call the edit_cell method
   */
  (*((XbaeMatrixWidgetClass) XtClass(w))->matrix_class.edit_cell)
    ((XbaeMatrixWidget) w, row, column);

  XmProcessTraversal(TextChild(((XbaeMatrixWidget) w)), XmTRAVERSE_CURRENT);
}

/*
 * Matrix select_cell method
 */
static void
SelectCell(mw, row, column)
     XbaeMatrixWidget mw;
     int row, column;
{
  if (row >= mw->matrix.rows || row < 0 ||
      column >= mw->matrix.columns || column < 0)
  {
    XtAppWarningMsg(XtWidgetToApplicationContext((Widget) mw),
		    "selectCell", "badIndex", "XbaeMatrix",
		    "XbaeMatrix: Row or column parameter out of bounds for SelectCell.",
		    NULL, 0);
    return;
  }

  /*
   * Scroll the cell onto the screen
   */
  if (mw->matrix.scroll_select)
    MakeCellVisible(mw, row, column);

  /*
   * If the cell is not already selected, select it and redraw it
   */
  if (!mw->matrix.selected_cells[row][column])
  {
    mw->matrix.selected_cells[row][column] = True;
    if (mw->matrix.scroll_select || IsCellVisible(mw, row, column))
      DrawCell(mw, row, column);
  }
}

/*
 * Public interface to select_cell method
 */
void
XbaeMatrixSelectCell(w, row, column)
     Widget w;
     int row, column;
{
  /*
   * Make sure w is a Matrix or a subclass
   */
  XtCheckSubclass(w, xbaeMatrixWidgetClass, NULL);

  /*
   * Call the select_cell method
   */
  (*((XbaeMatrixWidgetClass) XtClass(w))->matrix_class.select_cell)
    ((XbaeMatrixWidget) w, row, column);
}

/*
 * Matrix select_row method
 */
static void
SelectRow(mw, row)
     XbaeMatrixWidget mw;
     int row;
{
  int j, lc, rc;

  if (row >= mw->matrix.rows || row < 0)
  {
    XtAppWarningMsg(XtWidgetToApplicationContext((Widget) mw),
		    "selectRow", "badIndex", "XbaeMatrix",
		    "XbaeMatrix: Row out of bounds for SelectRow.",
		    NULL, 0);
    return;
  }

  /*
   * Scroll the row onto the screen
   */
  MakeRowVisible(mw, row);

  /*
   * For each cell in the row, if the cell is not already selected,
   * select it and redraw it
   */
  GetVisibleColumns(mw, &lc, &rc);
  for (j = 0; j < mw->matrix.columns; j++)
  {
    if (!mw->matrix.selected_cells[row][j])
    {
      mw->matrix.selected_cells[row][j] = True;
      if ((j >= lc && j <= rc) || j < (int)mw->matrix.fixed_columns)
      {
	ClearCell(mw, row, j);
	DrawCell(mw, row, j);
      }
    }
  }
}

/*
 * Public interface to select_row method
 */
void
XbaeMatrixSelectRow(w, row)
     Widget w;
     int row;
{
  /*
   * Make sure w is a Matrix or a subclass
   */
  XtCheckSubclass(w, xbaeMatrixWidgetClass, NULL);

  /*
   * Call the select_row method
   */
  (*((XbaeMatrixWidgetClass) XtClass(w))->matrix_class.select_row)
    ((XbaeMatrixWidget) w, row);
}

/*
 * Matrix select_column method
 */
static void
SelectColumn(mw, column)
     XbaeMatrixWidget mw;
     int column;
{
  int i, tr, br;

  if (column >= mw->matrix.columns || column < 0)
  {
    XtAppWarningMsg(XtWidgetToApplicationContext((Widget) mw),
		    "selectColumn", "badIndex", "XbaeMatrix",
		    "XbaeMatrix: Column out of bounds for SelectColumn.",
		    NULL, 0);
    return;
  }

  /*
   * Scroll the column onto the screen
   */
  MakeColumnVisible(mw, column);

  /*
   * For each cell in the column, if the cell is not already selected,
   * select it and redraw it
   */
  GetVisibleRows(mw, &tr, &br);
  for (i = 0; i < mw->matrix.rows; i++)
  {
    if (!mw->matrix.selected_cells[i][column])
    {
      mw->matrix.selected_cells[i][column] = True;
      if ((i >= tr && i <= br) || i < (int)mw->matrix.fixed_rows)
      {
	ClearCell(mw, i, column);
	DrawCell(mw, i, column);
      }
    }
  }
}

/*
 * Public interface to select_column method
 */
void
XbaeMatrixSelectColumn(w, column)
     Widget w;
     int column;
{
  /*
   * Make sure w is a Matrix or a subclass
   */
  XtCheckSubclass(w, xbaeMatrixWidgetClass, NULL);

  /*
   * Call the select_column method
   */
  (*((XbaeMatrixWidgetClass) XtClass(w))->matrix_class.select_column)
    ((XbaeMatrixWidget) w, column);
}

/*
 * Matrix deselect_all method
 */
static void
DeselectAll(mw)
     XbaeMatrixWidget mw;
{
  int i, j;
  int tr, br, lc, rc;

  GetVisibleCells(mw, &tr, &br, &lc, &rc);
  for (i = 0; i < mw->matrix.rows; i++)
    for (j = 0; j < mw->matrix.columns; j++)
    {
      if (mw->matrix.selected_cells[i][j])
      {
	mw->matrix.selected_cells[i][j] = False;
	if ((i >= tr && i <= br && j >= lc && j <= rc) ||
	    (i < (int)mw->matrix.fixed_rows && j >= lc && j <= rc) ||
	    (j < (int)mw->matrix.fixed_columns && i >= tr && i <= br))
	{
	  ClearCell(mw, i, j);
	  DrawCell(mw, i, j);
	}
      }
    }
}

/*
 * Public interface to deselect_all method
 */
void
XbaeMatrixDeselectAll(w)
     Widget w;
{
  /*
   * Make sure w is a Matrix or a subclass
   */
  XtCheckSubclass(w, xbaeMatrixWidgetClass, NULL);

  /*
   * Call the deselect_all method
   */
  (*((XbaeMatrixWidgetClass) XtClass(w))->matrix_class.deselect_all)
    ((XbaeMatrixWidget) w);
}

/*
 * Matrix deselect_cell method
 */
static void
DeselectCell(mw, row, column)
     XbaeMatrixWidget mw;
     int row;
     int column;
{
  if (row >= mw->matrix.rows || row < 0 ||
      column > mw->matrix.columns - 1 || column < 0)
  {
    XtAppWarningMsg(XtWidgetToApplicationContext((Widget) mw),
		    "deselectCell", "badIndex", "XbaeMatrix",
		  "XbaeMatrix: Row or column parameter out of bounds for DeselectCell.",
		    NULL, 0);
    return;
  }

  if (mw->matrix.selected_cells[row][column])
  {
    mw->matrix.selected_cells[row][column] = False;
    if (IsCellVisible(mw, row, column))
    {
      ClearCell(mw, row, column);
      DrawCell(mw, row, column);
    }
  }
}

/*
 * Public interface to deselect_cell method
 */
void
XbaeMatrixDeselectCell(w, row, column)
     Widget w;
     int row;
     int column;
{
  /*
   * Make sure w is a Matrix or a subclass
   */
  XtCheckSubclass(w, xbaeMatrixWidgetClass, NULL);

  /*
   * Call the deselect_cell method
   */
  (*((XbaeMatrixWidgetClass) XtClass(w))->matrix_class.deselect_cell)
    ((XbaeMatrixWidget) w, row, column);
}

/*
 * Matrix deselect_row method
 */
static void
DeselectRow(mw, row)
     XbaeMatrixWidget mw;
     int row;
{
  int j, lc, rc;

  if (row >= mw->matrix.rows || row < 0)
  {
    XtAppWarningMsg(XtWidgetToApplicationContext((Widget) mw),
		    "deselectRow", "badIndex", "XbaeMatrix",
		    "XbaeMatrix: Row parameter out of bounds for DeselectRow.",
		    NULL, 0);
    return;
  }

  /*
   * For each cell in the row, if the cell is selected,
   * deselect it and redraw it
   */
  GetVisibleColumns(mw, &lc, &rc);
  for (j = 0; j < mw->matrix.columns; j++)
  {
    if (mw->matrix.selected_cells[row][j])
    {
      mw->matrix.selected_cells[row][j] = False;
      if ((j >= lc && j <= rc) || j < (int)mw->matrix.fixed_columns)
      {
	ClearCell(mw, row, j);
	DrawCell(mw, row, j);
      }
    }
  }
}

/*
 * Public interface to deselect_row method
 */
void
XbaeMatrixDeselectRow(w, row)
     Widget w;
     int row;
{
  /*
   * Make sure w is a Matrix or a subclass
   */
  XtCheckSubclass(w, xbaeMatrixWidgetClass, NULL);

  /*
   * Call the deselect_row method
   */
  (*((XbaeMatrixWidgetClass) XtClass(w))->matrix_class.deselect_row)
    ((XbaeMatrixWidget) w, row);
}

/*
 * Matrix deselect_column method
 */
static void
DeselectColumn(mw, column)
     XbaeMatrixWidget mw;
     int column;
{
  int i, tr, br;

  if (column >= mw->matrix.columns || column < 0)
  {
    XtAppWarningMsg(XtWidgetToApplicationContext((Widget) mw),
		    "deselectColumn", "badIndex", "XbaeMatrix",
		    "XbaeMatrix: Column parameter out of bounds for DeselectColumn.",
		    NULL, 0);
    return;
  }

  /*
   * For each cell in the column, if the cell is selected,
   * deselect it and redraw it
   */
  GetVisibleRows(mw, &tr, &br);
  for (i = 0; i < mw->matrix.rows; i++)
  {
    if (mw->matrix.selected_cells[i][column])
    {
      mw->matrix.selected_cells[i][column] = False;
      if ((i >= tr && i <= br) || i < (int)mw->matrix.fixed_rows)
      {
	ClearCell(mw, i, column);
	DrawCell(mw, i, column);
      }
    }
  }
}

/*
 * Public interface to deselect_column method
 */
void
XbaeMatrixDeselectColumn(w, column)
     Widget w;
     int column;
{
  /*
   * Make sure w is a Matrix or a subclass
   */
  XtCheckSubclass(w, xbaeMatrixWidgetClass, NULL);

  /*
   * Call the deselect_column method
   */
  (*((XbaeMatrixWidgetClass) XtClass(w))->matrix_class.deselect_column)
    ((XbaeMatrixWidget) w, column);
}

/*
 * Matrix get_cell method
 */
static String
GetCell(mw, row, column)
     XbaeMatrixWidget mw;
     int row, column;
{
  if (row >= mw->matrix.rows || row < 0 ||
      column > mw->matrix.columns - 1 || column < 0)
  {
    XtAppWarningMsg(XtWidgetToApplicationContext((Widget) mw),
		    "getCell", "badIndex", "XbaeMatrix",
		    "XbaeMatrix: Row or column parameter out of bounds for GetCell.",
		    NULL, 0);
    return (NULL);
  }

  return mw->matrix.cells[row][column];
}

/*
 * Public interface to get_cell method
 */
String
XbaeMatrixGetCell(w, row, column)
     Widget w;
     int row, column;
{
  /*
   * Make sure w is a Matrix or a subclass
   */
  XtCheckSubclass(w, xbaeMatrixWidgetClass, NULL);

  /*
   * Call the get_cell method
   */
  return (*((XbaeMatrixWidgetClass) XtClass(w))->matrix_class.get_cell)
    ((XbaeMatrixWidget) w, row, column);
}

/*
 * Matrix commit_edit method
 */
static Boolean
CommitEdit(XbaeMatrixWidget mw, Boolean unmap)
{
  Boolean commit;

  if (!XtIsManaged(TextChild(mw)))
    return True;

  /*
   * Attempt to commit the edit
   */
  commit = DoCommitEdit(mw);

  /*
   * If the commit succeeded and we are supposed to unmap the textField,
   * then hide the textField and traverse out
   */
  if (commit && unmap)
  {
    XtUnmanageChild(TextChild(mw));
    XmProcessTraversal(TextChild(mw), XmTRAVERSE_RIGHT);
  }

  return commit;
}

/*
 * Public interface to commit_edit method
 */
Boolean
XbaeMatrixCommitEdit F_ARGS(w __ unmap , Widget w _ Boolean unmap)
{
  /*
   * Make sure w is a Matrix or a subclass
   */
  XtCheckSubclass(w, xbaeMatrixWidgetClass, NULL);

  /*
   * Call the commit_edit method
   */
  return (*((XbaeMatrixWidgetClass) XtClass(w))->matrix_class.commit_edit)
    ((XbaeMatrixWidget) w, unmap);
}

/*
 * Matrix cancel_edit method
 */
static void
CancelEdit(XbaeMatrixWidget mw, Boolean unmap)
{
  if (!XtIsManaged(TextChild(mw)))
    return;

  /*
   * If unmap is set, hide the textField and traverse out.
   */
  if (unmap)
  {
    XtUnmanageChild(TextChild(mw));
    XmProcessTraversal(TextChild(mw), XmTRAVERSE_RIGHT);
  }

  /*
   * Don't unmap, just restore original contents
   */
  else
  {
    XtVaSetValues(TextChild(mw),
		  XmNvalue, mw->matrix.cells[mw->matrix.current_row]
		  [mw->matrix.current_column],
		  NULL);
  }
}

/*
 * Public interface to cancel_edit method
 */
void
XbaeMatrixCancelEdit F_ARGS(w __ unmap , Widget w _ Boolean unmap)
{
  /*
   * Make sure w is a Matrix or a subclass
   */
  XtCheckSubclass(w, xbaeMatrixWidgetClass, NULL);

  /*
   * Call the cancel_edit method
   */
  (*((XbaeMatrixWidgetClass) XtClass(w))->matrix_class.cancel_edit)
    ((XbaeMatrixWidget) w, unmap);
}

/*
 * Matrix add_rows method
 */
static void
AddRows(mw, position, rows, labels, colors, num_rows)
     XbaeMatrixWidget mw;
     int position;
     String *rows;
     String *labels;
     Pixel *colors;
     int num_rows;
{
  /*
   * Do some error checking.
   */
  if (num_rows <= 0)
    return;
  if (position < 0 || position > mw->matrix.rows)
  {
    XtAppWarningMsg(XtWidgetToApplicationContext((Widget) mw),
		    "addRows", "badPosition", "XbaeMatrix",
		    "XbaeMatrix: Position out of bounds in AddRows.",
		    NULL, 0);
    return;
  }

  /*
   * Add the new rows into the internal cells/labels data structure.
   */
  AddRowsToTable(mw, position, rows, labels, colors, num_rows);

  /*
   * Reconfig the VSB maximum.
   */
  XtVaSetValues(VertScrollChild(mw),
		XmNmaximum, (mw->matrix.rows -
			     (int) mw->matrix.fixed_rows),
		NULL);

  /*
   * Relayout.
   */
  Resize(mw);

  /*
   * Call our cancel_edit method since the rows shifted underneath us
   */
  (*((XbaeMatrixWidgetClass) XtClass(mw))->matrix_class.cancel_edit)
    (mw, True);

  if (XtIsRealized((Widget)mw))
  {
    /*
     * Generate expose events on Matrix and Clip to force the
     * new rows to be drawn.
     */
    XClearArea(XtDisplay(mw), XtWindow(mw),
	       0, 0,
	       0 /*Full Width*/ , 0 /*Full Height*/ ,
	       True);
    XbaeClipRedraw(ClipChild(mw));
  }
}

/*
 * Public interface to add_rows method
 */
void
XbaeMatrixAddRows(w, position, rows, labels, colors, num_rows)
     Widget w;
     int position;
     String *rows;
     String *labels;
     Pixel *colors;
     int num_rows;
{
  /*
   * Make sure w is a Matrix or a subclass
   */
  XtCheckSubclass(w, xbaeMatrixWidgetClass, NULL);

  /*
   * Call the add_rows method
   */
  (*((XbaeMatrixWidgetClass) XtClass(w))->matrix_class.add_rows)
    ((XbaeMatrixWidget) w, position, rows, labels, colors, num_rows);
}

/*
 * Matrix delete_rows method
 */
static void
DeleteRows(mw, position, num_rows)
     XbaeMatrixWidget mw;
     int position;
     int num_rows;
{
  /*
   * Do some error checking.
   */
  if (num_rows <= 0)
    return;
  if (position < 0 || position + num_rows > mw->matrix.rows)
  {
    XtAppWarningMsg(XtWidgetToApplicationContext((Widget) mw),
		    "deleteRows", "badPosition", "XbaeMatrix",
		    "XbaeMatrix: Position out of bounds in DeleteRows.",
		    NULL, 0);
    return;
  }
  if (num_rows >= mw->matrix.rows - (int)mw->matrix.fixed_rows)
  {
    XtAppWarningMsg(XtWidgetToApplicationContext((Widget) mw),
		    "deleteRows", "tooMany", "XbaeMatrix",
		    "XbaeMatrix: Attempting to delete too many rows in DeleteRows.",
		    NULL, 0);
    return;
  }

  /*
   * Delete the new rows from the internal cells/labels data structure.
   */
  DeleteRowsFromTable(mw, position, num_rows);

  /*
   * Reconfig the VSB maximum. Reset the sliderSize to avoid warnings.
   */
  XtVaSetValues(VertScrollChild(mw),
		XmNmaximum, (mw->matrix.rows -
			     (int) mw->matrix.fixed_rows),
		XmNsliderSize, 1,
		NULL);

  /*
   * Relayout.
   */
  Resize(mw);

  /*
   * Call our cancel_edit method since the rows shifted underneath us
   */
  (*((XbaeMatrixWidgetClass) XtClass(mw))->matrix_class.cancel_edit)
    (mw, True);

  if (XtIsRealized((Widget)mw))
  {
    /*
     * Generate expose events on Matrix and Clip to force the
     * rows to be redrawn.
     */
    XClearArea(XtDisplay(mw), XtWindow(mw),
	       0, 0,
	       0 /*Full Width*/ , 0 /*Full Height*/ ,
	       True);
    XbaeClipRedraw(ClipChild(mw));
  }
}

/*
 * Public interface to delete_rows method
 */
void
XbaeMatrixDeleteRows(w, position, num_rows)
     Widget w;
     int position;
     int num_rows;
{
  /*
   * Make sure w is a Matrix or a subclass
   */
  XtCheckSubclass(w, xbaeMatrixWidgetClass, NULL);

  /*
   * Call the delete_rows method
   */
  (*((XbaeMatrixWidgetClass) XtClass(w))->matrix_class.delete_rows)
    ((XbaeMatrixWidget) w, position, num_rows);
}

/*
 * Matrix add_columns method.
 */
static void
AddColumns(mw, position, columns, labels, widths, max_lengths,
	   alignments, label_alignments, colors, num_columns)
     XbaeMatrixWidget mw;
     int position;
     String *columns;
     String *labels;
     short *widths;
     int *max_lengths;
     unsigned char *alignments;
     unsigned char *label_alignments;
     Pixel *colors;
     int num_columns;
{
  /*
   * Do some error checking.
   */
  if (num_columns <= 0)
    return;
  if (position < 0 || position > mw->matrix.columns)
  {
    XtAppWarningMsg(XtWidgetToApplicationContext((Widget) mw),
		    "addColumns", "badPosition", "XbaeMatrix",
		    "XbaeMatrix: Position out of bounds in AddColumns.",
		    NULL, 0);
    return;
  }
  if (!widths)
  {
    XtAppWarningMsg(XtWidgetToApplicationContext((Widget) mw),
		    "addColumns", "noWidths", "XbaeMatrix",
		    "XbaeMatrix: Must specify column widths in AddColumns.",
		    NULL, 0);
    return;
  }

  /*
   * Add the new rows into the internal cells/labels data structure.
   */
  AddColumnsToTable(mw, position, columns, labels, widths, max_lengths,
		    alignments, label_alignments, colors, num_columns);

  /*
   * Reconfig the HSB maximum.
   */
  XtVaSetValues(HorizScrollChild(mw),
		XmNmaximum, CELL_TOTAL_WIDTH(mw),
		NULL);

  /*
   * Relayout.
   */
  Resize(mw);

  /*
   * Call our cancel_edit method since the columns shifted underneath us
   */
  (*((XbaeMatrixWidgetClass) XtClass(mw))->matrix_class.cancel_edit)
    (mw, True);

  if (XtIsRealized((Widget)mw))
  {
    /*
     * Generate expose events on Matrix and Clip to force the
     * new columns to be drawn.
     */
    XClearArea(XtDisplay(mw), XtWindow(mw),
	       0, 0,
	       0 /*Full Width*/ , 0 /*Full Height*/ ,
	       True);
    XbaeClipRedraw(ClipChild(mw));
  }
}

/*
 * Public interface to add_columns method
 */
void
XbaeMatrixAddColumns(w, position, columns, labels, widths, max_lengths,
		     alignments, label_alignments, colors, num_columns)
     Widget w;
     int position;
     String *columns;
     String *labels;
     short *widths;
     int *max_lengths;
     unsigned char *alignments;
     unsigned char *label_alignments;
     Pixel *colors;
     int num_columns;
{
  /*
   * Make sure w is a Matrix or a subclass
   */
  XtCheckSubclass(w, xbaeMatrixWidgetClass, NULL);

  /*
   * Call the add_columns method
   */
  (*((XbaeMatrixWidgetClass) XtClass(w))->matrix_class.add_columns)
    ((XbaeMatrixWidget) w, position, columns, labels, widths,
     max_lengths, alignments, label_alignments, colors, num_columns);
}

/*
 * Matrix delete_columns method
 */
static void
DeleteColumns(mw, position, num_columns)
     XbaeMatrixWidget mw;
     int position;
     int num_columns;
{
  /*
   * Do some error checking.
   */
  if (num_columns <= 0)
    return;
  if (position < 0 || position + num_columns > mw->matrix.columns)
  {
    XtAppWarningMsg(XtWidgetToApplicationContext((Widget) mw),
		    "deleteColumns", "badPosition", "XbaeMatrix",
		    "XbaeMatrix: Position out of bounds in DeleteColumns.",
		    NULL, 0);
    return;
  }
  if (num_columns >= mw->matrix.columns - (int)mw->matrix.fixed_columns)
  {
    XtAppWarningMsg(XtWidgetToApplicationContext((Widget) mw),
		    "deleteColumns", "tooMany", "XbaeMatrix",
		  "XbaeMatrix: Attempting to delete too many columns in DeleteColumns.",
		    NULL, 0);
    return;
  }

  /*
   * Delete the new columns from the internal cells/labels data structure.
   */
  DeleteColumnsFromTable(mw, position, num_columns);

  /*
   * Reconfig the HSB maximum. Reset the sliderSize to avoid warnings.
   */
  XtVaSetValues(HorizScrollChild(mw),
		XmNmaximum, CELL_TOTAL_WIDTH(mw),
		XmNsliderSize, 1,
		NULL);

  /*
   * Relayout.
   */
  Resize(mw);

  /*
   * Call our cancel_edit method since the columns shifted underneath us
   */
  (*((XbaeMatrixWidgetClass) XtClass(mw))->matrix_class.cancel_edit)
    (mw, True);

  if (XtIsRealized((Widget)mw))
  {
    /*
     * Generate expose events on Matrix and Clip to force the
     * columns to be redrawn.
     */
    XClearArea(XtDisplay(mw), XtWindow(mw),
	       0, 0,
	       0 /*Full Width*/ , 0 /*Full Height*/ ,
	       True);
    XbaeClipRedraw(ClipChild(mw));
  }
}

/*
 * Public interface to delete_columns method
 */
void
XbaeMatrixDeleteColumns(w, position, num_columns)
     Widget w;
     int position;
     int num_columns;
{
  /*
   * Make sure w is a Matrix or a subclass
   */
  XtCheckSubclass(w, xbaeMatrixWidgetClass, NULL);

  /*
   * Call the delete_columns method
   */
  (*((XbaeMatrixWidgetClass) XtClass(w))->matrix_class.delete_columns)
    ((XbaeMatrixWidget) w, position, num_columns);
}

/*
 * Matrix set_row_colors method
 */
static void
SetRowColors(mw, position, colors, num_colors)
     XbaeMatrixWidget mw;
     int position;
     Pixel *colors;
     int num_colors;
{
  Rectangle rect;
  int i, j;

  /*
   * Do some error checking.
   */
  if (num_colors <= 0)
    return;
  if (position < 0 || position + num_colors > mw->matrix.rows)
  {
    XtAppWarningMsg(XtWidgetToApplicationContext((Widget) mw),
		    "setRowColors", "badPosition", "XbaeMatrix",
	       "XbaeMatrix: Position out of bounds or too many colors in SetRowColors.",
		    NULL, 0);
    return;
  }

  /*
   * If we don't have any colors yet, malloc them, and initialize
   * unused entries to foreground
   */
  if (mw->matrix.colors == NULL)
  {
    CreateColors(mw);
    for (i = 0; i < position; i++)
      for (j = 0; j < mw->matrix.columns; j++)
	mw->matrix.colors[i][j] = mw->manager.foreground;
    for (i = position + num_colors; i < mw->matrix.rows; i++)
      for (j = 0; j < mw->matrix.columns; j++)
	mw->matrix.colors[i][j] = mw->manager.foreground;
  }

  /*
   * Set each row to the appropriate color
   */
  for (i = 0; i < num_colors; i++)
    for (j = 0; j < mw->matrix.columns; j++)
      mw->matrix.colors[i + position][j] = colors[i];

  if (XtIsRealized((Widget)mw))
  {
    /*
     * Redraw all the visible non-fixed cells. We don't need to clear first
     * since only the color changed.
     */
    SETRECT(rect,
	    0, 0,
	    ClipChild(mw)->core.width - 1, ClipChild(mw)->core.height - 1);
    RedrawCells(mw, &rect);

    /*
     * Redraw all the visible fixed cells (but not the labels).
     * We don't need to clear first since only the color changed.
     */
    SETRECT(rect,
	    ROW_LABEL_WIDTH(mw), COLUMN_LABEL_HEIGHT(mw),
	    mw->core.width - 1, mw->core.height - 1);
    RedrawLabelsAndFixed(mw, &rect);
  }
}

/*
 * Public interface to set_row_colors method
 */
void
XbaeMatrixSetRowColors(w, position, colors, num_colors)
     Widget w;
     int position;
     Pixel *colors;
     int num_colors;
{
  /*
   * Make sure w is a Matrix or a subclass
   */
  XtCheckSubclass(w, xbaeMatrixWidgetClass, NULL);

  /*
   * Call the set_row_colors method
   */
  (*((XbaeMatrixWidgetClass) XtClass(w))->matrix_class.set_row_colors)
    ((XbaeMatrixWidget) w, position, colors, num_colors);
}

/*
 * Matrix set_column_colors method
 */
static void
SetColumnColors(mw, position, colors, num_colors)
     XbaeMatrixWidget mw;
     int position;
     Pixel *colors;
     int num_colors;
{
  Rectangle rect;
  int i, j;

  /*
   * Do some error checking.
   */
  if (num_colors <= 0)
    return;
  if (position < 0 || position + num_colors > mw->matrix.columns)
  {
    XtAppWarningMsg(XtWidgetToApplicationContext((Widget) mw),
		    "setColumnColors", "badPosition", "XbaeMatrix",
	    "XbaeMatrix: Position out of bounds or too many colors in SetColumnColors.",
		    NULL, 0);
    return;
  }

  /*
   * If we don't have any colors yet, malloc them, and initialize
   * unused entries to foreground
   */
  if (mw->matrix.colors == NULL)
  {
    CreateColors(mw);
    for (i = 0; i < mw->matrix.rows; i++)
      for (j = 0; j < position; j++)
	mw->matrix.colors[i][j] = mw->manager.foreground;
    for (i = 0; i < mw->matrix.rows; i++)
      for (j = position + num_colors; j < mw->matrix.columns; j++)
	mw->matrix.colors[i][j] = mw->manager.foreground;
  }

  /*
   * Set each column to the appropriate color
   */
  for (i = 0; i < mw->matrix.rows; i++)
    for (j = 0; j < num_colors; j++)
      mw->matrix.colors[i][j + position] = colors[j];

  if (XtIsRealized((Widget)mw))
  {
    /*
     * Redraw all the visible non-fixed cells. We don't need to clear first
     * since only the color changed.
     */
    SETRECT(rect,
	    0, 0,
	    ClipChild(mw)->core.width - 1, ClipChild(mw)->core.height - 1);
    RedrawCells(mw, &rect);

    /*
     * Redraw all the visible fixed cells (but not the labels).
     * We don't need to clear first since only the color changed.
     */
    SETRECT(rect,
	    ROW_LABEL_WIDTH(mw), COLUMN_LABEL_HEIGHT(mw),
	    mw->core.width - 1, mw->core.height - 1);
    RedrawLabelsAndFixed(mw, &rect);
  }
}

/*
 * Public interface to set_column_colors method
 */
void
XbaeMatrixSetColumnColors(w, position, colors, num_colors)
     Widget w;
     int position;
     Pixel *colors;
     int num_colors;
{
  /*
   * Make sure w is a Matrix or a subclass
   */
  XtCheckSubclass(w, xbaeMatrixWidgetClass, NULL);

  /*
   * Call the set_column_colors method
   */
  (*((XbaeMatrixWidgetClass) XtClass(w))->matrix_class.set_column_colors)
    ((XbaeMatrixWidget) w, position, colors, num_colors);
}

/*
 * Matrix set_cell_color method
 */
static void
SetCellColor(mw, row, column, color)
     XbaeMatrixWidget mw;
     int row;
     int column;
     Pixel color;
{
  int i, j;

  /*
   * Do some error checking.
   */
  if (row >= mw->matrix.rows || row < 0 ||
      column >= mw->matrix.columns || column < 0)
  {
    XtAppWarningMsg(XtWidgetToApplicationContext((Widget) mw),
		    "setCellColor", "badIndex", "XbaeMatrix",
		  "XbaeMatrix: Row or column parameter out of bounds for SetCellColor.",
		    NULL, 0);
    return;
  }

  /*
   * If we don't have any colors yet, malloc them and initialize them
   */
  if (mw->matrix.colors == NULL)
  {
    CreateColors(mw);
    for (i = 0; i < mw->matrix.rows; i++)
      for (j = 0; j < mw->matrix.columns; j++)
	mw->matrix.colors[i][j] = mw->manager.foreground;
  }

  /*
   * Set our cells color
   */
  mw->matrix.colors[row][column] = color;

  if (XtIsRealized((Widget)mw))
  {
    /*
     * Redraw the cell if it is visible
     */
    if (IsCellVisible(mw, row, column))
      DrawCell(mw, row, column);
  }
}

/*
 * Public interface to set_cell_color method
 */
void
XbaeMatrixSetCellColor(w, row, column, color)
     Widget w;
     int row;
     int column;
     Pixel color;
{
  /*
   * Make sure w is a Matrix or a subclass
   */
  XtCheckSubclass(w, xbaeMatrixWidgetClass, NULL);

  /*
   * Call the set_cell_color method
   */
  (*((XbaeMatrixWidgetClass) XtClass(w))->matrix_class.set_cell_color)
    ((XbaeMatrixWidget) w, row, column, color);
}


/*
 * Action to edit a non-fixed cell.
 */
/* ARGSUSED */
static void
EditCellACT(w, event, params, nparams)
     Widget w;
     XEvent *event;
     String *params;
     Cardinal *nparams;
{
  XbaeMatrixWidget mw;
  int row, column;
  XrmQuark q;
  static XrmQuark QPointer, QLeft, QRight, QUp, QDown;
  static Boolean haveQuarks = False;

  /*
   * Get static quarks for the parms we understand
   */
  if (!haveQuarks)
  {
    QPointer = XrmPermStringToQuark("Pointer");
    QLeft = XrmPermStringToQuark("Left");
    QRight = XrmPermStringToQuark("Right");
    QUp = XrmPermStringToQuark("Up");
    QDown = XrmPermStringToQuark("Down");
    haveQuarks = True;
  }

  /*
   * Get Matrix widget and make sure it is a Matrix subclass.
   * w could be Matrix, or the Clip or textField children of Matrix
   */
  if (XtIsSubclass(w, xbaeMatrixWidgetClass))
    mw = (XbaeMatrixWidget) w;
  else if (XtIsSubclass(XtParent(w), xbaeMatrixWidgetClass))
    mw = (XbaeMatrixWidget) XtParent(w);
  else
  {
    XtAppWarningMsg(XtWidgetToApplicationContext(w),
		    "editCellACT", "badWidget", "XbaeMatrix",
		    "XbaeMatrix: Bad widget passed to EditCell action",
		    NULL, 0);
    return;
  }

  /*
   * Make sure we have a single parm
   */
  if (*nparams != 1)
  {
    XtAppWarningMsg(XtWidgetToApplicationContext(w),
		    "editCellACT", "badParms", "XbaeMatrix",
	    "XbaeMatrix: Wrong number of parameters passed to EditCell action, needs 1",
		    NULL, 0);
    return;
  }

  /*
   * Initialize row/column to the current position
   */
  row = mw->matrix.current_row;
  column = mw->matrix.current_column;

  /*
   * Quarkify the string param
   */
  q = XrmStringToQuark(params[0]);

  /*
   * If we aren't currently editing, then the only kind of traversal that
   * makes sense is pointer.
   */
  if (!XtIsManaged(TextChild(mw)) && q != QPointer)
    return;

  if (q == QPointer)
  {
    int x, y;

    /*
     * Get the x,y point coordinate relative to the Clip window.
     * Return if this event did not occur in the Clip subwindow
     * (since we can only edit non-fixed cells).
     */
    switch (event->type)
	  {
	    case ButtonPress:
	    case ButtonRelease:
	      x = event->xbutton.x;
	      y = event->xbutton.y;
	      break;
	    case KeyPress:
	    case KeyRelease:
	      x = event->xkey.x;
	      y = event->xkey.y;
	      break;
	    case MotionNotify:
	      x = event->xmotion.x;
	      y = event->xmotion.y;
	      break;
	    default:
	      return;
	  }

    if (event->xbutton.subwindow == XtWindow(ClipChild(mw)))
    {
      x -= FIXED_COLUMN_LABEL_OFFSET(mw);
      y -= FIXED_ROW_LABEL_OFFSET(mw);
    }
    else if (event->xbutton.window != XtWindow(ClipChild(mw)))
      return;

    /*
     * Convert the point to a row,column. If it does not pick a valid
     * cell, then return.
     */
    if (!XYToRowCol(mw, &x, &y, &row, &column, NonFixedCell))
      return;
  }
  else if (q == QRight)
  {
    /*
     * If we are in the lower right corner, stay there.
     * Otherwise move over a column, if we move off the right edge,
     * then move down a row and back to the first non-fixed column.
     */
    if (mw->matrix.current_row != mw->matrix.rows - 1 ||
	mw->matrix.current_column != mw->matrix.columns - 1)
    {

      column++;

      if (column >= (int) mw->matrix.columns)
      {
	column = mw->matrix.fixed_columns;
	row++;
      }
    }
  }
  else if (q == QLeft)
  {
    /*
     * If we are in the upper left corner, stay there.
     * Otherwise move back a column, if we move before the fixed columns,
     * then move up a row and over to the last column.
     */
    if (mw->matrix.current_row != mw->matrix.fixed_rows ||
	mw->matrix.current_column !=
	mw->matrix.fixed_columns)
    {

      column--;

      if (column < (int) mw->matrix.fixed_columns)
      {
	column = mw->matrix.columns - 1;
	row--;
      }
    }
  }
  else if (q == QDown)
  {
    row++;

    if (row >= (int) mw->matrix.rows)
      row = mw->matrix.fixed_rows;
  }
  else if (q == QUp)
  {
    row--;

    if (row < (int) mw->matrix.fixed_rows)
      row = mw->matrix.rows - 1;
  }

  /*
   * Call the traverseCellCallback to allow the application to
   * perform custom traversal.
   */
  if (mw->matrix.traverse_cell_callback)
  {
    XbaeMatrixTraverseCellCallbackStruct call_data;

    call_data.reason = XbaeTraverseCellReason;
    call_data.row = mw->matrix.current_row;
    call_data.column = mw->matrix.current_column;
    call_data.next_row = row;
    call_data.next_column = column;
    call_data.fixed_rows = mw->matrix.fixed_rows;
    call_data.fixed_columns = mw->matrix.fixed_columns;
    call_data.num_rows = mw->matrix.rows;
    call_data.num_columns = mw->matrix.columns;
    call_data.param = params[0];
    call_data.qparam = q;

    XtCallCallbackList((Widget) mw, mw->matrix.traverse_cell_callback,
		       (XtPointer) & call_data);

    row = call_data.next_row;
    column = call_data.next_column;
  }

  /*
   * Attempt to edit the new cell using the edit_cell method.
   * If we are editing a cell based on pointer position, we always
   * call edit_cell.	Otherwise, we must be editing a new cell to
   * call edit_cell.
   */
  if (q == QPointer || (row != mw->matrix.current_row ||
			column != mw->matrix.current_column))
    (*((XbaeMatrixWidgetClass) XtClass(mw))->matrix_class.edit_cell)
      (mw, row, column);

  /*
   * Traverse to the textField
   */
  XmProcessTraversal(TextChild(mw), XmTRAVERSE_CURRENT);
}

/*
 * Action to unmap the textField and discard any edits made
 */
/* ARGSUSED */
static void
CancelEditACT(w, event, params, nparams)
     Widget w;
     XEvent *event;
     String *params;
     Cardinal *nparams;
{
  XbaeMatrixWidget mw;
  Boolean unmap;

  /*
   * Get Matrix widget and make sure it is a Matrix subclass.
   * w could be Matrix, or the Clip or textField children of Matrix
   */
  if (XtIsSubclass(w, xbaeMatrixWidgetClass))
    mw = (XbaeMatrixWidget) w;
  else if (XtIsSubclass(XtParent(w), xbaeMatrixWidgetClass))
    mw = (XbaeMatrixWidget) XtParent(w);
  else
  {
    XtAppWarningMsg(XtWidgetToApplicationContext(w),
		    "cancelEditACT", "badWidget", "XbaeMatrix",
		    "XbaeMatrix: Bad widget passed to CancelEdit action",
		    NULL, 0);
    return;
  }

  /*
   * Make sure we have a single param
   */
  if (*nparams != 1)
  {
    XtAppWarningMsg(XtWidgetToApplicationContext(w),
		    "cancelEditACT", "badParms", "XbaeMatrix",
	  "XbaeMatrix: Wrong number of parameters passed to CancelEdit action, needs 1",
		    NULL, 0);
    return;
  }

  /*
   * Validate our param
   */
  if (!strcmp(params[0], "True"))
    unmap = True;
  else if (!strcmp(params[0], "False"))
    unmap = False;
  else
  {
    XtAppWarningMsg(XtWidgetToApplicationContext(w),
		    "cancelEditACT", "badParm", "XbaeMatrix",
     "XbaeMatrix: Invalid parameter passed to CancelEdit action, must be True or False",
		    NULL, 0);
    return;
  }
  /*
   * Call the cancel_edit method
   */
  (*((XbaeMatrixWidgetClass) XtClass(mw))->matrix_class.cancel_edit)
    (mw, unmap);
}

/*
 * Action save any edits made and unmap the textField if params[0] is True
 */
/* ARGSUSED */
static void
CommitEditACT(w, event, params, nparams)
     Widget w;
     XEvent *event;
     String *params;
     Cardinal *nparams;
{
  XbaeMatrixWidget mw;
  Boolean unmap;

  /*
   * Get Matrix widget and make sure it is a Matrix subclass.
   * w could be Matrix, or the Clip or textField children of Matrix
   */
  if (XtIsSubclass(w, xbaeMatrixWidgetClass))
    mw = (XbaeMatrixWidget) w;
  else if (XtIsSubclass(XtParent(w), xbaeMatrixWidgetClass))
    mw = (XbaeMatrixWidget) XtParent(w);
  else
  {
    XtAppWarningMsg(XtWidgetToApplicationContext(w),
		    "commitEditACT", "badWidget", "XbaeMatrix",
		    "XbaeMatrix: Bad widget passed to CommitEdit action",
		    NULL, 0);
    return;
  }

  /*
   * Make sure we have a single param
   */
  if (*nparams != 1)
  {
    XtAppWarningMsg(XtWidgetToApplicationContext(w),
		    "commitEditACT", "badParms", "XbaeMatrix",
	  "XbaeMatrix: Wrong number of parameters passed to CommitEdit action, needs 1",
		    NULL, 0);
    return;
  }

  /*
   * Validate our param
   */
  if (!strcmp(params[0], "True"))
    unmap = True;
  else if (!strcmp(params[0], "False"))
    unmap = False;
  else
  {
    XtAppWarningMsg(XtWidgetToApplicationContext(w),
		    "commitEditACT", "badParm", "XbaeMatrix",
     "XbaeMatrix: Invalid parameter passed to CommitEdit action, must be True or False",
		    NULL, 0);
    return;
  }

  (void) (*((XbaeMatrixWidgetClass) XtClass(mw))->matrix_class.commit_edit)
    (mw, unmap);
}

/*
 * Convert the coordinates in an event to be relative to the Clip
 * window or the Matrix window.  Set the cell to indicate which one.
 * Used by some actions.
 */
/* ARGSUSED */
static Boolean
EventToXY(mw, event, x, y, cell)
     XbaeMatrixWidget mw;
     XEvent *event;
     int *x, *y;
     CellType *cell;
{
  switch (event->type)
	{
	  case ButtonPress:
	  case ButtonRelease:
	    *x = event->xbutton.x;
	    *y = event->xbutton.y;
	    break;
	  case KeyPress:
	  case KeyRelease:
	    *x = event->xkey.x;
	    *y = event->xkey.y;
	    break;
	  case MotionNotify:
	    *x = event->xmotion.x;
	    *y = event->xmotion.y;
	    break;
	  default:
	    return False;
	}

  if (event->xbutton.subwindow == XtWindow(ClipChild(mw)))
  {
    *cell = NonFixedCell;
    *x -= FIXED_COLUMN_LABEL_OFFSET(mw);
    *y -= FIXED_ROW_LABEL_OFFSET(mw);
  }
  else if (event->xbutton.window == XtWindow(mw))
    *cell = FixedCell;
  else if (event->xbutton.window == XtWindow(ClipChild(mw)))
    *cell = NonFixedCell;
  else if (event->xbutton.window == XtWindow(TextChild(mw)))
  {
    Position tx, ty;

    *cell = NonFixedCell;
    XtVaGetValues(TextChild(mw),
		  XmNx, &tx,
		  XmNy, &ty,
		  NULL);
    *x += tx;
    *y += ty;
  }
  else
    return False;

  return True;
}

/* ARGSUSED */
static void
SelectCellACT(w, event, params, nparams)
     Widget w;
     XEvent *event;
     String *params;
     Cardinal *nparams;
{
  XbaeMatrixWidget mw;
  int x, y;
  int row, column;
  CellType cell;
  XbaeMatrixSelectCellCallbackStruct call_data;

  /*
   * Get Matrix widget and make sure it is a Matrix subclass.
   * w could be Matrix, or the Clip or textField children of Matrix
   */
  if (XtIsSubclass(w, xbaeMatrixWidgetClass))
    mw = (XbaeMatrixWidget) w;
  else if (XtIsSubclass(XtParent(w), xbaeMatrixWidgetClass))
    mw = (XbaeMatrixWidget) XtParent(w);
  else
  {
    XtAppWarningMsg(XtWidgetToApplicationContext(w),
		    "selectCellACT", "badWidget", "XbaeMatrix",
		    "XbaeMatrix: Bad widget passed to SelectCell action",
		    NULL, 0);
    return;
  }

  /*
   * If we don't have a selectCellCallback, then return now
   */
  if (!mw->matrix.select_cell_callback)
    return;

  if (!EventToXY(mw, event, &x, &y, &cell))
    return;

  /*
   * Convert the point to a row,column. If it does not pick a valid
   * cell, then return.
   */
  if (!XYToRowCol(mw, &x, &y, &row, &column, cell))
    return;

  /*
   * Call our select_cell callbacks
   */
  call_data.reason = XbaeSelectCellReason;
  call_data.row = row;
  call_data.column = column;
  call_data.selected_cells = mw->matrix.selected_cells;
  call_data.cells = mw->matrix.cells;
  call_data.num_params = *nparams;
  call_data.params = params;
  call_data.event = event;

  XtCallCallbackList((Widget) mw, mw->matrix.select_cell_callback,
		     (XtPointer) & call_data);
}


/* ARGSUSED */
static void
TraverseNextACT(w, event, params, nparams)
     Widget w;
     XEvent *event;
     String *params;
     Cardinal *nparams;
{
  XbaeMatrixWidget mw;

  /*
   * Get Matrix widget and make sure it is a Matrix subclass.
   * w should be the textField widget.
   */
  if (XtIsSubclass(XtParent(w), xbaeMatrixWidgetClass))
    mw = (XbaeMatrixWidget) XtParent(w);
  else
  {
    XtAppWarningMsg(XtWidgetToApplicationContext(w),
		    "traverseNextACT", "badWidget", "XbaeMatrix",
		    "XbaeMatrix: Bad widget passed to TraverseNext action",
		    NULL, 0);
    return;
  }

  /*
   * Set the traversing direction flag.  XmProcessTraversal may traverse
   * to the Clip widget. If it does, then we will see this flag in
   * the Clip focusCallback, TraverseInCB, and we will continue to traverse
   * on out of the mw.  yuck!
   */
  mw->matrix.traversing = XmTRAVERSE_NEXT_TAB_GROUP;
  XmProcessTraversal(TextChild(mw), XmTRAVERSE_NEXT_TAB_GROUP);
  mw->matrix.traversing = NOT_TRAVERSING;
}

/* ARGSUSED */
static void
TraversePrevACT(w, event, params, nparams)
     Widget w;
     XEvent *event;
     String *params;
     Cardinal *nparams;
{
  XbaeMatrixWidget mw;

  /*
   * Get Matrix widget and make sure it is a Matrix subclass.
   * w should be the textField widget.
   */
  if (XtIsSubclass(XtParent(w), xbaeMatrixWidgetClass))
    mw = (XbaeMatrixWidget) XtParent(w);
  else
  {
    XtAppWarningMsg(XtWidgetToApplicationContext(w),
		    "traversePrevACT", "badWidget", "XbaeMatrix",
		    "XbaeMatrix: Bad widget passed to TraversePrev action",
		    NULL, 0);
    return;
  }

  /*
   * Set the traversing direction flag.  XmProcessTraversal may traverse
   * to the Clip widget. If it does, then we will see this flag in
   * the Clip focusCallback, TraverseInCB, and we will continue to traverse
   * on out of the mw.  yuck!
   */
  mw->matrix.traversing = XmTRAVERSE_PREV_TAB_GROUP;
  XmProcessTraversal(TextChild(mw), XmTRAVERSE_PREV_TAB_GROUP);
  mw->matrix.traversing = NOT_TRAVERSING;
}

/*
 * Convert a comma separated list of strings to a NULL terminated array
 * of substrings.  Handles escaped commas (\,) and escaped escaped commas (\\,)
 * - A comma (,) terminates a string
 * - A backslash-comma (\,) does not terminate a string and is copied as
 *   a comma (,)
 * - A backslash-backslash-comma (\\,) does not terminate a string and is
 *   copied as a backslash-comma (\,)
 */
/* ARGSUSED */
static Boolean
CvtStringToStringArray(dpy, args, num_args, from, to, data)
     Display *dpy;
     XrmValuePtr args;
     Cardinal *num_args;
     XrmValuePtr from, to;
     XtPointer *data;
{
  static String *array;
  String start = from->addr;

  if (*num_args != 0)
    XtAppWarningMsg(XtDisplayToApplicationContext(dpy),
		    "cvtStringToStringArray", "wrongParameters",
		    "XbaeMatrix",
		    "String to StringArray conversion needs no extra arguments",
		    NULL, NULL);

  /*
   * User didn't provide enough space
   */
  if (to->addr != NULL && to->size < sizeof(String *))
  {
    to->size = sizeof(String *);
    return False;
  }

  if (start == NULL || *start == '\0')
    array = NULL;
  else
  {
    char *ch, *next, *a;
    int i, sub, len, count;

    /*
     * Count the substrings
     */
    for (ch = start, count = 1; *ch != '\0'; ch++)
    {

      /*
	     * We hit a backslash
	     */
      if (*ch == '\\')
      {
	/*
		 * Take care of backslash-backslash-comma
		 */
	if (*(ch + 1) == '\\' && *(ch + 2) == ',')
	  ch += 2;

	/*
		 * Take care of backslash-comma
		 */
	else if (*(ch + 1) == ',')
	  ch++;
      }
      /*
	     * We hit an unescaped comma
	     */
      else if (*ch == ',')
	count++;
    }

    /*
     * Malloc the array, make it one bigger for a terminating NULL entry
     */
    array = (String *) XtMalloc((count + 1) * sizeof(String));
    array[count] = NULL;

    for (sub = 0; sub < count; sub++)
    {

      /*
       * Skip leading white space
       */
      while (isspace((unsigned char) *start))
	start++;

      /*
	     * Count the number of chars in this substring.
	     * backslash-comma counts as one and does not terminate.
	     * backslash-backslash-comma counts as two and does not terminate.
	     */
      for (ch = start, len = 0; *ch != '\0' && *ch != ','; ch++)
      {
	/*
		 * We hit a backslash
		 */
	if (*ch == '\\')
	{
	  /*
		     * Take care of backslash-backslash-comma
		     */
	  if (*(ch + 1) == '\\' && *(ch + 2) == ',')
	  {
	    len += 2;
	    ch += 2;
	  }
	  /*
		     * Take care of backslash-comma
		     */
	  else if (*(ch + 1) == ',')
	  {
	    len++;
	    ch++;
	  }
	  else
	    len++;
	}
	else
	  len++;
      }

      /*
	     * Save the beginning of the next substring
	     */
      next = ch + 1;

      /*
       * Back up over trailing white space if we moved at all
       */
      if (ch != start)
	while (isspace((unsigned char) *(--ch)))
	  len--;

      /*
	     * Malloc a String of the correct size
	     */
      array[sub] = (String) XtMalloc(len + 1);

      /*
	     * Copy the substring into our new string.
	     * backslash-comma gets copied as comma.
	     * backslash-backslash-comma gets copied as backslash-comma.
	     */
      for (i = 0, ch = start, a = array[sub];
	   i < len;
	   i++, ch++)
      {

	/*
		 * We hit a backslash
		 */
	if (*ch == '\\')
	{
	  /*
		     * Take care of backslash-backslash-comma
		     */
	  if (*(ch + 1) == '\\' && *(ch + 2) == ',')
	  {
	    *(a++) = '\\';
	    *(a++) = ',';
	    i++;
	    ch += 2;
	  }
	  /*
		     * Take care of backslash-comma
		     */
	  else if (*(ch + 1) == ',')
	  {
	    *(a++) = ',';
	    ch++;
	  }
	  else
	    *(a++) = *ch;
	}
	else
	  *(a++) = *ch;
      }
      *a = '\0';

      /*
	     * Point to the beginning of the next string.
	     */
      start = next;
    }
  }

  if (to->addr == NULL)
    to->addr = (caddr_t) & array;
  else
    *(String **) to->addr = array;
  to->size = sizeof(String *);

  return True;
}

/*
 * Free the string array allocated by the String to StringArray converter
 */
/* ARGSUSED */
static void
StringArrayDestructor(app, to, converter_data, args, num_args)
     XtAppContext app;
     XrmValuePtr to;
     XtPointer converter_data;
     XrmValuePtr args;
     Cardinal *num_args;
{
  String *array = *(String **) to->addr;
  String *entry;

  if (array == NULL)
    return;

  for (entry = array; *entry != NULL; entry++)
    XtFree((XtPointer) * entry);

  XtFree((XtPointer) array);
}

/*
 * Convert a comma separated list of short ints to array of widths.
 * The array is terminated with BAD_WIDTH.
 */
/* ARGSUSED */
static Boolean
CvtStringToWidthArray(dpy, args, num_args, from, to, data)
     Display *dpy;
     XrmValuePtr args;
     Cardinal *num_args;
     XrmValuePtr from, to;
     XtPointer *data;
{
  static short *array;
  String start = from->addr;
  char *ch;
  int i, count;

  if (*num_args != 0)
    XtAppWarningMsg(XtDisplayToApplicationContext(dpy),
		    "cvtStringToWidthArray", "wrongParameters",
		    "XbaeMatrix",
		    "String to WidthArray conversion needs no extra arguments",
		    NULL, NULL);

  /*
   * User didn't provide enough space
   */
  if (to->addr != NULL && to->size < sizeof(short *))
  {
    to->size = sizeof(short *);

    return False;
  }

  if (start == NULL || *start == '\0')
    array = NULL;

  else
  {

    /*
     * Count the comma separated shorts
     */
    for (ch = start, count = 1; *ch != '\0'; ch++)
      if (*ch == ',')
	count++;

    /*
     * Malloc the array
     */
    array = (short *) XtMalloc((count + 1) * sizeof(short));

    array[count] = BAD_WIDTH;

    for (i = 0; i < count; i++)
    {

      array[i] = (short) atoi(start);

      /*
	     * Find the comma at the end of this short
	     */
      /* EMPTY */
      for (; *start != '\0' && *start != ','; start++);
      start++;
    }
  }

  if (to->addr == NULL)
    to->addr = (caddr_t) & array;
  else
    *(short **) to->addr = array;
  to->size = sizeof(short *);

  return True;
}

/*
 * Free the width array allocated by the String to WidthArray converter
 */
/* ARGSUSED */
static void
WidthArrayDestructor(app, to, converter_data, args, num_args)
     XtAppContext app;
     XrmValuePtr to;
     XtPointer converter_data;
     XrmValuePtr args;
     Cardinal *num_args;
{
  short *array = *(short **) to->addr;

  XtFree((XtPointer) array);
}

/*
 * Convert a comma separated list of ints to array of max lengths.
 * The array is terminated with BAD_MAXLENGTH.
 */
/* ARGSUSED */
static Boolean
CvtStringToMaxLengthArray(dpy, args, num_args, from, to, data)
     Display *dpy;
     XrmValuePtr args;
     Cardinal *num_args;
     XrmValuePtr from, to;
     XtPointer *data;
{
  static int *array;
  String start = from->addr;
  char *ch;
  int i, count;

  if (*num_args != 0)
    XtAppWarningMsg(XtDisplayToApplicationContext(dpy),
		    "cvtStringToMaxLengthArray", "wrongParameters",
		    "XbaeMatrix",
		    "String to MaxLengthArray conversion needs no extra arguments",
		    NULL, NULL);

  /*
   * User didn't provide enough space
   */
  if (to->addr != NULL && to->size < sizeof(int *))
  {
    to->size = sizeof(int *);

    return False;
  }

  if (start == NULL || *start == '\0')
    array = NULL;

  else
  {

    /*
     * Count the comma separated ints
     */
    for (ch = start, count = 1; *ch != '\0'; ch++)
      if (*ch == ',')
	count++;

    /*
     * Malloc the array
     */
    array = (int *) XtMalloc((count + 1) * sizeof(int));

    array[count] = BAD_MAXLENGTH;

    for (i = 0; i < count; i++)
    {

      array[i] = (int) atoi(start);

      /*
	     * Find the comma at the end of this int
	     */
      /* EMPTY */
      for (; *start != '\0' && *start != ','; start++);
      start++;
    }
  }

  if (to->addr == NULL)
    to->addr = (caddr_t) & array;
  else
    *(int **) to->addr = array;
  to->size = sizeof(int *);

  return True;
}

/*
 * Free the max length array allocated by the String to
 * MaxLengthArray converter
 */
/* ARGSUSED */
static void
MaxLengthArrayDestructor(app, to, converter_data, args, num_args)
     XtAppContext app;
     XrmValuePtr to;
     XtPointer converter_data;
     XrmValuePtr args;
     Cardinal *num_args;
{
  int *array = *(int **) to->addr;

  XtFree((XtPointer) array);
}

/*
 * Compare two strings up to length chars, and return True if they are equal.
 * Handles Xm prefix too. The string test must be lower case.
 * Used by StringToAlignmentArray converter.
 */
static Boolean
StringsAreEqual(in, test, length)
     String in;
     String test;
     int length;
{
  int i;

  if ((in[0] == 'X' || in[0] == 'x') &&
      (in[1] == 'M' || in[1] == 'm'))
    in += 2;

  for (i = 0; i < length; i++)
  {
    char c = *in;

    if (isupper(c))
      c = tolower(c);

    if (c != test[i])
      return False;

    in++;
  }

  /*
   * String in may have trailing garbage, but as long as the first
   * length chars matched, we return True
   */
  return True;
}

/*
 * Convert a comma separated list of alignments to array of alignments
 * (unsigned chars).  The array is terminated by BAD_ALIGNMENT.
 */
/* ARGSUSED */
static Boolean
CvtStringToAlignmentArray(dpy, args, num_args, from, to, data)
     Display *dpy;
     XrmValuePtr args;
     Cardinal *num_args;
     XrmValuePtr from, to;
     XtPointer *data;
{
  static unsigned char *array;
  String start = from->addr;
  char *ch;
  int i, count;

  if (*num_args != 0)
    XtAppWarningMsg(XtDisplayToApplicationContext(dpy),
		    "cvtStringToAlignmentArray", "wrongParameters",
		    "XbaeMatrix",
		    "String to AlignmentArray conversion needs no extra arguments",
		    NULL, NULL);

  /*
   * User didn't provide enough space
   */
  if (to->addr != NULL && to->size < sizeof(unsigned char *))
  {
    to->size = sizeof(unsigned char *);

    return False;
  }

  if (start == NULL || *start == '\0')
    array = NULL;

  else
  {

    /*
     * Count the comma separated alignments
     */
    for (ch = start, count = 1; *ch != '\0'; ch++)
      if (*ch == ',')
	count++;

    /*
     * Malloc the array
     */
    array = (unsigned char *) XtMalloc((count + 1) * sizeof(unsigned char));

    array[count] = BAD_ALIGNMENT;

    /*
     * Compare each substring to the alignment strings.
     * If we find a bad one, display a warning and fail.
     * We should be able to use XtCallConverter on _XmCvtStringToAlignment,
     * but that function is static so we have to duplicate its
     * functionality.
     */
    for (i = 0; i < count; i++)
    {
      /*
       * Skip leading white space
       */
      while (isspace(*start))
	start++;

      if (StringsAreEqual(start, "alignment_beginning", 19))
	array[i] = XmALIGNMENT_BEGINNING;
      else if (StringsAreEqual(start, "alignment_center", 16))
	array[i] = XmALIGNMENT_CENTER;
      else if (StringsAreEqual(start, "alignment_end", 13))
	array[i] = XmALIGNMENT_END;
      else
      {
	XtDisplayStringConversionWarning(dpy, from->addr,
					 XmRAlignmentArray);
	XtFree((void*)array);
	return False;
      }

      /*
	     * Find the comma at the end of this alignment
	     */
      /* EMPTY */
      for (; *start != '\0' && *start != ','; start++);
      start++;
    }
  }

  if (to->addr == NULL)
    to->addr = (caddr_t) & array;
  else
    *(unsigned char **) to->addr = array;
  to->size = sizeof(unsigned char *);

  return True;
}

/*
 * Free the alignment array allocated by the String to AlignmentArray converter
 */
/* ARGSUSED */
static void
AlignmentArrayDestructor(app, to, converter_data, args, num_args)
     XtAppContext app;
     XrmValuePtr to;
     XtPointer converter_data;
     XrmValuePtr args;
     Cardinal *num_args;
{
  unsigned char *array = *(unsigned char **) to->addr;

  XtFree((XtPointer) array);
}


/*
 * ScrollMgr implementation.
 * When we scroll using XCopyArea, occluding windows will cause GraphicsExpose
 * events to be generated, if there are no occluding windows then NoExpose
 * events will be generated. The removal of occluding windows will cause Expose
 * events.  If a number of scrolls (XCopyAreas) occur in quick succession,
 * the events will contain obsolete x/y information since our internal
 * coordinates have been scrolled to a new location.  The ScrollMgr
 * keeps track of scrolls and offsets required to relocate the events to the
 * current coordinate system.
 *
 * The Matrix widget has two ScrollMgrs, one for the Matrixs window
 * and one for the Clip widgets window.
 *
 * Each widgets compress_exposures field should be XtExposeCompressSeries
 * or XtExposeNoCompress.
 *
 * The idea behind this code is based on the PanHandler posted by Chuck Ocheret
 * (chuck@fid.morgan.com)
 */

/*
 * Create and initialize a ScrollMgr
 */
static SmScrollMgr
SmCreateScrollMgr()
{
  SmScrollMgr scrollMgr = XtNew(SmScrollMgrRec);

  scrollMgr->offset_x = 0;
  scrollMgr->offset_y = 0;
  scrollMgr->scroll_count = 0;
  scrollMgr->scroll_queue = NULL;
  scrollMgr->scrolling = False;

  return scrollMgr;
}

/*
 * Destroy a ScrollMgr, including any queued scrolls
 */
static void
SmDestroyScrollMgr(scrollMgr)
     SmScrollMgr scrollMgr;
{
  if (scrollMgr->scroll_queue)
  {
    SmScrollNode node = scrollMgr->scroll_queue->next;

    while (node != scrollMgr->scroll_queue)
    {
      SmScrollNode d = node;

      node = node->next;
      XtFree((XtPointer) d);
    }
    XtFree((XtPointer) node);
  }

  XtFree((XtPointer) scrollMgr);
}

/*
 * Record a new scroll request in the ScrollMgr
 */
static void
SmAddScroll(scrollMgr, delta_x, delta_y)
     SmScrollMgr scrollMgr;
     int delta_x;
     int delta_y;
{
  SmScrollNode node = XtNew(SmScrollNodeRec);

  node->x = delta_x;
  node->y = delta_y;

  scrollMgr->offset_x += delta_x;
  scrollMgr->offset_y += delta_y;
  scrollMgr->scroll_count++;

  /*
   * Insert the node at the end of the queue
   */
  if (!scrollMgr->scroll_queue)
  {
    scrollMgr->scroll_queue = node;
    node->next = node;
    node->prev = node;
  }
  else
  {
    SmScrollNode last = scrollMgr->scroll_queue->prev;

    last->next = node;
    node->next = scrollMgr->scroll_queue;
    node->prev = last;
    scrollMgr->scroll_queue->prev = node;
  }
}

/*
 * Remove a scroll from the ScrollMgr queue
 */
static void
SmRemoveScroll(scrollMgr)
     SmScrollMgr scrollMgr;
{
  if (scrollMgr->scroll_count)
  {
    SmScrollNode node = scrollMgr->scroll_queue;

    scrollMgr->offset_x -= node->x;
    scrollMgr->offset_y -= node->y;

    /*
     * Remove node from head of queue
     */
    if (node->next == node)
      scrollMgr->scroll_queue = NULL;
    else
    {
      scrollMgr->scroll_queue = node->next;
      node->next->prev = node->prev;
      node->prev->next = node->next;
    }
    XtFree((XtPointer) node);

    scrollMgr->scroll_count--;
  }
}

/*
 * Handle an expose event
 */
static void
SmScrollEvent(scrollMgr, event)
     SmScrollMgr scrollMgr;
     XEvent *event;
{
  switch (event->type)
	{

	  case Expose:

	    /*
             * Normal Expose event, translate it into our scrolled
             * coordinate system.
             */
	    event->xexpose.x += scrollMgr->offset_x;
	    event->xexpose.y += scrollMgr->offset_y;
	    break;

	  case GraphicsExpose:

	    /*
             * If we are not scrolling, then this must be the first
             * GraphicsExpose event.  Remove the corresponding scroll from the
             * queue, and if we have more GraphicsExposes to come, set scrolling
             * to True.
             */
	    if (scrollMgr->scrolling == False)
	    {
	      SmRemoveScroll(scrollMgr);
	      if (event->xgraphicsexpose.count != 0)
		scrollMgr->scrolling = True;
	    }

	    /*
             * This is the last GraphicsExpose so set scrolling to False.
             */
	    else if (event->xgraphicsexpose.count == 0)
	      scrollMgr->scrolling = False;

	    /*
             * Translate the event into our scrolled coordinate system.
             */
	    event->xgraphicsexpose.x += scrollMgr->offset_x;
	    event->xgraphicsexpose.y += scrollMgr->offset_y;
	    break;

	  case NoExpose:

	    /*
             * A NoExpose event means we won't be getting any GraphicsExpose
             * events, so remove the scroll from the queue and set scrolling
             * to False.
             */
	    SmRemoveScroll(scrollMgr);
	    scrollMgr->scrolling = False;
	    break;

	  default:
	    break;
	}
}


/*
 * Help the user know what row & column he is in given an x & y (via an event).
 * Return True on success, False on failure.
 */
int
XbaeMatrixGetEventRowColumn(Widget w, XEvent * event, int *row, int *column)
{
  XbaeMatrixWidget mw;
  int x, y;
  CellType cell;

  if (!XtIsSubclass(w, xbaeMatrixWidgetClass))
    return (False);

  /* Convert the event to the correct XY for the matrix widget. */
  mw = (XbaeMatrixWidget) w;
  if (!EventToXY(mw, event, &x, &y, &cell))
    return (False);

  /* Convert the point to a row,column. If it does not pick a valid cell, then return. */
  if (!XYToRowCol(mw, &x, &y, row, column, cell))
    return (False);

  return (True);
}


static void
LosingFocusCB(Widget tf, XtPointer matrix, XtPointer call)
{
  XbaeMatrixCommitEdit((Widget) matrix, False);
}


/*
 * Help the programmer to know what row & column we are currently at.
 * Set the row & column to -1 if bad widget.  Maybe the program will core. :)
 */
void
XbaeMatrixGetCurrentCell(Widget w, int *row, int *column)
{
  XbaeMatrixWidget mw;

  if (!XtIsSubclass(w, xbaeMatrixWidgetClass))
    *row = *column = -1;
  else
  {
    mw = (XbaeMatrixWidget)w;
    *row = mw->matrix.current_row;
    *column = mw->matrix.current_column;
  }
}


/*
 * Allow the programmer to call the Expose method directly if he feels
 * that it is really needed.
 */
void
XbaeMatrixRefresh(Widget w)
{
  XbaeMatrixWidget mw;

  if (XtIsSubclass(w, xbaeMatrixWidgetClass) && XtIsRealized((Widget)mw))
  {
    /*
     * Generate expose events on Matrix and Clip to force the redrawing.
     */
    XClearArea(XtDisplay(mw), XtWindow(mw),
	       0, 0,
	       0 /*Full Width*/ , 0 /*Full Height*/ ,
	       True);
    XbaeClipRedraw(ClipChild(mw));
  }
}
