// OATextFieldCell:TextFieldCell
// 
// COPYRIGHT 1994 BY M. ONYSCHUK AND ASSOCIATES INC.
// ALL RIGHTS RESERVED.

#import "OATextFieldCell.h"
#import "OATextFieldFormatter.h"

#import <appkit/Font.h>
#import <appkit/Text.h>
#import <appkit/color.h>
#import <appkit/NXCType.h>
#import <appkit/publicWraps.h>

#import <defaults/defaults.h>

#include <libc.h>

#define CLASS_VERSION		1

#define DF_NAME			"OATextField"

#define DF_COLOR_OPTIONAL	"OptionalFieldColor"
#define COLOR_OPTIONAL		"0.53 0.66 1.0"

#define DF_COLOR_MANDATORY	"MandatoryFieldColor"
#define COLOR_MANDATORY		"1.0 0.6 0.6"

#define DF_FONT			"Font"
#define FONT			"Courier 12.0"

@interface OATextFieldCell (Private)
// Factory methods
+ (OATextFieldCell *)currentCell;
+ setCurrentCell:(OATextFieldCell *)aCell;

// Instance methods
- (void)_setBackgroundColorFromDefaultsDatabase;
- (void)_setFontFromDefaultsDatabase;

- (void)_setStringValueByRemovingWhitespace;
- (void)_setStringValueFromCaseMapping;
@end

@implementation OATextFieldCell (Private)

// Factory methods

static	OATextFieldCell *currentCell = nil;

+ (OATextFieldCell *)currentCell
{
    return currentCell;
}
+ setCurrentCell:(OATextFieldCell *)aCell
{
    currentCell = aCell;
    return self;
}


// Instance methods


static NXColor 
colorFromRGBString(const char *aString)
{
    if (aString) {
	float r, g, b;
	sscanf(aString, "%f %f %f", &r, &g, &b);
	return NXConvertRGBAToColor(r, g, b, NX_NOALPHA);
    } else {
    	return NX_COLORCLEAR;
    }
}



static Font        *
fontFromFontString(const char *aString)
{
    if (aString) {
	char                name[80];
	float               size;

	if (sscanf(aString, "%[^ \t]%f", name, &size) == 2) {
	    return[Font newFont:name size:size];
	} else {
	    return nil;
	}
    } else {
	return nil;
    }
}


- (void)_setBackgroundColorFromDefaultsDatabase
{
    const char         *value = [self stringValue];
    unsigned int        length = (value) ? strlen(value) : 0;

 // if the field is empty, and the defaults database
 // specifies a color for the mandatory flag setting, then
 // set the background color to the color specified...
    if (length == 0) {

   	const char *df;
	
	if ([self isMandatory]) {
	    df = NXGetDefaultValue(DF_NAME, DF_COLOR_MANDATORY);
	} else {
	    df = NXGetDefaultValue(DF_NAME, DF_COLOR_OPTIONAL);
	}	
	if (df) {
	    [self setBackgroundColor:colorFromRGBString(df)];
	}
    }
}


- (void)_setFontFromDefaultsDatabase
{
    const char			*df;

    if ((df = NXGetDefaultValue(DF_NAME, DF_FONT)) != NULL) {
	Font			*font;

	if ((font = fontFromFontString(df)) != nil) {
	    [self setFont:font];
	}
    }
}


- (void)_setStringValueByRemovingWhitespace
{
    const char *value = [self stringValue];
    unsigned int length = (value) ? strlen(value) : 0;
    
    if (isSettingValue) {
    	return;
    } 
 
    isSettingValue = YES;
    
    if (length > 0) {
    	int i, j;
    	char *copy = NXCopyStringBufferFromZone(value, [self zone]);
	
	if (removeLeadingWhitespace) {
	    for (i = 0; NXIsSpace(copy[i]); i++) {
	    }

	    if (i > 0) {
		for (j = 0; i < length; i++, j++) {
		    copy[j] = copy[i];
		}
		copy[j] = copy[i];
	    }
	}

	if (removeTrailingWhitespace) {
	    for (j = strlen(copy); j && NXIsSpace(copy[j-1]); j--) {
	    }
	    copy[j] = '\0';
	}
	
	[self setStringValueNoCopy:copy];
    }
 
    isSettingValue = NO;
}
	
- (void)_setStringValueFromCaseMapping
{
    const char *value = [self stringValue];
    unsigned int length = (value) ? strlen(value) : 0;

    if (isSettingValue) {
    	return;
    }
    
    isSettingValue = YES;
        
    if (length > 0) {
    	switch (caseMapping) {
	case MAP_TO_UPPER:
	    {
	    	int index = length;
	    	char *copy = NXCopyStringBufferFromZone(value,[self zone]);
		while (index--) {
			copy[index] = NXToUpper(copy[index]);
		}
		[self setStringValueNoCopy:copy];
	    }
	    break;
	case MAP_TO_LOWER:
	    {
	    	int index = length;
	    	char *copy = NXCopyStringBufferFromZone(value,[self zone]);
		while (index--) {
		    copy[index] = NXToLower(copy[index]);
		}
		[self setStringValueNoCopy:copy];
	    }
	    break;
	case MAP_TO_PROPER:
	    {
		int index = length;
		char *copy = NXCopyStringBufferFromZone(value,[self zone]);

		while (index--) {
		    if ((index == 0) || NXIsSpace(copy[index - 1])) {
			copy[index] = NXToUpper(copy[index]);
		    } else {
			copy[index] = NXToLower(copy[index]);
		    }
		}
		[self setStringValueNoCopy:copy];
	    }
	    break;
	case MAP_TO_STUDLY:
	    {
	    	// YeAH! StUDlY CapS
		int index = length;
		char *copy = NXCopyStringBufferFromZone(value,[self zone]);

		while (index--) {
		    if (random()&01) {
		    	copy[index] = NXToUpper(copy[index]);
		    } else {
		    	copy[index] = NXToLower(copy[index]);
		    }		    
		}
		[self setStringValueNoCopy:copy];
	    }
	    break;
	case MAP_TO_NONE:
	    break;
	}
    }
    
    isSettingValue = NO;
}

@end


@implementation OATextFieldCell

+ initialize
// Initializes the receiver, sets the class version, and registers
// several default values.
{
    static NXDefaultsVector defaults = {
					{DF_COLOR_OPTIONAL, NULL},
					{DF_COLOR_MANDATORY, NULL},
					{DF_FONT, NULL},
					{NULL}
    };

    NXRegisterDefaults(DF_NAME, defaults);
    
    [self setVersion:CLASS_VERSION];
    
    return self;
}

// Instance methods

static char	    emptyText[1] = {'\0'};
static char         outputText[80] = {'\0'};

char               *
OAFilterFun(id textObj,
	    unsigned char *inputText,
	    int *inputLength,
	    int position)
{
    OATextFieldCell           *cell = [OATextFieldCell currentCell];

    OACharacterFiltering  filtering = [cell characterFiltering];
    const char          *exceptions = [cell filteringExceptions];

    BOOL        checksMaximumLength = [cell doesCheckMaximumLength];
    int	              maximumLength = [cell maximumLength];
    
    OATextFieldFormatter *formatter = [cell textFieldFormatter];


    if (inputText == NULL) {
    // if the input is NULL, then return... 
	*inputLength = 0;
	return emptyText;
    }

    if (NXIsCntrl(inputText[0])) {
    	outputText[0] = inputText[0];
	outputText[1] = '\0';
	return outputText;
    }
        
    if (checksMaximumLength && (position >= maximumLength)) {
    // if the imput is too long then beep and return...
    	NXBeep();
	
   	*inputLength = 0;
	return emptyText;
    } 

    if ((filtering == ALLOW_ALL) || 
	(filtering == ALLOW_LETTERS && NXIsAlpha(inputText[0])) ||
	(filtering == ALLOW_NUMBERS && NXIsDigit(inputText[0])) ||
	(filtering == ALLOW_ALPHANUMERIC && NXIsAlNum(inputText[0])) ||
	(exceptions && index(exceptions, inputText[0]))) {
    // if the input passes filter presets, then if the
    // cell has a custom formatter, apply it here...
	if (formatter) {
	    const char *formatterOutput;
	    
	    [formatter filterSubstring:inputText
		ofSize:*inputLength
		at:position];
	    
	    formatterOutput = [formatter output];
    
    	    if (formatterOutput) {
	    	*inputLength = strlen(formatterOutput);
		return formatterOutput;
	    } else {
	    	*inputLength = 0;
		return emptyText;
	    }
	} else {
	    strncpy(outputText, inputText, *inputLength);
	    return outputText;
	}
    } else {
    // Beep to signal an input error...
    	NXBeep();
	
    	*inputLength = 0;
	return emptyText;
    }
}


- initTextCell:(const char *)stringValue
{
    [super initTextCell:stringValue];

    oldTextFilter = (NXTextFilterFunc)nil;
    
    [self _setStringValueFromCaseMapping];
    [self _setStringValueByRemovingWhitespace];

    [self _setFontFromDefaultsDatabase];
    [self _setBackgroundColorFromDefaultsDatabase];

    return self;
}

- free
{
    if (filteringExceptions) {
	NXZoneFree([self zone], filteringExceptions);
	filteringExceptions = NULL;
    }
    return[super free];
}

- select:(const NXRect *)aRect
    inView:controlView
    editor:textObj
    delegate:anObject
    start:(int)selStart
    length:(int)selLength
{
    [self setBackgroundColor:NX_COLORWHITE];

    [super select:aRect
	inView:controlView
	editor:textObj
	delegate:anObject
	start:selStart
	length:selLength];

    oldTextFilter = [textObj textFilter];

    [textObj setTextFilter:(NXTextFilterFunc)OAFilterFun];

    [OATextFieldCell setCurrentCell:self];
    
    return self;
}


- (BOOL)isEntryAcceptable:(const char *)value
{
    unsigned int        length = (value) ? strlen(value) : 0;
    int			ivalue = atoi(value);
    
 // perform field length check...
    if ((length == 0) && [self isNullable]) {
	return YES;
    }

    if ([self doesCheckMinimumLength] &&
	(length < [self minimumLength])) {
	NXBeep();
	return NO;
    } else {
    	goto BEGIN_CHECK_VALUES;		// GOTO
    }
    
    if ([self doesCheckMaximumLength] &&
	(length > [self maximumLength])) {
	NXBeep();
	return NO;
    }
    
BEGIN_CHECK_VALUES:

 // begin value check...
    if ([self doesCheckMinimumValue] &&
	       (ivalue < [self minimumValue])) {
	NXBeep();
	return NO;
    }

    if ([self doesCheckMaximumValue] &&
	       (ivalue > [self maximumValue])) {
	NXBeep();
	return NO;
    }

 // after preliminary checks, perform custom formatting and
 // validation defined by the formatter...
    if (formatter) {
    	if ([formatter formatString:[self stringValue]] == YES) {
	 // this is a bit bass-ackwards by my reasoning, but it is
	 // the way the text delegate indicates that an error in input
	 // has occurred...
	    NXBeep();
	    return NO;
	} else {
	    [self setStringValue:[formatter output]];
	    return YES;
	}
    }
    return YES;
}


- edit:(const NXRect *)aRect
    inView:controlView
    editor:textObj
    delegate:anObject
    event:(NXEvent *)theEvent
{
    [self setBackgroundColor:NX_COLORWHITE];

    [super edit:aRect
	inView:controlView
	editor:textObj
	delegate:anObject
	event:theEvent];

    oldTextFilter = [textObj textFilter];

    [textObj setTextFilter:(NXTextFilterFunc)OAFilterFun];

    [OATextFieldCell setCurrentCell:self];

    return self;
}
    
- endEditing:anObject
{
    [self _setStringValueFromCaseMapping];
    [self _setStringValueByRemovingWhitespace];
    [self _setBackgroundColorFromDefaultsDatabase];

    [anObject setTextFilter:(NXTextFilterFunc)oldTextFilter];

    [OATextFieldCell setCurrentCell:nil];

    return [super endEditing:anObject];
}


- (OACaseMapping)caseMapping
{
    return caseMapping;
}

- setCaseMapping:(OACaseMapping)aValue
{
    caseMapping = aValue;
    [self _setStringValueFromCaseMapping];
    return self;
}


- (OACharacterFiltering)characterFiltering
{
    return characterFiltering;
}

- setCharacterFiltering:(OACharacterFiltering)aValue
{
    characterFiltering = aValue;
    return self;
}


- (const char *)filteringExceptions
{
    return filteringExceptions;
}
- setFilteringExceptions:(const char *)aValue
{
    NXZone             *z = [self zone];

    if (filteringExceptions) {
	NXZoneFree(z, filteringExceptions);
    }

    filteringExceptions = (aValue) ?
    	NXCopyStringBufferFromZone(aValue,z) : NULL;

    return self;
}


- (BOOL)doesRemoveLeadingWhitespace
{
    return removeLeadingWhitespace;
}
- setRemovesLeadingWhitespace:(BOOL)aValue
{
    removeLeadingWhitespace = aValue;
    [self _setStringValueByRemovingWhitespace];
    return self;
}

- (BOOL)doesRemoveTrailingWhitespace
{
    return removeTrailingWhitespace;
}

- setRemovesTrailingWhitespace:(BOOL)aValue
{
    removeTrailingWhitespace = aValue;
    [self _setStringValueByRemovingWhitespace];
    return self;
}


- (int)minimumLength
{
    return minimumLength;
}

- setMinimumLength:(int)aValue
{
    minimumLength = aValue;
    return self;
}

- (BOOL)doesCheckMinimumLength
{
    return checkMinimumLength;
}

- setChecksMinimumLength:(BOOL)aValue
{
    checkMinimumLength = aValue;
    return self;
}

- (int)maximumLength
{
    return maximumLength;
}

- setMaximumLength:(int)aValue
{
    maximumLength = aValue;
    return self;
}


- (BOOL)doesCheckMaximumLength
{
    return checkMaximumLength;
}

- setChecksMaximumLength:(BOOL)aValue
{
    checkMaximumLength = aValue;
    return self;
}

- (BOOL)isNullable
{
    return isNullable;
}

- setNullable:(BOOL)aValue
{
    isNullable = aValue;
    return self;
}


- (BOOL)isMandatory
{
    return isMandatory;
}

- setMandatory:(BOOL)aValue
{
    isMandatory = aValue;
    [self _setBackgroundColorFromDefaultsDatabase];    
    return self;
}


- (int)minimumValue
{
    return minimumValue;
}

- setMinimumValue:(int)aValue
{
    minimumValue = aValue;
    return self;
}


- (BOOL)doesCheckMinimumValue
{
    return checkMinimumValue;
}

- setChecksMinimumValue:(BOOL)aValue
{
    checkMinimumValue = aValue;
    return self;
}


- (int)maximumValue
{
    return maximumValue;
}

- setMaximumValue:(int)aValue
{
    maximumValue = aValue;
    return self;
}


- (BOOL)doesCheckMaximumValue
{
    return checkMaximumValue;
}

- setChecksMaximumValue:(BOOL)aValue
{
    checkMaximumValue = aValue;
    return self;
}


- (OATextFieldFormatter *)textFieldFormatter
{
    return formatter;
}

- setTextFieldFormatter:(OATextFieldFormatter *)aValue
{
    return [self setTextFieldFormatterNoCopy:[aValue copy]];
}

- setTextFieldFormatterNoCopy:(OATextFieldFormatter *)aValue
{
    if (formatter != aValue) {
    	[formatter free];
    }
    
    formatter = aValue;
    
    [formatter setTextFieldCell:self];
    
    return self;
}


- read:(NXTypedStream *)aStream
{
    [super read:aStream];

    NXReadType(aStream,@encode(OACaseMapping), &caseMapping);
    NXReadType(aStream,@encode(OACharacterFiltering), &characterFiltering);
    NXReadType(aStream,@encode(char*), &filteringExceptions);
    NXReadType(aStream,@encode(BOOL), &removeLeadingWhitespace);
    NXReadType(aStream,@encode(BOOL), &removeTrailingWhitespace);
    NXReadType(aStream,@encode(int), &minimumLength);
    NXReadType(aStream,@encode(int), &maximumLength);
    NXReadType(aStream,@encode(BOOL), &checkMinimumLength);
    NXReadType(aStream,@encode(BOOL), &checkMaximumLength);
    NXReadType(aStream,@encode(BOOL), &isNullable);
    NXReadType(aStream,@encode(BOOL), &isMandatory);
    NXReadType(aStream,@encode(int), &minimumValue);
    NXReadType(aStream,@encode(int), &maximumValue);
    NXReadType(aStream,@encode(BOOL), &checkMinimumValue);
    NXReadType(aStream,@encode(BOOL), &checkMaximumValue);

    formatter = NXReadObject(aStream);
    
    return self;
}

- write:(NXTypedStream *)aStream
{
    [super write:aStream];
    
    NXWriteType(aStream,@encode(OACaseMapping), &caseMapping);
    NXWriteType(aStream,@encode(OACharacterFiltering), &characterFiltering);
    NXWriteType(aStream,@encode(char*), &filteringExceptions);
    NXWriteType(aStream,@encode(BOOL), &removeLeadingWhitespace);
    NXWriteType(aStream,@encode(BOOL), &removeTrailingWhitespace);
    NXWriteType(aStream,@encode(int), &minimumLength);
    NXWriteType(aStream,@encode(int), &maximumLength);
    NXWriteType(aStream,@encode(BOOL), &checkMinimumLength);
    NXWriteType(aStream,@encode(BOOL), &checkMaximumLength);
    NXWriteType(aStream,@encode(BOOL), &isNullable);
    NXWriteType(aStream,@encode(BOOL), &isMandatory);
    NXWriteType(aStream,@encode(int), &minimumValue);
    NXWriteType(aStream,@encode(int), &maximumValue);
    NXWriteType(aStream,@encode(BOOL), &checkMinimumValue);
    NXWriteType(aStream,@encode(BOOL), &checkMaximumValue);

    NXWriteObject(aStream,formatter);
    
    return self;
}

- copyFromZone:(NXZone *)aZone
{
    OATextFieldCell *copy = [super copyFromZone:aZone];
    
    copy->filteringExceptions = (filteringExceptions) ?
    	NXCopyStringBufferFromZone(filteringExceptions, aZone) : NULL;
	
    copy->formatter = [formatter copyFromZone:aZone];
    
    return copy;
}


- awake
{
    id ret = [super awake];
    
    [self _setStringValueFromCaseMapping];
    [self _setStringValueByRemovingWhitespace];

    [self _setFontFromDefaultsDatabase];
    [self _setBackgroundColorFromDefaultsDatabase];

    [self setTextFieldFormatterNoCopy:formatter];

    return ret;
}

- awakeFromNib
{
    return [self awake];
}

@end
