/*===============================
Copyright © 1998-2002 Brian Hall

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*===============================*/
#include "BlugsPrivate.h"
#if !TARGET_RT_MAC_MACHO
#include <fp.h>
#endif

#pragma mark ¥¥¥Module Data¥¥¥
#define blLargeScrollBarWidth	16				// normal scroll bars
#define blLargeScrollBarHeight	16
#define blSmallScrollBarWidth	11				// scroll bars for utility windows
#define blSmallScrollBarHeight	11
// Standard minima for list view rect, not taking into account scroll bars.
#define	blStandardMinHeight		16
#define blStandardMinWidth		16


static void			local_BLRecalcColOffsets( UInt16 inStartHere, BlugsRef inList );
static void			local_BLRecalcRowOffsets( UInt16 inStartHere, BlugsRef inList );
static void			local_BLRecalcVisCells( BlugsRef inList );
static void			local_BLRecalcVScroll( Boolean inRecalcThumb, ControlRef inVScrollBar, BlugsRef inList );
static void			local_BLRecalcHScroll( Boolean inRecalcThumb, ControlRef inHScrollBar, BlugsRef inList );
static void			local_BLRecalcPinRightCol( BLRecalcOptions* ioOptions, BlugsRef inList );
static void			local_BLDisableScrollBar( ControlRef inScrollBar );
static void			local_BLRecalcThumb( ControlRef inScrollBar, UInt32 inScrollLoc,
											UInt16 inViewSize, UInt32 inTotalSize );
static UInt16		local_BLCalcColMinimumWidth( UInt16 inColumn, BlugsRef inList );
static UInt16		local_BLCalcRowMinimumHeight( UInt16 inRow, BlugsRef inList );
static UInt16		local_BLCalcTitleMinimum( Boolean inVertical, UInt16 inTitle, BlugsRef inList );


#pragma mark -
//===============================
BL_PROC OSErr BLGetViewRect( Rect* outRect, BlugsRef inList )
{
	Rect	r = {0,0,0,0};
	OSErr	err;
	
	err = _BLValidateList( inList );
	if (!err) r = _BLViewRect( inList );
	*outRect = r;
	return err;
}

//===============================
BL_PROC OSErr BLGetRect( Rect* outRect, BlugsRef inList )
{
	Rect	r = {0,0,0,0};
	OSErr	err;
	
	err = _BLValidateList( inList );
	if (!err) r = bl_Rect(inList);
	*outRect = r;
	return err;
}

//===============================
// We check to make sure the new rect is not too small; we do nothing if it is.
// We could also constrain the new rect to the minima, but that would give unexpected results.
BL_PROC OSErr BLSetRect( const Rect* inRect, BlugsRef inList )
{
	OSErr			err;
	
	_CheckHandlerFlag(inList)
	err = _BLValidateList( inList );
	if (!err)
	{
		err = _BLSetRect( inRect, inList );
		if (!err)
		{
			ControlRef	userPane;
			
			userPane = bl_UserPane(inList);
			if (userPane)
			{
				Rect	controlRect;
				
				controlRect = _BLUserPaneRect( inList );
				MoveControl( userPane, controlRect.left, controlRect.top );
				SizeControl( userPane, bl_Width(controlRect), bl_Height(controlRect));
				//_BLAdjustScrollBarSizesAndLocations( inList );
			}
		}
	}
	return err;
}

//===============================
// We check to make sure the new rect is not too small; we do nothing if it is.
// We could also constrain the new rect to the minima, but that would give unexpected results.
OSErr _BLSetRect( const Rect* inRect, BlugsRef inList )
{
	OSErr		err = noErr;
	UInt16		minWidth, minHeight;
		
	BLGetMinimumSize( &minWidth, &minHeight, inList );
	if (blp_Width(inRect) < minWidth || blp_Height(inRect) < minHeight)
		err = inputOutOfBounds;
	else
	{
		GWorldPtr		world;
		Rect			saveRect;
		
		// New size for list is legal.
		// Save old rectangle to restore it on memory full err or such.
		// We put the new rect into the list so we can calculate
		//	how big the world needs to be.
		saveRect = bl_Rect(inList);
		if (bl_Aqua)
		{
			Rect	invalid;
			
			invalid = saveRect;
			InsetRect( &invalid, -5, -5 );
			InvalWindowRect( bl_Window(inList), &invalid );
		}
		bl_Rect(inList) = *inRect;
		world = bl_World(inList);
		if (world)
		{
			GWorldPtr		newWorld, saveWorld;
			Boolean			restore;
			Rect			globalRect;
			SInt16			depth = 0;
			GWorldFlags		flags;
			
			newWorld = world;
			restore = _BLGotoWindow( &saveWorld, inList );
			globalRect = _BLOffscreenRect( inList );
			_BLLocalToGlobalRect( &globalRect );
			if (bl_Test(_blGlobals.userSettings,blMax16BitGWorlds)) depth = 16;
			else if (bl_Test(_blGlobals.userSettings,blMax8BitGWorlds)) depth = 8;
			SetGWorld( world, nil );
			flags = UpdateGWorld( &newWorld, depth, &globalRect, nil, nil, 0 );
			if (bl_Test(flags,gwFlagErr))
			{
				err = QDError();
				if (!err) err = memFullErr;
				bl_Rect(inList) = saveRect;
			}
			else
			{
				if (newWorld != world) bl_World(inList) = newWorld;
				SetOrigin( inRect->left, inRect->top );
			}
			if (restore) SetGWorld( saveWorld, nil );
		}
		if (!err)
		{
			_BLAdjustScrollBarSizesAndLocations( inList );
			_BLRecalc( blRecalcVisCells | blRecalcScroll | blRecalcThumb | blRecalcRightPin, 0, inList );
			_BLUpdateWorld( blUpdateBlit | blUpdateBorder | blUpdateCorners, nil, inList );
		}
		else SysBeep( 30 );
	}
	return err;
}

//===============================
BL_PROC OSErr BLCellRect( BLCell inCell, Rect* outRect, BlugsRef inList )
{
	OSErr		err;
	Rect		r;
	
	err = _BLValidateList( inList );
	if (!err)
	{
		err = _BLValidateCellOrTitle( inCell, inList );
		if (!err)
		{
			UInt16			title;
			BLTitleBarHdl	bar;
			
			if (_BLCellToTitle( inCell, &title, &bar, inList))
				err = _BLTitleRect( title, bar, &r, inList );
			else err = _BLCellRect( inCell, &r, inList );
		}
	}
	if (!err) *outRect = r;
	else SetRect( outRect, 0, 0, 0, 0 );
	return err;
}

//===============================
// BSH 9/2/2000
// 1.0b1 was allowing the size to still get too small.
// The new algorithm is, if there is a scroll in some orientation,
//	the minimum for that orientation (horizontal->width) is increased by
//	at least three times the scroll bar dimension (16,11, dunno about Aqua).
//	And the minimum for the opposite orientation is increased by at least
//	the scroll bar dimension. So a normal-size horizontal scroll bar increases the
//	min width by  >= 48 pixels and the min height by >= 16.
BL_PROC void BLGetMinimumSize( UInt16* outMinWidth, UInt16* outMinHeight, BlugsRef inList )
{
	OSErr		err;
	UInt16		minW = 0, minH = 0;
	
	err = _BLValidateList( inList );
	_AssertStr(outMinWidth != nil,"Error: nil outMinWidth.");
	_AssertStr(outMinHeight != nil,"Error: nil outMinHeight.");
	if (!err)
	{
		BLTitleBarHdl	bar;
		SInt32			scrollSize;
		
		_BLMinView( &minW, &minH, inList );
		bar = bl_HTitle(inList);
		if (bar) minH += (*bar)->thickness;
		bar = bl_VTitle(inList);
		if (bar) minW += (*bar)->thickness;
		scrollSize = _BLScrollSize( inList );
		if (bl_HScroll(inList)) minH += scrollSize;
		if (bl_VScroll(inList)) minW += scrollSize;
	}
	if (outMinWidth) *outMinWidth = minW;
	if (outMinHeight) *outMinHeight = minH;
}

//===============================
// 11/14/2001 Moses -->1.1
// Use an update region to keep from drawing too much extra stuff,
//	especially under Aqua.
BL_PROC OSErr BLSetRowHeight( UInt16 inRow, UInt16 inHeight, BlugsRef inList )
{
	OSErr	err;
	
	_CheckHandlerFlag(inList)
	err = _BLValidateList( inList );
	if (!err)
	{
		if (!inHeight) err = paramErr;
		else
		{
			BLRecalcOptions	options = blRecalcVisCells | blRecalcVScroll | blRecalcThumb;
			Boolean			needUpdate = false;
			RgnHandle		updateRgn = nil;
			
			// Passing 0 for the row resizes the horizontal title bar.
			if (!inRow)
			{
				BLTitleBarHdl	bar;
				
				bar = bl_HTitle(inList);
				if (!bar) err = inputOutOfBounds;
				else
				{
					(*bar)->thickness = inHeight;
					needUpdate = true;
				}
			}
			else
			{
				err = _BLValidateRow( inRow, inList );
				if (!err)
				{
					UInt16			oldHt;
					
					oldHt = _BLGetRowHeight( inRow, inList );
					if (inHeight != oldHt)
					{
						_BLSetRowHeight( inRow, inHeight, inList );
						options |= blRecalcRowOffsets;
						needUpdate = true;
						updateRgn = NewRgn();
					}
				}
			}
			if (needUpdate)
			{
				Rect	viewRect;
				SInt16	top, bottom;
				
				_BLRecalc( options, inRow, inList );
				if (updateRgn)
				{
					viewRect = _BLViewRect( inList );
					(void)_BLRowTopBottom( inRow, &top, &bottom, inList );
					viewRect.top = top;
					viewRect.left = bl_Rect(inList).left;
					RectRgn( updateRgn, &viewRect );
				}
				_BLUpdateWorld( blUpdateBlit, updateRgn, inList );
			}
			if (updateRgn) DisposeRgn( updateRgn );
		}
	}
	return err;
}

//===============================
// 11/14/2001 Moses -->1.1
// Use an update region to keep from drawing too much extra stuff,
//	especially under Aqua.
BL_PROC OSErr BLSetColumnWidth( UInt16 inColumn, UInt16 inWidth, BlugsRef inList )
{
	OSErr	err;
	
	_CheckHandlerFlag(inList)
	err = _BLValidateList( inList );
	if (!err)
	{
		if (!inWidth) err = paramErr;
		else
		{
			BLRecalcOptions	options = blRecalcVisCells | blRecalcHScroll |
										blRecalcThumb | blRecalcRightPin;
			Boolean			needUpdate = false;
			RgnHandle		updateRgn = nil;
			
			if (!inColumn)
			{
				BLTitleBarHdl	bar;
				
				bar = bl_VTitle(inList);
				if (!bar) err = inputOutOfBounds;
				else
				{
					if (inWidth != (*bar)->thickness)
					{
						(*bar)->thickness = inWidth;
						needUpdate = true;
					}
				}
			}
			else
			{
				err = _BLValidateColumn( inColumn, inList );
				if (!err)
				{
					UInt16		oldWd;
					
					oldWd = _BLGetColumnWidth( inColumn, inList );
					if (inWidth != oldWd)
					{
						_BLSetColumnWidth( inColumn, inWidth, inList );
						options |= blRecalcColOffsets;
						needUpdate = true;
						updateRgn = NewRgn();
					}
				}
			}
			if (needUpdate)
			{
				Rect	viewRect;
				SInt16	left, right;
				
				_BLRecalc( options, inColumn, inList );
				if (updateRgn)
				{
					viewRect = _BLViewRect( inList );
					(void)_BLColumnLeftRight( inColumn, &left, &right, inList );
					viewRect.left = left;
					viewRect.top = bl_Rect(inList).top;
					RectRgn( updateRgn, &viewRect );
				}
				_BLUpdateWorld( blUpdateBlit, updateRgn, inList );
			}
			if (updateRgn) DisposeRgn( updateRgn );
		}
	}
	return err;
}

//===============================
BL_PROC OSErr BLSetDefaultCellSize( Point inSize, BlugsRef inList )
{
	OSErr	err;
	
	err = _BLValidateList( inList );
	if (!err)
	{
		if (inSize.h) (*inList)->defaultCellSize.h = inSize.h;
		if (inSize.v) (*inList)->defaultCellSize.v = inSize.v;
	}
	return err;
}

//===============================
BL_PROC UInt8 BLGetIndent( BlugsRef inList )
{
	OSErr	err;
	
	err = _BLValidateList( inList );
	return (err) ? 0 : bl_Indent(inList);
}

//===============================
BL_PROC OSErr BLSetIndent( UInt8 indent, BlugsRef inList )
{
	OSErr	err;
	
	_CheckHandlerFlag(inList)
	err = _BLValidateList( inList );
	if (!err)
	{
		if (indent > blMaxIndent) err = inputOutOfBounds;
		else
		{
			bl_Indent(inList) = indent;
			_BLUpdateWorld( blUpdateBlit | blUpdateCorners, nil, inList );
		}
	}
	return err;
}

//===============================
// Calculates minimum height and width for the view portion of the list.
// Used both for BLGetMinimumSize and for drag-resizing title bar thickness.
// All this really does is figure out how big the scroll bar buttons and thumb are.
void _BLMinView( UInt16* outMinWidth, UInt16* outMinHeight, BlugsRef inList )
{
	SInt32			minWidth = 0, minHeight = 0;
	ControlRef		hScroll, vScroll;
	BLTitleBarHdl	titleBar;
	
	hScroll = bl_HScroll(inList);
	vScroll = bl_VScroll(inList);
	if (hScroll)
	{
		minWidth += _BLMinScrollSize( inList );
		// If there is a vertical title bar, the scroll bar extends under it.
		titleBar = bl_VTitle(inList);
		if (titleBar) minWidth -= (*titleBar)->thickness;
		minWidth += _BLWidgetsTotalSize( false, inList );
	}
	if (vScroll)
	{
		minHeight += _BLMinScrollSize( inList );
		// If there is a vertical title bar, the scroll bar extends to the right of it,
		//	if there is no sort button.
		titleBar = bl_HTitle(inList);
		if (titleBar && !bl_SortButton(inList)) minHeight -= (*titleBar)->thickness;
		minHeight += _BLWidgetsTotalSize( true, inList );
	}
	if (minWidth < blStandardMinWidth) minWidth = blStandardMinWidth;
	if (minHeight < blStandardMinHeight) minHeight = blStandardMinHeight;
	*outMinWidth = minWidth;
	*outMinHeight = minHeight;
}

//===============================
// The smallest size a scroll bar can shrink to (on variable axis).
// This is used in determining minimum view size, and max widget size.
SInt16 _BLMinScrollSize( BlugsRef inList )
{
	return 3 * _BLScrollSize( inList ) + 2;
}


//===============================
void _BLAdjustScrollBarSizesAndLocations( BlugsRef inList )
{
	ControlRef		vScroll, hScroll;
	Rect			controlR, currentRect;
	
	vScroll = bl_VScroll(inList);
	hScroll = bl_HScroll(inList);
	if (hScroll)
	{
		controlR = _BLHScrollRect( inList );
		GetControlBounds( hScroll, &currentRect );
		//HideControl( hScroll );
		
		if (currentRect.left != controlR.left || currentRect.top != controlR.top)
			MoveControl( hScroll, controlR.left, controlR.top);
		
		if (bl_Width(currentRect) != bl_Width(controlR) || bl_Height(currentRect) != bl_Height(controlR))
			SizeControl( hScroll, bl_Width(controlR), bl_Height(controlR) );
		
		//if (visible) ShowControl( vScroll );
	}
	if (vScroll)
	{
		controlR = _BLVScrollRect( inList );
		GetControlBounds( vScroll, &currentRect );
		//HideControl( vScroll );
		
		if (currentRect.left != controlR.left || currentRect.top != controlR.top)
			MoveControl( vScroll, controlR.left, controlR.top);
		
		if (bl_Width(currentRect) != bl_Width(controlR) || bl_Height(currentRect) != bl_Height(controlR))
			SizeControl( vScroll, bl_Width(controlR), bl_Height(controlR) );
			
		//if (visible) ShowControl( vScroll );
	}
}

//===============================
Rect _BLHScrollAndWidgetsRect( BlugsRef inList )
{
	Rect	r, listRect;
	UInt8	thickness;
	Boolean	hasV;
	
	hasV = (bl_VScroll(inList) != nil);
	thickness = (bl_SmallScroll(inList)) ? blSmallScrollBarHeight : blLargeScrollBarHeight;
	listRect = bl_Rect(inList);
	r.bottom = listRect.bottom;
	r.top = r.bottom - thickness;
	r.left = listRect.left;
	if ((!bl_Aqua && bl_BorderMetrics(inList))) r.left--;
	r.right = listRect.right;
	// If there is a vertical scroll bar (meaning there is a grow/no-grow box area)
	//	assume border metrics.
	if (hasV || (!bl_Aqua && bl_BorderMetrics(inList))) r.right++;
	if (hasV || bl_HasGrow(inList)) r.right -= thickness;
	return r;
}

//===============================
Rect _BLHScrollRect( BlugsRef inList )
{
	Rect	r;
	
	r = _BLHScrollAndWidgetsRect( inList );
	r.left += _BLWidgetsTotalSize( false, inList );
	return r;
}

//===============================
Rect _BLVScrollAndWidgetsRect( BlugsRef inList )
{
	Rect	r, listRect;
	UInt8	thickness;
	UInt32	flags;
	Boolean	hasH;
	
	hasH = (bl_HScroll(inList) != nil);
	flags = (*inList)->listFlags;
	thickness = (bl_Test(flags,blSmallScroll)) ? blSmallScrollBarHeight : blLargeScrollBarHeight;
	listRect = bl_Rect(inList);
	r.right = listRect.right;
	r.left = r.right - thickness;
	r.top = listRect.top;
	if ((!bl_Aqua && bl_Test(flags,blBorderMetrics)) || bl_Test(flags,blDrawSortButton)) r.top--;
	// Reserve space for sort button.
	if (bl_Test(flags,blDrawSortButton))
	{
		BLTitleBarHdl	hBar;
		
		hBar = bl_HTitle(inList);
		// We can still make room for the sort button even if no htitle.
		r.top += ((hBar) ? (*hBar)->thickness : thickness);
	}
	r.bottom = listRect.bottom;
	// If there is a horizontal scroll bar (meaning there is a grow/no-grow box area)
	//	assume border metrics.
	if (hasH || (!bl_Aqua && bl_Test(flags,blBorderMetrics))) r.bottom++;
	if (hasH || bl_Test(flags,blHasGrow)) r.bottom -= thickness;
	return r;
}

//===============================
Rect _BLVScrollRect( BlugsRef inList )
{
	Rect	r;
	
	r = _BLVScrollAndWidgetsRect( inList );
	r.top += _BLWidgetsTotalSize( true, inList );
	return r;
}


//===============================
// Port-relative positions.
// invalidRect = -2036
OSErr _BLRowTopBottom( UInt16 inRow, SInt16* outTop, SInt16* outBottom, BlugsRef inList )
{
	Rect		viewRect;
	SInt32		bottom;
	OSErr		err;
	
	viewRect = _BLViewRect( inList );
	bottom = _BLRowBottom( inRow, inList ) - bl_VScrollLoc(inList) + viewRect.top;
	if (bottom > 32767 || bottom < -32768) err = invalidRect;
	else err = noErr;
	*outBottom = bottom;
	*outTop = bottom - _BLGetRowVisibleHeight( inRow, inList );
	return err;
}

//===============================
// Port-relative positions
OSErr _BLColumnLeftRight( UInt16 inColumn, SInt16* outLeft, SInt16* outRight, BlugsRef inList )
{
	Rect		viewRect;
	SInt32		right;
	OSErr		err;
	
	viewRect = _BLViewRect( inList );
	right = _BLColumnRight( inColumn, inList ) - bl_HScrollLoc(inList) + viewRect.left;
	if (right > 32767 || right < -32768) err = invalidRect;
	else err = noErr;
	*outRight = right;
	*outLeft = right - _BLGetColumnWidth( inColumn, inList );
	return err;
}

//===============================
// Yes, we should be using 32-bit rectangle components to be safe here.
OSErr _BLCellRect( BLCell inCell, Rect* outRect, BlugsRef inList )
{
	Rect		r;
	OSErr		err;
	
	err = _BLRowTopBottom( inCell.row, &r.top, &r.bottom, inList );
	if (!err)
	{
		Boolean	titleRow = bl_Test(BLGetRowFlags( inCell.row, inList ),blRowIsTitleRow);
		
		err = _BLColumnLeftRight( inCell.col, &r.left, &r.right, inList );
		if (titleRow) r.right = r.left + bl_TotalWd(inList);
	}
	if (!err) *outRect = r;
	else SetRect( outRect, 0, 0, 0, 0 );
	return err;
}

//===============================
// This is the rectangle of just the cells area, no scroll bars and no titles.
Rect _BLViewRect( BlugsRef inList )
{
	Rect			r;
	BLTitleBarHdl	theBar;
	SInt32			scrollSize;
	
	r = bl_Rect(inList);
	theBar = bl_HTitle(inList);
	if (theBar) r.top += (*theBar)->thickness;
	theBar = bl_VTitle(inList);
	if (theBar) r.left += (*theBar)->thickness;
	scrollSize = _BLScrollSize( inList );
	if (bl_HasHScroll(inList)) r.bottom -= scrollSize;
	if (bl_HasVScroll(inList)) r.right -= scrollSize;
	return r;
}

//===============================
// This is the rectangle of stuff drawn offscreen. Title bars yes, scroll bars no.
Rect _BLOffscreenRect( BlugsRef inList )
{
	Rect	r;
	SInt32	scrollSize;
	
	r = bl_Rect(inList);
	scrollSize = _BLScrollSize( inList );
	if (bl_HasHScroll(inList)) r.bottom -= scrollSize;
	if (bl_HasVScroll(inList)) r.right -= scrollSize;
	return r;
}

//===============================
// The rectangle of a grow box. Bottom right corner, either 11 or 16 pixels square depending
//	on whether the list as small or normal scroll bars.
Rect _BLGrowBoxRect( BlugsRef inList )
{
	Rect	boxRect;
	SInt32	scrollSize;
	
	scrollSize = _BLScrollSize( inList );
	boxRect = bl_Rect(inList);
	boxRect.left = boxRect.right - scrollSize;
	boxRect.top = boxRect.bottom - scrollSize;
	return boxRect;
}

//===============================
// Width of just the cells area, no scroll bars and no titles.
UInt16 _BLViewWidth( BlugsRef inList )
{
	Rect			r;
	BLTitleBarHdl	theBar;
	
	r = bl_Rect(inList);
	theBar = bl_VTitle(inList);
	if (theBar) r.left += (*theBar)->thickness;
	if (bl_HasVScroll(inList)) r.right -= _BLScrollSize( inList );
	return bl_Width(r);
}

//===============================
UInt16 _BLViewHeight( BlugsRef inList )
{
	Rect			r;
	BLTitleBarHdl	theBar;
	
	r = bl_Rect(inList);
	theBar = bl_HTitle(inList);
	if (theBar) r.top += (*theBar)->thickness;
	if (bl_HasHScroll(inList)) r.bottom -= _BLScrollSize( inList );
	return bl_Height(r);
}

//===============================
// Pixel distance from the top of the list to the bottom of the row
BLPixelOffset _BLRowBottom( UInt16 inRow, BlugsRef inList )
{
	BLPixelOffset	bottom;
	
	if (!inRow) bottom = 0;
	else bottom = _BLGetRowOffsetFromTop( inRow, inList );
	return bottom;
}

//===============================
// Pixel distance from the top of the list to the top of the row
BLPixelOffset _BLRowTop( UInt16 inRow, BlugsRef inList )
{
	BLPixelOffset	top;
	
	if (inRow < 2) top = 0;
	else top = _BLGetRowOffsetFromTop( inRow - 1, inList );
	return top;
}

//===============================
// Pixel distance from the left of the list to the right side of the column
BLPixelOffset _BLColumnRight( UInt16 inColumn, BlugsRef inList )
{
	BLPixelOffset	right;
	
	if (!inColumn) right = 0;
	else right = _BLGetColumnOffsetFromLeft( inColumn, inList );
	return right;
}

//===============================
// Pixel distance from the left of the list to the left side of the column
BLPixelOffset _BLColumnLeft( UInt16 inColumn, BlugsRef inList )
{
	BLPixelOffset	left;
	
	if (inColumn < 2) left = 0;
	else left = _BLGetColumnOffsetFromLeft( inColumn - 1, inList );
	return left;
}

//===============================
Boolean _BLCellIsVisible( BLCell inCell, BlugsRef inList )
{
	BLCellRange	visCells;
	Boolean		visible = false;
	
	visCells = (*inList)->visCells;
	if ((inCell.row >= visCells.top) && (inCell.row <= visCells.bottom) &&
		(inCell.col >= visCells.left) && (inCell.col <= visCells.right)) visible = true;
	return visible;
}

//===============================
// Gets the rect for the sort button at the upper right of the list, above the scroll bar.
// These calculations are based on the vertical scroll bar's metrics, which should be entirely
//	correct for these purposes (it takes into account the sort button, and whether there's
//	an hTitle.
Rect _BLSortButtonRect( BlugsRef inList )
{
	Rect			r = {0,0,0,0};
	ControlRef		vScroll;
	
	vScroll = bl_VScroll(inList);
	if (vScroll)
	{
		BLTitleBarHdl	hBar;
		
		GetControlBounds( vScroll, &r );
		r.top -= _BLWidgetsTotalSize( true, inList );
		r.top++;
		r.bottom = r.top;
		hBar = bl_HTitle(inList);
		r.top -= ((hBar) ? (*hBar)->thickness : blLargeScrollBarHeight);
	}
	return r;
}

//===============================
// Takes a point local to the list's port, returns the cell it's in.
// If inTreatTitleRowAsOneCol is true, translates title row cells to indicate column 1.
//	Otherwise (as is needed in marquee-selection) it treats all rows as multi-column.
// WARNING! If you call this with a point outside all cells (e.g., in the bottom blank area),
//	you will get an out-of-range cell. This happens when drag-selecting and hit-testing.
// For example, if the point is just to the right of the last column, the cell will have zero
//	in the col field, but the row will be correct.
BLCell _BLPointToCell( Boolean inTreatTitleRowAsOneCol, Point inWhere, BlugsRef inList )
{
	BLCell			cell = {0,0};
	UInt16			i;
	Rect			viewRect;
	BLCellRange		visCells;
	
	visCells = (*inList)->visCells;
	viewRect = _BLViewRect( inList );
	if (visCells.top)
	{
		SInt16			cursDYFromLTop;
		BLPixelOffset	mouseOffsetFromTop;
		
		// Relation between cursor and top of list
		cursDYFromLTop = inWhere.v - viewRect.top;
		mouseOffsetFromTop = bl_VScrollLoc(inList) + cursDYFromLTop;
		// Find what row contains the vertical coordinate
		for (i = visCells.top; i <= visCells.bottom; i++)
		{
			if (_BLGetRowOffsetFromTop( i, inList ) > mouseOffsetFromTop)
			{
				cell.row = i;
				break;
			}
		}
	}
	if (visCells.left)
	{
		SInt16			cursDXFromLLeft;
		BLPixelOffset	mouseOffsetFromLeft;
		
		// Relation between cursor and left of list
		cursDXFromLLeft = inWhere.h - viewRect.left;
		mouseOffsetFromLeft = bl_HScrollLoc(inList) + cursDXFromLLeft;
		// Find what row contains the horizontal coordinate
		for (i = visCells.left; i <= visCells.right; i++)
		{
			if (_BLGetColumnOffsetFromLeft( i, inList ) > mouseOffsetFromLeft)
			{
				cell.col = i;
				break;
			}
		}
	}
	// If the row is a title row, we force column number to be 1, unless we are in a blank rect. 
	if (inTreatTitleRowAsOneCol)
		if (cell.row && cell.col)
			if (cell.col != 1)
				if (bl_Test(_BLGetRowFlags( cell.row, inList ),blRowIsTitleRow)) cell.col = 1;
	return cell;
}

//===============================
// inStartHere is for the col and row offsets.
void _BLRecalc( BLRecalcOptions inOptions, UInt16 inStartHere, BlugsRef inList )
{
	ControlRef	control;
	
	if (bl_Test(inOptions,blRecalcColOffsets)) local_BLRecalcColOffsets( inStartHere, inList );
	if (bl_Test(inOptions,blRecalcRowOffsets)) local_BLRecalcRowOffsets( inStartHere, inList );
	if (bl_Test(inOptions,blRecalcVisCells)) local_BLRecalcVisCells( inList );
	if (bl_Test(inOptions,blRecalcRightPin)) local_BLRecalcPinRightCol( &inOptions, inList );
	if (bl_Test(inOptions,blRecalcVScroll))
	{
		control = bl_VScroll(inList);
		if (control) local_BLRecalcVScroll( bl_Test(inOptions,blRecalcThumb), control, inList );
	}
	if (bl_Test(inOptions,blRecalcHScroll))
	{
		control = bl_HScroll(inList);
		if (control) local_BLRecalcHScroll( bl_Test(inOptions,blRecalcThumb), control, inList );
	}
}

//===============================
// This routine makes sure all offsets are in synch with row/column metrics
void local_BLRecalcColOffsets( UInt16 inStartHere, BlugsRef inList )
{
	UInt32				offset;
	UInt16				nCols;
	
	nCols = bl_NCols(inList);
	if (!inStartHere) inStartHere = 1;
	if (inStartHere == 1) offset = 0;
	else offset = _BLGetColumnOffsetFromLeft( inStartHere - 1, inList );
	for (; inStartHere <= nCols; inStartHere++)
	{
		UInt16		width;
		
		// For each entry, add its width to the offset of the previous row/col
		width = _BLGetColumnWidth( inStartHere, inList );
		_BLSetColumnOffsetFromLeft( inStartHere, offset + width, inList );
		offset += width;
	}
	bl_TotalWd(inList) = offset; // Safe to use as inited or a calculated in loop
}

//===============================
// This routine makes sure all offsets are in synch with row/column metrics
// Also, checks disclosure and if set, makes visibleHeight and offset reflect
//	whether or not the row is disclosed.
void local_BLRecalcRowOffsets( UInt16 inStartHere, BlugsRef inList )
{
	UInt32				offset;
	Boolean				disclosure;
	UInt16				nRows, height;
	
	disclosure = bl_Disclosure(inList);
	nRows = bl_NRows(inList);
	// If disclosure, then we really need to start at the beginning to take all parent rows
	//	into account
	if (!inStartHere || disclosure) inStartHere = 1;
	if (inStartHere == 1) offset = 0;
	else offset = _BLGetRowOffsetFromTop( inStartHere - 1, inList );
	if (disclosure)
	{
		for (; inStartHere <= nRows; inStartHere++)
		{
			UInt16		visHt;
			
			// For each entry, add its height to the offset of the previous row/col
			height = _BLGetRowHeight( inStartHere, inList );
			//height = entryP->height;
			if (bl_Test(_BLGetRowFlags( inStartHere, inList ),blUndisclosed)) visHt = 0;
			else visHt = height;
			_BLSetRowVisibleHeight( inStartHere, visHt, inList );
			_BLSetRowOffsetFromTop( inStartHere, offset + visHt, inList );
			offset += visHt;
		}
	}
	else for (; inStartHere <= nRows; inStartHere++)
	{
		// For each entry, add its height to the offset of the previous row/col
		height = _BLGetRowHeight( inStartHere, inList );
		_BLSetRowVisibleHeight( inStartHere, height, inList );
		_BLSetRowOffsetFromTop( inStartHere, offset + height, inList );
		offset += height;
	}
	// Set the list total height to reflect the situation with visible rows.
	bl_TotalHt(inList) = offset;
}

//===============================
static void local_BLRecalcVisCells( BlugsRef inList )
{
	UInt16			nRows, nCols, i;
	UInt32			vLoc, hLoc, bLoc, rLoc, rBottom, cRight;
	Rect			viewRect;
	BLCellRange		visCells;
	
	viewRect = _BLViewRect( inList );
	nRows = bl_NRows(inList);
	nCols = bl_NCols(inList);
	vLoc = bl_VScrollLoc(inList);
	hLoc = bl_HScrollLoc(inList);
	bLoc = vLoc + bl_Height(viewRect);
	rLoc = hLoc + bl_Width(viewRect);
	
	// If we are scrolled below all cells, none are visible
	if (vLoc >= bl_TotalHt(inList)) visCells.top = visCells.bottom = 0;
	else // Some are visible...
	{
		// Find the first cell whose bottom is greater than scroll location
		for (i = 1; i <= nRows; i++)
		{
			rBottom = _BLRowBottom( i, inList );
			if (rBottom > vLoc) break;
		}
		// That's the top visible row
		visCells.top = i;
		// Now continue towards the bottom
		// Find the first cell whose bottom is below view edge
		for (; i <= nRows; i++)
		{
			rBottom = _BLRowBottom( i, inList );
			if (rBottom >= bLoc)
			{
				visCells.bottom = i;
				break;
			}
		}
		// If the loop didn't get an answer for us, use the bottom row
		if (i > nRows) visCells.bottom = nRows;
	}
	// Do the same deal for the horizontal dimension
	if (hLoc >= bl_TotalWd(inList)) visCells.left = visCells.right = 0;
	else
	{
		for (i = 1; i <= nCols; i++)
		{
			cRight = _BLColumnRight( i, inList );
			if (cRight > hLoc) break;
		}
		visCells.left = i;
		for (; i <= nCols; i++)
		{
			cRight = _BLColumnRight( i, inList );
			if (cRight >= rLoc)
			{
				visCells.right = i;
				break;
			}
		}
		if (i > nCols) visCells.right = nCols;
	}
	// Record the new bounds in the list
	(*inList)->visCells = visCells;
}

//===============================
// 11/20/2000 Moses
// Removed check for bl_Active because we can autoscroll (or app can call to scroll)
//	an inactive list during a drag. We need to recalc for when the list is reactivated.
static void local_BLRecalcVScroll( Boolean inRecalcThumb, ControlRef inVScrollBar, BlugsRef inList )
{
	UInt16			viewHt;
	UInt32			vScrollLoc, totalHt;
	
	vScrollLoc = bl_VScrollLoc(inList);
	totalHt = bl_TotalHt(inList);
	viewHt = _BLViewHeight( inList );
	// If everything is visible, disable scroll bar
	if ((vScrollLoc == 0) && (totalHt <= viewHt)) local_BLDisableScrollBar( inVScrollBar );
	else if (inRecalcThumb) local_BLRecalcThumb( inVScrollBar, vScrollLoc, viewHt, totalHt );
}

//===============================
// Removed check for bl_Active because we can autoscroll (or app can call to scroll)
//	an inactive list during a drag. We need to recalc for when the list is reactivated.
static void local_BLRecalcHScroll( Boolean inRecalcThumb, ControlRef inHScrollBar, BlugsRef inList )
{
	UInt16			viewWd;
	UInt32			hScrollLoc, totalWd;
	
	hScrollLoc = bl_HScrollLoc(inList);
	totalWd = bl_TotalWd(inList);
	viewWd = _BLViewWidth( inList );
	// If everything is visible, disable scroll bar
	if ((hScrollLoc == 0) && (totalWd <= viewWd)) local_BLDisableScrollBar( inHScrollBar );
	else if (inRecalcThumb) local_BLRecalcThumb( inHScrollBar, hScrollLoc, viewWd, totalWd );
}

//===============================
static void local_BLDisableScrollBar( ControlRef inScrollBar )
{
	if (bl_ControlMgr2)
	{
		SetControl32BitMinimum( inScrollBar, 0L );
		SetControl32BitMaximum( inScrollBar, 0L );
		SetControl32BitValue( inScrollBar, 0L );
	}
	else
	{
		SetControlMinimum( inScrollBar, 0 );
		SetControlMaximum( inScrollBar, 0 );
		SetControlValue( inScrollBar, 0 );
	}
}

//===============================
static void local_BLRecalcThumb( ControlRef inScrollBar, UInt32 inScrollLoc,
									UInt16 inViewSize, UInt32 inTotalSize )
{
	UInt32		edge, edgeLoc;
	
	/*	'Edge' represents the furthest location to which we can scroll; you can think of
		it as the hightest possible value for inScrollLoc. Typically it is the measure in
		whatever dimension minus the size of the list, but sometimes we must infer it
		from where we already are -- could have deleted cells and thus be scrolled past
		what should be the edge; in a sense, deleting cells puts us in an 'illegal'
		scroll position. If bottom of bottom-most cell is visible then obviously we can't
		scroll down any further.
		
		Any time a blank area is present, either the view rect is more than big enough
		for what cells there are (in which scroll bars will be disabled),
		or we're at one of these 'illegal' positions.
		
			"It's that simple."
				-- H. Ross Perot */
	edgeLoc = inScrollLoc + inViewSize; // The edge in list coordinates.
	if (inTotalSize <= edgeLoc) edge = inScrollLoc;  // our 'illegal' scroll loc
	else edge = inTotalSize - inViewSize; // Can't scroll farther than scroll loc = edge
	if (bl_ControlMgr2)
	{
		// Since we shift this value, make sure it won't be lost
		if (edge == 1) edge = 2;
		SetControl32BitMinimum( inScrollBar, 0L );
		SetControl32BitMaximum( inScrollBar, edge >> 1 );
		SetControl32BitValue( inScrollBar, inScrollLoc >> 1 );
		// Now set the view size for the proportional thumb.
		SetControlViewSize( inScrollBar, inViewSize >> 1);
	}
#if !TARGET_RT_MAC_MACHO
	else
	{
		// For the 16-bit version of the control value calculation, we maximize
		//	the range of our numbers.
		SInt16		val16;
		double_t	floatEdge, floatScrollLoc;
		
		SetControlMinimum( inScrollBar, blMinShort );
		SetControlMaximum( inScrollBar, blMaxShort );
		floatEdge = edge;
		floatScrollLoc = inScrollLoc;
		// Yes, we are *guaranteed* a nonzero edge value. No danger of divide-by-zero.
		val16 = roundtol( floatScrollLoc * 65535.0 / floatEdge ) - 32768;
		SetControlValue( inScrollBar, val16 );
	}
#endif // !TARGET_RT_MAC_MACHO
}

//===============================
// This is only called from _BLRecalc. It may make modifications that require extra recalculation.
static void local_BLRecalcPinRightCol( BLRecalcOptions* ioOptions, BlugsRef inList )
{
	//Boolean			madeChange = false;
	
	if (!bl_Test((*inList)->privateFlags,blDraggingTitle))
	{
		if (bl_PinRight(inList))
		{
			UInt16			nCols;
			
			nCols = bl_NCols(inList);
			if (nCols)
			{
				SInt16	left, right;
				Rect	viewRect;
				OSErr	err;
				
				viewRect = _BLViewRect( inList );
				err = _BLColumnLeftRight( nCols, &left, &right, inList );
				if (!err)
				{
					UInt16	 min, newWidth = 0;
					
					min = _BLGetColMinimumWidth( nCols, inList );
					if (right < viewRect.right) newWidth = viewRect.right - left;
					else if (right > viewRect.right)
					{
						if (viewRect.right > left && viewRect.right - left >= min)
							newWidth = viewRect.right - left;
					}
					if (newWidth && _BLGetColumnWidth( nCols, inList ) != newWidth)
					{
						_BLSetColumnWidth( nCols, newWidth, inList );
						local_BLRecalcColOffsets( nCols, inList );
						bl_Set(*ioOptions,blRecalcHScroll | blRecalcThumb);
						//viewRect.left = left;
						_BLUpdateWorld( blUpdateBlit, nil, inList );
					}
				}
			}
		}
	}
}


//===============================
// Content handler-defined minima take priority, then application-defined minima
//	(not yet implemented), then lastly Blugs default constants.
UInt16 _BLGetColMinimumWidth( UInt16 inColumn, BlugsRef inList )
{
	UInt16	min, titleMin;
	
	min = local_BLCalcColMinimumWidth( inColumn, inList );
	titleMin = local_BLCalcTitleMinimum( false, inColumn, inList );
	if (titleMin > min) min = titleMin;
	// Insert support for app-defined minima here
	if (min < blDefaultMinSize) min = blDefaultMinSize;
	return min;
}

//===============================
UInt16 _BLGetRowMinimumHeight( UInt16 inRow, BlugsRef inList )
{
	UInt16	min, titleMin;
	
	min = local_BLCalcRowMinimumHeight( inRow, inList );
	titleMin = local_BLCalcTitleMinimum( true, inRow, inList );
	if (titleMin > min) min = titleMin;
	if (min < blDefaultMinSize) min = blDefaultMinSize;
	return min;
}

//===============================
// Set region to cell rectangle, get region if supported, make sure handler's region is within
//	the cell rectangle. (Doing otherwise can mess up drag images in alarming ways.)
void _BLCellRegion( RgnHandle inRgn, BLCell inCell, const Rect* inCellRect, BlugsRef inList )
{
	UInt32			features;
	OSErr			err;
	
	RectRgn( inRgn, inCellRect );
	err = _BLGetCellHandlerFeatures( inCell, &features, inList );
	if (!err)
	{
		if (bl_Test(features,blWantsRegion))
		{
			BLHandlerParamBlock		pb;
			UInt32					reply;
			RgnHandle				tempRgn = NewRgn();
			
			if (tempRgn)
			{
				pb.inRect = *inCellRect;
				pb.inIndent = _BLIndent( inCell, inList );
				pb.inRegion = tempRgn;
				reply = _BLCallHandlerForCell( blCellRegionMsg, inCell, &pb, inList );
				SectRgn( tempRgn, inRgn, inRgn );
				DisposeRgn( tempRgn );
			}
		}
	}
}

//===============================
void _BLInlineRegion( RgnHandle inRgn, BLCell inCell, const Rect* inCellRect, BlugsRef inList )
{
	BLHandlerParamBlock	pb;
	UInt32				reply;
	
	pb.inRect = *inCellRect;
	pb.inIndent = _BLIndent( inCell, inList );
	pb.inRegion = inRgn;
	reply = _BLCallHandlerForCell( blInlineEditRegionMsg, inCell, &pb, inList );
}

//===================================
void _BLLocalToGlobalRect( Rect* ioRect )
{
	LocalToGlobal((Point*)(&ioRect->top));
	LocalToGlobal((Point*)(&ioRect->bottom));
}

//===============================
// Figure out how big a scroll bar is.
SInt32 _BLScrollSize( BlugsRef inList )
{
	SInt32		scrollSize;
#if TARGET_API_MAC_CARBON
	OSStatus	err;
#endif
	
#if TARGET_API_MAC_CARBON
	err = GetThemeMetric( (bl_SmallScroll(inList)) ? kThemeMetricSmallScrollBarWidth :
							kThemeMetricScrollBarWidth, &scrollSize );
	if (err) // Do it the normal way if there is an error....
#endif
	scrollSize = (bl_SmallScroll(inList)) ? blSmallScrollBarWidth : blLargeScrollBarWidth;
	return scrollSize;
}

//===============================
// Queries each content handler in column (if not table) to see what's the smallest
//	minimum, and returns it. Returns 0 if no handler has a preference.
static UInt16 local_BLCalcColMinimumWidth( UInt16 inColumn, BlugsRef inList )
{
	UInt16		min = 0, nRows;
	BLCell		cell;
	
	// Just check the content type stored in the column data once if it's a table.
	if (bl_Table(inList)) nRows = 1;
	else nRows = bl_NRows(inList);
	cell.col = inColumn;
	for (cell.row = 1; cell.row <= nRows; cell.row++)
	{
		UInt32			features;
		OSErr			err;
		
		err = _BLGetCellHandlerFeatures( cell, &features, inList );
		if (err) continue;
		if (bl_Test(features,blWantsMin))
		{
			BLHandlerParamBlock		pb;
			
			_BLCallHandlerForCell( blCellMinSizeMsg, cell, &pb, inList );
			if (pb.outMinWidth > min) min = pb.outMinWidth;
		}
	}
	return min;
}

//===============================
static UInt16 local_BLCalcRowMinimumHeight( UInt16 inRow, BlugsRef inList )
{
	UInt16		min = 0, nCols;
	BLCell		cell;
	
	nCols = bl_NCols(inList);
	cell.row = inRow;
	for (cell.col = 1; cell.col <= nCols; cell.col++)
	{
		UInt32					features;
		BLHandlerParamBlock		pb;
		OSErr					err;
		
		err = _BLGetCellHandlerFeatures( cell, &features, inList );
		if (err) continue;
		if (bl_Test(features,blWantsMin))
		{
			_BLCallHandlerForCell( blCellMinSizeMsg, cell, &pb, inList );
			if (pb.outMinHeight > min) min = pb.outMinHeight;
		}
	}
	return min;
}

//===============================
// Queries the content handler for a title bar title to get the minimum:
//	if vert, it gets the height from the vertical title bar;
//	if not, it gets width from horizontal.
static UInt16 local_BLCalcTitleMinimum( Boolean inVertical, UInt16 inTitle, BlugsRef inList )
{
	UInt16					min = 0;
	BLTitleBarHdl			bar;
	
	if (inVertical) bar = bl_VTitle(inList);
	else bar = bl_HTitle(inList);
	if (bar)
	{
		BLContentType			content;
		UInt32					features;
		OSErr					err;
		
		content = _BLGetTitleContentType( inTitle, bar );
		err = _BLGetHandlerFeatures( content, &features );
		if (!err)
		{
			if (bl_Test(features,blWantsMin))
			{
				BLHandlerParamBlock	pb;
				
				_BLCallHandlerForTitle( blCellMinSizeMsg, inTitle, &pb, bar, inList );
				min = (inVertical) ? pb.outMinHeight : pb.outMinWidth;
			}
		}
	}
	return min;
}
