/*===============================
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"


#pragma mark ¥¥¥Module Data¥¥¥


static BLPart		local_BLHitTestCell( Point inWhere, BLHitTestPtr ioHitTest, BlugsRef inList );
static BLPart		local_BLHitTestScrollBars( Point inWhere, BLHitTestPtr ioHitTest, BlugsRef inList );
static BLPart		local_BLHitTestTitleBars( Point inWhere, BLHitTestPtr ioHitTest, BlugsRef inList );
static UInt8		local_BLCellClick( BLHitTestPtr inTest, UInt32 inWhen, Point inWhereLocal,
										EventModifiers inModifiers, BlugsRef inList, Boolean* outDoClickLoop );
static void			local_BLClickLoop( Point inWhere, EventModifiers inModifiers, BLPart inPart,
										BLCell inClickedCell, BlugsRef inList );
static void			local_BLScrollBarClick( Point inWhere, EventModifiers inModifiers,
											ControlRef inControl, ControlPartCode inPart,
											BlugsRef inList );
#if !TARGET_RT_MAC_MACHO
static void			local_BLSortButtonClick( const Rect* inSortButtonRect, BlugsRef inList );
#endif
static void			local_BLTopLeftClick( Point inWhere, EventModifiers inModifiers,
											const Rect* inRect, BlugsRef inList );
static void			local_BLShiftClick( BLCell inClickedCell, BlugsRef inList );
static void			local_BLCommandClick( BLCell inClickedCell, BlugsRef inList );
static void			local_BLDragSelect( BLCell inAnchorCell, Point inAnchorPoint,
										EventModifiers inModifiers, BlugsRef inList );
static void			local_BLGrabScroll( Point inAnchorPoint, BlugsRef inList );
static void			local_BLCellsToCellRange( BLCell inFirstCell, BLCell inSecondCell,
												BLCellRange* outRange );
static void			local_BLTrackDisclosure( BLHitTestPtr inHitTest, Point inWhere,
											EventModifiers inModifiers, BlugsRef inList );
static Boolean		local_BLCellIsFirst( BLCell inFirst, BLCell inSecond );
static Boolean		local_BLPreviousSelected( BLCell* ioCell, BlugsRef inList );
static void			local_BLDeselectAllBut( Boolean inUpdate, BLCell inButThisOne, BlugsRef inList );
static void			local_BLPtInHResizeZone( Point inWhere, UInt16 inTitle,
											BLHitTestPtr ioHitTest, BlugsRef inList );
static void			local_BLPtInVResizeZone( Point inWhere, UInt16 inTitle,
											BLHitTestPtr ioHitTest, BlugsRef inList );


#pragma mark -
//===============================
// Now returns 1, 2, or 3 clicks only when the click is in a cell's content area.
// Otherwise returns 0. So a title bar click returns 0. Maybe return value should be 1 if
//	Blugs handles it, no matter where the click is; 2 or 3 would be reserved for cells only.
// Who the heck cares if you triple-click in the sort button.
BL_PROC BLClickResult BLClick( UInt32 inWhen, Point inWhereLocal, EventModifiers inModifiers,
								BlugsRef inList )
{
	BLClickResult	result = blNothingHappenedClickResult;
	OSErr			err;
	
	_CheckHandlerFlag(inList)
	err = _BLValidateList( inList );
	if (!err) result = _BLClick( true, inWhen, inWhereLocal, inModifiers, inList );
	return result;
}

//===============================
// Now returns 1, 2, or 3 clicks only when the click is in a cell's content area.
// Otherwise returns 0. So a title bar click returns 0. Maybe return value should be 1 if
//	Blugs handles it, no matter where the click is; 2 or 3 would be reserved for cells only.
// Who the heck cares if you triple-click in the sort button.
BLClickResult _BLClick( Boolean inHitTestScroll, UInt32 inWhen, Point inWhereLocal,
						EventModifiers inModifiers, BlugsRef inList )
{
	BLClickResult	result = blNothingHappenedClickResult;
	OSErr			err = noErr;
	
	if (/*bl_Active(inList) && */bl_Visible(inList))
	{
		Rect		r;
		
		r = bl_Rect(inList);
		if (PtInRect( inWhereLocal, &r ))
		{
			GWorldPtr		saveWorld;
			Boolean			restore;
			Rect			viewRect;
			Boolean			inlineHandled = false, doClickLoop = false;
			BLCell			clickedCell;
			UInt16			newSelection; // New title selection
			BLHitTestRec	test;
			
			bl_Set((*inList)->privateFlags,blSuppressIdle);
			restore = _BLGotoWindow( &saveWorld, inList );
			viewRect = _BLViewRect( inList );
			(void)_BLHitTest( inHitTestScroll, inWhereLocal, &test, inList );
			clickedCell = test.cell;
			/*	When there is an inline edit going on, a click anywhere in the list ends it.
				So, _BLInlineEditClick should always end the edit session unless the click was
				explicitly taken care of by the handler. A click on a title bar, for example,
				should end the edit (since we can see right away that the click wasn't in a cell)
				and then keep processing the click to hilite the title. */
			if (bl_InlineNow(inList))
				inlineHandled = _BLInlineEditClick( clickedCell, inWhereLocal, inModifiers, inList );
			else
			{
				// If we have an inline session pending we clear it if we click anywhere
				//	except in that cell.
				// We could further require that the part hit be the content area and not just
				//	the cell rect, as I have commented out below.
				if (bl_PendingInline(inList))
				{
					BLCellUID	cellUID;
					
					(void)BLGetCellUID( clickedCell, &cellUID, inList );
					if (!_BLEqualCellUID( &cellUID, &bl_InlineCell(inList) )
						 /* || part != blCellRegionPart */ )
						bl_Clear((*inList)->privateFlags,blPendingInline);
				}
			}
			switch (test.part)
			{
				case blHScrollPart:
				local_BLScrollBarClick( inWhereLocal, inModifiers, bl_HScroll(inList),
										test.u.controlPart, inList );
				break;
				
				case blVScrollPart:
				local_BLScrollBarClick( inWhereLocal, inModifiers, bl_VScroll(inList),
										test.u.controlPart, inList );
				break;
				
				case blHTitleBarTitlePart:
				case blHTitleBarFillerPart:
				{
					UInt16	currentSortColumn;
					
					currentSortColumn = BLGetColumnFromUID( &(*inList)->primarySortColumn, inList );
					if (_BLHTitleBarClick( &test, inWhereLocal, inModifiers, inWhen, &newSelection, inList ))
					{
						// When we select a title that wasn't selected before,
						//	the best thing to do is to sort in the natural direction:
						//	that is, small to large. We set the list's sort direction to this and
						//	sort without 'advancing' to a new state.
						BLGetColumnUID( newSelection, &(*inList)->primarySortColumn, inList );
					#if __BL_USE_CARBON_API__
						if (currentSortColumn == newSelection)
						{
							if (bl_Aqua)
							{
								BLSortState		newState;
								
								newState = (*inList)->sortState;
								if (bl_Test(newState,blSortStateSorted))
									bl_Toggle(newState,blSortStateLargeToSmall);
								else newState = blSortStateSorted;
								_BLSort( newState, inList );
							}
						}
						else
					#endif
							_BLSort( blSortStateSorted, inList );
					}
				}
				break;
				
				case blVTitleBarTitlePart:
				case blVTitleBarFillerPart:
				_BLVTitleBarClick( &test, inWhereLocal, inModifiers, inWhen, inList );
				break;
				
				case blTopLeftPart:
				local_BLTopLeftClick( inWhereLocal, inModifiers, &test.rect, inList );
				break;
				
			#if !TARGET_RT_MAC_MACHO
				case blSortButtonPart:
				local_BLSortButtonClick( &test.rect, inList );
				break;
			#endif
				
				// If the cell is the inline cell, it's already been processed.
				// Since we pass a valid region handle to BLHitTest, we will get back a region
				//	that might be the whole cell rect, or something handler-defined.
				// We only want to drag, select, etc. if the user is messing with the real cell
				//	contents.
				case blCellPart:
				case blCellRegionPart:
				if (!inlineHandled) result = local_BLCellClick( &test, inWhen, inWhereLocal,
																inModifiers, inList, &doClickLoop );
				else doClickLoop = true;
				break;
				
				case blRightBlankPart:
				case blBottomBlankPart:
				case blBottomRightBlankPart:
				if (!(inModifiers & shiftKey)) _BLDeselectAll( true, inList );
				doClickLoop = true;
				break;
				
			#if !TARGET_RT_MAC_MACHO
				case blGrowBoxPart:
				// We should only react to grow box if we are also in charge of drawing it.
				if (bl_DrawGrow(inList)) result = blGrowBoxClickResult;
				break;
			#endif
			
				case blDisclosureTrianglePart:
				local_BLTrackDisclosure( &test, inWhereLocal, inModifiers, inList );
				break;
				
				case blWidgetPart:
				_BLTrackWidgetClick( &test, inWhereLocal, inModifiers, inList );
				break;
				
				default:
				_BLDeselectAll( true, inList );
				break;
			}
			if (result == blCellRgnDoubleClickResult/* || result == blCellRgnTripleClickResult*/)
			{
				BLNotificationProcPtr	proc;
				
				proc = (*inList)->callbacks.notificationProc;
				if (proc)
				{
					BLCell	cell;
					
					(void)BLGetCellFromUID( &bl_ClickCell(inList), &cell, inList );
					(*proc)( blCellDoubleClickNotificationMsg, blNoNotificationCommand,
							test.part, cell, inList );
				}
			}
			else if (result != blCellRgnSingleClickResult)
				_BLZeroMemory( (Ptr)&bl_ClickCell(inList), sizeof(bl_ClickCell(inList)) );
			//DisposeRgn( cellRgn );
			bl_ClickTime(inList) = inWhen;
			// Do this last, when we have cleaned up needed memory, unlocked the BlugsRef.
			if (doClickLoop)
				local_BLClickLoop( inWhereLocal, inModifiers, test.part, clickedCell, inList );
			if (restore) SetGWorld( saveWorld, nil );
			bl_Clear((*inList)->privateFlags,blSuppressIdle);
		} // mouse in list rect
	} // visible, active list
	return result;
}

//===============================
BL_PROC OSErr BLSetCellSelectable( BLCell inCell, Boolean inSelectable, BlugsRef inList )
{
	OSErr	err;
	
	_CheckHandlerFlag(inList)
	err = _BLValidateList( inList );
	if (!err) err = _BLValidateCell( inCell, inList );
	if (!err)
	{
		BLIndexPtr	index = bl_CellIndexAddr(inCell,inList);
		
		if (inSelectable) bl_Clear(index->flags,blCantSelect);
		else
		{
			bl_Set(index->flags,blCantSelect);
			_BLDeselectCell( true, inCell, inList ); // Make sure it's not selected
		}
	}
	return err;
}


//===============================
BL_PROC OSErr BLSetRepresentativeColumn( UInt16 inColumn, BlugsRef inList )
{
	OSErr	err;
	
	err = _BLValidateList( inList );
	if (!err) err = _BLValidateColumn( inColumn, inList );
	if (!err) BLGetColumnUID( inColumn, &bl_Rep(inList), inList );
	return err;
}

//===============================
BL_PROC OSErr BLSetSelect( Boolean inSelect, BLCell inCell, BlugsRef inList )
{
	OSErr	err;
	
	_CheckHandlerFlag(inList)
	err = _BLValidateList( inList );
	if (!err) err = _BLValidateCell( inCell, inList );
	if (!err)
	{
		if (inSelect) _BLSelectCell( blSelectUpdate | blSelectMakeVisible, inCell, inList );
		else _BLDeselectCell( true, inCell, inList );
	}
	return err;
}


//===============================
BL_PROC void BLSelectOneCell( BLCell inCell, BlugsRef inList )
{
	OSErr	err;
	
	_CheckHandlerFlag(inList)
	err = _BLValidateList( inList );
	if (!err) err = _BLValidateCell( inCell, inList );
	if (!err) _BLSelectCell( blSelectOneCell + blSelectUpdate + blSelectMakeVisible, inCell, inList );
}

//===============================
BL_PROC OSErr BLSelectAll( BlugsRef inList )
{
	OSErr    err;

	err = _BLValidateList( inList );
	if (!err)
	{
		BLIndexPtr		index;
		UInt16			nRows, nCols;
		BLCell			cell;
		Boolean			needUpdate = false;
		
		nRows = bl_NRows(inList);
		nCols  = bl_NCols(inList);
		index = (BLIndexPtr)*bl_Index(inList);
		for (cell.row = 1; cell.row <= nRows; cell.row++)
		{
			for (cell.col = 1; cell.col <= nCols; cell.col++, index++)
			{
				if (!bl_Selected(index))
				{
					if (_BLCanSelect( cell, inList ))
					{
						needUpdate = true;
						bl_Set(index->flags,blSelected);
					}
				}
			}
		}
		if (needUpdate) _BLUpdateView( blUpdateBlit, inList );
	}
	return err;
}

//===============================
BL_PROC void BLDeselectAll( BlugsRef inList )
{
	OSErr	err;
	
	_CheckHandlerFlag(inList)
	err = _BLValidateList( inList );
	if (!err) _BLDeselectAll( true, inList );
}

//===============================
BL_PROC Boolean BLIsCellSelected( BLCell inCell, BlugsRef inList )
{
	OSErr	err;
	
	err = _BLValidateList( inList );
	if (!err) err = _BLValidateCell( inCell, inList );
	return (err) ? false : bl_Selected(bl_CellIndexAddr(inCell,inList));
}

//===============================
// 10/22/2001 Moses -->1.1
// Rewrote completely and added BLGetSelectMethod enumeration.
// ioCell only changes if the result is noErr.
BL_PROC OSErr BLGetSelect( BLGetSelectMethod inMethod, BLCell* ioCell, BlugsRef inList )
{
	OSErr	err;
	BLCell	cell;
	
	err = _BLValidateList( inList );
	if (!err)
	{
		err = _BLValidateCell( *ioCell, inList );
		if (!err)
		{
			UInt16		nRows, nCols;
			
			err = errAENoUserSelection;
			nRows = bl_NRows(inList);
			nCols = bl_NCols(inList);
			cell = *ioCell;
			switch (inMethod)
			{
				case blCellGetSelectMethod:
				for (; cell.row <= nRows; cell.row++)
				{
					if (cell.row == ioCell->row) cell.col = ioCell->col;
					else cell.col = 1;
					for (; cell.col <= nCols; cell.col++)
					{
						if (bl_Selected(bl_CellIndexAddr(cell,inList)))
						{
							err = noErr;
							break;
						}
					}
					if (!err) break;
				}
				break;
				
				case blRowGetSelectMethod:
				for (; cell.col <= nCols; cell.col++)
				{
					if (bl_Selected(bl_CellIndexAddr(cell,inList)))
					{
						err = noErr;
						break;
					}
				}
				break;
				
				case blColumnGetSelectMethod:
				for (; cell.row <= nRows; cell.row++)
				{
					if (bl_Selected(bl_CellIndexAddr(cell,inList)))
					{
						err = noErr;
						break;
					}
				}
				break;
				
				default:
				err = paramErr;
				break;
			}
			
		}
	}
	if (!err) *ioCell = cell;
	return err;
}

//===============================
BL_PROC OSErr BLHitTest( Point inWhere, BLHitTestPtr outHitTest, BlugsRef inList )
{
	OSErr		err;
	
	outHitTest->cell.row = 0;
	outHitTest->cell.col = 0;
	err = _BLValidateList( inList );
	if (!err) err = _BLHitTest( true, inWhere, outHitTest, inList );
	return err;
}

//===============================
OSErr _BLHitTest( Boolean inHitTestScroll, Point inWhere, BLHitTestPtr outHitTest, BlugsRef inList )
{
	OSErr		err = noErr;
	BLPart		part = blNoPart;
	
	part = local_BLHitTestCell( inWhere, outHitTest, inList );
	if (!part)
	{
		if (inHitTestScroll) part = local_BLHitTestScrollBars( inWhere, outHitTest, inList );
		if (!part)
		{
			part = local_BLHitTestTitleBars( inWhere, outHitTest, inList );
			if (!part)
			{
				// Try sort button
				if (bl_SortButton(inList))
				{
					Rect	sortButtonRect;
					
					sortButtonRect = _BLSortButtonRect( inList );
					if (PtInRect( inWhere, &sortButtonRect ))
					{
						part = blSortButtonPart;
						outHitTest->rect = sortButtonRect;
					}
				}
			}
			if (!part)
			{
				if (bl_HasGrow(inList) && !bl_Aqua && bl_DrawGrow(inList))
				{
					Rect			boxRect;
					
					boxRect = _BLGrowBoxRect( inList );
					if (PtInRect( inWhere, &boxRect ))
					{
						part = blGrowBoxPart;
						outHitTest->rect = boxRect;
					}
				}
			}
			if (!part) part = _BLHitTestWidgets( false, inWhere, outHitTest, inList );
			if (!part) part = _BLHitTestWidgets( true, inWhere, outHitTest, inList );
		}
	}
	outHitTest->part = part;
	return err;
}


#pragma mark -
//===============================
// If blSelectOneCell option is set, it deselects everything but that cell.
//	(I rolled _BLSelectOneCell into this routine.)
// If the list has a representative set up, we select that.
// Selects and makes visible only if the cell can be selected.
// Is there ever an instance whan you want to select a cell and don't want to make visible?
//	YES, when you call _BLSelectRange you definitely don't want to do that to each cell.
void _BLSelectCell( BLSelectOptions inOptions, BLCell inCell, BlugsRef inList )
{
	BLIndexPtr		index;
	Boolean			needUpdate = false, makeVisible;
	UInt16			rep;
	
	makeVisible = bl_Test(inOptions,blSelectMakeVisible);
	if (bl_Test(_BLGetRowFlags( inCell.row, inList ),blRowSelectAll))
	{
		BLCell	cell;
		
		cell.row = inCell.row;
		for (cell.col = 1; cell.col <= bl_NCols(inList); cell.col++)
		{
			needUpdate = false;
			index = bl_CellIndexAddr(cell,inList);
			if (!bl_Selected(index))
			{
				if (_BLCanSelect(inCell,inList))
				{
					bl_Set(index->flags,blSelected);
					needUpdate = true;
				}			
			}
			if (bl_Test(inOptions,blSelectUpdate) && needUpdate)
				_BLUpdateCell( blUpdateBlit | blUpdateTriangle, cell, inList );
		}
		if (bl_Test(inOptions,blSelectOneCell) || bl_OnlyOne(inList))
		{
			BLCell	startCell, endCell;
			
			endCell.col = bl_NCols(inList);
			if (inCell.row > 1)
			{
				startCell.row = 1;
				startCell.col = 1;
				endCell.row = inCell.row - 1;
				_BLDeselectRange( true, startCell, endCell, inList );
			}
			if (inCell.row < bl_NRows(inList))
			{
				startCell.row = inCell.row + 1;
				startCell.col = 1;
				endCell.row = bl_NRows(inList);
				_BLDeselectRange( true, startCell, endCell, inList );
			}
		}
		inCell.col = 1;
	}
	else
	{
		rep = BLGetColumnFromUID( &bl_Rep(inList), inList );
		if (rep) inCell.col = rep;
		// If it's a title row and the column is > 1, we transform the cell so column is 1
		if (inCell.col > 1) if (bl_Test(_BLGetRowFlags( inCell.row, inList ),blRowIsTitleRow)) inCell.col = 1;
		index = bl_CellIndexAddr(inCell,inList);
		// Select the cell
		if (!bl_Selected(index))
		{
			if (_BLCanSelect(inCell,inList))
			{
				bl_Set(index->flags,blSelected);
				needUpdate = true;
			}
			// only make visible if cell can be selected
			else makeVisible = false;
		}
		if (bl_Test(inOptions,blSelectOneCell) || bl_OnlyOne(inList))
			local_BLDeselectAllBut( bl_Test(inOptions,blSelectUpdate), inCell, inList );
		if (bl_Test(inOptions,blSelectUpdate) && needUpdate)
			_BLUpdateCell( blUpdateBlit | blUpdateTriangle, inCell, inList );
	}
	if (makeVisible) _BLMakeVisible( inCell, inList );
}

//===============================
// If the list has a representative set up, deselect that.
void _BLDeselectCell( Boolean inUpdate, BLCell inCell, BlugsRef inList )
{
	BLIndexPtr		index;
	UInt16			rep;
	
	rep = BLGetColumnFromUID( &bl_Rep(inList), inList );
	if (rep) inCell.col = rep;
	index = bl_CellIndexAddr(inCell,inList);
	if (bl_Selected(index))
	{
		bl_Clear(index->flags,blSelected);
		if (inUpdate) _BLUpdateCell( blUpdateBlit | blUpdateTriangle, inCell, inList );
	}
}

//===============================
// Toggles selection, like with a command click.
void _BLToggleCellSelection( Boolean inUpdate, BLCell inCell, BlugsRef inList )
{
	BLIndexPtr		index;
	UInt16			rep;
	
	rep = BLGetColumnFromUID( &bl_Rep(inList), inList );
	if (rep) inCell.col = rep;
	index = bl_CellIndexAddr(inCell, inList);
	bl_Toggle(index->flags,blSelected);
	if (inUpdate) _BLUpdateCell( blUpdateBlit | blUpdateTriangle, inCell, inList );
}


//===============================
// Only updates if something actually changed.
void _BLDeselectAll( Boolean inUpdate, BlugsRef inList )
{
	BLIndexPtr		index;
	UInt32			i, nCells;
	Boolean			needUpdate = false;
	
	nCells = bl_NCells(inList);
	index = (BLIndexPtr)*bl_Index(inList);
	for (i = 1; i <= nCells; i++, index++)
	{
		if (bl_Selected(index))
		{
			needUpdate = true;
			bl_Clear(index->flags,blSelected);
		}
	}
	if (inUpdate && needUpdate) _BLUpdateView( blUpdateBlit, inList );
}

//===============================
// Only updates if something actually changed.
void _BLDeselectColumn( Boolean inUpdate, UInt16 inColumn, BlugsRef inList )
{
	BLIndexPtr		index;
	Boolean			needUpdate = false;
	UInt16			nRows, nCols, i;
	BLCell			cell;
	
	cell.row = 1;
	cell.col = inColumn;
	nRows = bl_NRows(inList);
	nCols = bl_NCols(inList);
	index = bl_CellIndexAddr(cell,inList);
	for (i = 1; i <= nRows; i++)
	{
		if (bl_Selected(index))
		{
			needUpdate = true;
			bl_Clear(index->flags,blSelected);
		}
		index += nCols;
	}
	if (inUpdate && needUpdate) _BLUpdateView( blUpdateBlit, inList );
}

//===============================
// Returns the last cell selected in the list
BLCell _BLLastSelected( BlugsRef inList )
{
	UInt32		nthCell = 0, nCells, i;
	BLIndexPtr	index;
	
	nCells = bl_NCells(inList);
	index = bl_NthCellIndexAddr(nCells,inList);
	for (i = nCells; i > 0; i--, index--)
	{
		if (bl_Selected(index))
		{
			nthCell = i;
			break;
		}
	}
	return _BLNthCellToCell( nthCell, inList );
}

//===============================
// No longer cares which comes first.
// Assumes first and last are disclosed.
void _BLSelectRange( Boolean inUpdate, BLCell inFirstCell, BLCell inLastCell, BlugsRef inList )
{
	UInt16			lastFullRow, nCols = bl_NCols(inList), endCol, row;
	BLCell			tempCell;
	BLSelectOptions	options = (inUpdate) ? blSelectUpdate : blSelectNoOptions;
	
	if (!local_BLCellIsFirst( inFirstCell, inLastCell ))
	{
		tempCell = inFirstCell;
		inFirstCell = inLastCell;
		inLastCell = tempCell;
	}
	lastFullRow = (inLastCell.row >= inFirstCell.row + 2) ? ((inLastCell.col < nCols) ?
					inLastCell.row - 1 : inLastCell.row) : 0;
	// Select cells in the first row
	endCol = (inFirstCell.row == inLastCell.row) ? inLastCell.col : nCols;
	for (tempCell = inFirstCell; tempCell.col <= endCol; tempCell.col++)
	{
		_BLSelectCell( options, tempCell, inList );
	}
	if (lastFullRow)
	{
		Boolean			disclosure;
		
		disclosure = bl_Disclosure(inList);
		for (row = 1; inFirstCell.row + row <= lastFullRow; row++)
		{
			tempCell.row = inFirstCell.row + row;
			if (disclosure) if (!_BLRowIsDisclosed(tempCell.row,inList)) continue;
			for (tempCell.col = 1; tempCell.col <= nCols; tempCell.col++)
			{
				_BLSelectCell( options, tempCell, inList );
			}
		}
	}
	// Select the last row
	if (lastFullRow < inLastCell.row)
	{
		tempCell.row = inLastCell.row;
		for (tempCell.col = 1; tempCell.col <= inLastCell.col; tempCell.col++)
			_BLSelectCell( options, tempCell, inList );
	}
}

//===============================
void _BLDeselectRange( Boolean inUpdate, BLCell inFirstCell, BLCell inLastCell, BlugsRef inList )
{
	Boolean			disclosure = bl_Disclosure(inList);
	UInt16			lastFullRow, nCols = bl_NCols(inList), endCol, row;
	BLCell			tempCell;
	BLSelectOptions	options = (inUpdate) ? blSelectUpdate : blSelectNoOptions;
	
	if (!local_BLCellIsFirst( inFirstCell, inLastCell ))
	{
		tempCell = inFirstCell;
		inFirstCell = inLastCell;
		inLastCell = tempCell;
	}
	lastFullRow = (inLastCell.row >= inFirstCell.row + 2) ?
					((inLastCell.col < nCols) ? inLastCell.row - 1 : inLastCell.row) : 0;
	// Select cells in the first row
	endCol = (inFirstCell.row == inLastCell.row) ? inLastCell.col : nCols;
	for (tempCell = inFirstCell; tempCell.col <= endCol; tempCell.col++)
	{
		_BLDeselectCell( options, tempCell, inList );
	}
	if (lastFullRow)
	{
		for (row = 1; inFirstCell.row + row <= lastFullRow; row++)
		{
			tempCell.row = inFirstCell.row + row;
			if (disclosure) if (!_BLRowIsDisclosed(tempCell.row,inList)) continue;
			for (tempCell.col = 1; tempCell.col <= nCols; tempCell.col++)
			{
				_BLDeselectCell( options, tempCell, inList );
			}
		}
	}
	// Deselect the last row
	if (lastFullRow < inLastCell.row)
	{
		tempCell.row = inLastCell.row;
		for (tempCell.col = 1; tempCell.col <= inLastCell.col; tempCell.col++)
			_BLDeselectCell( options, tempCell, inList );
	}
}

//===============================
// Returns true if the cell is not the last one in the list,
//	and updates the cell to the next one
Boolean _BLNextCell( BLCell* ioCell, BlugsRef inList )
{
	UInt32	nthCell = _BLCellToNthCell( *ioCell, inList );
	Boolean	gotNext = false;
	
	if (nthCell < bl_NCells(inList))
	{
		nthCell++;
		*ioCell = _BLNthCellToCell( nthCell, inList );
		gotNext = true;
	}
	return gotNext;
}


//===============================
// A cell cannot be selected if any of these are true:
//	Its index entry (flags) has blCantSelect set
//	It is in a column that is not selectable
//	?Its cellRect is an empty rectangle?
// But if it's in a title row and its column > 1, it can be selected, sort of, by having
//	its selection redirected. In a sense it selects the whole row.
Boolean _BLCanSelect( BLCell inCell, BlugsRef inList )
{
	Boolean	canSelect = false;
	
	if (!bl_Test((bl_CellIndexAddr(inCell,inList))->flags,blCantSelect))
	{
		if (!bl_Test(_BLGetColumnFlags( inCell.col, inList ),blColumnCantSelect)) canSelect = true;
	}
	return canSelect;
}

#pragma mark -
//===============================
static BLPart local_BLHitTestCell( Point inWhere, BLHitTestPtr ioHitTest, BlugsRef inList )
{
	Rect	viewRect;
	BLCell	clickedCell = {0,0};
	BLPart	part = blNoPart;
	
	viewRect = _BLViewRect( inList );
	if (PtInRect( inWhere, &viewRect ))
	{
		clickedCell = _BLPointToCell( true, inWhere, inList );
		if (clickedCell.col) // Valid column
		{
			if (!clickedCell.row) part = blBottomBlankPart;
			else
			{
				Rect		cellRect;
				
				(void)_BLCellRect( clickedCell, &cellRect, inList );
				// Check to see if we're in a disclosure triangle.
				// If so we put the cell rect in itemRect and triangleRect into zoneRect
				//	of the hit test struct.
				if (clickedCell.col == (*inList)->disclosureColumn)
				{
					if (bl_Disclosure(inList))
					{
						if (bl_Test(_BLGetRowFlags( clickedCell.row, inList ),blRowHasChildren))
						{
							Rect		triangleRect;
							
							triangleRect = _BLTriangleRect( cellRect,
								_BLGetRowDisclosureLevel( clickedCell.row, inList), inList );
							if (PtInRect(inWhere,&triangleRect))
							{
								part = blDisclosureTrianglePart;
								//ioHitTest->subpartRect = triangleRect;
							}
						}
					}
				}
				if (!part)
				{
					RgnHandle	rgn = NewRgn();
					
					// Not in disclosure triangle.
					_BLCellRegion( rgn, clickedCell, &cellRect, inList );
					if (PtInRgn( inWhere, rgn )) part = blCellRegionPart;
					else part = blCellPart;
					DisposeRgn( rgn );
				}
				ioHitTest->rect = cellRect;
			}
		}
		else // Not a valid column
		{
			if (clickedCell.row) part = blRightBlankPart;
			else part = blRightBlankPart;
		}
	}
	ioHitTest->cell = clickedCell;
	ioHitTest->part = part;
	return part;
}

//===============================
static BLPart local_BLHitTestScrollBars( Point inWhere, BLHitTestPtr ioHitTest, BlugsRef inList )
{
	ControlRef		control, hScroll, vScroll;
	WindowRef		window;
	ControlPartCode	controlPart;
	BLPart			part = blNoPart;
	
	window = bl_Window(inList);
	hScroll = bl_HScroll(inList);
	vScroll = bl_VScroll(inList);
	if (bl_Appearance) control = FindControlUnderMouse( inWhere, window, &controlPart );
	else controlPart = FindControl( inWhere, window, &control );
	if (hScroll != nil && control == hScroll) part = blHScrollPart;
	else if (vScroll != nil && control == vScroll) part = blVScrollPart;
	if (part != blNoPart) ioHitTest->u.controlPart = controlPart;
	return part;
}

//===============================
static BLPart local_BLHitTestTitleBars( Point inWhere, BLHitTestPtr ioHitTest, BlugsRef inList )
{
	UInt16			nthTitle;
	BLTitleBarHdl	hBar, vBar;
	Rect			hBarRect, vBarRect;
	BLPart			part = blNoPart;
	
	hBar = bl_HTitle(inList);
	vBar = bl_VTitle(inList);
	if (hBar)
	{
		hBarRect = _BLTitleBarRect( hBar, inList );
		if (PtInRect( inWhere, &hBarRect ))
		{
			nthTitle = _BLPointToTitle( inWhere, hBar, inList );
			ioHitTest->cell.col = nthTitle;
			if (nthTitle)
			{
				part = blHTitleBarTitlePart;
				(void)_BLTitleRect( nthTitle, hBar, &ioHitTest->rect, inList );
			}
			else
			{
				part = blHTitleBarFillerPart;
				_BLTitleBarUnclaimedRect( false, hBar, &ioHitTest->rect, inList );
			}
			local_BLPtInHResizeZone( inWhere, nthTitle, ioHitTest, inList );
		}
	}
	if (!part && vBar)
	{
		vBarRect = _BLTitleBarRect( vBar, inList );
		if (PtInRect( inWhere, &vBarRect ))
		{
			nthTitle = _BLPointToTitle( inWhere, vBar, inList );
			ioHitTest->cell.row = nthTitle;
			if (nthTitle)
			{
				part = blVTitleBarTitlePart;
				(void)_BLTitleRect( nthTitle, vBar, &ioHitTest->rect, inList );
			}
			else
			{
				part = blVTitleBarFillerPart;
				_BLTitleBarUnclaimedRect( true, vBar, &ioHitTest->rect, inList );
			}
			local_BLPtInVResizeZone( inWhere, nthTitle, ioHitTest, inList );
		}
		else
		{
			// We can try to detect corners and such.
			if (hBar)
			{
				Rect	topLeftRect;
				
				topLeftRect.top = hBarRect.top;
				topLeftRect.bottom = hBarRect.bottom;
				topLeftRect.left = vBarRect.left;
				topLeftRect.right = vBarRect.right;
				if (PtInRect( inWhere, &topLeftRect ))
				{
					part = blTopLeftPart;
					ioHitTest->rect = topLeftRect;
				}
			}
		}
	}
	ioHitTest->part = part;
	return part;
}

//===============================
static UInt8 local_BLCellClick( BLHitTestPtr inTest, UInt32 inWhen, Point inWhereLocal,
								EventModifiers inModifiers, BlugsRef inList, Boolean* outDoClickLoop )
{
	BLCell		clickedCell, repCell;
	UInt8		nClicks = 1;
	Boolean		doClickLoop = false,
				tryDragAfter = false; // Call to start drag after new selection effects.
	OSErr		err;
	UInt16		rep;
	
	clickedCell = inTest->cell;
	repCell = clickedCell;
	rep = BLGetColumnFromUID( &bl_Rep(inList), inList );
	if (rep) repCell.col = rep;
	// Do some testing specific to the content region case.
	if (inTest->part == blCellRegionPart)
	{
		SInt32		reply;
		UInt32		features;
		BLCellUID	clickID;
		
		err = _BLGetCellHandlerFeatures( clickedCell, &features, inList );
		if (!err)
		{
			if (!bl_Test(features,blWantsClick))
			{
				// Try to start a drag.
				err = _BLStartDrag( repCell, inWhen, inWhereLocal, inModifiers, inList );
				// If we started a drag but zoomed back for whatever reason, we're done.
				// We don't want to deselect cells or do the click loop.
				if (err && err != userCanceledErr)
				{
					// Maybe the cell supports inline edit, and we are allowed to click to begin.
					// But we do not want to start an inline edit in a cell that was dragged.
					// Should we try to start an inline edit if a modifier key is down?
					if (bl_InlineOnClick(inList))
						if (_BLTrackBeginInlineEdit( inWhen, clickedCell, inWhereLocal,
													&inTest->rect, inList )) goto Done;
					// If there was a "no user interaction" error, then do the normal click loop.
					if (err == errAENoUserInteraction) tryDragAfter = true;
				}
				else goto Done;
			}
			else
			{
				RgnHandle			saveClip;
				BLHandlerParamBlock	pb;
				
				pb.inRect = inTest->rect;
				pb.inEventWhereLocal = inWhereLocal;
				pb.inEventModifiers = inModifiers;
				pb.inIndent = _BLIndent( clickedCell, inList );
				saveClip = _BLClipToViewRectIntersection( &inTest->rect, inList );
				reply = _BLCallHandlerForCell( blCellClickMsg, clickedCell, &pb, inList );
				if (saveClip)
				{
					SetClip( saveClip );
					DisposeRgn( saveClip );
				}
				if (bl_Test(reply,blCellClickUpdate))
					_BLUpdateCell( blUpdateBlit | blUpdateTriangle, clickedCell, inList );
				if (bl_Test(reply,blCellClickHandled)) goto Done;
				tryDragAfter = true;
			}
		} // no error getting handler features
		// In Mac List Mgr, command click takes precedence over shift click
		if (inModifiers & cmdKey) local_BLCommandClick( repCell, inList );
		else if (inModifiers & shiftKey) local_BLShiftClick( repCell, inList );
		// Need alter selection status if clicking in a new cell
		else /*if (!bl_Selected(bl_CellIndexAddr(repCell,inList)))*/
			_BLSelectCell( blSelectOneCell | blSelectUpdate | blSelectMakeVisible,
							repCell, inList );
		// Check to see if there was a double or triple click
		(void)BLGetCellUID( clickedCell, &clickID, inList );
		if (_BLEqualCellUID( &bl_ClickCell(inList), &clickID ) &&
			(inWhen < bl_ClickTime(inList) + GetDblTime()))
		{
			if (inWhen < bl_ClickTime(inList) + GetDblTime())
			{
				if (bl_Test((*inList)->privateFlags,blHaveDoubleClick))
				{
					nClicks = 3;
					bl_Clear((*inList)->privateFlags,blHaveDoubleClick);
				}
				else
				{
					nClicks = 2;
					bl_Set((*inList)->privateFlags,blHaveDoubleClick);
				}
			}
		}
		else bl_ClickCell(inList) = clickID;
		// Now that everything is squared away wrt selection, try to drag.
		if (tryDragAfter)
		{
			err = _BLStartDrag( repCell, inWhen, inWhereLocal, inModifiers, inList );
			if (err == errAENoUserInteraction) doClickLoop = true;
		}
	} // part is blCellRegionPart
	else if (!(inModifiers & shiftKey))
	{
		// Don't need to deselect if we're going to be grab-scrolling.
		if (!(inModifiers & cmdKey)) _BLDeselectAll( true, inList );
		doClickLoop = true;
	}
Done:
	*outDoClickLoop = doClickLoop;
	return nClicks;
}

//===============================
static void local_BLClickLoop( Point inWhere, EventModifiers inModifiers, BLPart inPart,
								BLCell inClickedCell, BlugsRef inList )
{
	if (inPart != blCellRegionPart)
	{
		if (inModifiers & cmdKey) local_BLGrabScroll( inWhere, inList );
		else if (!bl_OnlyOne(inList) && !bl_NoDisjoint(inList))
			local_BLDragSelect( inClickedCell, inWhere, inModifiers, inList );
	}
}

//===============================
static void local_BLScrollBarClick( Point inWhere, EventModifiers inModifiers,
									ControlRef inControl, ControlPartCode inPart, BlugsRef inList )
{
	SInt16			oldVal, newVal;
	Boolean			liveScroll, horiz;
	
	horiz = (inControl == bl_HScroll(inList));
	liveScroll = bl_LiveScroll(inList);
	// Use the live scrolling control variant only with appearance and live scroll.
	if (inPart == kControlIndicatorPart && (!bl_Appearance || !liveScroll))
	{
	#if !__BL_USE_CARBON_API__
		// Presuppose Appearance; plus the IndicatorActionUPP stuff doesn't work.
		if (liveScroll) // no Appearance
		{
			IndicatorActionUPP	action;
			
			action = NewIndicatorActionProc( &_ScrollThumbActionProc );
			_BeginThumbTracking( inControl, inList );
			inPart = TrackControl( inControl, inWhere, (ControlActionUPP)action );
			_EndThumbTracking();
			DisposeRoutineDescriptor( action );
		}
		else
	#endif
		{
			// No live scroll, maybe appearance.
			oldVal = GetControlValue( inControl );
			if (bl_Appearance) inPart = HandleControlClick( inControl, inWhere, inModifiers, nil );
			else inPart = TrackControl( inControl, inWhere, nil );
			if (inPart)
			{
				newVal = GetControlValue( inControl );
				if (newVal != oldVal) _BLIndicatorMoved( newVal, horiz, inList );
			}
		} // not live scroll
	} // In thumb
	else
	{
		if (bl_Appearance) inPart = HandleControlClick( inControl, inWhere, inModifiers, (ControlActionUPP)-1L );
		else TrackControl( inControl, inWhere, (ControlActionUPP)-1L );
	} // Not in thumb
	if (inPart == kControlIndicatorPart)
	{
		BLRecalcOptions	options = blRecalcThumb;
		
		bl_Set(options,(horiz) ? blRecalcHScroll : blRecalcVScroll);
		_BLRecalc( options, 0, inList );
	}
}

#if !TARGET_RT_MAC_MACHO
//===============================
static void local_BLSortButtonClick( const Rect* inSortButtonRect, BlugsRef inList )
{
	if (bl_Sortable(inList) && !bl_Aqua)
	{
		Point					mouse;
		BLSortState				state;
		Boolean					inRect = true;
		MouseTrackingResult		result;
		Boolean					firstTimeThru = true; // Don't play enter/leave sound until state changes.
		
		state = _BLSortButtonState( inList );
		if (bl_AllegroAppearance) (void)PlayThemeSound( kThemeSoundBevelPress );
		do
		{
			if (inRect != bl_Test(state,blSortButtonPressed))
			{
				bl_Toggle(state,blSortButtonPressed);
				_BLDrawSortButton( inSortButtonRect, state, inList );
				if (!firstTimeThru && bl_AllegroAppearance)
					(void)PlayThemeSound( (inRect) ? kThemeSoundBevelEnter : kThemeSoundBevelExit );
			}
			_BLTrackMouseLocation( &mouse, &result );
			inRect = PtInRect(mouse,inSortButtonRect);
			firstTimeThru = false;
		} while (result != kMouseTrackingMouseReleased);
		if (bl_Test(state,blSortButtonPressed))
		{
			bl_Clear(state,blSortButtonPressed);
			_BLDrawSortButton( inSortButtonRect, state, inList );
		}
		if (inRect)
		{
			UInt16	sortColumn;
			
			if (bl_AllegroAppearance) (void)PlayThemeSound( kThemeSoundBevelRelease );
			if (bl_Test(state,blSortStateSorted)) bl_Toggle(state,blSortStateLargeToSmall);
			else state = blSortStateSorted;
			(void)BLGetSortState( nil, &sortColumn, inList );
			if (!sortColumn) sortColumn = 1;
			BLSort( state, sortColumn, inList );
		}
	}
}
#endif //!TARGET_RT_MAC_MACHO

//===============================
static void local_BLTopLeftClick( Point inWhere, EventModifiers inModifiers,
									const Rect* inRect, BlugsRef inList )
{
	BLContentType	content;
	BLTitleBarHdl	vBar;

	vBar = bl_VTitle(inList);
	content = (*vBar)->topLeftContent;
	if (content)
	{
		OSErr			err;
		UInt32			features;
		
		err = _BLGetHandlerFeatures( content, &features );
		if (!err)
		{
			if (bl_Test(features,blWantsClick))
			{
				RgnHandle			saveClip = NewRgn();
				BLHandlerParamBlock	pb;
				BLCell				cell = {0,0};
				UInt32				reply;
				
				pb.inRect = *inRect;
				pb.inEventWhereLocal = inWhere;
				pb.inEventModifiers = inModifiers;
				pb.inIndent = _BLIndent( cell, inList );
				GetClip( saveClip );
				ClipRect( inRect );
				reply = _BLCallHandlerForTitle( blCellClickMsg, 0, &pb, vBar, inList );
				SetClip( saveClip );
				DisposeRgn( saveClip );
				if (bl_Test(reply,blCellClickUpdate)) _BLUpdateTitle( 0, vBar, inList );
			}
		}
	}
}

//===============================
// Relevant flags:
//	blNoExtend, blUseSense, blOnlyOne
// Under *no* circumstances should this routine cheat and call BLCommandClick!!
//	(possible infinite loop)
static void local_BLShiftClick( BLCell inCell, BlugsRef inList )
{
	Boolean			selected;
	UInt32			flags;
	BLSelectOptions	options = blSelectUpdate | blSelectMakeVisible;
	
	flags = (*inList)->listFlags;
	selected = bl_Selected(bl_CellIndexAddr(inCell,inList));
	if (selected)
	{
		if (bl_Test(flags,blUseSense)) _BLDeselectCell( true, inCell, inList );
		// Otherwise ignore a shift-click on a selected cell.
		return;
	}
	// If it's only possible to select one, treat like normal click.
	if (bl_Test(flags,blOnlyOne)) bl_Set(options,blSelectOneCell);
	else // Multiple selections possible
	{
		if (!bl_Test(flags,blNoExtend))
		{
			// Do some sort of extending.
			BLCell		anchor;
			Boolean		selectRange = true;
			
			// See if there is a previous cell
			anchor = inCell;
			if (!local_BLPreviousSelected( &anchor, inList ))
			{
				// If not, try a following cell that's selected.
				// BLGetSelect does not change ioCell if it returns false.
				if (_BLNextCell( &anchor, inList ))
					selectRange = (BLGetSelect( blCellGetSelectMethod, &anchor, inList ) == noErr);
			}
			if (selectRange)
			{
				_BLSelectRange( true, anchor, inCell, inList );
				return;
			}
		}
	}
	_BLSelectCell( options, inCell, inList );
}

//===============================
// Flags consulted: blNoDisjoint, blOnlyOne
static void local_BLCommandClick( BLCell clickedCell, BlugsRef inList )
{
	// If noDisjoint, treat as shift click
	if (bl_NoDisjoint(inList)) local_BLShiftClick( clickedCell, inList );
	else if (bl_OnlyOne(inList))
		_BLSelectCell( blSelectOneCell | blSelectUpdate | blSelectMakeVisible, clickedCell, inList );
	else _BLToggleCellSelection( true, clickedCell, inList );
}

typedef struct
{
	SInt32	h;
	SInt32	v;
} LongPoint;

//===============================
// The way this routine handles the shift key is a little different from Finder's way.
// Finder apparently identifies 2 classes of selected cells: those previously selected,
//	and those selected by the current drag-select. Those previously selected are deselected
//	(rather like lUseSense) and the 'new' batch are selected/deselected like in an unmodified
//	drag. Blugs, on the other hand, never deselects a cell once it has been selected with the
//	shift key down. To support more Finder-like behavior, we will probably need some temp flags
//	in the index record, or an array of state information, or both.
static void local_BLDragSelect( BLCell inAnchorCell, Point inAnchorPoint,
								EventModifiers inModifiers, BlugsRef inList )
{
	Rect			marqueeRect, viewRect;
	UInt16			nRows, nCols, oldStartRow, oldEndRow;
	RgnHandle		cellRgn, rgn, sectRgn, viewRgn, saveClip;
	Boolean			disclosure, shift;
	GWorldPtr		world, saveWorld;
	WindowRef		window;
	GDHandle		saveDev;
	PixMapHandle	pm;
	UInt32			rep;
	LongPoint		anchorPoint, lastMouse;
	CGrafPtr		windowPort;
	MouseTrackingResult	result;
	Point			mouse;
	
	GetMouse ( &mouse );
	_BLTrackMouseLocation( &mouse, &result );
	//if (result == kMouseTrackingMouseReleased) return;
	anchorPoint.h = inAnchorPoint.h;
	anchorPoint.v = inAnchorPoint.v;
	rep = BLGetColumnFromUID( &bl_Rep(inList), inList );
	nRows = bl_NRows(inList);
	nCols = bl_NCols(inList);
	// The first time thru the loop, we will deselect all
	oldStartRow = 1;
	oldEndRow = nRows;
	GetGWorld( &saveWorld, &saveDev );
	world = bl_World(inList);
	window = bl_Window(inList);
	windowPort = GetWindowPort( window );
	disclosure = bl_Disclosure(inList);
	shift = (inModifiers & shiftKey);
	cellRgn = NewRgn();
	rgn = NewRgn();
	viewRgn = NewRgn();
	sectRgn = NewRgn();
	saveClip = NewRgn();
	viewRect = _BLViewRect( inList );
	RectRgn( viewRgn, &viewRect );
	SetGWorld( world, nil );
	GetClip( saveClip );
	pm = GetGWorldPixMap( world );
	lastMouse = anchorPoint;
	if (!inAnchorCell.row) inAnchorCell.row = nRows + 1;
	if (!inAnchorCell.col) inAnchorCell.col = nCols + 1;
	while (result != kMouseTrackingMouseReleased)
	{
		// We only need do something if the mouse moved.
		if (result == kMouseTrackingMouseMoved || result == kMouseTrackingMouseDragged)
		{
			Point			adjustedMouse;
			SInt32			actualDX, actualDY;
			BLCell			cell, mouseCell;
			BLCellRange		selectionRange;
			UInt16			row;
			
			SetGWorld( windowPort, nil );
			GetMouse ( &mouse );
			_BLAutoscroll( mouse, viewRect, 0, &actualDX, &actualDY, inList );
			SetGWorld( world, nil );
			// If delta is positive the anchor has moved up/left.
			anchorPoint.v -= actualDY;
			anchorPoint.h -= actualDX;
			adjustedMouse = mouse;
			// Constrain our mouse to the viewRect.
			if (adjustedMouse.h < viewRect.left) adjustedMouse.h = viewRect.left;
			else if (adjustedMouse.h >= viewRect.right) adjustedMouse.h = viewRect.right - 1;
			if (adjustedMouse.v < viewRect.top) adjustedMouse.v = viewRect.top;
			else if (adjustedMouse.v >= viewRect.bottom) adjustedMouse.v = viewRect.bottom - 1;
			// Calc the marquee rect based on constrained mouse and anchor
			if (adjustedMouse.h < anchorPoint.h)
			{
				marqueeRect.left = adjustedMouse.h;
				marqueeRect.right = anchorPoint.h;
			}
			else
			{
				marqueeRect.left = anchorPoint.h;
				marqueeRect.right = adjustedMouse.h;
			}
			if (adjustedMouse.v < anchorPoint.v)
			{
				marqueeRect.top = adjustedMouse.v;
				marqueeRect.bottom = anchorPoint.v;
			}
			else
			{
				marqueeRect.top = anchorPoint.v;
				marqueeRect.bottom = adjustedMouse.v;
			}
			// Make sure the marquee has width and height so it's visible,
			//	and for intersecting (so it's not an empty rect or region).
			if (marqueeRect.left == marqueeRect.right)
			{
				marqueeRect.left--;
				marqueeRect.right++;
			}
			if (marqueeRect.top == marqueeRect.bottom)
			{
				marqueeRect.top--;
				marqueeRect.bottom++;
			}
			RectRgn( rgn, &marqueeRect );
			mouseCell = _BLPointToCell( false, adjustedMouse, inList );
			if (!mouseCell.row) mouseCell.row = nRows + 1;
			if (!mouseCell.col) mouseCell.col = nCols + 1;
			local_BLCellsToCellRange( inAnchorCell, mouseCell, &selectionRange );
			// Deselect everything in old selection range
			if (!shift)
			{
				BLCell	first, last;
				
				if (oldStartRow <= nRows)
				{
					first.row = oldStartRow;
					first.col = 1;
					last.row = (oldEndRow <= nRows) ? oldEndRow : nRows;
					last.col = nCols;
					_BLDeselectRange( blUpdateNoOptions, first, last, inList );
				}
			}
			for (row = selectionRange.top; row <= selectionRange.bottom && row <= nRows; row++)
			{
				UInt16	col;
				
				if (disclosure) if (!_BLRowIsDisclosed(row,inList)) continue;
				cell.row = row;
				for (col = selectionRange.left; col <= selectionRange.right && col <= nCols; col++)
				{
					OSErr		err;
					UInt32		features;
					Boolean		titleRow;
					
					titleRow = bl_Test(_BLGetRowFlags( cell.row, inList ),blRowIsTitleRow);
					cell.col = (titleRow) ? 1 : col;
					err = _BLGetCellHandlerFeatures( cell, &features, inList );
					if (!err)
					{
						Boolean		doSelect = true;
						
						if (bl_Test(features,blWantsRegion))
						{
							BLHandlerParamBlock		pb;
							
							(void)_BLCellRect( cell, &pb.inRect, inList );
							pb.inRegion = cellRgn;
							pb.inIndent = _BLIndent( cell, inList );
							_BLCallHandlerForCell( blCellRegionMsg, cell, &pb, inList );
							SectRgn( cellRgn, rgn, sectRgn );
							if (EmptyRgn(sectRgn)) doSelect = false;
						}
						if (doSelect)
						{
							_BLSelectCell( blSelectNoOptions, cell, inList );
							if (rep) break;
						}
					}
					if (titleRow) break;
				}
			}
			_BLUpdateView( blUpdateNoOptions, inList );
			SetClip( viewRgn );
			_BLDrawMarquee( pm, &marqueeRect );
			SetClip( saveClip );
			_BLUpdate( blUpdateNoOptions, viewRgn, inList );
			oldStartRow = selectionRange.top;
			oldEndRow = selectionRange.bottom;
		}
		_BLTrackMouseLocation( &mouse, &result );
	};
	SetClip( saveClip );
	DisposeRgn( cellRgn );
	DisposeRgn( rgn );
	DisposeRgn( sectRgn );
	DisposeRgn( viewRgn );
	DisposeRgn( saveClip );
	SetGWorld( saveWorld, saveDev );
	_BLUpdateWorld( blUpdateBlit, nil, inList );
}

//===============================
static void local_BLGrabScroll( Point inAnchorPoint, BlugsRef inList )
{
	MouseTrackingResult		result;
	
	if (bl_AllegroAppearance) SetThemeCursor( kThemeClosedHandCursor );
	do
	{
		SInt32	dx, dy;
		Point	mouse;
		
		_BLTrackMouseLocation( &mouse, &result );
		dx = inAnchorPoint.h - mouse.h;
		dy = inAnchorPoint.v - mouse.v;
		if (dx || dy)
		{
			SInt32	actualDX, actualDY;
			
			_BLScroll( blScrollAdjustThumb, dx, dy, &actualDX, &actualDY, inList );
			inAnchorPoint.h -= actualDX;
			inAnchorPoint.v -= actualDY;
		}
	} while (result != kMouseTrackingMouseReleased);
	InitCursor();
}

//===============================
static void local_BLCellsToCellRange( BLCell inFirstCell, BLCell inSecondCell,
										BLCellRange* outRange )
{
	if (inFirstCell.row < inSecondCell.row)
	{
		outRange->top = inFirstCell.row;
		outRange->bottom = inSecondCell.row;
	}
	else
	{
		outRange->top = inSecondCell.row;
		outRange->bottom = inFirstCell.row;
	}
	if (inFirstCell.col < inSecondCell.col)
	{
		outRange->left = inFirstCell.col;
		outRange->right = inSecondCell.col;
	}
	else
	{
		outRange->left = inSecondCell.col;
		outRange->right = inFirstCell.col;
	}
}

//===============================
// Once the user has clicked in the triangle, no other interaction takes place.... no click loop
//	or dragging or any such cell stuff.
// The hit test record contains the cell rect in ->itemRect and the disclosure triangle rect
//	in ->zoneRect.
static void local_BLTrackDisclosure( BLHitTestPtr inHitTest, Point inWhere,
										EventModifiers inModifiers, BlugsRef inList )
{
	Rect						triangleRect;
	Boolean						wasExpanded, wasInside = false;
	BLDisclosureTriangleState	oldState;
	BLCell						clickedCell;
	MouseTrackingResult			result = kMouseTrackingMousePressed;
	
	clickedCell = inHitTest->cell;
	triangleRect = _BLTriangleRect( inHitTest->rect, _BLGetRowDisclosureLevel( clickedCell.row, inList ), inList );
	//triangleRect = inHitTest->zoneRect;
	wasExpanded = bl_Test(_BLGetRowFlags( clickedCell.row, inList ),blRowIsExpanded);
	oldState = (wasExpanded) ? blTriStateDown : blTriStateRight;
	if (bl_AllegroAppearance) (void)PlayThemeSound( kThemeSoundDisclosurePress );
	do
	{
		Boolean		inRect;
		
		inRect = PtInRect( inWhere, &triangleRect );
		if (wasInside != inRect)
		{
			_BLSetRowFlags( clickedCell.row, blTrianglePressed, (inRect) ? blTrianglePressed : 0, inList );
			if (bl_AllegroAppearance) (void)PlayThemeSound( (inRect) ?
													kThemeSoundDisclosureEnter : kThemeSoundDisclosureExit );
			wasInside = inRect;
			_BLUpdateCell( blUpdateBlit | blUpdateTriangle, clickedCell, inList );
		}
		_BLTrackMouseLocation( &inWhere, &result );
	} while (result != kMouseTrackingMouseReleased);
	if (wasInside)
	{
		Boolean	deep;
		
		if (bl_AllegroAppearance) (void)PlayThemeSound( kThemeSoundDisclosureRelease );
		deep = bl_Test(inModifiers,optionKey);
		if (wasExpanded) BLCollapseRow( deep, clickedCell.row, inList );
		else BLExpandRow( deep, clickedCell.row, inList );
	}								
	_BLSetRowFlags( clickedCell.row, blTrianglePressed, 0, inList );
}

//===============================
// Returns true if the 'first' cell really is first in order.
// If you pass identical cells you get false.
static Boolean local_BLCellIsFirst( BLCell first, BLCell second )
{
	Boolean	isFirst = true;
	
	if (first.row == second.row)
	{
		if (first.col >= second.col) isFirst = false;
	}
	else if (first.row > second.row) isFirst = false;
	return isFirst;
}

//===============================
static Boolean local_BLPreviousSelected( BLCell* ioCell, BlugsRef inList )
{
	UInt32		nthCell;
	Boolean		gotOne = false;
	
	nthCell = _BLCellToNthCell( *ioCell, inList );
	if (nthCell > 1)
	{
		BLIndexPtr	index;
		
		nthCell--;
		index = bl_NthCellIndexAddr(nthCell,inList);
		for (; nthCell >= 1; nthCell--)
		{
			if (bl_Selected(index))
			{
				*ioCell = _BLNthCellToCell( nthCell, inList );
				gotOne = true;
				break;
			}
			index--;
		}
	}
	return gotOne;
}


//===============================
// Only updates if something actually changed.
static void local_BLDeselectAllBut( Boolean inUpdate, BLCell inButThisOne, BlugsRef inList )
{
	BLIndexPtr		index;
	Boolean			needUpdate = false;
	UInt16			nRows, nCols, row, col;
	
	nRows = bl_NRows(inList);
	nCols = bl_NCols(inList);
	index = bl_NthCellIndexAddr(1,inList);
	for (row = 1; row <= nRows; row++)
	{
		for (col = 1; col <= nCols; col++)
		{
			if (col != inButThisOne.col || row != inButThisOne.row)
			{
				if (bl_Selected(index))
				{
					needUpdate = true;
					bl_Clear(index->flags,blSelected);
				}
			}
			index++;
		}
	}
	if (inUpdate && needUpdate) _BLUpdateView( blUpdateBlit, inList );
}

//===============================
// Is the mouse in an area where we change to a 'resize' cursor, or drag the title edge?
static void local_BLPtInHResizeZone( Point inWhere, UInt16 inTitle, BLHitTestPtr ioHitTest,
									BlugsRef inList )
{
	Rect			titleBarRect, zoneRect, titleRect = ioHitTest->rect;
	BLTitleZone		zone = blNotInZone;
	BLTitleBarHdl	theBar;
	
	theBar = bl_HTitle(inList);
	titleBarRect = ioHitTest->rect;
	zoneRect = titleBarRect;
	if (bl_ResizableWd(inList))
	{
		if (inTitle)
		{
			// If the cursor is w/i 3 pixels of either edge,
			//	and we're between two titles, OK to adjust cursor
			if (inWhere.h <= titleRect.left + 3 && inTitle > 1 )
			{
				zoneRect.right = titleRect.left + 4;
				zoneRect.left = zoneRect.right - 7;
				zone = blZoneLeft;
			}
			// You can resize the rightmost column unless it's pinned.
			else if (inWhere.h >= titleRect.right - 3)
			{
				if (!bl_PinRight(inList) || inTitle != bl_NCols(inList))
				{
					zoneRect.left = titleRect.right - 3;
					zoneRect.right = zoneRect.left + 7;
					zone = blZoneRight;
				}
			}
		}
		// If no title, we are in the filler area. Its left edge should also allow resizing.
		else
		{
			if (PtInRect( inWhere, &titleRect) && inWhere.h <= titleRect.left + 3)
			{
				zoneRect.right = titleRect.left + 4;
				zoneRect.left = zoneRect.right - 7;
				if (zoneRect.right > titleRect.right) zoneRect.right = titleRect.right;
				zone = blZoneLeft;
			}
		}
	} // if resizable width
	if (!zone)
	{
		if (bl_PinRight(inList) &&
			inWhere.v >= titleBarRect.bottom - 3 &&
			inWhere.v <= titleBarRect.bottom + 3)
		{
			zoneRect.top = titleBarRect.bottom - 3;
			zoneRect.bottom = zoneRect.top + 7;
			zone = blZoneBottom;
		}
	}
	//ioHitTest->zoneRect = zoneRect;
	ioHitTest->u.zone = zone;
}

//===============================
static void local_BLPtInVResizeZone( Point inWhere, UInt16 inTitle, BLHitTestPtr ioHitTest,
									BlugsRef inList )
{
	Rect			titleBarRect, zoneRect, titleRect = ioHitTest->rect;
	BLTitleZone		zone = blNotInZone;
	BLTitleBarHdl	theBar;
	
	theBar = bl_VTitle(inList);
	titleBarRect = ioHitTest->rect;
	zoneRect = titleBarRect;
	if (bl_ResizableHt(inList))
	{
		if (inTitle)
		{
			if (inWhere.v <= titleRect.top + 3 && inTitle > 1 )
			{
				zoneRect.bottom = titleRect.top + 4;
				zoneRect.top = zoneRect.bottom - 7;
				zone = blZoneTop;
			}
			else if (inWhere.v >= titleRect.bottom - 3)
			{
				zoneRect.top = titleRect.bottom - 3;
				zoneRect.bottom = zoneRect.top + 7;
				zone = blZoneBottom;
			}
		}
		// If no title, we are in the filler area. Its top edge should also allow resizing.
		else
		{		
			if (PtInRect( inWhere, &titleRect ) && inWhere.v <= titleRect.top + 3)
			{
				zoneRect.bottom = titleRect.top + 4;
				zoneRect.top = zoneRect.bottom - 7;
				if (zoneRect.bottom > titleRect.bottom) zoneRect.bottom = titleRect.bottom;
				zone = blZoneTop;
			}
		}
	} // if resizable height
	if (!zone) // Top and bottom can take precedence over right
	{
		if (bl_PinRight(inList) &&
			inWhere.h >= titleBarRect.right - 3 &&
			inWhere.h <= titleBarRect.right + 3)
		{
			zoneRect.left = titleBarRect.right - 3;
			zoneRect.right = zoneRect.left + 7;
			zone = blZoneRight;
		}
	}
	ioHitTest->u.zone = zone;
	//ioHitTest->zoneRect = zoneRect;
}
