#include <string.h>
#import <appkit/Cursor.h>
#import <appkit/NXCType.h>
#import "MiscFormField.h"
#import "MiscFormFieldCell.h"

#define FORM_FIELD_CURRENT_VERSION 4	// caveat: 4 reserved for stefan (skip to 5)

/*	ToDo
 *			im .r mode multiple jumpchars erlauben
 *			Wenn sprungzeichen getippt (zB das in mask sichtbare), in value durch jumpchar substituieren
 *			error when inserting into a full r-field when cursor is not adjacent to jumpchar-> Beep() and ignore
 *			outoinserting of a point when crossing validfields is possible
 *			unpleasant removal of pixels under blinking caret
 *			"rrrr" needs to be "rrrr." to work
 *			setStringValue should check strlen and reset cursor if necess.
 *			displayProblems when no bordertype specified
 *			pasteing into full r-run should clip text
 *			prevent deleting of jumpchar when in deleted selection
 *			NXEditableFieldFormatter support
 *			resignFirsResponder returnvalue must not be ignored
 *			keybindings, firstresponderproblem (no nextText: resignFirstResponder)
 *
 * test 	control="    .rrrr"
 *			enter jumpchar-> select 'cell' (esp. in r-mode)
 *			valid-support
 *
 */
 
/******************************************** Spacer ********************************************************************/

/*	Documentation:
 *
 *	mask:		this string determines the visual appearance of the content string:
 *				every blank gives home to a corresponding char in the value string.
 *				Example: mask:"  .  .  " and value="123456" prints and copys as "12.34.56"
 *				Note that every non-blank gets onscreen at corresponding his char-position
 *	modify:		this string gives you control over the cursorbehaviour (ignore this if you want simple 
 *				left to right typing behaviour).
 *				Every run of 'r's lets the corresponding typed chars behave as in a calculator
 *				eg 'rrrr.  ' with '1.50' typed prints as '   1.50'. Note that the actual '.'-char printed onscreen is
 *				specified in the template string which would be '    .  ' in the above example.
 *				Also note that the very character you have to type that determines the jump from the section before and
 *				behind the '.' is the one in mask (at the corresponding char position). for german use
 *				mask could spell: '    ,  '; In modifiy it has always to be '.'.  
 *				If no special cursorbehaviour is needed, modiy can be omitted.
 *	valid:		gives the characterclass allowed for every corresponding position in the value string. note that
 *(not readyly)	nonblank positions in the mask do not count. Example: mask='   .  ', valid='NNNNN'.
 *	avail.		Defined classes will some day be:
 *				'C': [A-Z] (If [a-z] is typed, toupper is automatically invoked)
 *				'c': [a-z] (If [A-Z] is typed, tolower is automatically invoked)
 *				'.': every character (same as no valid)
 *				'p': every printable character
 *				'9': [0-9]
 *				'U': Userdefined
 */
#define jumpchar '.'

char* mergeText(const char *t,const char *control,const char *c,char *buffer,char bc)
{	char a,*b,a1,*cntrl,*contstrt=(char*)c;
	BOOL hasCntrl=strlen(control);
	for(cntrl=(char*)control,a1=(char)*c,b=buffer;a=(char)*t;t++,cntrl++)
	{	if(hasCntrl && cntrl[0] == 'r')
		{	char *cp=cntrl,*cont,*lspc=strchr(c,jumpchar);
			int rspc,orspc,len=strlen(c);
			if(lspc) len=MIN(lspc-c,len);
			for(rspc=0;*cp && *cp == 'r';cp++) rspc++;
			cntrl=cp,orspc=rspc,c+=MIN(len,rspc),cont=len?((char*)c-1):0L;
			while (rspc > 0)	 b[--rspc]=(cont >= contstrt)? *cont--:bc;
			b+=orspc,t+=orspc-1,cntrl=cp-1;
			if(*c == jumpchar) c++, contstrt=(char*)c;	//c=oldcont+orspc;
			continue;
		} if(a == bc)
		{	if(!a1) *b++=bc;
			else *b++=(a1=*c++)? a1:bc;
		} else *b++=a;
	} *b++=0;
	return buffer;
}

void filterText(const char *val,char *cp,const char *template,const char *control)
{	int i;
	for(i=0;*val;i++,val++)
	{	if(*val == template[i] && !(control && control[i]== jumpchar && control[MAX(i-1,0)] == 'r'))
			continue;
		*cp++=*val;
	} *cp=0;
}


int nCharsInStr(const char *control,char bc)
{	int erg,i;
	for(erg=i=0;control[i];i++)
		if(control[i] == bc) erg++;
	return erg;
}
int addrOfChars(const char *control,int cnt,char bc)
{	int i;
	for(i=0;control[i];i++)
		if (control[i] == bc) if(--cnt< 0) return i;
	return i;
}


@implementation MiscFormField

static id FormFieldCellClass;	// class variable
+ initialize
{	FormFieldCellClass=[MiscFormFieldCell class];

	if(self == [MiscFormField class])
	{	[self setVersion:FORM_FIELD_CURRENT_VERSION];
	
	}

	return self;
}
+ setCellClass:classId
{	FormFieldCellClass=classId;

	return self;
}

- (const char *) getInspectorClassName
{	return "MiscFormFieldInspector";
}

-(BOOL) charIsJumpChar:(char) theChar atPosition:(int) position
{	if(NXIsAlNum(theChar)) return NO;
	if(control && strstr(control,"r."))	//pocketcalculatormode
	{	if(strchr(content,jumpchar))	return YES;	// better: addrOfChars(content,cnt,jumpchar)
	}
	else if(strchr(template,theChar) && theChar!=blankchar)	return YES;
	return NO;
}

int getRwidth(const char *control,int cnt)
{	int jcharPos=addrOfChars(control,cnt,jumpchar),rCnt;
	char	 om=control [jcharPos],*right;
		((char*)control)[jcharPos]=0,rCnt=nCharsInStr((right=strrchr(control,jumpchar))?right:control,'r');
		((char*)control)[jcharPos]=om;
	return rCnt;
}

-(int) physPosForLogPos:(int)logpos
{	int			physpos,olp=logpos;
	char		c;
	const char *p=template;
	logpos=MIN(logpos,strlen(content));
	for(physpos=0;logpos && (c=*p);physpos++,p++) if(c == blankchar) logpos--;

	if(control && strstr(control,"r."))	//pocketcalculatormode
	{	int cnt,mcnt;
		char om=content [olp];
		((char*)content)[olp]=0,cnt=nCharsInStr(content,jumpchar),
		((char*)content)[olp]=om;
			 om=control [physpos];
		((char*)control)[physpos]=0,mcnt=nCharsInStr(control,jumpchar),
		((char*)control)[physpos]=om;
	
		if(strchr(content,jumpchar) || !mcnt)
		{	int erg=addrOfChars(control,cnt,jumpchar);
			if(control[MAX(erg-1,0)] != 'r')
				erg=addrOfChars(control,cnt-1,jumpchar)+
							  ((content+olp)-strchr(content,jumpchar));
			else
			{	int contWidth=strlen(content);							// cnt-1????
				if(strchr(content,jumpchar)) contWidth=addrOfChars(content,cnt,jumpchar);
				erg=getRwidth(control,mcnt)-(contWidth-olp);
			}
			return MIN(erg,strlen(template));
		}
	}
	return physpos; 
}

-(int) spaceAfterPosition:(int)logpos
{	int			physpos,olp;
	char		c;
	const char *p=template;
	logpos=olp=MIN(logpos,strlen(content));
	for(physpos=0;logpos && (c=*p);physpos++,p++) if(c == blankchar) logpos--;

	if(control && strstr(control,"r."))	//pocketcalculatormode
	{	int cnt,mcnt;
		char om=content [olp];
		((char*)content)[olp]=0,cnt=nCharsInStr(content,jumpchar),
		((char*)content)[olp]=om;
			 om=control [physpos];
		((char*)control)[physpos]=0,mcnt=nCharsInStr(control,jumpchar),
		((char*)control)[physpos]=om;

		if(strchr(content,jumpchar) || !mcnt)
		{	if(control[MAX(addrOfChars(control,cnt,jumpchar)-1,0)] != 'r')
				physpos=addrOfChars(control,cnt-1,jumpchar)+
							  ((content+olp)-strchr(content,jumpchar));
			physpos=MIN(physpos,strlen(template));
		}

	} return nCharsInStr(template+physpos, blankchar);
}

-(int) logPosForPhysPos:(int)physpos
{	int			i;
	char		c;
	const char *p=template;
	if(control && strstr(control,"r."))	//pocketcalculatormode
	{	int cnt;
		char om=control [physpos],*ptr;
		((char*)control)[physpos]=0,cnt=nCharsInStr(control,jumpchar);
		((char*)control)[physpos]=om;
		if(control[MAX(physpos-1,0)] == 'r')
		{	int contWidth=strlen(content);
			if(strchr(content,jumpchar)) contWidth=addrOfChars(content,cnt-1,jumpchar);
			physpos-=getRwidth(control,cnt-1)-contWidth;
			physpos=MAX(physpos,0);
		}
		else if(ptr=strchr(content,jumpchar))
			return MIN((ptr-content)+physpos-addrOfChars(control,cnt-1,jumpchar),strlen(content));
		else return strlen(content);	// legal due to auto-insertion of jumpchar
	}

	for(i=physpos;i && (c=*p);i--,p++) if(c != blankchar) physpos--;
	return MIN(physpos,strlen(content)); 
}

char* TextFilter(MiscFormField *self,uchar *insertText,int *insertLength,int position)
{	const char	*mask=self->template;
	char		*content=self->content,
				*p=content+position;
	int			 l,sWidth,il=*insertLength;
	
	if(sWidth=self->spN.cp-self->sp0.cp)	//remove selected text
	{	sWidth=MIN(sWidth,strlen(p));
		memmove(p,p+sWidth,strlen(p)+1-sWidth);
	}
	if(insertText[0] == NX_BS && !(self->spN.cp != self->sp0.cp) && position)	// backspace without selection
		memmove(p-1,p,strlen(p)+1);		// delete one char
	else if(il)
	{	if((l=([self spaceAfterPosition:position]-il)) > 0)
			memmove(p+il,p,MIN(strlen(p)+1,l+nCharsInStr(p,jumpchar)));	// shuffle space for insertion
		else if(l < 0) p--;		// bumped against the right margin
		memmove(p,insertText,MIN(il,MAX(strlen(mask)-position,1)));	// insert new text take care of width
	} return insertText;
}
unsigned short FieldFilter(unsigned short theChar, int flags, unsigned short charSet)
{	if(theChar == NX_ESC) return NX_ESC;
	return NXFieldFilter(theChar,flags,charSet);

}

-(NXCoord) borderWidth
{
	return BEZEL_WIDTH;
}

- getBounds:(NXRect *)lr
{	NXCoord borderWidth=[self borderWidth];
	[super getBounds:lr];
	lr->origin.x+=borderWidth,lr->origin.y+=borderWidth;
	lr->size.width-=(borderWidth*2),lr->size.height-=(borderWidth*2);
	return self;
}

- setFont:f
{	font=f;
	return [super setFont:f];
}

- setFromCell:(MiscFormFieldCell*)theCell
{	[[[[self setMask:[theCell mask]]
     setModifyString:[theCell modifyString]]
	  setStringValue:[theCell stringValue]]	// calls cell's setStringValue with slightly altered string. (problm?)
		setBlankchar:[theCell blank]];
	[self setValidString:[theCell validString]];
	backgroundGray=[theCell backgroundGray];
	textGray=[theCell textGray];
	return self;
}

- discardSelection
{	sp0.x=-1.0;
	sp0.cp=spN.cp=0,sp0.op=spN.op=-1;
	return [self display];
}
- setUpPointers
{
	textFilterFunc=TextFilter;
	charFilterFunc=FieldFilter;
	return self;
}

/*
 *	Designated Initializer
 */
- initFrame:(const NXRect *)frameRect withString:(const char *)value
										 andMask:(const char *)mask
								andControlString:(const char *)theControlString
								  andValidString:(const char *)theValidString

{	Font *f=[Font userFixedPitchFontOfSize:14 matrix:NX_FLIPPEDMATRIX];
	blankchar=' ';
	mergeText(mask,theControlString,value,buffer,blankchar);
	[super initFrame:frameRect];
	[f set];
	charHeight=[f pointSize];	//-[f metrics]->baseline;
	[[self setCell:[[FormFieldCellClass alloc]
			initFormCell:value andMask:mask
		andControlString:theControlString
		  andValidString:theValidString
				andBlank:blankchar]]
		free];
	[[[self setFromCell:[self cell]]
			  setFont:f]
		  setEditable:YES];
	[self setUpPointers];
	[self discardSelection];
	[self showCaret];
	return self;
}
- initFrame:(const NXRect *)frameRect andMask:(const char *)theMask
{	[self initFrame:frameRect withString:"" andMask:theMask
						andControlString:"" andValidString:"99999999"];
	return self;
}
- initFrame:(const NXRect *)frameRect
{	[self initFrame:frameRect withString:"1234.1" andMask:"    .    "
						andControlString:"rrrr.    "
						  andValidString:"99999999"];
	return self;
}
- setBackgroundGray:(float)value
{	backgroundGray=value;
	return self;
}
- setTextGray:(float)value
{	textGray=value;
	return self;
}
- init
{	NXRect dummyFrame={0,0,10,10};
	[self initFrame:&dummyFrame];
	return self;
}

- awake		//FromNib
{	timer=NULL;

	[self setUpPointers];
	[self setFromCell:[self cell]];
	[self discardSelection];
	if(!blankchar) blankchar=' ';

	return self;
}

-(NXTextFilterFunc)textFilter
{	return textFilterFunc;
}

#define BASELINE_HEIGHT 3

static void DrawALine(MiscFormField *self,const NXRect *lr)
{	PSgsave();
	NXRectClip(lr);
	[self->font set];
	if(self->backgroundGray >=0)	// transparent background does not work anymore!
		PSsetgray(self->backgroundGray),NXRectFill(lr);
	PSsetgray(self->textGray);
	PSmoveto(lr->origin.x,lr->origin.y+(self->charHeight-BASELINE_HEIGHT)+(lr->size.height-self->charHeight)/2);
	if(self->textGray >=0) PSsetgray([self isEnabled]? self->textGray:NX_DKGRAY);
	PSshow(mergeText(self->template,self->control,self->content,self->buffer,self->blankchar));
	PSgrestore();
}

void runOneCaret(DPSTimedEntry timedEntry, double timeNow, void *data)
{	NXRect	lr;
	float	greyV=NX_BLACK;
	MiscFormField	*self=data;
	[self getBounds:&lr];
	if(self->sp0.x <0) return;	// do not draw invalidated cartet
	if(self->sp0._lcVisible=!self->sp0._lcVisible)
		 greyV=self->backgroundGray;
	[self lockFocus];
	NXRectClip(&lr);
	if(self->sp0.ox != self->sp0.x)	// cursor moving?
	{	DrawALine(self,&lr);		// remove last cursor
		self->sp0.ox=self->sp0.x;
		greyV=NX_BLACK;				// no blink when cursor moves
	} else if(!timedEntry)	// prevent flickering when bumping margins
	{	[self unlockFocus];
		return;
	}
	lr.origin.x=self->sp0.x,lr.size.width=0,
	lr.origin.y+=(lr.size.height-self->charHeight)/2,lr.size.height=self->charHeight;
	PSsetgray(greyV),PSrectfill(NX_X(&lr),NX_Y(&lr),NX_WIDTH(&lr),NX_HEIGHT(&lr));	// draw cursor
	[self unlockFocus];
	[[self window] flushWindow];
}
-(BOOL) hasSelection:(int)l:(int)r
{	return !(l == r || l <0 || r <0);
}
-(BOOL) hasSelection
{	return [self hasSelection:sp0.cp:spN.cp];
}


- showCaret
{	if ([self hasSelection]) return self;
	if(!timer)
	{	timer=DPSAddTimedEntry(0.8,&runOneCaret,self,NX_BASETHRESHOLD);
		spN.cp=sp0.cp;	//Caret to left margin of sel
		sp0.ox=-1;		//ensure refresh
	} runOneCaret(NULL,0,self);	// Force drawing
	return self;
}
- hideCaret
{	if(timer) DPSRemoveTimedEntry(timer),timer=NULL,
			  [self display];
	return self;
}


- (BOOL)acceptsFirstMouse
{	return YES;
}
- becomeFirstResponder
{	[self showCaret];
	endChar=0;
	if(textDelegate && [textDelegate respondsTo:@selector(willBecomeFirstResponder:)])
		[textDelegate willBecomeFirstResponder:self];
	return self;
}
- becomeKeyWindow
{	[self becomeFirstResponder];
	return self;
}
- resignFirstResponder
{	id erg=self;
	if(textDelegate && [textDelegate respondsTo:@selector(textWillEnd:)])
	{	if([textDelegate textWillEnd:self]) erg=nil;
	}
	if(erg)
	{	[self discardSelection],[self hideCaret];
		if(textDelegate && [textDelegate respondsTo:@selector(textDidEnd:endChar:)])
			[textDelegate textDidEnd:self endChar:endChar];
		[self discardSelection],[self hideCaret];
	} return erg;
}
- resignKeyWindow
{	[self hideCaret];
	return self;
}

- windowChanged:newWindow
{	return [self hideCaret];
}


- setSel:(int)start :(int)end				//logical postions
{	sp0.op=sp0.cp,spN.op=spN.cp;			//remember prev
	sp0.cp=start,spN.cp=end;				//establish new sel.
	if(start == end && [self isEditable])	// no selection but caret
	{	NXRect lr;
		int nPos=[self physPosForLogPos:MAX(MIN(start,strlen(content)),0)];
		sp0.x=[self widthForCp:nPos];	// caret coordinate
		[self getBounds:&lr],sp0.x+=lr.origin.x;
		[self showCaret];
	}
	else
	{	[self hideCaret];
		[self display];
	}
	return self;
}
- selectAll:sender
{	return [self setSel:0 :strlen(content)];
}
//for textclass compatibility only
- setText:(const char *)theText
{	[self setStringValue:theText];
	[self selectAll:self];
	return self;
}
- selectText:sender
{	[self selectAll:self];
	[window makeFirstResponder:self];
	return self;

}

-(float)widthForCp:(int)cp	//pixeloffset for characterposition
{	float erg;
	char om=buffer [cp];
	((char*)buffer)[cp]=0,erg=[font getWidthOf:buffer],
	((char*)buffer)[cp]=om;
	return erg;

}
-(int)cpForEvent:(NXEvent *)event wasInside:(BOOL*)was	// returns a physical position
{	NXPoint p=event->location;
	NXRect	lr;
	char	om;
	int		i;

	[self getBounds:&lr];
	[self convertPoint:&p fromView:nil];
	*was=NXPointInRect(&p,&lr);
	if(p.x <= 0 || p.y <0)	return 0;
	if(p.y> lr.size.height)	return strlen(template);
	for(i=0
		;om=buffer[i],((char*)buffer)[i]=0,YES
		;((char*)buffer)[i++]=om)
	{	if([font getWidthOf:buffer] >= p.x)
		{	((char*)buffer)[i]=om;
			return MAX(0,i-1);
		} if(!om) break;
	} return strlen(template);
}

- drawSelection:(int)l :(int)r deleteIfEmptySel:(BOOL) delSel	//physical positions
{	NXRect lr;
	float	x;
	[self getBounds:&lr];
	if (![self hasSelection:l:r])
	{	if(delSel)
		{	[self lockFocus];
			DrawALine(self,&lr);
			[self unlockFocus];
		} return nil;	// no selection present
	}
	else [self hideCaret];
	while(template[l] != blankchar) l++;	//(only for cosmetical reasons)
	x=lr.origin.x;
	[self lockFocus];
	DrawALine(self,&lr);		// remove Background
	lr.origin.x=[self widthForCp:l],	//clc selection coordinates
	lr.size.width=[self widthForCp:r]-lr.origin.x-1;
	lr.size.height-=2,lr.origin.x+=x;
	lr.origin.y+=1;
	NXHighlightRect(&lr);				// display selection
	[window flushWindow];
	[self unlockFocus];
	return self;
}
- drawSelection:(int)l :(int)r
{	return [self drawSelection:l:r deleteIfEmptySel:NO];
}

- drawSelection
{	return [self drawSelection:[self physPosForLogPos:sp0.cp]
							  :[self physPosForLogPos:spN.cp]];
}

#define MOVE_MASK NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK
- mouseDown:(NXEvent *)theEvent
{	NXEvent	*event=theEvent;
	BOOL	 inside;
	int		 eventMask,peekPos=[self cpForEvent:theEvent wasInside:&inside];


	if(!inside || ![self isSelectable]) return self;

	if(theEvent->flags & NX_CONTROLMASK)		//control-cliq
	{	//so what should i do here?
	}
	else if(theEvent->flags & NX_ALTERNATEMASK)	//Alt-cliq
	{
	}
	else
	{	peekPos=[self physPosForLogPos:[self logPosForPhysPos:peekPos]];
		sp0.cp=spN.cp=peekPos;	//no sel. present, so
		[self display];			//remove phenotype of prev. selection
	}
	switch(theEvent->data.mouse.click)
	{	case 2: 								//Doubleclick
		case 3:	return [self selectAll:self];	//Trippleclick-> select all
	}
	for(eventMask=[window addToEventMask:NX_MOUSEDRAGGEDMASK]	//This for runs the modalsession
		;event->type != NX_MOUSEUP
		;event=[NXApp getNextEvent:MOVE_MASK])
	{	peekPos=[self cpForEvent:event wasInside:&inside];
		peekPos=[self physPosForLogPos:[self logPosForPhysPos:peekPos]];	// for peekPos supervising
		[self drawSelection:MIN(peekPos,sp0.cp):MAX(peekPos,sp0.cp) deleteIfEmptySel:YES];		// draw but not set the selection
	} [window setEventMask:eventMask];
	[self setSel:[self logPosForPhysPos:MIN(peekPos,sp0.cp)]	//at least establish the selection
				:[self logPosForPhysPos:MAX(peekPos,sp0.cp)]];
	return self;
}

- setCaretFromLogPos:(int) pos
{	[self setSel:pos:pos];
	return self;
}
-(int)checkForNewPos:(int)npos prev:(int)prev	//as log pos
{	int bonus=0;
	if(control && strstr(control,"r."))
	{	bonus=nCharsInStr(content,jumpchar);
		if(prev < 0)	//check whether typed char bumps the right margin
		{	if ((control[MAX([self physPosForLogPos:sp0.cp]-1,0)] !=  'r') &&
				([self physPosForLogPos:npos] == [self physPosForLogPos:sp0.cp])) return sp0.cp;
		} else if([self physPosForLogPos:prev] == [self physPosForLogPos:npos]) return prev;
	} return MIN(MIN(MAX(0,npos),strlen(content)),nCharsInStr(template, blankchar)+bonus);
}

-(BOOL) checkCrossingPoint:(int) position
{	int physpos=strchr(control,jumpchar)-control,mcnt;	// calculate offset from control because
	char om=template [physpos];							//jumpchar only makes sense here
	((char*)template)[physpos]=0,mcnt=nCharsInStr(template, blankchar),	//"logpos"
	((char*)template)[physpos]=om;
	return (position >= mcnt);
}
-(BOOL) shouldRejectChar:(int) theChar atPosition:(int) position
{	int cnt=nCharsInStr(content,jumpchar);
	const char *p;
	if	(theChar !=NX_BS && control && strstr(control,"r.") &&
		(p=strchr(content+position,jumpchar)) && p-content>= getRwidth(control,cnt-1)) return YES;
	else	// check the valid string¼
	{	if(theChar !=NX_BS && valid && strlen(valid))
		{	switch(valid[position])
			{	case '9': return !NXIsDigit(theChar);	// only numbers allowed
				case 'A': break;	// capital charactes (unimplemented)
			}
		}
		return NO;
	}
}
-(BOOL) shouldSkipJumpchar:(int) position :(int) startingPosition
{	if(!(control && strstr(control,"r.")) ||
	   !(strchr(content,jumpchar))) return NO;
	else
	{	if(position <=strlen(content) && content[MIN(position,startingPosition)] == jumpchar)
		{	if(startingPosition< position)	//crsr right
			{	if([self checkCrossingPoint:startingPosition]) return YES;
			} else  return YES;
		}
	} return NO;
}

- (char *)doTextFilterFunction:(unsigned char *) c length:(int *)insertLength position:(int)insertPosition
{	char	*ret=0;
	BOOL	editNotAllowed=NO;
	if (textDelegate && [textDelegate respondsTo:@selector(textWillChange:)])
		editNotAllowed=[textDelegate textWillChange:self];
	if (!editNotAllowed)
	{	ret=textFilterFunc(self,c,insertLength,insertPosition);
#if 0
		if(textDelegate && [textDelegate respondsTo:@selector(textDidGetKeys:isEmpty:)])
			[textDelegate textDidGetKeys:self isEmpty:content[0]];
#endif
		if(textDelegate && [textDelegate respondsTo:@selector(textDidChange:)])
			[textDelegate textDidChange:self];
	}
	return ret;
}

- keyDown:(NXEvent *)theEvent
{	BOOL	hasSel=spN.cp != sp0.cp;
	int		position=sp0.cp,startingPosition=position,insertPosition=position;
	ushort theChar;
	id target=[self target];

	if (![self isEditable]) return self;

	switch(theChar=charFilterFunc(theEvent->data.key.charCode,
								  theEvent->flags,
								  theEvent->data.key.charSet))
	{	case NX_RETURN:	// Enter+ Return
#if 1
			if(target)
				[NXApp sendAction:[self action] to:target from:self];
		// fall thru (tab follows cr)
#else
		return [super keyDown:theEvent];
#endif
		case NX_DOWN:// if(theChar== NX_DOWN && !fFlags.crscUPDWN) break;
		case NX_TAB:
		{	if(nextText && [nextText respondsTo:@selector(selectText:)])
			{	endChar=theChar;
				[self resignFirstResponder],
				[[nextText selectText:self] setPreviousText:self];
			} else [self selectText:self];
		}
#if 0
		 if(theChar == NX_RETURN)
		{	endChar=theChar;
			[window makeFirstResponder:nextResponder];
		}
#endif
		return self;
		case NX_UP:	// if(theChar== NX_DOWN && !fFlags.crscUPDWN) break;
		case NX_BACKTAB:
		{	if(previousText && [previousText respondsTo:@selector(selectText:)])
			{	endChar=theChar;
				[self resignFirstResponder],[previousText selectText:self];
			}
		} return self;
		case NX_LEFT:
			if(hasSel) position=sp0.cp;
			else position--;
			position=[self checkForNewPos:position prev:sp0.cp];
		break;
		case NX_RIGHT:
			if(hasSel) position=spN.cp;
			else position++;
			position=[self checkForNewPos:position prev:spN.cp];
		break;
		case NX_ESC:
			if(fFlags.escClearLine && strlen(content)) [self setStringValue:""],position=0;
			if(textDelegate && [textDelegate respondsTo:@selector(escPressed:)])
				[textDelegate escPressed:self];
		break;
		default:
		{	if(theChar == NX_BS && (!sp0.cp && !spN.cp))	// BS at left margin?
			{	NXBeep();
				return self;
			} else
			{	int		pos=MAX(sp0.cp,spN.cp),cposition;
				const char	*cellL,*cellR,*nCell;
				if(!pos || (pos ==strlen(content) && theChar != NX_BS)) cellL=template-1,cellR=NULL;
				else
				{	char om=template[pos];
				   ((char*)template)[pos]=0,cellL=strrchr(template,theChar),((char*)template)[pos]=om;
					cellR=strchr(template+pos,theChar);
				}
				cposition=MAX(cellL,cellR)-template+1;
				if([self charIsJumpChar:theChar atPosition:position] &&  (cellL || cellR))	//is typed char a jumpchar?
				{	nCell=(nCell=strchr(template+cposition,theChar))? nCell:template+strlen(template);
					[self setSel:	 [self logPosForPhysPos:cposition]
								:MIN([self logPosForPhysPos:nCell-template],
						  			  strlen(content))];
					return self;
				} else	// displaying is done by automatic caret refresh
				{	uchar	c[2]={(uchar) theChar,(uchar) theChar};
					int		insertLength=1;
					if(theChar == NX_BS)
					{	insertLength=0;
						if(hasSel) return [self delete:self];
						else position--;
					} else	 position++;
					if([self shouldSkipJumpchar:position:startingPosition])
					{	if (position > startingPosition)				//protect jumpchar from edititng
							 insertPosition++, position++;
						else insertPosition--, position--;
					} else if([self shouldRejectChar:theChar atPosition:startingPosition] && ![self hasSelection])
					{	NXBeep();
						return self;
					} else if(control && strstr(control,"r.") && [self checkCrossingPoint:startingPosition] &&
							!strchr(content,jumpchar))	// in calculatormode add jumpchchar if absent (crude calculation)
					{	c[0]=jumpchar,insertLength++,position++;
					}
					[self doTextFilterFunction:c length:&insertLength position:insertPosition];
					position=[self checkForNewPos:position prev:-1];
					if(startingPosition == position)
					{	if(fFlags.beepWhenRight) NXBeep();
						[self display]; // force drawing when caret did not move
					} if(self->sp0.ox == self->sp0.x)	[self display];	// force drawing in some condition
				}
			}
		}
	} [self setCaretFromLogPos:position];
	return self;
}

- displayInRect:(const NXRect *)rect	// gets called from the cell
{	if(![self drawSelection]) DrawALine(self,rect);
	return self;
}


- (int)getSubstring:(char *)buf start:(int)startPos length:(int)length	// as physpos
{	char *c=buffer;
	int copyLen=MIN(startPos+length,strlen(c))-startPos;

	if(startPos > strlen(c)) return -1;
	if(copyLen) strncpy(buf,c+startPos,copyLen)[copyLen]=0;
	return copyLen;
}
- delete:sender
{	char data=NX_BS;
	int length=0;
	if(![self hasSelection]) return self;
	[self doTextFilterFunction:&data length:&length position:sp0.cp];
	[[self setCaretFromLogPos:sp0.cp] display];
	return self;
}
- copy:sender
{	id gpb=[Pasteboard new];
	int l=[self physPosForLogPos:sp0.cp],
		r=[self physPosForLogPos:spN.cp];
	[self getSubstring:lb start:l length:r-l];
	[gpb declareTypes:&NXAsciiPboardType num:1 owner:self];
	[gpb writeType:NXAsciiPboardType data:lb length:strlen(lb)];
	return self;
}
- cut:sender
{	[[self copy:self] delete:self];
	return self;
}
- paste:sender
{	id		gpb=[Pasteboard new];
	char   *data; 
	int   	length;

	[gpb types];
	if([gpb readType:NXAsciiPboardType data:&data length:&length])
	{	int	 nl;
		data[length]=0;	// truncation
		filterText(data,lb,template+sp0.cp,control+sp0.cp),nl=strlen(lb);
		[self doTextFilterFunction:lb length:&nl position:sp0.cp];
		[[self setCaretFromLogPos:sp0.cp+strlen(lb)]
			display];
		[gpb deallocatePasteboardData:data length:length];
	}
	return self;
}
#if 1
- setEnabled:(BOOL) flag
{	[super setEnabled:flag];
	//[self setSelectable:flag];
	[self setEditable:flag];
	if(flag)	[self selectText:self];
	else		[self discardSelection];
	fFlags.disabled= !flag;
	[self display];
	return self;
}
-(BOOL) isEnabled
{	return !fFlags.disabled;
}
#endif

#define	PT_CHR	jumpchar
#define	MX_FBF	32

-(const char *)stringValue
{	char *erg=mergeText(template,control,content,buffer,blankchar);
#if 1
	if(completionChar)
	{	int		contLen=strlen(content),fillLen=nCharsInStr(template,blankchar)-contLen;
		if(fillLen > 0)
		{	char	*dstPtr=(char *)erg+contLen;
			memset(dstPtr,completionChar,fillLen);
			dstPtr[fillLen]=0;
		}
	}
#endif
	return erg;
}
- setStringValue:(const char *)val atOffset:(int)o
{	filterText((char *)val,content+o,template,control);
	[cell setStringValue:content];
	if([window firstResponder] == self)
		[self setSel:MIN(sp0.cp,strlen(content)):MIN(spN.cp,strlen(content))];
	[self display];
	return self;
}
#define	DIGITS	"0123456789"
BOOL	isFloat(const char *val)
{	int	l1;
	if (*val=='-') val++;
	if (val[l1=strspn(val,DIGITS)]!=PT_CHR) return NO;
	return (strspn(val+l1+1,DIGITS)+l1+1)==strlen(val)? YES: NO;
}
- setStringValue:(const char *)val
{
	strcpy(lb,val);
	if (isFloat(val))
	{	const char	*cStr=strchr(control, PT_CHR);
		if (cStr && !strchr(cStr+1,PT_CHR))	//floting-point-like formatting
		{	int	length=strlen(control);
			sprintf(lb,"%*.*f",length,length-(int)(cStr-control)-1,atof(val));
		}
	}

	if (delegate && [delegate respondsTo:@selector(modifyStringValue:for:)])
		[delegate modifyStringValue:content for:self];	//<!>??
	[self setStringValue:lb atOffset:0];
	//[cell setStringValue:content];
	return self;
}
- setDoubleValue:(double)val
{	char		floatingStr[MX_FBF];
	sprintf(floatingStr,"%f",val);
	return [self setStringValue:floatingStr];
}
- takeDoubleValueFrom:sender
{	return [self setDoubleValue:[sender doubleValue]];
}
- (double)doubleValue
{	return atof([self pureStringValue]);
}
- takeFloatValueFrom:sender
{	return [self setDoubleValue:[sender floatValue]];
}
- (float)floatValue
{	return (float)atof([self pureStringValue]);
}
- setIntValue:(int)val
{	char	intStr[MX_FBF];
	sprintf(intStr,"%*d",nCharsInStr(content, blankchar),val);
	[self setStringValue:intStr];
	return self;
}
- takeIntValueFrom:sender
{	return [self setIntValue:[sender intValue]];
}
- (int)intValue
{	return atoi([self pureStringValue]);
}

- setValue:(const char *)val
{	strcpy(content,val);
	return self;
}
- setMask:(const char *)mask
{	template=mask;
	return self;
}
-(const char*) mask
{	return template;
}
-(const char*) validString
{	return valid;
}
- setModifyString:(const char *)mask
{	control=mask;
	return self;
}
-(const char*) modifyString
{	return control;
}
-(const char*) pureStringValue
{	return content;
}
- setGrid:(BOOL)flag
{	fFlags.drawGrid=flag;
	return [self display];
}
- setOverwrite:(BOOL)flag
{	fFlags.overwrite=flag;
	return self;
}
-(BOOL)grid
{	return fFlags.drawGrid;
}
-(BOOL)overwrite
{	return fFlags.overwrite;
}
-(BOOL) specialKeybindings
{	return fFlags.specialKeybindings;
}
- setSpecialKeybindings:(BOOL)flag
{	fFlags.specialKeybindings=flag;
	return self;
}
-(char)blankchar
{	return blankchar;
}
- setBlankchar:(char)bc
{	blankchar=bc;
	return self;
}
- (const char*)valid
{	return valid;
}
- setValidString:(const char*)aValid
{	valid=aValid;
	return self;
}

- (BOOL)beepWhenRight
{	return fFlags.beepWhenRight;
}
- setBeepWhenRight:(BOOL)aBeepWhenRight
{	fFlags.beepWhenRight=aBeepWhenRight;
	return self;
}

- (BOOL)escClearLine
{	return fFlags.escClearLine;
}
- setEscClearLine:(BOOL)aEscClearLine
{	fFlags.escClearLine=aEscClearLine;
	return self;
}
- read: (NXTypedStream*)stream
{	int version;
	[super read:stream];

	switch(version=NXTypedStreamClassVersion(stream,"MiscFormField"))
	{	case 1:
		case 2:
			NXReadTypes(stream,"@fi",&font,&charHeight,&fFlags);
		break;
		case 3:
			NXReadTypes(stream,"@fi",&font,&charHeight,&fFlags);
			NXReadObject(stream);
		break;
		case FORM_FIELD_CURRENT_VERSION:
		{	NXReadTypes(stream,"@fic",&font,&charHeight,&fFlags,&completionChar);
			delegate=NXReadObject(stream);
		} break;
		default: NXRunAlertPanel("Error","Unsupported MiscFormField version %d","Exit",NULL,NULL,version);
	}
	return self;
}
- write: (NXTypedStream*)stream
{	[super write:stream];
	NXWriteTypes(stream,"@fic",&font,&charHeight,&fFlags,&completionChar);
	NXWriteObjectReference(stream,delegate);
	return self;
}

- delegate
{	return delegate;
}
- setDelegate:aDelegate
{	delegate=aDelegate;
	return self;
}

- (char)completionChar
{	return completionChar;
}
- setCompletionChar:(char)aCompletionChar
{	completionChar=aCompletionChar;
	return self;
}

@end



@interface FormField:MiscFormField
{
}
@end
@implementation FormField
@end
@interface FormFieldCell:MiscFormFieldCell
{
}
@end
@implementation FormFieldCell
@end
