#include "BlugsPrivate.h"

#pragma mark ¥¥¥Module Data¥¥¥



typedef struct
{
	UInt16		nColumns;
	UInt16		nRows;
	UInt32		listFlags;
	UInt16		dragFlags;
	Rect		r;
	Point		defaultCellSize;
	UInt16		nRowEntries;
	UInt8		everythingElse[1];
	// Everything else goes here.
} BLListResTemplate, **BLListResTemplateHdl;


typedef struct
{
	UInt16			nTitles;
	UInt16			thickness;
	BLContentType	defaultContentType;
	UInt16			flags;
	UInt8			data[1];
} BLTitleResTemplate, *BLTitleResTemplatePtr;



typedef struct
{
	UInt16		height;
	UInt16		disclosureLevel;
	UInt16		rowFlags;
	UInt16		reserved;
	UInt32		identifier;
} BLResourceRowDataRec, *BLResourceRowDataPtr;

typedef struct
{
	UInt16			width;
	BLContentType	contentType;
	UInt16			flags;
	UInt16			reserved;
	UInt32			identifier;
} BLResourceColumnDataRec, *BLResourceColumnDataPtr;

typedef struct
{
	OSType		dataFlavor;
	UInt16		contentType;
	UInt16		dataSize;
	// variable length data goes here
	// Pad to word boundary here.
} BLResourceCellDataRec, *BLResourceCellDataPtr;



static void			local_BLReadRowData( UInt16 inNRowEntries, Ptr* ioPosition, BlugsRef inList );
static void			local_BLReadColumnData( Ptr* ioPosition, BlugsRef inList );
static void			local_BLReadCellData( Ptr inStartPosition, Ptr* ioPosition, BlugsRef inList );
static OSErr		local_BLLoadTitleBar( Boolean inIsVertical, Ptr inStartPosition,
											Ptr* ioPosition, BlugsRef inList );
static UInt32		local_BLConstrainListFlags( UInt32 inFlags );
static UInt16		local_BLExpandDragFlags( UInt16 inFlags );
static OSErr		local_FlattenCellOrTitleData( BLFlattenProcPtr inFlattenProc, BLCell inCell,
												Handle inHandle, UInt32* ioCurrOffset,
												UInt32* ioHandleSize, BlugsRef inList );


//===============================
BL_PROC BlugsRef BLNew( UInt16 inColumns, UInt16 inRows, const Rect* inRect, Point inDefaultCellSize,
					WindowRef inWindow, UInt32 inListFlags, UInt16 inDragFlags )
{
	BlugsRef			list = nil;
	ControlRef			hScroll = nil, vScroll = nil;
	Handle				index = nil;
	OSErr				err = noErr;
	
	if (!inWindow) err = paramErr;
	else
	{
		err = BLEnter( 0 );
		if (!err)
		{
			list = (BlugsRef)NewHandle( sizeof(BlugsRec) );
			if (!list) err = memFullErr;
			else
			{
				GWorldPtr		saveWorld;
				Rect			controlRect;	// I reuse it for h and v scrollbars.
				SInt16			procID;	// Which control proc to use with scroll bars
				Boolean			restore;
				
				restore = _BLSwapPort( GetWindowPort( inWindow ), &saveWorld );
				_BLZeroMemory( (Ptr)*list, sizeof(BlugsRec) );
			#if __BL_USE_CARBON_API__
				{
					WindowAttributes	attr;
					
					GetWindowAttributes( inWindow, &attr );
					if (bl_Test(attr,kWindowCompositingAttribute)) bl_Set((*list)->privateFlags,blIsInCompositingWindow);
				}
			#endif
				(*list)->nextRowID.lo = 1;
				(*list)->nextColID.lo = 1;
				(*list)->rect = *inRect;
				(*list)->window = inWindow;
				(*list)->indent = blDefaultIndent;
				(*list)->nColumns = inColumns;
				(*list)->nRows = inRows;
				(*list)->defaultCellSize = inDefaultCellSize;
				(*list)->visCells.top = 1;
				(*list)->visCells.left = 1;
				(*list)->disclosureColumn = 1;
				(*list)->listFlags = local_BLConstrainListFlags( inListFlags );
				(*list)->dragFlags = local_BLExpandDragFlags( inDragFlags );
				if (bl_Appearance)
				{
					if (bl_Test(inListFlags,blLiveScroll)) procID = kControlScrollBarLiveProc;
					else procID = kControlScrollBarProc;
				}
				else procID = scrollBarProc;
				if (bl_Test(inListFlags,blHorizontalScroll))
				{
					ControlActionUPP	hScrollProc = nil;
					
					hScrollProc = NewControlActionUPP( &_BLHScrollActionProc );
					controlRect = _BLHScrollRect( list );
				#if TARGET_RT_MAC_MACHO
					err = CreateScrollBarControl( inWindow, &controlRect, 0, 0, 0, 0, true, hScrollProc, &hScroll );
				#else
					hScroll = NewControl( inWindow, &controlRect, "\p", false, 0, 0, 0,
											procID, 0x8000 );
				#endif
					if (!hScroll) err = memFullErr;
					else
					{
						bl_HScroll(list) = hScroll;
						SetControlReference( hScroll, (SInt32)(bl_HiBitSet(list)) );
					#if !TARGET_RT_MAC_MACHO
						if (hScrollProc) SetControlAction( hScroll, hScrollProc );
					#endif
					}
				}
				if (!err)
				{
					if (bl_Test(inListFlags,blVerticalScroll))
					{
						ControlActionUPP	vScrollProc = nil;
						
						vScrollProc = NewControlActionUPP( &_BLVScrollActionProc );
						controlRect = _BLVScrollRect( list );
					#if TARGET_RT_MAC_MACHO
						err = CreateScrollBarControl( inWindow, &controlRect, 0, 0, 0, 0, true, vScrollProc, &vScroll );
					#else
						vScroll = NewControl( inWindow, &controlRect, "\p", false, 0, 0, 0,
											procID, 0x8000 );
					#endif
						if (!vScroll) err = memFullErr;
						else
						{
							(*list)->vScroll = vScroll;
							SetControlReference( vScroll, (SInt32)(bl_HiBitSet(list)) );
						#if !TARGET_RT_MAC_MACHO
							if (vScrollProc) SetControlAction( vScroll, vScrollProc );
						#endif
						}
					}
					if (!err)
					{
						err = _BLAllocateRowData( inRows, list );
						if (!err)
						{
							err = _BLAllocateColumnData( inColumns, list );
							if (!err)
							{
								// Allocate memory for index
								index = _VANew( inRows * inColumns, sizeof(BLIndexRec), true );
								if (!index) err = memFullErr;
								else
								{
									bl_Index(list) = index;
									// Get the offscreen drawing environment if
									//	the list is visible, or if invisible and not in
									//	memory saver mode.
									if (bl_Test(inListFlags,blVisible) ||
										(!bl_Test(inListFlags,blVisible) &&
										!bl_DumpGWorlds)) err = _BLNewWorld( list );
									if (!err)
									{
									#if __BL_USE_CARBON_API__
										if ((void*)InstallEventLoopTimer != (void*)kUnresolvedCFragSymbolAddress)
										{
											static EventLoopTimerUPP timerProc = nil;
											
											if (!timerProc) timerProc = NewEventLoopTimerUPP( _BLTimerProc );
											err = InstallEventLoopTimer( GetMainEventLoop(), 0.25, 0.25,
																		timerProc, list, &(*list)->timer );
										}
										if (!err)
									#endif
										{
											// At this point we may start drawing stuff
											_BLRecalc( blRecalcOffsets | blRecalcVisCells | blRecalcScroll | blRecalcThumb, 1, list );
											if (bl_Test(inListFlags,blVisible))
											{
												if (vScroll) ShowControl( vScroll );
												if (hScroll) ShowControl( hScroll );
											}
											// Draw everything, offscreen at least
											_BLUpdateWorld( blUpdateBlit | blUpdateBorder | blUpdateCorners, nil, list );
											// If we have not already, load the cursors necessary for resizing if appropriate
											if ((bl_Test(inListFlags,blResizableWidth)) || (bl_Test(inListFlags,blResizableHeight)))
											{
												if (!_blGlobals.rowResizeCursHdl || !_blGlobals.colResizeCursHdl) _BLLoadCursors();
											}
										}
									}
								}
							}
						}
					}
				}
				if (restore) SetGWorld( saveWorld, nil );
			} // allocated list handle
		} // noErr from BLEnter
	} // valid window
	if (err)
	{
		BLDispose( list );
		list = nil;
	}
	return list;
}

//===============================
BL_PROC BlugsRef BLLoad( SInt16 inResID, WindowRef inWindow )
{
	Handle		res;
	BlugsRef			list = nil;
	
	//_CheckHandlerFlag(return nil)
	res = GetResource( blListResType, inResID );
	if (res)
	{
		list = BLUnflatten( res, inWindow );
		ReleaseResource( res );
	}
	return list;
}

//===============================
static void local_BLReadRowData( UInt16 inNRowEntries, Ptr* ioPosition, BlugsRef inList )
{
	UInt16			nRows, i;
	Ptr				tempPosition;
	
	nRows = bl_NRows(inList);
	tempPosition = *ioPosition;
	// May have more data than we need
	//_AssertStr( inNRowEntries <= nRows, "Notice: resource contains extra row data entries.");
	// Set to beginning of repeating block
	//rowDataP = bl_NthRowDataAddr(1,bl_RowData(inList));
	for (i = 1; i <= inNRowEntries; i++)
	{
		BLResourceRowDataPtr	rowRecord;
		
		rowRecord = (BLResourceRowDataPtr)tempPosition;
		// If there are too many entries and not enough rows, we need to parse the extras
		//	in order to come to the column data section correctly.
		if (i <= nRows)
		{
			_BLSetRowHeight( i, rowRecord->height, inList );
			_BLSetRowDisclosureLevel( i, rowRecord->disclosureLevel, inList );
			_BLSetRowFlags( i, blAllPublicRowFlags, rowRecord->rowFlags, inList );
			BLSetRowIdentifier( i, rowRecord->identifier, inList );
		}
		tempPosition += sizeof(BLResourceRowDataRec);
	}
	*ioPosition = tempPosition;
}

//===============================
static void local_BLReadColumnData( Ptr* ioPosition, BlugsRef inList )
{
	UInt16			nEntries, nCols, nRows, i;
	Ptr				tempPosition;
	Boolean			table = bl_Table(inList);
	
	nCols = bl_NCols(inList);
	nRows = bl_NRows(inList);
	tempPosition = *ioPosition;
	nEntries = *(UInt16*)tempPosition;
	// May have more data than we need
	tempPosition += 2;
	for (i = 1; i <= nEntries; i++)
	{
		if (i <= nCols)
		{
			BLResourceColumnDataPtr	colRecord;
			
			colRecord = (BLResourceColumnDataPtr)tempPosition;
			_BLSetColumnWidth( i, colRecord->width, inList );
			_BLSetColumnFlags( i, blAllPublicColumnFlags, colRecord->flags, inList );
			BLSetColumnIdentifier( i, colRecord->identifier, inList );
			if (table)
			{
				BLContentType	content = colRecord->contentType;
				
				// Filter out bogus content types.
				if (_BLCheckValidContentType( content ) != noErr)
				{
					// I reckon content type 0 is OK for column data. For some reason
					//	the app doesn't want to set the type yet. But any other is
					//	probably the sign of an error in the resource.
					_AssertStr(content == 0,"Error: resource column content type does not exist.");
					content = 0;
				}
				// Give the handler a chance to initialize cells if there are any rows.
				// This only has to be done for one row, since in a table the change propagates
				//	to all cells in the column.
				if (nRows)
				{
					BLCell			cell;
					
					cell.col = i;
					cell.row = 1;
					_BLSetCellContentType( cell, content, inList );
				}
				// Otherwise just store content type until rows are added.
				else _BLSetColumnContentType( i, content, inList );
			}
		}
		//colDataP++;
		tempPosition += sizeof(BLResourceColumnDataRec);
	}
	//HUnlock( (Handle)ah );
	*ioPosition = tempPosition;
}

//===============================
// 5/5/2000 found bug where extra data wouldn't be parsed.
// Now we make sure thisEntry is not greater than nCells.
static void local_BLReadCellData( Ptr inStartPosition, Ptr* ioPosition, BlugsRef inList )
{
	UInt32	nEntries, entry, nCells;
	Ptr		pos;
	
	nCells = bl_NCells(inList);
	pos = *ioPosition;
	nEntries = *(UInt32*)pos;
	pos += 4;
	//_AssertStr( nCells >= nEntries, "Notice: resource contains extra cell data entries." );
	// Read the entries
	for (entry = 1; entry <= nEntries; entry++)
	{
		void*					data;
		BLResourceCellDataPtr	cellRecord;
			
		cellRecord = (BLResourceCellDataPtr)pos;
		// Count past the fixed-size block to get data address
		pos += sizeof(BLResourceCellDataRec);
		data = pos;
		// Only try to alter a cell if this entry is <= the number of cells.
		if (entry <= nCells)
		{
			BLCell			cell;
			BLContentType	content;
			
			cell = _BLNthCellToCell( entry, inList );
			if (!bl_Table(inList))
			{
				content = cellRecord->contentType;
				// Filter out bogus content types.
				if (_BLCheckValidContentType( content ) != noErr) content = 0;
				// Since the index is zeroed when allocated, _BLSetCellContentType will not
				//	get garbage for the cell's 'previous' type and try to deinit it.
				_BLSetCellContentType( cell, content, inList );
			}
			// We always want to try to set cell data if it's in a table.
			// So we use a fake nonzero content type.
			else content = 1;
			if (content) BLSetCellData( cell, cellRecord->dataFlavor, cellRecord->dataSize, data, inList );
		}
		// Count past variable-length data
		pos += cellRecord->dataSize;
		// Align to 4-byte boundary
		if ((UInt32)(pos - inStartPosition) & 1) pos++;
		if ((UInt32)(pos - inStartPosition) & 2) pos += 2;
	}
	*ioPosition = pos;
}

//===============================
static OSErr local_BLLoadTitleBar( Boolean inVertical, Ptr inStartPosition,
								   Ptr* ioPosition, BlugsRef inList )
{
	UInt16					nTitleEntries, i;
	BLTitleResTemplatePtr	res;
	OSErr					err;
	BLTitleArrayHdl			titles;
	BLTitlePtr				title;
	BLContentType			content = 0;
	Ptr						pos;
	BLTitleBarInfo			info;
	
	res = (BLTitleResTemplatePtr)*ioPosition;
	nTitleEntries = res->nTitles;
	info.thickness = res->thickness;
	info.titleBarFlags = res->flags;
	info.defaultTitleFlags = 0;
	info.defaultContentType = res->defaultContentType;
	err = BLNewTitleBar( inVertical, &info, inList );
	if (!err)
	{
		BLTitleBarHdl	theBar;
		BLCell			cell = {0,0};
		UInt16			nTitles;
		
		if (inVertical)
		{
			nTitles = bl_NRows(inList);
			theBar = bl_VTitle(inList);
		}
		else
		{
			nTitles = bl_NCols(inList);
			theBar = bl_HTitle(inList);
		}
		titles = (*theBar)->titles;
		HLock( (Handle)titles );
		title = &((**titles)[0]);
		pos = (Ptr)&res->data[0]; // set position to beginning of repeating block
		for (i = 1; i <= nTitleEntries; i++)
		{
			BLHandlerParamBlock	pb;
			UInt16				dataSize;
			
			if (i > nTitles) break;
			// Read data from resource and add it as appropriate...
			// Data flavor
			pb.ioDataFlavor = *(OSType*)pos;
			pos += sizeof(OSType);
			// Content type
			content = *(UInt16*)pos;
			if (_BLCheckValidContentType( content ) != noErr) content = 0;
			pos += sizeof(UInt16);
			// Data length
			dataSize = *(UInt16*)pos; 
			pb.ioDataSize = dataSize;
			pos += sizeof(UInt16);
			pb.ioDataBuffer = pos;
			pos += dataSize;
			// Resources may have entries w/ no content (mine did!)
			if (content)
			{
				if (inVertical) cell.row = i;
				else cell.col = i;
				_BLSetTitleContentType( false, i, content, theBar, inList );
				pb.inIndent = _BLIndent( cell, inList );
				err = _BLTitleRect( i, theBar, &pb.inRect, inList );
				if (!err) _BLCallHandlerForTitle( blCellSetDataMsg, i, &pb, theBar, inList );
			}
			title++;
			// Read past pad-to-32b if necessary
			if ((UInt32)(pos - inStartPosition) & 1) pos++;
			if ((UInt32)(pos - inStartPosition) & 2) pos += 2;
		}
		*ioPosition = pos;
		HUnlock( (Handle)titles );
	}
	return err;
}

//===============================
// Adjusts bits in listFlags field so they are internally consistent.
static UInt32 local_BLConstrainListFlags( UInt32 inFlags )
{
	if (!bl_Test(inFlags,blTable)) bl_Clear(inFlags,blSortable);
	if (!bl_Test(inFlags,blVerticalScroll)) bl_Clear(inFlags,blDrawSortButton);
	if (bl_Test(inFlags,blDrawBorder)) bl_Set(inFlags,blBorderMetrics);
	return inFlags;
}


//===============================
// Adds bits in dragFlags field as needed.
static UInt16 local_BLExpandDragFlags( UInt16 inFlags )
{
	if (bl_Test(inFlags,blAllowDragsOnlyToSelf)) bl_Set(inFlags,blCanStartDrags);
	if (bl_Test(inFlags,blImmediateDrag)) bl_Set(inFlags,blCanStartDrags);
	if (bl_Test(inFlags,blReceiveDragsOnlyFromSelf)) bl_Set(inFlags,blReceiveDragsFromSelf);
	if (bl_Test(inFlags,blReceiveDragsFromSelf))
	{
		bl_Set(inFlags,blCanStartDrags);
		bl_Set(inFlags,blReceiveDrags);
	}
	return inFlags;
}


//===============================
// Returns:
//	errDataSizeMismatch (-30591) Handler returned wrong size data.
//	memFullErr
//	noErr
BL_PROC OSErr BLFlatten( BLFlattenProcPtr inFlattenProc, Handle* outHandle, BlugsRef inList )
{
	UInt16					nRows, nCols;
	BLListResTemplateHdl	h;
	OSErr					err = noErr;
	UInt32					handleSize;
	BLTitleBarHdl			hBar, vBar;
	
	nRows = bl_NRows( inList );
	nCols = bl_NCols( inList );
	hBar = bl_HTitle(inList);
	vBar = bl_VTitle(inList);
	// Allocate space for everything we know we will need.
	handleSize = sizeof(BLListResTemplate); // List information
	handleSize += (nRows * sizeof(BLResourceRowDataRec)); // Rows
	handleSize += (nCols * sizeof(BLResourceColumnDataRec)); // Columns
	// Padding before column data count, column data count field, count of cell data entries,
	//	'has htitlebar' word, 'has vtitlebar' word
	handleSize += (sizeof(UInt16) + sizeof(UInt16) + sizeof(UInt32) +
					sizeof(UInt16) + sizeof(UInt16));
	if (hBar) handleSize += sizeof(BLTitleResTemplate);
	if (vBar) handleSize += sizeof(BLTitleResTemplate);
	h = (BLListResTemplateHdl)NewHandleClear( handleSize );
	if (!h) err = memFullErr;
	else
	{
		//BLRowDataArrayHdl		rowData;
		//BLColDataArrayHdl		colData;
		//BLRowDataPtr			rowDataP;
		BLResourceRowDataPtr	resRowDataP;
		//BLColDataPtr			colDataP;
		BLResourceColumnDataPtr	resColDataP;
		UInt32					nCells, currentOffset;
		BLCell					cell;
		
		//rowData = bl_RowData(inList);
		//colData = bl_ColData(inList);
		(*h)->nColumns = nCols;
		(*h)->nRows = nRows;
		(*h)->listFlags = (*inList)->listFlags;
		(*h)->dragFlags = (*inList)->dragFlags;
		(*h)->r = bl_Rect(inList);
		(*h)->defaultCellSize = (*inList)->defaultCellSize;
		(*h)->nRowEntries = nRows;
		//rowDataP = bl_NthRowDataAddr(1,rowData);
		resRowDataP = (BLResourceRowDataPtr)&(*h)->everythingElse[0];
		for (cell.row = 1; cell.row <= nRows; cell.row++)
		{
			resRowDataP->height = _BLGetRowHeight( cell.row, inList );
			resRowDataP->disclosureLevel = _BLGetRowDisclosureLevel( cell.row, inList );
			resRowDataP->rowFlags = _BLGetRowFlags( cell.row, inList );
			BLGetRowIdentifier( cell.row, &resRowDataP->identifier, inList );
			//rowDataP++;
			resRowDataP++;
		}
		//colDataP = bl_NthColDataAddr(1,colData);
		// Hand current position off to next pointer type, incremented past padding.
		resColDataP = (BLResourceColumnDataPtr)((Ptr)resRowDataP + sizeof(UInt16));
		*(UInt16*)resColDataP = nCols;
		resColDataP = (BLResourceColumnDataPtr)((Ptr)resColDataP + sizeof(UInt16));
		for (cell.col = 1; cell.col <= nCols; cell.col++)
		{
			resColDataP->width = _BLGetColumnWidth( cell.col, inList );
			resColDataP->contentType = _BLGetColumnContentType( cell.col, inList );
			resColDataP->flags = _BLGetColumnFlags( cell.col, inList );
			BLGetColumnIdentifier( cell.col, &resColDataP->identifier, inList );
			//colDataP++;
			resColDataP++;
		}
		// From here on out we will be resizing the handle. We can't lock it.
		// So we keep an offset from the beginning of the block to where we're writing.
		currentOffset = (UInt32)resColDataP - (UInt32)*h;
		if (inFlattenProc) nCells = nRows * nCols;
		else nCells = 0;
		// Write number of cell data entries.
		*(UInt32*)(*(Handle)h + currentOffset) = nCells;
		currentOffset += sizeof(UInt32);
		if (inFlattenProc)
		{
			for (cell.row = 1; cell.row <= nRows; cell.row++)
			{
				for (cell.col = 1; cell.col <= nCols; cell.col++)
				{
					err = local_FlattenCellOrTitleData( inFlattenProc, cell, (Handle)h,
														&currentOffset, &handleSize, inList );
					if (err) break;
				} 
				if (err) break;
			}
		}
		if (!err)
		{
			// Do title bar data. H first, then V.
			BLTitleResTemplatePtr	barPtr;
			
			*(UInt16*)(*(Handle)h + currentOffset) = (hBar != nil);
			currentOffset += sizeof(UInt16);
			if (hBar)
			{
				barPtr = (BLTitleResTemplatePtr)(*(Handle)h + currentOffset);
				if (inFlattenProc) barPtr->nTitles = nCols;
				else barPtr->nTitles = 0;
				barPtr->thickness = (*hBar)->thickness;
				barPtr->defaultContentType = (*hBar)->defaultContentType;
				barPtr->flags = (*hBar)->flags;
				currentOffset = (UInt32)&barPtr->data[0] - (UInt32)*h;
				if (inFlattenProc)
				{
					cell.row = 0;
					for (cell.col = 1; cell.col <= nCols; cell.col++)
					{
						err = local_FlattenCellOrTitleData( inFlattenProc, cell, (Handle)h,
															&currentOffset, &handleSize, inList );
						if (err) break;
					}
				}
			} // if hBar
			if (!err)
			{
				*(UInt16*)(*(Handle)h + currentOffset) = (vBar != nil);
				currentOffset += sizeof(UInt16);
				if (vBar)
				{
					barPtr = (BLTitleResTemplatePtr)(*(Handle)h + currentOffset);
					if (inFlattenProc) barPtr->nTitles = nRows;
					else barPtr->nTitles = 0;
					barPtr->thickness = (*vBar)->thickness;
					barPtr->defaultContentType = (*vBar)->defaultContentType;
					barPtr->flags = (*vBar)->flags;
					currentOffset = (UInt32)&barPtr->data[0] - (UInt32)*h;
					if (inFlattenProc)
					{
						cell.col = 0;
						for (cell.row = 1; cell.row <= nRows; cell.row++)
						{
							err = local_FlattenCellOrTitleData( inFlattenProc, cell, (Handle)h,
																&currentOffset, &handleSize, inList );
							if (err) break;
						}
					}
				}
			}
		}
	}
	if (err)
	{
		DisposeHandle( (Handle)h );
		h = nil;
	}
	*outHandle = (Handle)h;
	return err;
}

//===============================
static OSErr local_FlattenCellOrTitleData( BLFlattenProcPtr inFlattenProc, BLCell inCell,
											Handle inHandle, UInt32* ioCurrOffset,
											UInt32* ioHandleSize, BlugsRef inList )
{
	UInt32		dataSize, totalSize;
	OSErr		err = noErr;
	OSType		flavor;
	
	// Get size we need for the cell data.
	(*inFlattenProc)( inCell, &flavor, &dataSize, nil, inList );
	// Make sure we don't get more than 0x0000FFFF bytes of data.
	if (dataSize > 0x0000FFFF) dataSize = 0x0000FFFF;
	totalSize = dataSize + sizeof(BLResourceCellDataRec);
	// Pad to 32 bits.
	if (((UInt32)*inHandle + *ioCurrOffset + totalSize) & 1) totalSize++;
	if (((UInt32)*inHandle + *ioCurrOffset + totalSize) & 2) totalSize += 2;
	// Resize handle.
	*ioHandleSize += totalSize;
	SetHandleSize( (Handle)inHandle, *ioHandleSize );
	if (GetHandleSize( (Handle)inHandle ) != *ioHandleSize || MemError()) err = memFullErr;
	else
	{
		Ptr						dest;
		BLResourceCellDataPtr	cellDataP;
		
		dest = *(Handle)inHandle + *ioCurrOffset;
		cellDataP = (BLResourceCellDataPtr)dest;
		cellDataP->dataFlavor = flavor;
		cellDataP->contentType = BLGetCellContentType( inCell, inList );
		cellDataP->dataSize = dataSize;
		// Get data for real this time.
		if (dataSize)
		{
			HLock( (Handle)inHandle );
			(*inFlattenProc)( inCell, &flavor, &dataSize, dest + sizeof(BLResourceCellDataRec), inList );
			// What if proc returned an amount of data not equal to what it promised?
			if (cellDataP->dataSize != dataSize)
			{
				_AssertStr(false,"Error: flatten proc returned a different amount of data than it promised.");
				err = errDataSizeMismatch; // -30591
			}
			HUnlock( (Handle)inHandle );
		}
		// Update offset.
		*ioCurrOffset += totalSize;
	}
	return err;
}

//===============================
BL_PROC BlugsRef BLUnflatten( Handle inHandle, WindowRef inWindow )
{
	BlugsRef		list = nil;
	
	if (inHandle)
	{
		UInt16					nRows;
		BLListResTemplate*		resPtr;
		UInt32					listFlags;
		UInt8					hState;
		
		hState = HGetState( inHandle );
		HLock( inHandle );
		resPtr = *(BLListResTemplateHdl)inHandle;
		nRows = resPtr->nRows;
		listFlags = resPtr->listFlags; // Used later
		list = BLNew( resPtr->nColumns, nRows, &resPtr->r, resPtr->defaultCellSize, inWindow,
						listFlags, resPtr->dragFlags );
		if (list)
		{
			Ptr					pos, posStart;
			BLRecalcOptions		options;
			OSErr				err;
			UInt16				nRowEntries, hasBar;
			
			nRowEntries = resPtr->nRowEntries;
			posStart = *inHandle;
			pos = (Ptr)&(resPtr->everythingElse[0]);
			if (nRowEntries) local_BLReadRowData( nRowEntries, &pos, list );
			// Count past pad bytes
			pos += 2;
			local_BLReadColumnData( &pos, list );
			_BLRecalc( blRecalcOffsets, 1, list );
			local_BLReadCellData( posStart, &pos, list );
			// Load title bars if appropriate.
			hasBar = *(UInt16*)pos;
			pos += 2;
			if (hasBar != 0) err = local_BLLoadTitleBar( false, posStart, &pos, list );
			hasBar = *(UInt16*)pos;
			pos += 2;
			if (hasBar != 0) err = local_BLLoadTitleBar( true, posStart, &pos, list );
			if (bl_Test(listFlags,blDisclosure) && nRows)
			{
				_BLRecalcDisclosure( false, 1, list );
				// need do limited recalculating because _BLRecalcDisclosure also calls _BLRecalc
				// Changed from blRecalcColOffsets to blRecalcOffsets because _BLRecalcDisclosure
				//	does not do this for all rows.
				options = blRecalcOffsets | blRecalcHScroll | blRecalcThumb | blRecalcRightPin;
			}
			else options = blRecalcOffsets | blRecalcVisCells | blRecalcScroll | blRecalcThumb | blRecalcRightPin;
			_BLRecalc( options, 1, list );
			_BLUpdateWorld( blUpdateBlit | blUpdateBorder | blUpdateCorners, nil, list );
		}
		HSetState( inHandle, hState );
	}
	return list;
}

//===============================
BL_PROC void BLDispose( BlugsRef inList )
{
	_CheckHandlerFlag(inList)
	if (inList)
	{
		BLTitleBarHdl		vBar, hBar;
		ControlRef			vScroll, hScroll;
		Handle				rowArray, colArray;
		Handle				index, userData;
		UInt16				nRows, nCols, row, col;
		BLCell				cell;
		
		nRows = bl_NRows(inList);
		nCols = bl_NCols(inList);
		vScroll = bl_VScroll(inList);
		hScroll = bl_HScroll(inList);
		vBar = bl_VTitle(inList);
		hBar = bl_HTitle(inList);
		index = bl_Index(inList);
		rowArray = (*inList)->rowData;
		colArray = (*inList)->colData;
		userData = (*inList)->userData;
		// Deinit widgets
		(void)BLDeleteWidgets( true, 0, 1, inList );
		(void)BLDeleteWidgets( false, 0, 1, inList );
		if (vBar) _BLDisposeTitleBar( vBar, inList );
		if (hBar) _BLDisposeTitleBar( hBar, inList );
		if (vScroll)
		{
			DisposeControlActionUPP( GetControlAction( vScroll ) );
			DisposeControl( vScroll );
		}
		if (hScroll)
		{
			DisposeControlActionUPP( GetControlAction( hScroll ) );
			DisposeControl( hScroll );
		}
		// Deinit cells
		for (row = 1; row <= nRows; row++)
		{
			cell.row = row;
			for (col = 1; col <= nCols; col++)
			{
				BLHandlerParamBlock	pb;
				
				cell.col = col;
				_BLCallHandlerForCell( blCellDeinitMsg, cell, &pb, inList );
			}
		}
		if (rowArray) DisposeHandle( rowArray );
		if (colArray) DisposeHandle( colArray );
		_BLDisposeWorld( inList );
		if (index) DisposeHandle( index );
		if (userData) DisposeHandle( userData );
		if ((*inList)->vWidgets) DisposeHandle( (*inList)->vWidgets );
		if ((*inList)->hWidgets) DisposeHandle( (*inList)->hWidgets );
	#if __BL_USE_CARBON_API__
		if ((*inList)->timer) RemoveEventLoopTimer( (*inList)->timer );
	#endif
		DisposeHandle( (Handle)inList );
	}
}

//===============================
BL_PROC WindowRef BLWindow( BlugsRef inList )
{
	OSErr		err;
	WindowRef	window = nil;
	
	err = _BLValidateList( inList );
	if (!err) window = bl_Window(inList);
	return window;
}
