
/*------------------------------------------------------------------- 
   Copyright 1991, 1992 Don McGregor
   
    The implementation of a subclass that adds new types of error checking
    to a FormCell class.
    
    9/2/91 Don McGregor
    
   3/19/92: cleaned up some code, made things more compact,
            attemped to do a custom inspector for IB without
	    success.  Made changes to -init to make it work
	    better with GeneralForm in a custom IB palette.
   3/21/92  Screw it.  Rewrote it extensively to get rid of the
            stuff that requires an int to be set that switches
	    between modes.  Now it works off a SEL that corresponds
	    to a method name, which makes it all a lot more extensible.
	    Backward compatible with the old way, so long as an
	    initial qualifier function is not set.IB inspector for the 
	    Cell now works.  Stupid bug was throwing me off.
   	    
   You may freely use this code in any way you wish.
   however, you may not charge any money for the use
   of it.
   
   No warranty of any kind, etc, etc.	    
-------------------------------------------------------------------*/

#import "GeneralFormCell.h"
#import "GeneralFormCellInspector.h"
#import <strings.h>
#import <stdio.h>
#import <stdlib.h>

	//A psuedo-class variable.  This won't be inherited by subclasses,
	//but will be shared among all instances of this class.  It's 
	//terminated with a null char as a sentinel.
	//See comments for NXBrowser delegate methods
			
static char *typeList[] = {"anyType:",
			   "isDouble:", "posDouble:", "nonNegDouble:",
 			   "isFloat:", "posFloat:", "nonNegFloat:",
			   "isInt:", "posInt:", "nonNegInt:",
			   "isDate:", "isSSN:",
			   "\0"};



@implementation GeneralFormCell


- init
{
  /*-------------------------------------------------------------------
     Does some initialization work on a few things to get a "standard"
     look and feel.  Sets the entry type to be anyType:, which accepts
     all input.
  -------------------------------------------------------------------*/
  [super init];
  
  [self setTitleAlignment:NX_RIGHTALIGNED];
  [self setBezeled:YES];
  [self setEntryQual:"anyType:"];
  
  return self;
}


-(BOOL)isEntryAcceptable:(const char *)aString
{
  /*-------------------------------------------------------------------
    Override of the FormCell method.  this is a reworking of the
    way that NeXT went about it.  They relied on using a case statement
    and defined constants to dispatch to the correct entry field checker.
    that really doesn't scale too well; you need to keep track of what
    constants have already been used and so on.  Blech.
    
    This uses the SEL that points to the entry checking method. Some
    magic with pointers to functions and prototypes; see methodFor:
    method in Object docs. 
   -------------------------------------------------------------------*/
   
   typedef BOOL (*entryCheck)(id, SEL, NXAtom);
   
   int		entryType;
   entryCheck	entryCheckingFcn;    
  
 	//get ptr to fcn that does the checking
	
  entryCheckingFcn = (entryCheck)[self methodFor:entryQual];
  
  if([self respondsTo:entryQual])
    {
      return entryCheckingFcn(self, entryQual, aString);
    } 
  
  	//Fallback.  It might be that the programmer is trying to 
	//use the old method, which involves setting an integer.
	//This is here for backward compatability.
	//If no entry qualifier is set, we look at the
	//entryType field next.

  entryType = [super entryType];
  	
  if((entryType >=0) && (entryType <= 7))
     return [super isEntryAcceptable:aString];
  
    
    	//Oughta never get here.  If it does, the programmer screwed up
	//by specifiying a non-existent entry Qualifier.  In effect, this
	//is an NX_ANYTYPE

  return YES;

}


-(BOOL)anyType:(const char *)aString
{
  /*-------------------------------------------------------------------
     Returns YES, always.  Any type of field is acceptable.
  -------------------------------------------------------------------*/
  
  return YES;
}

-(BOOL)isDouble:(const char *)aString
{
  /*-------------------------------------------------------------------
      Checks for a double type entry.  Works by sending messages to
      super that use the old data checking routines.
  -------------------------------------------------------------------*/
  
  [self setEntryType:NX_DOUBLETYPE];
  return [super isEntryAcceptable:aString];
}

-(BOOL)posDouble:(const char *)aString
{
  /*-------------------------------------------------------------------
      Checks for a positive double type entry.  Works by sending messages to
      super that use the old data checking routines.
  -------------------------------------------------------------------*/
  
  [self setEntryType:NX_POSDOUBLETYPE];
  return [super isEntryAcceptable:aString];
}

-(BOOL)nonNegDouble:(const char *)aString
{
  /*-------------------------------------------------------------------
     Checks for a non-negative double, that is any double greater than
     or equal to zero.
    --------------------------------------------------------------------*/
  
  double	theNumber;
  
   
  	//If it is a double type, make sure it's greater than zero.
	//If it's not, we know to return false.
	
  if(([self isDouble:aString]) == YES)
    {
      theNumber = atof(aString);
      
      if(theNumber >= 0.0)
          return YES;
    }
   
  return NO;   
}      



-(BOOL)isFloat:(const char *)aString
{
  /*-------------------------------------------------------------------
      Checks for a float type entry.  Works by sending messages to
      super that use the old data checking routines.
  -------------------------------------------------------------------*/
  
  [self setEntryType:NX_FLOATTYPE];
  return [super isEntryAcceptable:aString];
}

-(BOOL)posFloat:(const char *)aString
{
  /*-------------------------------------------------------------------
      Checks for a positive float type entry.  Works by sending messages to
      super that use the old data checking routines.
  -------------------------------------------------------------------*/
  
  [self setEntryType:NX_POSFLOATTYPE];
  return [super isEntryAcceptable:aString];
}

-(BOOL)nonNegFloat:(const char *)aString
{
  /*-------------------------------------------------------------------
      Checks for a non-neg float type entry.    -------------------------------------------------------------------*/
  
  float	theNumber;
  
   
  	//If it is a float type, make sure it's greater than zero.
	//If it's not, we know to return false.
	
  if(([self isFloat:aString]) == YES)
    {
      theNumber = atof(aString);
      
      if(theNumber >= 0.0)
          return YES;
    }
   
  return NO;   
}      

-(BOOL)isInt:(const char*)aString
{
  /*-------------------------------------------------------------------
      A boolean check to see if the string parses into an int.  This is
      more restrictive than just a "number."
  -------------------------------------------------------------------*/  
  
  [self setEntryType:NX_INTTYPE];
  
  return [super isEntryAcceptable:aString];
}

-(BOOL)posInt:(const char *)aString
{
  /*-------------------------------------------------------------------
      Checks for a positive float type entry.  Works by sending messages to
      super that use the old data checking routines.
  -------------------------------------------------------------------*/
  
  [self setEntryType:NX_POSINTTYPE];
  return [super isEntryAcceptable:aString];
}


-(BOOL)nonNegInt:(const char *)aString
{
  /*-------------------------------------------------------------------
     Similar to the above, but this checks for the possiblity of a 
     non-negative integer.   -------------------------------------------------------------------*/
  
  int		theNumber;
      
  	//If it is an int type, make sure it's greater than zero.
	//If it's not, we know to return false.
	
  if([self isInt:aString] == YES)
    {
      theNumber = atoi(aString);
      
      if(theNumber >= 0)
          return YES;
    }
   
  return NO;   
}

-(BOOL)isDate:(const char *)aString
{
  /*-------------------------------------------------------------------
     Checks the string passed to see if it is something like 
     a date, eg mo/day/yr.  Only very modest checking is implemented
     here, and this could be much improved.  We don't check
     to see if a date is valid for a possible month (2/31/92
     would pass, for example.)
   -------------------------------------------------------------------*/
  int day = -1, month = -1, year = -1;
  BOOL	okInput = YES;
  
  sscanf(aString, "%d/%d/%d", &month, &day, &year);
  
  if((day <= 0) || (day > 31))
     okInput = NO;
     
  if((month <= 0) || (month > 12))
     okInput = NO;
     
  if(year <= 0)
     okInput = NO;
     
  return okInput;
}

-(BOOL)isSSN:(const char*)aString
{
  /*-------------------------------------------------------------------
     Checks to see if the input is an SSN of the form
     nnn-nn-nnnn.  the first number should be between
     zero and 999, the second between zero and 99, 
     the third between zero and 9999.  SSA may put
     more restrictions on the numbers, but I don't know
     about them.
     
     The field lengths should also be appropriate; eg, 
     1-1-1 should not be allowed, but 001-01-0001 should
     be.
  -------------------------------------------------------------------*/

   int	number1 = -1, number2 = -1, number3 = -1;
   BOOL	okInput = YES;
   
   sscanf(aString, "%d-%d-%d", &number1, &number2, &number3);
   
   	//Check to see if the numbers are within
	//reasonable bounds.
	
   if((number1 < 0) || (number1 > 999))
     okInput = NO;
     
   if((number2 < 0) || (number2 > 99))
     okInput = NO;

   if((number3 < 0) || (number3 > 9999))
     okInput = NO;
     
    	//check on lengths of the fields

   if(strlen(aString) != 11) 
     okInput = NO;
     
   if(okInput)	//Put in this check to prevent past end of array errors
     {
        if((aString[3] != '-') || (aString[6] != '-'))
	  okInput = NO;
     }
	
	
   return okInput;
}


-(BOOL)isNumber:(const char*)aString
{
  /*-------------------------------------------------------------------
      A boolean check to see if the string parses into a number, any
      number--double, integer, postive, negative, whatever.
  -------------------------------------------------------------------*/  
  
  int		itsType;
  BOOL		result;
  
  itsType = [self entryType];
  [self setEntryType:NX_DOUBLETYPE];
  
  result = [super isEntryAcceptable:aString];
  [self setEntryType:itsType];
  
  return result;
}

  
-setEntryQual:(NXAtom)aString;
{
  /*-------------------------------------------------------------------
      sets the entry qualifying method to be the method who's string 
      is passed.
  -------------------------------------------------------------------*/
  SEL filterSEL;
  
  filterSEL = sel_getUid((STR)aString);
  
  if([self respondsTo:filterSEL])
     entryQual = filterSEL;
   else
     [self error:"attempt to set invalid entry qualifier; no such method as %s exists\n",
      aString];
  
  return self;
}

-(STR)currentQualName
{
  /*-------------------------------------------------------------------
     Returns the string name for the method of the current entry 
     qualifying method.
  -------------------------------------------------------------------*/
  
  return sel_getName(entryQual);
}  

-(const char *)inspectorName
{
  /*-----------------------------------------------------------------
     Returns the name of the Cell's inspector for IB.
    -----------------------------------------------------------------*/
  
  return "GeneralFormCellInspector";
}
  
    
-(int)browser:sender fillMatrix:matrix inColumn:(int)column
{
  /*-------------------------------------------------------------------
      Delegate method to fill the browser in the IB inspector
      with the names of the valid qualifiers.
    -------------------------------------------------------------------*/
  int	i, j=0;
  id	browserCell;
  
   while(*typeList[j])
    {
      
      [matrix addRow];
      i = [[matrix cellList] count];
      browserCell = [matrix cellAt:(i-1) :0];
      
      [browserCell setStringValue:typeList[j]];
      [browserCell setLeaf:YES];
      [browserCell setLoaded:YES];
      
      j++;
    }
     	
 return j;
}


-(BOOL)browser:sender columnIsValid:(int)column
{
  /*-------------------------------------------------------------------
     More delegate methods for the NXBrowser in the IB inspector.
    -------------------------------------------------------------------*/
  
  return YES;
}    
    
-write:typedStream
{
  /*-------------------------------------------------------------------
     Archiving stuff.
  -------------------------------------------------------------------*/
  
  [super write:typedStream];
  
  NXWriteType(typedStream,":", &entryQual);

  return self;
}

-read:typedStream
{
  
  [super read:typedStream];
  
  NXReadType(typedStream,":", &entryQual);
  
  return self;
}
    
@end
