implementation module EdKeyboard;

/*	Keyboard handling routines for the editor */

import StdClass ,StdInt, StdChar, StdString, StdBool,StdArray;
import deltaEventIO, deltaWindow;

import	EdTypes, EdConstants, EdProgramState, EdDrawWindow, EdSearchMenu, EdTextWindow, EdMenuItems;
import  EdCleanSystem;

CommandOptionDown a	:== (False,True,True,a);
ControlShiftDown a  :== (True,False,True,a);
NoModifiers			:== (False,False,False,False);
OptionG				:== '¨';
OptionH				:== '”';
OptionT				:== 'á';
OptionE				:== '½';
ControlG            :== '\007';
ControlH            :== '\010';
ControlT            :== '\024';
ControlE            :== '\005';

from deltaIOState import ObscureCursor;

//	
// The keyboard function of the various windows.
//

TypeUndo :: !KeyboardState !ProgState !IO -> ProgIO;
TypeUndo kstate prog=:{editor=ed=:{editwindows}} io
	| SpecialCommandKey kstate
		= Type kstate prog io;
	| KeyChangesText kstate && front.wstate.wdtype == EditWd
		| no_selection 	= Type kstate prog` io`;
						= TypeSelect kstate frontid front prog io;
		| no_selection	= Type kstate prog io;
						= TypeSelect kstate frontid front prog io;
	where {
	no_selection	= IsEmptySelection front.wtext.WinText.selection;
	(prog`,io`)		= Undo_UpdateMenuItems {prog & editor={ed & editwindows=editwindows`}} io1;
	io1				= ChangeActiveKeyboardFunction Type io;
	undoinfo		= UndoInfoTypeUndo front;
	editwindows`	= SetFrontWindow front` editwindows;
	(frontid,front)	= GetFrontWindow editwindows;
	front`			= {front & wstate = {front.wstate & undoinfo = undoinfo}};
	};
	
UndoInfoTypeUndo :: !EditWindow -> UndoInfo;
UndoInfoTypeUndo front=:{wtext={curline}}
	= { EmptyUndo &	added	= {	l1 = curline.CurLine.lnr, c1 = curline.CurLine.cnr, l2 = curline.CurLine.lnr, c2 = curline.CurLine.cnr },
					menu	= { undo = True,action = " Typing" } };

/*	The keyboard function which is used when there is no selection. */



Type :: !KeyboardState !ProgState !IO -> ProgIO;
Type kstate prog=:{editor={editwindows}} io
	= case kstate of {
			(key, KeyUp, mod_new)					-> (prog, io);
		
			(key, mode, mod_new) | IsScrollKey key	-> TypeScroll kstate frontid front prog io;
		
			_ | ignore								-> (prog, io);
			
		-> case kstate of {
			
			(OptionG,_,CommandOptionDown _) | textw	-> FindNext True prog io;
			(OptionH,_,CommandOptionDown _) | textw	-> FindSelection True prog io;
			(OptionT,_,CommandOptionDown _) | edit	-> ReplaceAndFind True prog io;
			(OptionE,_,CommandOptionDown _) | errors-> FindError True prog io;
			
			(key, mode, (_,option,command,control))
				| edit && ((key >= FirstPrintable && key <= LastPrintable) || key == TabKey)
					&& (IfMacintoshSystem (not command) (not option || control))
					-> TypePrintable_Tab key frontid front prog (ObscureCursor io);

		-> case kstate of {
			
			(ReturnKey, mode, NoModifiers) | edit	-> TypeReturn autoi frontid front prog (ObscureCursor io);
			
			(ReturnKey, mode, OptionOnly) | edit	-> TypeReturn (not autoi) frontid front prog (ObscureCursor io);
			
			(key, mode, mod_new)
			| edit && IsDeletionKey key				-> TypeBackSpace_Delete kstate frontid front prog (ObscureCursor io);
			
			(key, mode, mod_new) | IsArrowKey key	-> TypeArrow kstate frontid front prog io;
						
			other									-> TypeSpecialKeys kstate prog io;

		}
		}
		};
	where {
		(frontid,front)	= GetFrontWindow editwindows;
		errors			= WindowPresent ErrorWdID editwindows;
		edit			= wdtype == EditWd;
		textw			= edit || wdtype == ErrorWd || wdtype == TypeWd;
		ignore			= wdtype == ClpbrdWd || wdtype == ProjectWd;
		autoi			= front.wformat.WinFormat.autoi;
		wdtype			= front.wstate.wdtype;
	};
	
TypeSpecialKeys :: !KeyboardState !ProgState !IO -> ProgIO;
TypeSpecialKeys kstate prog=:{editor={editwindows}} io
	= IfMacintoshSystem
		(case kstate of {
				(OptionG,_,CommandOptionDown _) | textw	-> FindNext True prog io;
				(OptionH,_,CommandOptionDown _) | textw	-> FindSelection True prog io;
				(OptionT,_,CommandOptionDown _) | edit	-> ReplaceAndFind True prog io;
				(OptionE,_,CommandOptionDown _) | errors-> FindError True prog io;
				other									-> (prog,io) 
			})
		(case kstate of {
				(ControlG,_,ControlShiftDown _) | textw -> FindNext True prog io;
				(ControlH,_,ControlShiftDown _) | textw -> FindSelection True prog io;
				(ControlT,_,ControlShiftDown _) | edit	 -> ReplaceAndFind True prog io;
				(ControlE,_,ControlShiftDown _) | errors-> FindError True prog io;
				other									 -> (prog,io) 
			});
	where {
		textw		= edit || wdtype == ErrorWd || wdtype == TypeWd;
		edit		= wdtype == EditWd;
		wdtype		= front.wstate.wdtype;
		(_,front)	= GetFrontWindow editwindows;
		errors		= WindowPresent ErrorWdID editwindows;
	};

/*	TypeAfterSelect is the keyboard-function that is used after a selection is unhilited */

TypeAfterSelect :: !KeyboardState !ProgState !IO -> ProgIO;
TypeAfterSelect kstate prog=:{editor=ed=:{editwindows}} io
	| KeyChangesText kstate && front.wstate.wdtype == EditWd
		= Type kstate prog` io`;
		= Type kstate prog io;
	where {
	io`				= ChangeActiveKeyboardFunction Type io;
	prog`			= {prog & editor={ed & editwindows=editwindows`}};
	editwindows`	= SetFrontWindow front` editwindows;
	undoinfo		= UndoInfoTypeUndo front;
	front`			= {front & wstate = {front.wstate & undoinfo = undoinfo}};
	(_,front)		= GetFrontWindow editwindows;
	};

	
/*	TypeSelect is the keyboard-function that is used when a portion of text has been
	selected */

TypeSelect :: !KeyboardState !EditWdId !EditWindow !ProgState !IO -> ProgIO;
// RWS ... ignore all key ups
TypeSelect	(_,KeyUp,_) _ _ prog io
	=	(prog, io);
// ... RWS
TypeSelect	kstate=:(key,mode,(shift,_,_,_))
			frontid front=:{wtext={WinText | selection},wstate={wdtype}}
			proga=:{editor} io
	| scrollkey						= Type kstate proga io;
	| arrowkey && shift				= Type kstate proga io;
	| arrowkey						= (progc,ioc);
	| overwrite_selection && delkey	= (progb,iob);
	| overwrite_selection			= Type kstate progb iob;
									= (proga,io);
	where {
	(progb,iob)				= TypeRemoveSelection frontid front selection proga io;
	(progc,ioc)				= TypeUnHiliteSelection kstate frontid front proga io;
	overwrite_selection		= OverwriteSelection kstate && wdtype == EditWd;
	delkey					= IsDeletionKey key;
	arrowkey				= IsArrowKey key;
	scrollkey				= IsScrollKey key;
	};

/*	TypeRemoveSelection removes the evt. selected part of the text, enables the cursor
	and changes the keyboard-function */

TypeRemoveSelection	:: !EditWdId !EditWindow !Selection !ProgState !IO -> ProgIO;
TypeRemoveSelection frontid front selection prog=:{editor=ed=:{editwindows}} io
	= Edit_UpdateMenuItems prog` io`;
	where {
	io`					= ChangeActiveKeyboardFunction Type io1;
	(prog`,io1)			= DrawTextUpdate frontid ScrollHalfWin wu prog1 io;
	prog1				= {prog & editor={ed & editwindows=editwindows`}};
	front`				= {front1 & wstate = {front1.wstate & undoinfo = undoinfo}};
	(front1,wu)			= RemoveText front selection.tsel;
	undoinfo			= UndoInfoTypeRemoveSelection wu;
	editwindows`		= SetFrontWindow front` editwindows;
	};
	
/* Determines undo info after overtyping a selected text */
	
UndoInfoTypeRemoveSelection :: !WindowUpdate -> UndoInfo;
UndoInfoTypeRemoveSelection {WindowUpdate | added={l1,c1},removed}
	=	{ EmptyUndo &	
			removed	= {	Removed | EmptyRemoved &
							before = removed, lnr = l1, cnr = c1 },
			added	= {	l1 = l1, c1	= c1, l2 = l1, c2 = c1 },
			menu	= { undo = True, action = " Typing" } };

/*	TypeUnHiliteSelection unhilites the evt. selected part of the text,
	enables the cursor and changes the keyboard-function */

TypeUnHiliteSelection :: !KeyboardState !EditWdId !EditWindow !ProgState !IO -> ProgIO;
TypeUnHiliteSelection	kstate=:(key,_,(_,option,command,_)) frontid front
						prog=:{editor=ed=:{editwindows}} io
	= Edit_UpdateMenuItems prog` io`;
	where {
	io`					= ChangeActiveKeyboardFunction TypeAfterSelect io2;
	(prog`,io2)			= DrawTextUpdate frontid ScrollHalfWin wu prog1 io1;
	prog1				= {prog & editor={ed & editwindows=editwindows`}};
	(frame,io1)			= ActiveWindowGetFrame io;
	editwindows`		= SetFrontWindow front` editwindows;
	(front`,wu)			= front`_wu;
	front`_wu | updown	= DirectionKey frame False mod_new key front;
						= UnselectText side front;
	mod_new	| option	= Optn;
			| command	= Comd;
						= None;
	side				= key == LeftKey;
	updown				= key == UpKey || key == DownKey;
	};


/*	Shows a character when a printable or tab is typed */

TypePrintable_Tab :: !Char !EditWdId !EditWindow !ProgState !IO -> ProgIO;
TypePrintable_Tab key frontid front prog=:{editor=ed=:{editwindows}} io
	= Changed_UpdateMenuItems prog` io`;
	where {
	(prog`,io`)			= DrawCurChars key front` update prog1 io;
	prog1				= {prog & editor={ed & editwindows=editwindows`}};
	front`				= {front1 & wstate = {front1.wstate & undoinfo = undoinfo}};
	(front1,update)		= InsertChar key front;
	undoinfo			= UndoInfoInsertCharInLine front1;
	editwindows`		= SetFrontWindow front` editwindows;
	};
	
UndoInfoInsertCharInLine :: !EditWindow -> UndoInfo;
UndoInfoInsertCharInLine front=:{wstate={undoinfo}}
	= {UndoInfo | undoinfo &
			added = {undoinfo.UndoInfo.added &
						c2 = inc undoinfo.UndoInfo.added.c2}};

/* Deletes the next/previous character/word when delete/backspace is typed */

TypeBackSpace_Delete :: !KeyboardState !EditWdId !EditWindow !ProgState !IO -> ProgIO;
TypeBackSpace_Delete kstate=:(key,_,_) frontid front prog=:{editor=ed=:{editwindows}} io
	| delline &&	removed	= DrawCurLine front` update prog` io`;  
	|				removed	= DrawCurChars key front` update prog` io`;
							= (prog,io);
	where {
	(prog`,io`)				= Changed_UpdateMenuItems prog1 io;
	prog1					= {prog & editor={ed & editwindows=editwindows`}};
	(front1,removed,delline,word,update)
							= RemoveChars mod_new before_cursor front;
	front`					= {front1 & wstate = {front1.wstate & undoinfo = undoinfo}};
	undoinfo | delline		= UndoInfoRemoveLineFromText before_cursor front1;
							= UndoInfoRemoveCharsFromLine before_cursor word front1;
	editwindows`			= SetFrontWindow front` editwindows;
	(mod_new,before_cursor)	= WhichDelete kstate;
	};

/* Determines undo info after a line has been deleted from the text */
	
UndoInfoRemoveLineFromText :: !Bool !EditWindow -> UndoInfo;
UndoInfoRemoveLineFromText	before_cursor front=:{wtext={curline},wstate={undoinfo}}
	| before_cursor && nothing_added
		= {UndoInfo | undoinfo & clipcopied=EmptyClipCopied, removed=removeda, added=addeda};
	| before_cursor
		= {UndoInfo | undoinfo & clipcopied=EmptyClipCopied, removed=removeda, added=addedb};
		= {UndoInfo | undoinfo & clipcopied=EmptyClipCopied, removed=removedc};
		{
			removedc		= {Removed | removed & after=afterc,lnr=added.l1,cnr=added.c1};
			afterc			= case removed.Removed.after of {
								Nil			-> "" :! NewlStr :! Nil;
								line:!rest	-> "" :! (line +++ NewlStr) :! rest;
							};
/*
			afterc			= Case removed.Removed.after;
			{
							Case :: (List {#Char#}) -> (List {#Char#});
							Case Nil			= "" :! NewlStr :! Nil;
							Case (line:!rest)	= "" :! (line +++ NewlStr) :! rest;
			}
*/
		}
	where {
		removeda		= {Removed | removed & before=beforea,lnr=curline.CurLine.lnr,cnr=curline.CurLine.cnr};
		addeda			= {addedb & l1=curline.CurLine.lnr,c1=curline.CurLine.cnr};
		addedb			= {added & l2=curline.CurLine.lnr,c2=curline.CurLine.cnr};
		nothing_added	= added.l1 == added.l2 && added.c1 == added.c2;
		beforea			= case removed.Removed.before of {
							Nil			-> NewlStr :! "" :! Nil;
							_			-> NewlStr :! removed.Removed.before;
						};
/*		beforea			= Case removed.Removed.before;
						{
							Case :: (List {#Char#}) -> (List {#Char#});
							Case Nil	= NewlStr :! "" :! Nil;
							Case _		= NewlStr :! removed.Removed.before;
						};
*/
		added			= undoinfo.UndoInfo.added;
		removed			= undoinfo.UndoInfo.removed;
	};

/* Determines undo info after a word has been deleted from the text */

UndoInfoRemoveCharsFromLine :: !Bool !String !EditWindow -> UndoInfo;
UndoInfoRemoveCharsFromLine before_cursor word front=:{wtext={curline},wstate={undoinfo}}
	| after_cursor	= {UndoInfo | undoinfo` & removed=removedc};
	| nothing_added	= {UndoInfo | undoinfo` & removed=removeda, added=addeda};
	| part_added	= {UndoInfo | undoinfo` & removed=removedb, added=addeda};
					= {UndoInfo | undoinfo` & added=addedb};
	where {
	undoinfo`		= {undoinfo & clipcopied = EmptyClipCopied};
	addeda			= {l1=curline.CurLine.lnr,c1=curline.CurLine.cnr,l2=curline.CurLine.lnr,c2=curline.CurLine.cnr};
	addedb			= {added & c2=added.c2 - lenword};
	removeda		= {Removed | removed & before=beforea,lnr=curline.CurLine.lnr,cnr=cnr`};
	removedb		= {Removed | removed & before=beforea,lnr=curline.CurLine.lnr,cnr=curline.CurLine.cnr};
	removedc		= {Removed | removed & after=afterc, lnr=added.l1,cnr=added.c1};
	beforea			= case removed.Removed.before of
						{	Nil			-> word` :! Nil;
							str:!rest	-> (word` +++ str) :! rest;
						};
	afterc			= case removed.Removed.after of
						{	Nil			-> word:!Nil;
							str:!rest	-> (str +++ word) :! rest;
						};
	word`
		| part_added= word % (0, dec (added.c1 - curline.CurLine.cnr));
					= word;
	cnr`			= curline.CurLine.cnr + lenword;
//	lenword			= #word;
	lenword			= size word;
	after_cursor	= not before_cursor;
	nothing_added	= added.l1 == added.l2 && added.c1 == added.c2;
	part_added		= added.l1 == added.l2 && cnr` < added.c1;
	removed			= undoinfo.UndoInfo.removed;
	added			= undoinfo.UndoInfo.added;
	};
		
/*	Add a line when a return is typed */

TypeReturn :: !Bool !EditWdId !EditWindow !ProgState !IO -> ProgIO;
TypeReturn autoi frontid front prog=:{editor=ed=:{editwindows}} io
	= Changed_UpdateMenuItems prog` io`;
	where {
	(prog`,io`)			= DrawCurLine front` update prog1 io;
	prog1				= {prog & editor={ed & editwindows=editwindows`}};
	front`				= {front1 & wstate = {front1.wstate & undoinfo = undoinfo}};
	(front1, update)	= InsertLine autoi front;
	undoinfo			= UndoInfoInsertLineInText front1;
	editwindows`		= SetFrontWindow front` editwindows;	
	};
	
UndoInfoInsertLineInText :: !EditWindow -> UndoInfo;
UndoInfoInsertLineInText front=:{wtext={curline},wstate={undoinfo}}
	= { UndoInfo | undoinfo &
			added = { undoinfo.UndoInfo.added &
				l2	= inc undoinfo.UndoInfo.added.l2,
				c2	= curline.CurLine.cnr } };

TypeArrow :: !KeyboardState !EditWdId !EditWindow !ProgState !IO -> ProgIO;
TypeArrow kstate=:(key,_,(shift,option,command,_)) frontid front prog=:{editor=ed=:{editwindows}} io
	| shift = Edit_UpdateMenuItems prog` io`
			= (prog`,io`);
	where {
	(prog`,io`)			= DrawTextUpdate frontid scroll_mode wu prog1 io1;
	prog1				= {prog & editor={ed & editwindows=editwindows`}};
	(frame,io1)			= ActiveWindowGetFrame io;
	editwindows`		= SetFrontWindow front` editwindows;	
	(front`, wu)		= DirectionKey frame shift mod_new key front;
	mod_new	| option	= Optn;
			| command	= Comd;
						= None;
	scroll_mode			= GetScrollMode kstate;
	};

TypeScroll :: !KeyboardState !EditWdId !EditWindow !ProgState !IO -> ProgIO;	
TypeScroll (key,_,_) frontid front prog io
	= DrawScrollWindow frontid key prog io;

/* Aux. function: determines whether this key is an arrow key */

// IsArrowKey :: !Char -> Bool;
IsArrowKey key
	:== key == LeftKey || key == RightKey || key == UpKey || key == DownKey;

/* Aux. function: determines whether this key is a scroll key */

// IsScrollKey :: !Char -> Bool;
IsScrollKey key
	:== key == PgUpKey || key == PgDownKey || key == BeginKey || key == EndKey;

/* Aux. function: determines whether this key is an backspace or del key */	

// IsDeletionKey :: !Char -> Bool;
IsDeletionKey key
	:== key == BackSpKey || key == DelKey;

/*	Aux. function: determines whether word/char must be deleted and whether the text before/after
	the cursor must be deleted */
	
WhichDelete :: !KeyboardState -> (!KeyMode, !Bool);
WhichDelete (key,_,(_,option,command,_))
	| option	= (Optn, key == BackSpKey);
	| command	= (Comd, key == BackSpKey);
				= (None, key == BackSpKey);

GetScrollMode :: !KeyboardState -> ScrollMode;
GetScrollMode (key,_,(_,_,command,_))
	| command && (key == UpKey || key == DownKey)	= ScrollFullWin;
													= ScrollLine;

/*	Aux. function: determines whether this key(-combination) changes the text */

KeyChangesText :: !KeyboardState -> Bool;
KeyChangesText (key,KeyUp,mod_new)						= False;
KeyChangesText (key,KeyStillDown,mod_new)				= False;
KeyChangesText (key,down,(shift,option,command,control))
	| key >= FirstPrintable && key <= LastPrintable
	  && (IfMacintoshSystem (not command) (not option || control))
		= True;
KeyChangesText (key, mode, NoModifiers)					= key == TabKey || key == ReturnKey ||
															key == BackSpKey || key == DelKey;
KeyChangesText (key, mode, OptionOnly)					= key == ReturnKey || key == BackSpKey ||
															key == DelKey;
KeyChangesText (key, mode, CommandOnly)					= key == BackSpKey || key == DelKey;
KeyChangesText (key,mode,mod_new)						= False;

SpecialCommandKey :: !KeyboardState -> Bool;
SpecialCommandKey (k,_, CommandOptionDown _)
	= IfMacintoshSystem
		(k==OptionG  || k==OptionH  || k==OptionT  || k==OptionE)
		False;
SpecialCommandKey (k,_, ControlShiftDown _)
	= IfMacintoshSystem
		False
		(k==ControlG || k==ControlH || k==ControlT || k==ControlE);
SpecialCommandKey kstate
	= False;

/*	Aux. function: determines whether this key(-combination) overwrites the selection */

OverwriteSelection :: !KeyboardState -> Bool;
OverwriteSelection (key,KeyUp,mod_new) =  False;
OverwriteSelection (key,mode,(shift,option,command,control))
	| key >= FirstPrintable && key <= LastPrintable
	  && (IfMacintoshSystem (not command) (not option || control))
		= True;
	| (key == BackSpKey || key == ReturnKey || key == TabKey || key == DelKey)
	  && ((IfMacintoshSystem False True) || not command)
		= True;
		= False;
