//=============================================================================
//
//		Copyright (C) 1996-1997 by Paul S. McCarthy and Eric Sunshine.
//				Written by Paul S. McCarthy and Eric Sunshine.
//							All Rights Reserved.
//
//		This notice may not be removed from this source code.
//
//		This object is included in the MiscKit by permission from the authors
//		and its use is governed by the MiscKit license, found in the file
//		"License.rtf" in the MiscKit distribution.	Please refer to that file
//		for a list of all applicable permissions and restrictions.
//		
//=============================================================================
//-----------------------------------------------------------------------------
// MiscTableScrollKB.M
//
//		Keyboard event handling for MiscTableScroll.
//
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// $Id: MiscTableScrollKB.M,v 1.4 97/02/11 19:23:53 sunshine Exp $
// $Log:		MiscTableScrollKB.M,v $
//	Revision 1.4  97/02/11	19:23:53  sunshine
//	v123: reflectSelection --> selectionChanged
//	Now forces autoDisplay ON when responding to keyboard selection.
//	
//	Revision 1.3  96/12/17	22:24:24  sunshine
//	v112: Now disables flushWindow during methods which scroll and change the
//	keyboard cursor position so that the operations occur in a visually atomic
//	fashion.  MiscTableView's moveFocusBy: --> moveCursorBy:.
//	
//	Revision 1.2  96/05/05	11:18:20  sunshine
//	Totally rewrote.  Now handles all manner of keyboard movement: page-up,
//	page-down, home, and end for both rows and columns.	 These functions can
//	be accessed from both the numeric keypad and the text editing keypads.
//	Now also understands alt-space for selection since under new OpenStep-
//	conformance, alt-space now has meaning.
//-----------------------------------------------------------------------------
#import <misckit/MiscTableScroll.h>
#import "MiscTableView.h"

enum KB_Action
	{
	ACT_IGNORE,
	ACT_NEXT_TEXT,
	ACT_PREV_TEXT,
	ACT_SELECT,
	ACT_PERFORM,
	ACT_LEFT,
	ACT_RIGHT,
	ACT_UP,
	ACT_DOWN,
	ACT_PAGE_LEFT,
	ACT_PAGE_RIGHT,
	ACT_PAGE_UP,
	ACT_PAGE_DOWN,
	ACT_LEFT_EDGE,
	ACT_RIGHT_EDGE,
	ACT_TOP_EDGE,
	ACT_BOT_EDGE,

	ACT_NUM_ACTIONS
	};

enum
	{
	K_TAB				= '\t',			// ascii-set
	K_BTAB				= 0x19,			// ascii-set
	K_RETURN			= '\r',			// ascii-set
	K_SPACE				= ' ' ,			// ascii-set
	K_ALT_SPACE			= 0x80,			// ascii-set
	K_LEFT				= 0xac,			// symbol-set
	K_UP				= 0xad,			// symbol-set
	K_RIGHT				= 0xae,			// symbol-set
	K_DOWN				= 0xaf,			// symbol-set
	K_KP_LEFT			= '4',			// ascii-set, numeric-pad mask
	K_KP_UP				= '8',			// ascii-set, numeric-pad mask
	K_KP_RIGHT			= '6',			// ascii-set, numeric-pad mask
	K_KP_DOWN			= '2',			// ascii-set, numeric-pad mask
	K_KP_PAGE_UP		= '9',			// ascii-set, numeric-pad mask
	K_KP_PAGE_DOWN		= '3',			// ascii-set, numeric-pad mask
	K_KP_HOME			= '7',			// ascii-set, numeric-pad mask
	K_KP_END			= '1',			// ascii-set, numeric-pad mask
	K_KP_INSERT			= '0',			// ascii-set, numeric-pad mask
	K_KP_ENTER			= 0x03,			// 254-set
	K_PAGE_UP			= 0x30,			// 254-set
	K_PAGE_DOWN			= 0x31,			// 254-set
	K_HOME				= 0x2e,			// 254-set
	K_END				= 0x2f,			// 254-set
	};


//=============================================================================
// IMPLEMENTATION
//=============================================================================
@implementation MiscTableScroll(Keyboard)

//-----------------------------------------------------------------------------
// sendSelect:
//-----------------------------------------------------------------------------
- (void)sendSelect:(id)targ
	{
	if (targ != 0 && [targ respondsTo:@selector(selectText:)])
		[targ selectText:self];
	}


//-----------------------------------------------------------------------------
// - keyboardSelect:
//-----------------------------------------------------------------------------
- (void)keyboardSelect:(NXEvent const*)p
	{
	BOOL const was_auto = [self isAutodisplay]; // For selection reflection.
	[self setAutodisplay:YES];
	[tableView keyboardSelect:p];
	[self borderClearSelection:MISC_OTHER_BORDER([tableView trackingBy])];
	[self selectionChanged];
	[self setAutodisplay:was_auto];
	[self sendActionIfEnabled];
	}


//-----------------------------------------------------------------------------
// goFirstSlot:
//-----------------------------------------------------------------------------
- (void)goFirstSlot:(MiscBorderType)b
	{
	if ([self numSlots:b] > 0)
		{
		int const s = [self border:b slotAtPosition:0];
		Window* const w = [self window];
		[w disableFlushWindow];
		[self border:b setCursor:s];
		[self border:b setFirstVisibleSlot:s];
		[w reenableFlushWindow];
		[w flushWindow];
		}
	}


//-----------------------------------------------------------------------------
// goLastSlot
//-----------------------------------------------------------------------------
- (void)goLastSlot:(MiscBorderType)b
	{
	int const n = [self numSlots:b];
	if (n > 0)
		{
		int s = [self border:b slotAtPosition:(n - 1)];
		Window* const w = [self window];
		[w disableFlushWindow];
		[self border:b setCursor:s];
		[self border:b setLastVisibleSlot:s];
		[w reenableFlushWindow];
		[w flushWindow];
		}
	}


//-----------------------------------------------------------------------------
// goPrevPage:
//-----------------------------------------------------------------------------
- (void)goPrevPage:(MiscBorderType)b
	{
	if ([self numSlots:b] > 0)
		{
		int sFirst = [self firstVisibleSlot:b];
		int const sLast	 = [self lastVisibleSlot:b];
		int const v = [self border:b slotPosition:sFirst];
		if (sFirst == sLast && v > 0)
			sFirst = [self border:b slotAtPosition:(v - 1)];
		Window* const w = [self window];
		[w disableFlushWindow];
		[self border:b setLastVisibleSlot:sFirst];
		if (b == [self trackingBy])
			[self border:b setCursor:[self firstVisibleSlot:b]];
		[w reenableFlushWindow];
		[w flushWindow];
		}
	}


//-----------------------------------------------------------------------------
// goNextPage:
//-----------------------------------------------------------------------------
- (void)goNextPage:(MiscBorderType)b
	{
	int const lim = [self numSlots:b];
	if (lim > 0)
		{
		int const sFirst = [self firstVisibleSlot:b];
		int sLast = [self lastVisibleSlot:b];
		int const v = [self border:b slotPosition:sLast];
		if (sFirst == sLast && v < lim - 1)
			sLast = [self border:b slotAtPosition:(v + 1)];
		Window* const w = [self window];
		[w disableFlushWindow];
		[self border:b setFirstVisibleSlot:sLast];
		if (b == [self trackingBy])
			[self border:b setCursor:[self lastVisibleSlot:b]];
		[w reenableFlushWindow];
		[w flushWindow];
		}
	}


//-----------------------------------------------------------------------------
// - classifyOtherSet:
//-----------------------------------------------------------------------------
- (KB_Action)classifyOtherSet:(NXEvent const*)p
	{
	KB_Action deed = ACT_IGNORE;
	switch (p->data.key.charCode)
		{
		case K_LEFT:			deed = ACT_LEFT;		break;
		case K_RIGHT:			deed = ACT_RIGHT;		break;
		case K_UP:				deed = ACT_UP;			break;
		case K_DOWN:			deed = ACT_DOWN;		break;
		case K_PAGE_UP:			deed = ACT_PAGE_UP;		break;
		case K_PAGE_DOWN:		deed = ACT_PAGE_DOWN;	break;
		case K_HOME:			deed = ACT_TOP_EDGE;	break;
		case K_END:				deed = ACT_BOT_EDGE;	break;
		}
	return deed;
	}


//-----------------------------------------------------------------------------
// - classifyAsciiSet:
//-----------------------------------------------------------------------------
- (KB_Action)classifyAsciiSet:(NXEvent const*)p
	{
	KB_Action deed = ACT_IGNORE;
	switch (p->data.key.charCode)
		{
		case K_TAB:				deed = ACT_NEXT_TEXT;	break;
		case K_BTAB:			deed = ACT_PREV_TEXT;	break;
		case K_SPACE:			deed = ACT_SELECT;		break;
		case K_ALT_SPACE:		deed = ACT_SELECT;		break;
		case K_RETURN:			deed = ACT_PERFORM;		break;
		}
	return deed;
	}


//-----------------------------------------------------------------------------
// - classifyKeypad:
//-----------------------------------------------------------------------------
- (KB_Action)classifyKeypad:(NXEvent const*)p
	{
	KB_Action deed = ACT_IGNORE;
	unsigned short const charCode = p->data.key.charCode;

	if (charCode == K_KP_ENTER)
		deed = ACT_PERFORM;
	else if ('0' <= charCode && charCode <= '9')
		{
		static KB_Action const ACTS[] =
			{
			ACT_SELECT,			// KP 0 (insert)
			ACT_BOT_EDGE,		// KP 1 (end)
			ACT_DOWN,			// KP 2 (arrow down)
			ACT_PAGE_DOWN,		// KP 3 (page down)
			ACT_LEFT,			// KP 4 (arrow left)
			ACT_IGNORE,			// KP 5
			ACT_RIGHT,			// KP 6 (arrow right)
			ACT_TOP_EDGE,		// KP 7 (home)
			ACT_UP,				// KP 8 (arrow up)
			ACT_PAGE_UP			// KP 9 (page up)
			};
		deed = ACTS[ charCode - '0' ];
		}
	return deed;
	}


//-----------------------------------------------------------------------------
// - classifyFlags:forAction:
//-----------------------------------------------------------------------------
- (KB_Action)classifyFlags:(int)flags forAction:(KB_Action)deed
	{
	static KB_Action const MODIFIED_ACTIONS[ ACT_NUM_ACTIONS ] =
		{
		ACT_IGNORE,				// ACT_IGNORE
		ACT_NEXT_TEXT,			// ACT_NEXT_TEXT
		ACT_PREV_TEXT,			// ACT_PREV_TEXT
		ACT_SELECT,				// ACT_SELECT
		ACT_PERFORM,			// ACT_PERFORM
		ACT_PAGE_LEFT,			// ACT_LEFT
		ACT_PAGE_RIGHT,			// ACT_RIGHT
		ACT_PAGE_UP,			// ACT_UP
		ACT_PAGE_DOWN,			// ACT_DOWN
		ACT_PAGE_LEFT,			// ACT_PAGE_LEFT
		ACT_PAGE_RIGHT,			// ACT_PAGE_RIGHT
		ACT_PAGE_LEFT,			// ACT_PAGE_UP
		ACT_PAGE_RIGHT,			// ACT_PAGE_DOWN
		ACT_LEFT_EDGE,			// ACT_LEFT_EDGE
		ACT_RIGHT_EDGE,			// ACT_RIGHT_EDGE
		ACT_LEFT_EDGE,			// ACT_TOP_EDGE
		ACT_RIGHT_EDGE			// ACT_BOT_EDG
		};
	int const FLAGS = NX_CONTROLMASK | NX_ALTERNATEMASK | NX_SHIFTMASK;
	if ((flags & FLAGS) != 0)
		return MODIFIED_ACTIONS[ deed ];
	return deed;
	}


//-----------------------------------------------------------------------------
// - classifyKeyDown:
//-----------------------------------------------------------------------------
- (KB_Action)classifyKeyDown:(NXEvent const*)p
	{
	KB_Action deed = ACT_IGNORE;
	if ((p->flags & NX_COMMANDMASK) == 0)
		{
		if (p->data.key.charSet != NX_ASCIISET)
			deed = [self classifyOtherSet:p];
		else if ((p->flags & NX_NUMERICPADMASK) != 0)
			deed = [self classifyKeypad:p];
		else
			deed = [self classifyAsciiSet:p];

		if (deed != ACT_IGNORE)
			deed = [self classifyFlags:p->flags forAction:deed];
		}
	return deed;
	}


//-----------------------------------------------------------------------------
// - keyDown:
//-----------------------------------------------------------------------------
- keyDown:(NXEvent*)p
	{
	KB_Action deed = [self classifyKeyDown:p];
	BOOL handled = (deed != ACT_IGNORE);
	if (handled)
		{
		switch (deed)
			{
			case ACT_NEXT_TEXT: [self sendSelect:[self nextText]];		break;
			case ACT_PREV_TEXT: [self sendSelect:[self previousText]];	break;
			case ACT_SELECT:	[self keyboardSelect:p];				break;
			case ACT_PERFORM:	[self sendDoubleActionIfEnabled];		break;
			case ACT_UP:		[tableView moveCursorBy:-1];			break;
			case ACT_DOWN:		[tableView moveCursorBy: 1];			break;
			case ACT_LEFT:		[tableView moveCursorBy:-1];			break;
			case ACT_RIGHT:		[tableView moveCursorBy: 1];			break;
			case ACT_PAGE_LEFT: [self goPrevPage: MISC_COL_BORDER];		break;
			case ACT_PAGE_RIGHT:[self goNextPage: MISC_COL_BORDER];		break;
			case ACT_PAGE_UP:	[self goPrevPage: MISC_ROW_BORDER];		break;
			case ACT_PAGE_DOWN: [self goNextPage: MISC_ROW_BORDER];		break;
			case ACT_LEFT_EDGE: [self goFirstSlot:MISC_COL_BORDER];		break;
			case ACT_RIGHT_EDGE:[self goLastSlot: MISC_COL_BORDER];		break;
			case ACT_TOP_EDGE:	[self goFirstSlot:MISC_ROW_BORDER];		break;
			case ACT_BOT_EDGE:	[self goLastSlot: MISC_ROW_BORDER];		break;	
			default:			handled = NO;							break;
			}
		}

	if (!handled && ![self incrementalSearch:p])
		return [super keyDown:p];

	return self;
	}

@end
