/* TurboTFCell.m
 *
 *   TurboTFCell is a subclass of TextFieldCell which supports
 *		a) length-watching on a string -- maxLength  [length of 0
 *		    indicates that length-watching is not active]
 *		b) auto-jumping to nextText if the maximum is reached
 *		c) acceptsReturn will cause the Return character to be
 *		    interpreted literally rather than as a signal to end editing
 * 
 *
 * You may freely copy, distribute, and reuse the code in this example.
 * NeXT disclaims any warranty of any kind, expressed or  implied, as to its
 * fitness for any particular use.
 *
 * Written by:  Sharon Zakhour 
 * Created:  Oct/91
 */

#import "TurboTFCell.h"

@implementation TurboTFCell

/* In order for the length watching (and auto-jumping since that is 
 * determined by length) to work properly, the character and text
 * filters need to know the identity of the cell they are working with.
 */
static id theCell = NULL;

- init
{
    return [self initTextCell:""];
}

- initTextCell: (const char*) aString
{
    [super initTextCell:aString];
    
    // Initialize these to the default TextField behavior
    originalText = NULL;
    customTextFilter = (NXTextFilterFunc)lengthFilter;
    [self setAutoJump: NO forLength: 0];
    [self setAcceptsReturn: NO];
    return self;
}

- free
{
    if (originalText)
	free(originalText);
    originalText = NULL;
    return[super free];
}

/* The primary reason I've overridden select: and edit: (below) is to set
 * the local static variable "theCell" to self.  The filter functions must know
 * about the current cell in order to query it for information on length,
 * autojumping, etc.  The only way that a cell becomes active are
 * through one of these methods.
 */
- select:(const NXRect *)aRect inView:controlView editor:textObj delegate:anObject start:(int)selStart length:(int)selLength
{
    /* do what the superclass would do */
    [super select:aRect inView:controlView editor:textObj 
	    delegate:anObject start:selStart length:selLength];
    
    theCell = self;
    if (maxLength && originalText)
	    strcpy(originalText, [self stringValue]);

    [textObj setTextFilter:(NXTextFilterFunc)customTextFilter];
    [textObj setCharFilter:autoJumpCharFilter];
    return self;
}

/* The primary reason I've overridden edit: and select: (above) is to set
 * the local static variable "theCell" to self.  The filter functions must know
 * about the current cell in order to query it for information on length,
 * autojumping, etc.  The only way that a cell becomes active are
 * through one of these methods.
 */
- edit:(const NXRect *)aRect inView:controlView editor:textObj delegate:anObject event:(NXEvent *)theEvent
{
    /* do what the superclass would do */
    [super edit:aRect inView:controlView editor:textObj 
	    delegate:anObject event:theEvent];

    theCell = self;
    if (maxLength && originalText)
	    strcpy(originalText, [self stringValue]);

    [textObj setTextFilter:(NXTextFilterFunc)customTextFilter];
    [textObj setCharFilter:autoJumpCharFilter];
    return self;
}

- setMaxLength: (int) length
{
    if (originalText)
    {
	free(originalText);
	originalText = NULL;
    }
    maxLength = length;
    if (length)
	originalText = (char *)malloc(maxLength);
    return self;
}

- (int) maxLength;
{
    return maxLength;
}

- setAutoJump: (BOOL) flag forLength: (int)length
{
    autoJump = flag;
    [self setMaxLength: length];
    return self;
}

- (BOOL) autoJump
{
    return autoJump;
}

- setAcceptsReturn: (BOOL) flag 
{
    acceptsReturn = flag;
    return self;
}

- (BOOL) acceptsReturn
{
    return acceptsReturn;
}

- setOriginalText: (char *)aString
{
    if (originalText)
	strcpy(originalText, aString);
    return self;
}

- (char *)originalText
{
    return originalText;
}

- setCustomFilter: (NXTextFilterFunc) aFilter;
{
    customTextFilter = (NXTextFilterFunc)aFilter;
    return self;
}

- (NXTextFilterFunc) customTextFilter
{
    return (NXTextFilterFunc) customTextFilter;
}


/* Run the given string through the Cell's text filter to 
 * see if it passes muster.
 */
- (BOOL) checkString: (char *)aString
{
    int	 i, len;
    char *cp;
    id textObj;
    void *fooPtr;
    
    if (customTextFilter == (NXTextFilterFunc)nil)
    	return YES;
    
    textObj = [[[self controlView] window] getFieldEditor :YES for: self];
    for (i = 0, cp = aString; i < strlen(aString); i++, cp++)
    {
    	len = 1;
	// fooPtr will eliminate a compiler warning about "unused value"
	fooPtr = (void*)(*(NXTextFilterFunc)customTextFilter)((id)textObj,
		(unsigned char *)cp, (int *)&len, (int)i);
	if (len == 0) 
	    return NO;
    }
    return YES;
}

// This is the default text filter.  If another has been installed via the setCustomFilter:
// method, then this one will not get called.
char *lengthFilter(id textObj, char *inputText, int *inputLength, int position)
{
    int maxLength;
    BOOL autoJump;
   
    maxLength = [theCell maxLength];
    autoJump = [theCell autoJump];
    
    if (maxLength && !autoJump && [textObj textLength] > maxLength)
    {
	*inputLength = 0;
	return "";
    }
    
    return inputText;
}

// This could easily be extended to include letters with diacritics.
// See Chapter 6 Summary for KeyInfo for a full listing of keyboard
// event information.
#define IS_VALID_ASCII(ch)	((ch) >= ' ' && (ch) <= '~')

unsigned short autoJumpCharFilter(unsigned short charCode, int flags, unsigned
short charSet)
{
    NXSelPt	 start, end;
    NXEvent	*event, fakeEvent;
    id			textObj;
    int			curLength, maxLength;
    BOOL 		autoJump, acceptsReturn;
    
    maxLength = [theCell maxLength];
    autoJump = [theCell autoJump];
    acceptsReturn = [theCell acceptsReturn];
    
    event = [NXApp currentEvent];
    textObj = [[[theCell controlView] window] getFieldEditor :YES for: theCell];
    curLength = [textObj textLength] +1;
    
    /* Anything that's highlighted is going to get clobbered, so let's adjust */
    [textObj getSel: &start : &end];
    if (start.cp != end.cp)
    	curLength -= end.cp - start.cp; 

    // Currently acceptsReturn doesn't work very well with maxLength because
    // it counts newlines which you probably don't want.  If you want something
    // more sophisticated then you'll have to parse the runs!
    if (acceptsReturn && charCode == NX_CR)
	return NXEditorFilter(charCode, flags, charSet);
	
    // Watch out for interpreted rather than real keystrokes -- let them
    // through even though we have reached our maximum length.
    if (autoJump && maxLength && curLength > maxLength &&
	    IS_VALID_ASCII(charCode))
    {
	fakeEvent = *event;
	fakeEvent.time++;
	DPSPostEvent(&fakeEvent, YES);
	return NX_TAB;
    } 
    return NXFieldFilter(charCode, flags, charSet);
}

@end
