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

static void		local_BLArrowKey( char inChar, EventModifiers inModifiers, BlugsRef inList );
static void		local_BLCommandArrowKey( char inChar, Boolean inIsShift, BlugsRef inList );
static void		local_BLShiftArrowKey( char inChar, BlugsRef inList );
static Boolean	local_BLNextSelectableInDirection( char inArrowKey, BLCell* ioCell, BlugsRef inList );
static Boolean	local_BLIsDisplayChar( char inChar );
static Boolean	local_BLIsTEChar( UInt8 theChar );

//===================================
// If there's an inline edit happening, the key will be sent to the handler, with a few
//	exceptions. Alphanumeric and arrow input will be sent. Return ends the inline session.
BL_PROC BLKeyResult	BLKey( UInt32 inMessage, UInt32 inWhen, EventModifiers inModifiers,
							BlugsRef inList )
{
	BLKeyResult		result = blNothingHappenedKeyResult;
	OSErr			err;
	
	_CheckHandlerFlag(inList)
	err = _BLValidateList( inList );
	if (!err)
	{
		GWorldPtr	saveWorld;
		Boolean		restore, displayChar;
		char		theChar;
		
		restore = _BLGotoWindow( &saveWorld, inList );
		theChar = inMessage & charCodeMask;
		displayChar = local_BLIsDisplayChar( theChar );
		// Only continue if there is no cmd-combo that should be handled by the Menu Mgr.
		// Hmmm... Mercutio supports modifier-arrow menu equivalents.
		// Host app will have to filter these out before BLKey.
		if (!(displayChar && bl_Test(inModifiers,cmdKey)))
		{
			if (bl_InlineNow(inList))
			{
				Boolean	teChar;
		
				// If it's some character that would cause an annoying bogus square character
				//	when sent to TEKey, filter it out.
				// We could also (in situations like the page down key) end the inline session,
				//	then pass that key to the section of this routine that would handle it.
				teChar = local_BLIsTEChar( theChar );
				if (teChar) result = _BLInlineEditKey( inMessage, inModifiers, inList );
			}
			// No inline session going on
			else
			{
				if (displayChar)
				{
					if (bl_Table(inList))
					{
						SInt16	keyThresh = LMGetKeyThresh();
						Boolean	okToAdd = true; // only if search string is not full
						
						// Adjust the key threshold as is done in IM:MTE 4-46
						keyThresh <<= 1;
						if (keyThresh > 120) keyThresh = 120;
						// Add to search string if within key threshold
						if ((*inList)->lastKeyTime + keyThresh > inWhen)
						{
							if ((*inList)->keyString[0] < 255) (*inList)->keyString[0]++;
							else okToAdd = false;
						}
						// Otherwise reset
						else (*inList)->keyString[0] = 1;
						if (okToAdd)
						{
							UInt16	primarySortColumn;
							
							primarySortColumn = BLGetColumnFromUID( &(*inList)->primarySortColumn, inList );
							(*inList)->keyString[(*inList)->keyString[0]] = theChar;
							(*inList)->lastKeyTime = inWhen;
							// Call search function if the list is sorted.
							if (primarySortColumn)
							{
								OSErr	searchErr;
								BLCell	newCell = {0,0};
								
								searchErr = _BLSearch( primarySortColumn,
														&((*inList)->keyString[1]),
														(*inList)->keyString[0], &newCell, inList );
								if (newCell.row)
									_BLSelectCell( blSelectOneCell | blSelectUpdate | blSelectMakeVisible,
													newCell, inList );
							}
							result = blSearchKeyResult;
						}
					}
				}
				// Not display character
				else
				{
					if (theChar >= kLeftArrowCharCode && theChar <= kDownArrowCharCode)
					{
						local_BLArrowKey( theChar, inModifiers, inList );
						result = blArrowKeyResult;
					}
					else
					{
						BLScrollTarget	target = 0;
						
						result = blNavigationKeyResult;
						switch (theChar)
						{
							case kPageUpCharCode:
							BLPageUp( inList );
							break;
							
							case kPageDownCharCode:
							BLPageDown( inList );
							break;
							
							case kHomeCharCode:
							target = blScrollTopLeft;
							break;
							
							case kEndCharCode:
							target = blScrollBottomRight;
							break;
							
							case kReturnCharCode:
							case kEnterCharCode:
							{
								BLCell		selectedCell = {1,1};
								
								if (BLGetSelect( blCellGetSelectMethod, &selectedCell, inList ) == noErr)
								{
									// If the cell is not editable, we get editingNotAllowed.
									err = BLBeginInlineEdit( selectedCell, inList );
									if (err) result = blInlineEditBegunKeyResult;
								}
							}
							break;
							
							default:
							result = blNothingHappenedKeyResult;
							break;
						}
						if (target) _BLScrollTo( target, inList );
					} // left or down arrow
				} // Not a display character
			} // No inline session
		} // Not cmd-character
		if (restore) SetGWorld( saveWorld, nil );
	} // valid list
	else result = blErrorKeyResult;
	return result;
}

//===================================
static void local_BLArrowKey( char inChar, EventModifiers inModifiers, BlugsRef inList )
{
	UInt16	nCols, nRows;
	
	// It makes absolutely no sense to try to do an arrow key if no columns or rows.
	nCols = bl_NCols(inList);
	nRows = bl_NRows(inList);
	if (nCols && nRows)
	{
		if (inModifiers & cmdKey) local_BLCommandArrowKey( inChar, (inModifiers & shiftKey), inList );
		else
		{
			Boolean		somethingSelected;
			BLCell		cell = {1,1};
			
			// Is there anything selected?
			somethingSelected = (BLGetSelect( blCellGetSelectMethod, &cell, inList ) == noErr);
			if (somethingSelected)
			{
				if (inModifiers & shiftKey) local_BLShiftArrowKey( inChar, inList );
				else
				{
					Boolean	doSelect = false;
					
					// There is a selected cell, so we move the selection
					switch (inChar)
					{
						case kLeftArrowCharCode:
						case kRightArrowCharCode:
						case kUpArrowCharCode:
						doSelect = local_BLNextSelectableInDirection( inChar, &cell, inList );
						break;
						
						case kDownArrowCharCode:
						cell = _BLLastSelected(inList);
						doSelect = local_BLNextSelectableInDirection( inChar, &cell, inList );
						break;
					}
					// Is there something new to select?
					if (doSelect)
						_BLSelectCell( blSelectOneCell | blSelectUpdate | blSelectMakeVisible,
										cell, inList );
				} // End not shift key
			}
			else	// No selected cell; select at and scroll to edge of the list
			{
				// cell is still set to {1,1}
				switch (inChar)
				{
					case kLeftArrowCharCode:
					cell.row = bl_TVisRow(inList);
					break;
					
					case kRightArrowCharCode:
					cell.col = nCols;
					cell.row = bl_TVisRow(inList);
					break;
					
					case kUpArrowCharCode:
					cell.col = bl_LVisCol(inList);
					break;
					
					case kDownArrowCharCode:
					cell.col = bl_LVisCol(inList);
					cell.row = bl_NRows(inList);
					break;
				}
				// bl_ValidCell chack may be unnecessary.
				if (bl_ValidCell(cell) && _BLCanSelect( cell, inList ))
					_BLSelectCell( blSelectUpdate | blSelectMakeVisible, cell, inList );
			} // End no selected cell
		} // End not cmd arrow
	} // End rows and columns exist
}

//===================================
// Finder uses command-arrows differently. Cmd-right expands, cmd-left collapses.
static void local_BLCommandArrowKey( char inChar, Boolean inIsShift, BlugsRef inList )
{
	BLCell	cellToSelect, firstSelected = {1,1};
	Boolean	somethingSelected,
			extendFirst = true; // Left or up arrow; use first selected cell as anchor
	
	switch (inChar)
	{
		case kLeftArrowCharCode:
		cellToSelect.col = 1;
		cellToSelect.row = bl_TVisRow(inList);
		break;
		
		case kRightArrowCharCode:
		cellToSelect.col = bl_NCols(inList);
		cellToSelect.row = bl_BVisRow(inList);
		extendFirst = false;
		break;
		
		case kUpArrowCharCode:
		cellToSelect.col = bl_LVisCol(inList);
		cellToSelect.row = 1;
		break;
		
		case kDownArrowCharCode:
		cellToSelect.col = bl_LVisCol(inList);
		cellToSelect.row = bl_NRows(inList);
		extendFirst = false;
		break;
	}
	somethingSelected = (BLGetSelect( blCellGetSelectMethod, &firstSelected, inList ) == noErr);
	if (inIsShift && somethingSelected)
	{
		_BLSelectRange( blSelectUpdate, cellToSelect,  (extendFirst) ?
						firstSelected : _BLLastSelected( inList ), inList );
	}
	else _BLSelectCell( blSelectOneCell | blSelectUpdate | blSelectMakeVisible, cellToSelect, inList );
}

//===================================
// Only called if there is at least one cell selected
static void local_BLShiftArrowKey( char inChar, BlugsRef inList )
{
	BLCell			cellToExtend = {1,1}, newCell;
	Boolean			doSelect = false;
	
	switch (inChar)
	{
		case kLeftArrowCharCode:
		case kUpArrowCharCode:
		(void)BLGetSelect( blCellGetSelectMethod, &cellToExtend, inList );
		break;
		
		case kRightArrowCharCode:
		case kDownArrowCharCode:
		cellToExtend = _BLLastSelected( inList );
		break;
	}
	newCell = cellToExtend;
	doSelect = local_BLNextSelectableInDirection( inChar, &newCell, inList );
	if (doSelect)
	{
		BLSelectOptions	options = blSelectUpdate | blSelectMakeVisible;
		
		if (bl_OnlyOne(inList)) bl_Set(options,blSelectOneCell);
		_BLSelectCell( options, newCell, inList );
	}
}


//===================================
// Returns true if it found a selectable cell.
// If nothing is selected, ioCell points to {0,0} on entry
static Boolean local_BLNextSelectableInDirection( char inArrowKey, BLCell* ioCell, BlugsRef inList )
{
	Boolean		gotOne = false;
	BLCell		newCell;
	
	newCell = *ioCell;
	switch (inArrowKey)
	{
		case kLeftArrowCharCode:
		// Make sure we have columns to the left of the selection.
		if (newCell.col > 1)
		{
			newCell.col--;
			for (; newCell.col >= 1; newCell.col--)
			{
				if (_BLCanSelect( newCell, inList ))
				{
					gotOne = true;
					break;
				}
			}
		}
		break;
		
		case kRightArrowCharCode:
		newCell.col++;
		for (; newCell.col <= bl_NCols(inList); newCell.col++)
		{
			if (_BLCanSelect( newCell, inList ))
			{
				gotOne = true;
				break;
			}
		}
		break;
		
		case kUpArrowCharCode:
		// Make sure we have rows above the selection.
		if (newCell.row > 1)
		{
			newCell.row = _BLPreviousDisclosedRow( newCell.row, inList );
			for (; newCell.row >= 1; newCell.row = _BLPreviousDisclosedRow( newCell.row, inList ))
			{
				if (_BLCanSelect( newCell, inList ))
				{
					gotOne = true;
					break;
				}
			}
		}
		break;
		
		case kDownArrowCharCode:
		newCell.row = _BLNextDisclosedRow( newCell.row, inList );
		for (; newCell.row && newCell.row <= bl_NRows(inList); newCell.row = _BLNextDisclosedRow( newCell.row, inList ))
		{
			if (_BLCanSelect( newCell, inList ))
			{
				gotOne = true;
				break;
			}
		}
		break;
	}
	if (gotOne) *ioCell = newCell;
	return gotOne;
}


//===================================
// Returns true if the key is alphanumeric, punctuation, that could be displayed onscreen
//	and thus searched for by keyboard methods.
// After fiddling with IM:Text quite a bit I can't find a general-purpose international-savvy
//	way of filtering 'special characters'. CharacterType() just doesn't report the kind of info
//	we need. This is sort of a horrid hack.
static Boolean local_BLIsDisplayChar( char inChar )
{
	if (	//inChar != kNullCharCode &&			// 0
			inChar > kHomeCharCode &&			// 1
			inChar != kEnterCharCode &&			// 3
			inChar != kEndCharCode &&			// 4
			inChar != kHelpCharCode &&			// 5
			inChar != kBellCharCode &&			// 7
			inChar != kBackspaceCharCode &&		// 8
			inChar != kTabCharCode &&			// 9
			inChar != kPageUpCharCode &&		// 11
			inChar != kPageDownCharCode &&		// 12
			inChar != kReturnCharCode &&		// 13
			inChar != kFunctionKeyCharCode &&	// 16
			inChar != kEscapeCharCode &&		// 27
			inChar != kLeftArrowCharCode &&		// 28
			inChar != kRightArrowCharCode &&	// 29
			inChar != kUpArrowCharCode &&		// 30
			inChar != kDownArrowCharCode &&		// 31
			inChar != kDeleteCharCode		)	// 127
				return true;
	return false;
}

//===============================
// Should this character be passed to inline module (for use with TEKey or internal routines) ?
static Boolean local_BLIsTEChar( UInt8 inChar )
{
	Boolean	isTEChar = true;
	
	if (!local_BLIsDisplayChar( inChar ))
	{
		// Allow return/enter, arrow keys and delete, too.
		// Tab is no longer allowed.
		// Return/enter may be sent to handler; all others definitely will be.
		if (	inChar != kEnterCharCode &&
				inChar != kBackspaceCharCode &&
				inChar != kReturnCharCode &&
				//inChar != kTabCharCode &&
				inChar != kLeftArrowCharCode &&
				inChar != kRightArrowCharCode &&
				inChar != kUpArrowCharCode &&
				inChar != kDownArrowCharCode &&
				inChar != kDeleteCharCode		) isTEChar = false;
	}
	return isTEChar;
}
