/* Copyright (c) 2017, Computer History Museum 
All rights reserved. 
Redistribution and use in source and binary forms, with or without modification, are permitted (subject to 
the limitations in the disclaimer below) provided that the following conditions are met: 
 * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following 
   disclaimer in the documentation and/or other materials provided with the distribution. 
 * Neither the name of Computer History Museum nor the names of its contributors may be used to endorse or promote products 
   derived from this software without specific prior written permission. 
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE 
COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 
DAMAGE. */

#include "table.h"
#define FILE_NUM 102
/* Copyright (c) 2000 by QUALCOMM Incorporated */

#pragma segment Table

static Boolean EditingTable(MyWindowPtr win);
static OSErr InsertTable(MyWindowPtr win,TIHandle table);
static void MakeTable(MyWindowPtr win);
static void TableDisplay(PETEHandle pte,TIHandle table,long offset);
static void SizeTable(PETEHandle pte,TIHandle tableInfo,short maxWidth);
static short **GetColWidths(PETEHandle pte,TIHandle tableInfo,short *sumWidths,short tableWidth,Boolean tableWidthSpecified);
static short **GetRowHeights(PETEHandle pte,TIHandle tableInfo,short *sumHeights,short **hColWidths);
static void SetCellBounds(PETEHandle pte,TableHandle table,short **hColWidths,short **hRowHeights);
static short GetCellWidth(PETEHandle pte,TIHandle tableInfo,short cellIdx);
static short GetCellHeight(PETEHandle pte,TIHandle tableInfo,short cellIdx,short colWidth);
static OSErr SetCellText(PETEHandle pte,TIHandle tableInfo,short cellIdx,short cellWidth);
static OSErr TableClone(TableHandle *table);
static void CloneHandle(Handle *pHand,OSErr *pErr);
RgnHandle SavePeteWinUpdateRgn(PETEHandle pte);
void RestorePeteWinUpdateRgn(PETEHandle pte,RgnHandle rgn);
static OSErr CreateSparePETE(MyWindowPtr win,PETEHandle *pte);
static OSErr ResetCellStyle(TIHandle tableInfo, short cellIdx);

/************************************************************************
 * MakeTableGraphic - make a table, insert as graphic
 ************************************************************************/
OSErr MakeTableGraphic(PETEHandle pte,long offset,TableHandle table,PETEStyleEntryPtr pse)
{
	TIHandle hTableInfo;
	OSErr err = noErr;
	PETEHandle	pteSpare;
	MyWindowPtr	win = (*PeteExtra(pte))->win;
	
	if (!(hTableInfo = NuHandleClear(sizeof(TableInfo))))
		return MemError();

	(*hTableInfo)->pgi.itemProc = TableGraphic;
	(*hTableInfo)->pgi.wantsEvents = true;
	(*hTableInfo)->table = table;
	(*hTableInfo)->offset = offset;

	// make a PETEHandle to measure, draw cells
	if (err = CreateSparePETE(win,&pteSpare)) return err;
	(*hTableInfo)->pteSpare = pteSpare;

	SizeTable(pte,hTableInfo,RectWi(win->contR));

	Zero(*pse);
	pse->psGraphic = 1;
	pse->psStyle.graphicStyle.tsFont = kPETEDefaultFont;
	pse->psStyle.graphicStyle.tsSize = kPETERelativeSizeBase;
	pse->psStyle.graphicStyle.graphicInfo = (PETEGraphicInfoHandle)hTableInfo;

	return(err);
}

/**********************************************************************
 * TableGraphic - editor callback to handle tables
 **********************************************************************/
pascal OSErr TableGraphic(PETEHandle pte,TIHandle table,long offset,PETEGraphicMessage message,void *data)
{
	OSErr err = noErr;
	short oldResFile = CurResFile();
	EventRecord	*event;
	MyWindowPtr	win;
	Boolean	keyEvent;
	Rect	r;
	short	maxWidth;
	
	PushGWorld();
	
	(*table)->offset = offset;
	switch (message)
	{
		case peGraphicDraw:	/* data is (Point *) with penLoc at baseline left as a point */
			TableDisplay(pte,table,offset);
			break;

		case peGraphicClone:	/* data is (PETEGraphicInfoHandle *) into which to put duplicate */
			//	When user does copy or drag and drop, make copy of table
			if (!(err = MyHandToHand(&table)))
			{
				TableHandle tempTable;
				
				// make clone
				tempTable = (*table)->table;
				err = TableClone(&tempTable);	
				if (err) ZapHandle(table);
				else
				{
					(*table)->table = tempTable;
					(*table)->pteSpare = nil;
				}
			}
			*(TIHandle*)data = table;
			break;
		
		case peGraphicInsert:		/* data is nil. Graphic has been inserted into a new document. */
			err = InsertTable((*PeteExtra(pte))->win,table);
			break;
			
		case peGraphicTest:	/* data is (Point *) from top left; return errOffsetIsOutsideOfView if not hit */
			//	Can cancel hit by returning errOffsetIsOutsideOfView
			break;
			
		case peGraphicHit:	/* data is (EventRecord *) if mouse down, nil if time to turn off */
			break;
			
		case peGraphicRemove:	/* data is nil; just dispose of table info */
			TableDispose((*table)->table);
			if((*table)->pteSpare)
				PeteDispose((*PeteExtra((*table)->pteSpare))->win,(*table)->pteSpare);
			ZapHandle(table);
			break;
			
		case peGraphicResize:	/* data is a (short *) of the max width */
			maxWidth = *(short*)data;
			//	don't bother calculating size if the maxWidth hasn't changed
			if (maxWidth != (*table)->maxWidth)
			{
				(*table)->maxWidth = maxWidth;
				SizeTable(pte,table,maxWidth);
			}
			break;
		
		case peGraphicRegion:	/* data is a RgnHandle which is to be changed to the graphic's region */
			//	Used mostly for changing the cursor
			PeteGraphicRect(&r,pte,(PETEGraphicInfoHandle)table,offset);
			RectRgn((RgnHandle)data,&r);
			break;
		
		case peGraphicEvent:	/* data is (EventRecord *) */
			event = (EventRecord *)data;
			win = (*PeteExtra(pte))->win;
			keyEvent = event->what==keyDown || event->what==autoKey;
			break;
	}
	
	PopGWorld();
	UseResFile(oldResFile);

	return(err);
}


/**********************************************************************
 * TableMenuChoice - handle a selection from the table submenu
 **********************************************************************/
void TableMenuChoice(MyWindowPtr win, short item)
{
	//Styled Text - don't allow user to compose or send styled text
	if (!HasFeature (featureExtendedStyles))
		return;
		
	switch (item)
	{
		case tblInsert:
			if (EditingTable(win))
			{
				//	Insert columns or rows
			}
			else
			{
				//	Insert or make table
				if (PeteLen(win->pte))
					MakeTable(win);
				else
					InsertTable(win,nil);
			}
			break;
			
		case tblDelete:
			break;
			
		case tblGridLine:
			break;
			
		case tblBorder:
			break;
			
		case tblRowHt:
			break;
			
		case tblColWd:
			break;
			
	}
}

/**********************************************************************
 * CheckTableItems - check items in Table submenu
 **********************************************************************/
void CheckTableItems(MyWindowPtr win,Boolean allMenu)
{
	Boolean	inTable;
	
	//Styled Text - don't allow user to compose or send styled text
	if (!HasFeature (featureExtendedStyles))
		return;

	inTable = EditingTable(win);
#if 0
	if (inTable)
	{
		MenuHandle mh = GetMHandle(TEXT_TABLE_HIER_MENU);
		TableHandle	hTable = win->table;
	
		SetItemMark(mh,tblGridLine,(*hTable)->gridline ? checkMark : noMark);
		SetItemMark(mh,tblBorder,(*hTable)->borders ? checkMark : noMark);
	}
#endif
}

/**********************************************************************
 * EnableTableMenu - enable/disable/rename items in Table submenu
 **********************************************************************/
void EnableTableMenu(MyWindowPtr win,Boolean all)
{
	Boolean	inTable;	
#if 0
	MenuHandle mh;
#endif
	
	//Styled Text - don't allow user to compose or send styled text
	if (!HasFeature (featureExtendedStyles))
		return;

	inTable = EditingTable(win);	
#if 0
	mh = GetMHandle(TEXT_TABLE_HIER_MENU);

	if (mh)
	{
		EnableIf(mh,tblInsert,GetWindowKind(GetMyWindowWindowPtr(win))==COMP_WIN);
		EnableIf(mh,tblDelete,inTable);
		EnableIf(mh,tblGridLine,inTable);
		EnableIf(mh,tblBorder,inTable);
		EnableIf(mh,tblRowHt,inTable);
		EnableIf(mh,tblColWd,inTable);
	}
#endif
}

/**********************************************************************
 * EditingTable - are we editing a table?
 **********************************************************************/
static Boolean EditingTable(MyWindowPtr win)
{
	return win && IsMyWindow(GetMyWindowWindowPtr(win)) && win->table && (*win->table)->pteEdit && (*win->table)->pteEdit == win->pte; 
}

/**********************************************************************
 * MakeTable - make a table from existing text
 **********************************************************************/
static void MakeTable(MyWindowPtr win)
{

}

/**********************************************************************
 * InsertTable - insert a new table
 **********************************************************************/
static OSErr InsertTable(MyWindowPtr win,TIHandle table)
{
	PETEHandle pteSpare;
	OSErr err = noErr;
	
	if (!(*table)->pteSpare && !(err = CreateSparePETE(win,&pteSpare)))
		(*table)->pteSpare = pteSpare;
	return err;
}

/**********************************************************************
 * InsertTable - insert a new table
 **********************************************************************/
static void TableDisplay(PETEHandle pte,TIHandle tableInfo,long offset)
{
	Rect	rTable;
	TableHandle	table = (*tableInfo)->table;
	TabCellHandle	hCells = (*table)->cellData;
	PETEHandle	pteSpare = (*tableInfo)->pteSpare;
	short	border,cellIdx;
	RgnHandle	clipRgn,saveUpdate = SavePeteWinUpdateRgn(pte);
	
	PeteGraphicRect(&rTable,pte,(PETEGraphicInfoHandle)tableInfo,offset);
	if (border = (*table)->border)
	{
		// draw border
		SetForeGrey(0x8000);
		PenSize(border,border);
		FrameRect(&rTable);
		PenNormal();
		SetForeGrey(0);
	}
	
	// draw each cell if in clip region
	if(clipRgn=NewRgn()) GetClip(clipRgn);
	for(cellIdx=0;cellIdx<(*table)->cells;cellIdx++)
	{
		Rect	rCell = (*hCells)[cellIdx].bounds;
		
		OffsetRect(&rCell,rTable.left,rTable.top);
		if (RectInRgn(&rCell,clipRgn))
		{
			short	align,top,left;
			PETEDocInfo info;
			
			ClipRect(&rCell);
			if (!SetCellText(pte,tableInfo,cellIdx,RectWi(rCell)))
			{
				top = rCell.top;
				left = rCell.left;
				if ((align = (*hCells)[cellIdx].align.vAlign) != htmlTopAlign)
				if (!PETEGetDocInfo(PETE,pteSpare,&info))
				{
					//	vertical alignment
					switch (align)
					{
						case htmlBottomAlign:	top = rCell.bottom-info.docHeight; break;
						case htmlMiddleAlign:	top = (rCell.top+rCell.bottom-info.docHeight)/2; break;
					}
				}
				PETESizeDoc(PETE,pteSpare,RectWi(rCell),RectHi(rCell));
				PETEMoveDoc(PETE,pteSpare,left,top);
				PETEDraw(PETE,pteSpare);
				ResetCellStyle(tableInfo, cellIdx);
			}
			
			if (border)
			{
				short	inset = -(*table)->cellPadding-1;
				
				SetForeGrey(0x8000);
				InsetRect(&rCell,inset,inset);
				ClipRect(&rCell);
				FrameRect(&rCell);
				SetForeGrey(0);
			}
		}
	}
	if(clipRgn) SetClip(clipRgn);

	RestorePeteWinUpdateRgn(pte,saveUpdate);
}

/**********************************************************************
 * TableColIndex - column to index
 **********************************************************************/
short TableColIndex(TabColHandle columnData, short c)
{
	if(columnData)
	{
		short i;
		
		i = GetHandleSize(columnData) / sizeof(TabColData);
		while(--i >= 0)
		{
			if(((*columnData)[i].column >= c) && ((*columnData)[i].column + (*columnData)[i].span < c))
				return i;
		}
	}
	return -1;
}

/**********************************************************************
 * TableColGroupIndex - column group to index
 **********************************************************************/
short TableColGroupIndex(TabColHandle columnGroupData, short c)
{
	if(columnGroupData)
	{
		short i;
		
		i = GetHandleSize(columnGroupData) / sizeof(TabColData);
		while(--i >= 0)
		{
			if((((*columnGroupData)[i].span == 0) && ((*columnGroupData)[i].column < c)) ||
			   (((*columnGroupData)[i].column >= c) && ((*columnGroupData)[i].column + (*columnGroupData)[i].span < c)))
				return i;
		}
	}
	return -1;
}

/**********************************************************************
 * TableRowIndex - table row to index
 **********************************************************************/
short TableRowIndex(TabRowHandle rowData, short r)
{
	if(rowData)
	{
		short i;
		
		i = GetHandleSize(rowData) / sizeof(TabRowData);
		while(--i >= 0)
		{
			if((*rowData)[i].row == r)
				return i;
		}
	}
	return -1;
}

/**********************************************************************
 * TableRowGroupIndex - table group to index
 **********************************************************************/
short TableRowGroupIndex(TabRowGroupHandle rowGroupData, short r)
{
	if(rowGroupData)
	{
		short i;
		
		i = GetHandleSize(rowGroupData) / sizeof(TabRowGroupData);
		while(--i >= 0)
		{
			if((*rowGroupData)[i].row < r)
				return i;
		}
	}
	return -1;
}

/**********************************************************************
 * GetCellHAlign - get horizontal alignment for cell
 **********************************************************************/
void GetCellHAlign(TableHandle table, short cell, short *hAlign, UniChar *alignChar, short *charOff)
{
	TabAlignData align;
	short r, c, i;
	
	r = (*(**table).cellData)[cell].row;
	c = (*(**table).cellData)[cell].column;
	align = (*(**table).cellData)[cell].align;
	if(!align.hAlign)
	{
		i = TableColIndex((**table).columnData, c);
		if(i >= 0)
		{
			align = (*(**table).columnData)[i].align;
		}
		if(!align.hAlign)
		{
			i = TableColIndex((**table).columnGroupData, c);
			if(i >= 0)
			{
				align = (*(**table).columnGroupData)[i].align;
			}
			if(!align.hAlign)
			{
				i = TableRowIndex((**table).rowData, r);
				if(i >= 0)
				{
					align = (*(**table).rowData)[i].align;
				}
				if(!align.hAlign)
				{
					i = TableRowGroupIndex((**table).rowGroupData, r);
					if(i >= 0)
					{
						align = (*(**table).rowGroupData)[i].align;
					}
					if(!align.hAlign)
					{
						align.hAlign = (**table).align;
					}
				}
			}
		}
	}
	if(hAlign)
		*hAlign = align.hAlign;
	if(alignChar)
		*alignChar = align.alignChar;
	if(charOff)
		*charOff = align.charOff;
}

/**********************************************************************
 * GetCellVAlign - get vertical alignment for cell
 **********************************************************************/
void GetCellVAlign(TableHandle table, short cell, short *vAlign)
{
	short r, c, i, v;
	
	r = (*(**table).cellData)[cell].row;
	c = (*(**table).cellData)[cell].column;
	v = (*(**table).cellData)[cell].align.vAlign;
	if(!v)
	{
		i = TableRowIndex((**table).rowData, r);
		if(i >= 0)
		{
			v = (*(**table).rowData)[i].align.vAlign;
		}
		if(!v)
		{
			i = TableRowGroupIndex((**table).rowGroupData, r);
			if(i >= 0)
			{
				v = (*(**table).rowGroupData)[i].align.vAlign;
			}
			if(!v)
			{
				i = TableColIndex((**table).columnData, c);
				if(i >= 0)
				{
					v = (*(**table).columnData)[i].align.vAlign;
				}
				if(!v)
				{
					i = TableColIndex((**table).columnGroupData, c);
					if(i >= 0)
					{
						v = (*(**table).columnGroupData)[i].align.vAlign;
					}
				}
			}
		}
	}
	if(vAlign)
		*vAlign = v;
}

/**********************************************************************
 * GetCellDirection - get cell direction
 **********************************************************************/
void GetCellDirection(TableHandle table, short cell, short *direction)
{
	short r, c, i, d;
	
	r = (*(**table).cellData)[cell].row;
	c = (*(**table).cellData)[cell].column;
	d = (*(**table).cellData)[cell].align.direction;
	if(d == kHilite)
	{
		i = TableColIndex((**table).columnData, c);
		if(i >= 0)
		{
			d = (*(**table).columnData)[i].align.direction;
		}
		if(d == kHilite)
		{
			i = TableColIndex((**table).columnGroupData, c);
			if(i >= 0)
			{
				d = (*(**table).columnGroupData)[i].align.direction;
			}
			if(d == kHilite)
			{
				i = TableRowIndex((**table).rowData, r);
				if(i >= 0)
				{
					d = (*(**table).rowData)[i].align.direction;
				}
				if(d == kHilite)
				{
					i = TableRowGroupIndex((**table).rowGroupData, r);
					if(i >= 0)
					{
						d = (*(**table).rowGroupData)[i].align.direction;
					}
					if(d == kHilite)
						d = (**table).direction;
				}
			}
		}
	}
	if(direction)
		*direction = d;
}

/**********************************************************************
 * TableDispose - dispose of table data
 **********************************************************************/
void TableDispose(TableHandle table)
{
	TabCellHandle	hCells = (*table)->cellData;
	short		cellIdx;
	
	if(hCells)
		for(cellIdx=0;cellIdx<(*table)->cells;cellIdx++)
		{
			DisposeHandle((*hCells)[cellIdx].abbr);
			PETEDisposeStyleScrap(PETE, (*hCells)[cellIdx].styleInfo);
			PETEDisposeParaScrap(PETE, (*hCells)[cellIdx].paraInfo);
		}
	DisposeHandle((*table)->rowGroupData);
	DisposeHandle((*table)->rowData);
	DisposeHandle((*table)->columnData);
	DisposeHandle((*table)->columnGroupData);
	DisposeHandle((*table)->cellData);
	DisposeHandle(table);
}

/**********************************************************************
 * TableClone - make a copy of table data
 **********************************************************************/
static OSErr TableClone(TableHandle *table)
{
	TableData tableData;
	OSErr	err;
	long cellIdx;
	
	if((err = MyHandToHand(table)) != noErr)
		return err;
	
	tableData = ***table;
	(**table)->rowGroupData = nil;
	(**table)->rowData = nil;
	(**table)->columnData = nil;
	(**table)->columnGroupData = nil;
	(**table)->cellData = nil;
	if(tableData.cellData && !(err = MyHandToHand(&tableData.cellData)))
	{
		(**table)->cellData = tableData.cellData;
		for(cellIdx=0;cellIdx<(**table)->cells;cellIdx++)
		{
			Handle	abbr;
			PETEParaScrapHandle paraInfo= nil;
			PETEStyleListHandle styleInfo = nil;
			
			abbr = (*tableData.cellData)[cellIdx].abbr;
			if(!abbr || !(err = MyHandToHand(&abbr)))
			{
				paraInfo = (*tableData.cellData)[cellIdx].paraInfo;
				if(!paraInfo || !(err = MyHandToHand(&paraInfo)))
				{
					styleInfo = (*tableData.cellData)[cellIdx].styleInfo;
					if(styleInfo && (err = PETEDuplicateStyleScrap(PETE, &styleInfo)) != noErr)
						styleInfo = nil;
				}
				else
					paraInfo = nil;
			}
			else
				abbr = nil;

			(*tableData.cellData)[cellIdx].abbr = abbr;
			(*tableData.cellData)[cellIdx].paraInfo = paraInfo;
			(*tableData.cellData)[cellIdx].styleInfo = styleInfo;
			
			if(err)
			{
				while(cellIdx >= 0)
				{
					DisposeHandle((*tableData.cellData)[cellIdx].abbr);
					PETEDisposeStyleScrap(PETE, (*tableData.cellData)[cellIdx].styleInfo);
					PETEDisposeParaScrap(PETE, (*tableData.cellData)[cellIdx].paraInfo);
					--cellIdx;
				}
				break;
			}
		}
	}
	
	if(!err)
	{
		if(!tableData.rowGroupData || !(err = MyHandToHand(&tableData.rowGroupData)))
		{
			(**table)->rowGroupData = tableData.rowGroupData;
			if(!tableData.rowData || !(err = MyHandToHand(&tableData.rowData)))
			{
				(**table)->rowData = tableData.rowData;
				if(!tableData.columnData || !(err = MyHandToHand(&tableData.columnData)))
				{
					(**table)->columnData = tableData.columnData;
					if(!tableData.columnGroupData || !(err = MyHandToHand(&tableData.columnGroupData)))
					{
						(**table)->columnGroupData = tableData.columnGroupData;
					}
				}
			}
		}
	}
	
	if(err)
		TableDispose(*table);
	
	return err;
}

/**********************************************************************
 * DuplicateHandle - duplicate a handle
 **********************************************************************/
static void CloneHandle(Handle *pHand,OSErr *pErr)
{
	if (!*pHand || *pErr) return;
	
	*pErr = HandToHand(pHand);
}

/**********************************************************************
 * GetColWidths - get column widths
 **********************************************************************/
static short **GetColWidths(PETEHandle pte,TIHandle tableInfo,short *sumWidths,short tableWidth,Boolean tableWidthSpecified)
{
	short	**hWidths,colCount;
	TabColHandle	hColData;
	short		propCount = 0,cellIdx;
	Str255		colStat;
	enum	{ kNoWidth,kFixedWidth,kPropWidth };
	short	column;
	TableHandle	table = (*tableInfo)->table;
	short	width,span,i;
	short	leftoverWidth,noSpecCount;
	
	colCount = (*table)->columns;
	if (!(hWidths = NuHandleClear(sizeof(short)*colCount))) return nil;
	
	Zero(colStat);
	if ((hColData = (*table)->columnData) || (hColData = (*table)->columnGroupData))
	{
		short	colDataCount,width;
		TabColPtr	pColData;
		
		colDataCount = HandleCount(hColData);
		for(colDataCount=HandleCount(hColData),pColData=*hColData;colDataCount--;)
		{
			span = pColData->span ? pColData->span : 1;
			if ((width = pColData->width) < 0)
				width = tableWidth*(-width)/100;	// width is %
			if (pColData->column + span <= colCount)
			{
				for (column = pColData->column;span--;column++)
				{
					(*hWidths)[column] = width;
					if (pColData->propWidth)
					{
						propCount += width;
						colStat[column] = kPropWidth;
					}
					else
						colStat[column] = kFixedWidth;
				}
			}
		}
		
		//	do we have any columns that we still don't know the width of?
		for(column=0;column<colCount;column++)
			if (!colStat[column])
			{
				hColData = 0;	// signal we need to do some column calculations
				break;
			}
	}
	
	if (!hColData)
	{
		//	we need to calculate column sizes based on cell contents
		TabCellHandle	hCells = (*table)->cellData;
		short			sumWidth,overCount,aveWidth;
		Boolean			doSpan=false;
		
		for(cellIdx=0;cellIdx<(*table)->cells;cellIdx++)
		{
			column = (*hCells)[cellIdx].column;
			if (!colStat[column])
			{
				if ((*hCells)[cellIdx].colSpan<2)
				{
					// need to calculate this column
					width = GetCellWidth(pte,tableInfo,cellIdx);
					if (width > (*hWidths)[column]) (*hWidths)[column] = width;
				}
				else
					doSpan=true;
			}
		}
		
		if (doSpan)
		{
			// there are column spans we need to check
			for(cellIdx=0;cellIdx<(*table)->cells;cellIdx++)
			{
				span = (*hCells)[cellIdx].colSpan;
				column = (*hCells)[cellIdx].column;
				if (!colStat[column] && span>1)
				{
					// calculate this span
					short	sum=0;
					
					width = GetCellWidth(pte,tableInfo,cellIdx);
					// get sum of widths in span
					for(i=column;i<column+span;i++)
						sum += (*hWidths)[i];
					sum += (span-1)*((*table)->cellSpacing+2*((*table)->cellPadding+(*table)->border?1:0));						
					if (width > sum)
					{
						// cell is too wide for span
						// divide excess up equally between each column
						short	addThis = (width-sum+span-1)/span;
						for(i=column;i<column+span;i++)
							(*hWidths)[i] += addThis;
					}
				}
			}		
		}
		
		// make sure none of the columns are too wide
		sumWidth = 0;
		leftoverWidth = 0;
		overCount = 0;
		aveWidth = tableWidth/colCount;
		for(column=0;column<colCount;column++)
		{
			if (colStat[column] != kPropWidth)
			{
				short	thisWidth = (*hWidths)[column];
				
				sumWidth += thisWidth;
				if (thisWidth <= aveWidth) leftoverWidth += thisWidth;
				else overCount++;
			}
		}
		
		if (sumWidth > tableWidth)
		{
			//	too wide, reduce width of wide ones
			short	bigWidth = (tableWidth+leftoverWidth)/colCount;
			for(column=0;column<colCount;column++)
				if (colStat[column] != kPropWidth && (*hWidths)[column]>aveWidth)
					(*hWidths)[column] = bigWidth;
		
		}
	}
	
	if (propCount)
	{
		// calculate size of proportional columns
		leftoverWidth = tableWidth;
		
		// how much table width do we have left over?
		for(column=0;column<colCount;column++)
		{
			if (colStat[column] != kPropWidth)
				leftoverWidth -= (*hWidths)[column];
		}
		
		if (leftoverWidth < 0) leftoverWidth = 0;	// this should never happen

		for(column=0;column<colCount;column++)
		{
			if (colStat[column] == kPropWidth)
				(*hWidths)[column] = (*hWidths)[column]*leftoverWidth/propCount;
		}
	}
	
	if (tableWidthSpecified)
	{
		//	Any extra remaining width should be divided up among
		//	cells with no specified width
		leftoverWidth = tableWidth;
		noSpecCount = 0;
		for(column=0;column<colCount;column++)
		{
			leftoverWidth -= (*hWidths)[column];
			if (!colStat[column])
				noSpecCount++;
		}
			
		if (leftoverWidth)
		{
			short	lastNoSpec=0;
			for(column=0;column<colCount;column++)
				if (!colStat[column])
				{
					(*hWidths)[column] += leftoverWidth/noSpecCount;
					lastNoSpec = column;
				}
			(*hWidths)[lastNoSpec] += leftoverWidth%noSpecCount;	//	Fill up to very end
		}
	}
	

	// calculate sum of widths
	*sumWidths = 0;
	for(column=0;column<colCount;column++)
		*sumWidths += (*hWidths)[column];
	
	return hWidths;
}

/**********************************************************************
 * SetCellText - put cell text in edit pte
 **********************************************************************/
static OSErr SetCellText(PETEHandle pte,TIHandle tableInfo,short cellIdx,short cellWidth)
{
	TableHandle	table = (*tableInfo)->table;
	PETEHandle	pteSpare = (*tableInfo)->pteSpare;
	UHandle		text = nil;
	TabCellHandle	hCells = (*table)->cellData;
	OSErr		err;
	Boolean		saveDirty;
	MyWindowPtr	win;
	
	err = PETESetRecalcState(PETE,pteSpare,false);
	if (err) return err;
	
	win = (*PeteExtra(pteSpare))->win;
	saveDirty = win->isDirty;
	err = PeteDelete(pteSpare,0,0x7fffffff);
	if (!err)
	{
		err = PETEGetText(PETE, pte, (*hCells)[cellIdx].textOffset + (*tableInfo)->offset, (*hCells)[cellIdx].textOffset + (*tableInfo)->offset + (*hCells)[cellIdx].textLength, &text);
		if(!err)
		{
			err = PETEChangeDocWidth(PETE,pteSpare,cellWidth,true);
			if(!err)
			{
				err = PETEInsertTextScrap(PETE,pteSpare,0,text,(*hCells)[cellIdx].styleInfo,(*hCells)[cellIdx].paraInfo,false);
				if(!err)
					ZapHandle((*hCells)[cellIdx].styleInfo);
			}
			ZapHandle(text);
		}
	}
	win->isDirty = saveDirty;
	PETESetRecalcState(PETE,pteSpare,true);
	return err;
}

/**********************************************************************
 * GetCellWidth - get width of cell contents
 **********************************************************************/
static short GetCellWidth(PETEHandle pte,TIHandle tableInfo,short cellIdx)
{
	TableHandle	table = (*tableInfo)->table;
	TabCellHandle	hCells = (*table)->cellData;
	Point startPt,endPt;
	LHElement lineHeight;
	short	width;
	
	width = 0;
	if ((*hCells)[cellIdx].width)
		width = (*hCells)[cellIdx].width;
	else if (!SetCellText(pte,tableInfo,cellIdx,REAL_BIG)) 
	{
		if (!PETEOffsetToPosition(PETE,(*tableInfo)->pteSpare,0,&startPt,&lineHeight))
		if (!PETEOffsetToPosition(PETE,(*tableInfo)->pteSpare,(*hCells)[cellIdx].textLength,&endPt,&lineHeight))
			width = endPt.h - startPt.h + 9;
		ResetCellStyle(tableInfo, cellIdx);
	}
	return width;
}

/**********************************************************************
 * GetRowHeights - get row heights
 **********************************************************************/
static short **GetRowHeights(PETEHandle pte,TIHandle tableInfo,short *sumHeights,short **hColWidths)
{
	short	**hHeights,rowCount;
	short		cellIdx, row, height, column, span, cellWidth, i;
	TableHandle	table = (*tableInfo)->table;
	TabCellHandle	hCells = (*table)->cellData;
	
	rowCount = (*table)->rows;
	if (!(hHeights = NuHandleClear(sizeof(short)*rowCount))) return nil;
	
	for(cellIdx=0;cellIdx<(*table)->cells;cellIdx++)
	{
		row = (*hCells)[cellIdx].row;
		span = (*hCells)[cellIdx].colSpan;
		column = (*hCells)[cellIdx].column;
		cellWidth = (*hColWidths)[column];
		if (span>1)
		{
			for(i=column+1;i<column+span;i++)
				cellWidth += (*hColWidths)[i];
			cellWidth += (span-1)*((*table)->cellSpacing+2*((*table)->cellPadding+(*table)->border?1:0));		
		}
		height = GetCellHeight(pte,tableInfo,cellIdx,cellWidth);
		if (height > (*hHeights)[row]) (*hHeights)[row] = height;
	}
	
	// calculate sum of heights
	*sumHeights = 0;
	for(row=0;row<rowCount;row++)
		*sumHeights += (*hHeights)[row];
	
	return hHeights;
}

/**********************************************************************
 * GetCellHeight - get height of cell contents
 **********************************************************************/
static short GetCellHeight(PETEHandle pte,TIHandle tableInfo,short cellIdx,short colWidth)
{
	TableHandle	table = (*tableInfo)->table;
	PETEHandle	pteEdit = (*table)->pteEdit;
	TabCellHandle	hCells = (*table)->cellData;
	short	height;
	
	height = 0;
	if ((*hCells)[cellIdx].height)
		height = (*hCells)[cellIdx].height;
	else if (!SetCellText(pte,tableInfo,cellIdx,colWidth)) 
	{
		PETEDocInfo info;

		if (!PETEGetDocInfo(PETE,(*tableInfo)->pteSpare,&info))
			height = info.docHeight;
		ResetCellStyle(tableInfo, cellIdx);
	}
	return height;
}

/**********************************************************************
 * SetCellBounds - set the size and position of each cell
 **********************************************************************/
static void SetCellBounds(PETEHandle pte,TableHandle table,short **hColWidths,short **hRowHeights)
{
	TabCellHandle	hCells = (*table)->cellData;
	short		lastRow=0,colIdx,cellCount;
	Rect		r;
	TabCellPtr	pCell;
	short	border = (*table)->border;
	short	cellPadding = (*table)->cellPadding;
	short	cellSpacing = (*table)->cellSpacing;
	short	cellBorder = border?1:0;
	short	row,column,span;
	
	if (border && !(*table)->cellSpacingSpecified)
		cellSpacing = 1;	// if we're drawing cell borders, make sure there is at least some cell spacing
	r.top = border+cellSpacing+cellBorder+cellPadding;
	for (cellCount=(*table)->cells,pCell=*hCells;cellCount--;pCell++)
	{		
		while (pCell->row>lastRow)
			// new row, might have skipped some with row span
			r.top += (*hRowHeights)[lastRow++]+cellSpacing+2*(cellPadding+cellBorder);
		
		r.left = border+cellSpacing+cellBorder+cellPadding;
		for(colIdx=0;colIdx<pCell->column;colIdx++)
			r.left += (*hColWidths)[colIdx]+cellSpacing+2*(cellPadding+cellBorder);
		
		row = pCell->row;
		column = pCell->column;
		r.bottom = r.top + (*hRowHeights)[row++];
		r.right = r.left + (*hColWidths)[column++];

		for (span = pCell->colSpan?pCell->colSpan-1:0;span-- && column < (*table)->columns;)
		{
			// column span
			r.right += (*hColWidths)[column++] + cellSpacing+2*(cellPadding+cellBorder);
		}
		
		for (span = pCell->rowSpan?pCell->rowSpan-1:0;span-- && row < (*table)->rows;)
		{
			// row span
			r.bottom += (*hRowHeights)[row++] + cellSpacing+2*(cellPadding+cellBorder);
		}
		
		pCell->bounds = r;
	}
}

/**********************************************************************
 * SizeTable - set the size of the table
 **********************************************************************/
static void SizeTable(PETEHandle pte,TIHandle tableInfo,short maxWidth)
{
	short	width, height, colWidths;
	short	**hColWidths=nil,**hRowHeights=nil;
	short	widthPadding,heightPadding,widthAvail,columns,rows;
	TableHandle	table = (*tableInfo)->table;
	short	cellSpacing;
	RgnHandle	saveClip = SavePeteWinUpdateRgn(pte);
	
	height = 0;
	width = (*table)->width;
	if (width < 0) width =  maxWidth*(-width)/100;	// width is %
	
	//	determine extra padding
	columns = (*table)->columns;
	rows = (*table)->rows;
	cellSpacing = (*table)->cellSpacing;
	if ((*table)->border && !(*table)->cellSpacingSpecified)
		cellSpacing = 1;	// if we're drawing cell borders, make sure there is at least some cell spacing
	widthPadding = 2*(*table)->border + 2*columns*(*table)->cellPadding
		+ (columns+1)*cellSpacing;
	heightPadding = 2*(*table)->border + 2*rows*(*table)->cellPadding
		+ (rows+1)*cellSpacing;
	
	widthAvail = width?width:maxWidth;
	if (widthPadding >= widthAvail) widthPadding = 0;
	else widthAvail -= widthPadding;
	if (hColWidths = GetColWidths(pte,tableInfo,&colWidths,widthAvail,width!=0))
	if (hRowHeights = GetRowHeights(pte,tableInfo,&height,hColWidths))
	{
		if (!width)
			width = colWidths;
		SetCellBounds(pte,table,hColWidths,hRowHeights);
	}
	
	ZapHandle(hColWidths);
	ZapHandle(hRowHeights);

	//	if we're measuring the width of a cell that contains a table
	//	this may be really large. Reduce it down a bit to avoid short int overflow
	width = MIN(width,16000);
	width += widthPadding;
	(*tableInfo)->pgi.width = MIN(width,maxWidth);
	(*tableInfo)->pgi.height = height + heightPadding;
	(*tableInfo)->pgi.descent = 3;

	RestorePeteWinUpdateRgn(pte,saveClip);
}

/**********************************************************************
 * SavePeteWinUpdateRgn - save the updatergn of the window containing a pete handle
 **********************************************************************/
RgnHandle SavePeteWinUpdateRgn(PETEHandle pte)
{
	RgnHandle	rgn;
	
	if (rgn = NewRgn())
		GetWindowUpdateRgn(GetMyWindowWindowPtr((*PeteExtra(pte))->win),rgn);
	return rgn;	
}

/**********************************************************************
 * RestorePeteWinUpdateRgn - restore the updatergn of the window containing a pete handle
 **********************************************************************/
void RestorePeteWinUpdateRgn(PETEHandle pte,RgnHandle rgn)
{
	if (rgn)
	{
		LocalizeRgn(rgn);
		InvalWindowRgn(GetMyWindowWindowPtr((*PeteExtra(pte))->win),rgn);
		DisposeRgn(rgn);
	}
}

/**********************************************************************
 * CreateSparePTE - create a spare PETE handle
 **********************************************************************/
static OSErr CreateSparePETE(MyWindowPtr win,PETEHandle *pte)
{
	PETEDocInitInfo pdi;
	OSErr	err;

	DefaultPII(win,false,peEmbedded,&pdi);
	pdi.inParaInfo.paraFlags = 0;
	pdi.inParaInfo.startMargin = 0;
	pdi.inParaInfo.endMargin = WindowWi(GetMyWindowWindowPtr(win))+1;
	err = PeteCreate(win,pte,peEmbedded,&pdi);
	CleanPII(&pdi);
	if (err) return err;

	// remove from win's pteList, we don't want anyone to know about this pte
	win->pteList = PeteRemove(win->pteList,*pte);

	// get rid of progress callback function
	// it can cause re-entrancy problems
	PETESetCallback(PETE,*pte,nil,peProgressLoop);
	return noErr;
}

static OSErr ResetCellStyle(TIHandle tableInfo, short cellIdx)
{
	PETEStyleListHandle styleInfo;
	TabCellHandle hCells = (*(*tableInfo)->table)->cellData;
	OSErr err;

	if(!(err = PeteGetTextStyleScrap((*tableInfo)->pteSpare, 0, LONG_MAX, nil, &styleInfo, nil)))
	{
		(*hCells)[cellIdx].styleInfo = styleInfo;
	}
	return err;
}