/*
**  CriterionView.m
**
**  Copyright (c) 2004
**
**  Author: Yen-Ju Chen <yjchenx@hotmail.com>
**
**  This program is free software; you can redistribute it and/or modify
**  it under the terms of the GNU General Public License as published by
**  the Free Software Foundation; either version 2 of the License, or
**  (at your option) any later version.
**
**  This program is distributed in the hope that it will be useful,
**  but WITHOUT ANY WARRANTY; without even the implied warranty of
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**  GNU General Public License for more details.
**
**  You should have received a copy of the GNU General Public License
**  along with this program; if not, write to the Free Software
**  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include "CriterionView.h"
#include "CriterionObjectView.h"
#include "Constants.h"

#define PaddingX 5
#define PaddingY 8
#define SpaceBetweenCell 3
#define SpaceBetweenCellSmall 0 /* use only vertically */
#define SubjectPopUpButtonCellWidth 100
#define VerbPopUpButtonCellWidth 150
#define AddRemoveButtonWidth 19 /* each */

#ifdef GNUSTEP
static id clickedPopUpCell = nil;
#endif

@implementation CriterionView

- (NSString *) _stringFromVerb: (CriterionVerb) verb
{
  switch (verb)
    {
      case CriterionContainsVerb:
	return CriterionContainsVerbString;
      case CriterionDoesNotContainVerb:
        return CriterionDoesNotContainVerbString;
      case CriterionIsVerb:
        return CriterionIsVerbString;
      case CriterionIsNotVerb:
        return CriterionIsNotVerbString;
      case CriterionStartsWithVerb:
       	return CriterionStartsWithVerbString;
      case CriterionEndsWithVerb:
        return CriterionEndsWithVerbString;
      case CriterionMatchRegularExpressionVerb:
        return CriterionMatchRegularExpressionVerbString;
      case CriterionIsAfterVerb:
        return CriterionIsAfterVerbString;
      case CriterionIsBeforeVerb:
       	return CriterionIsBeforeVerbString;
      case CriterionIsInTheLastVerb:
	return CriterionIsInTheLastVerbString;
      case CriterionIsNotInTheLastVerb:
	return CriterionIsNotInTheLastVerbString;
      case CriterionIsInTheRangeVerb:
	return CriterionIsInTheRangeVerbString;
      case CriterionIsGreaterThanVerb:
	return CriterionIsGreaterThanVerbString;
      case CriterionIsLessThanVerb:
	return CriterionIsLessThanVerbString;
      default:
	return nil;
    }
}

- (CriterionVerb) _verbFromString: (NSString *) string
{
#define EQUAL(verb) [string isEqualToString: verb]
  if (EQUAL(CriterionContainsVerbString))
    {
      return CriterionContainsVerb;
    }
  else if (EQUAL(CriterionDoesNotContainVerbString))
    {
      return CriterionDoesNotContainVerb;
    }
  else if (EQUAL(CriterionIsVerbString))
    {
      return CriterionIsVerb;
    }
  else if (EQUAL(CriterionIsNotVerbString))
    {
      return CriterionIsNotVerb;
    }
  else if (EQUAL(CriterionStartsWithVerbString))
    {
      return CriterionStartsWithVerb;
    }
  else if (EQUAL(CriterionEndsWithVerbString))
    {
      return CriterionEndsWithVerb;
    }
  else if (EQUAL(CriterionMatchRegularExpressionVerbString))
    {
      return CriterionMatchRegularExpressionVerb;
    }
  else if (EQUAL(CriterionIsAfterVerbString))
    {
      return CriterionIsAfterVerb;
    }
  else if (EQUAL(CriterionIsBeforeVerbString))
    {
      return CriterionIsBeforeVerb;
    }
  else if (EQUAL(CriterionIsInTheLastVerbString))
    {
      return CriterionIsInTheLastVerb;
    }
  else if (EQUAL(CriterionIsNotInTheLastVerbString))
    {
      return CriterionIsNotInTheLastVerb;
    }
  else if (EQUAL(CriterionIsInTheRangeVerbString))
    {
      return CriterionIsInTheRangeVerb;
    }
  else if (EQUAL(CriterionIsGreaterThanVerbString))
    {
      return CriterionIsGreaterThanVerb;
    }
  else if (EQUAL(CriterionIsLessThanVerbString))
    {
      return CriterionIsLessThanVerb;
    }
}

- (void) _updateVerbPopUpCell: (NSPopUpButtonCell *) cell
          accordingToSubject: (NSString *) subject
{
  [cell removeAllItems];
  CriterionType type = [dataSource criterionView: self
	                           typeOfSubject: subject];
  switch(type)
    {
      case CriterionAnyStringType:
        [cell addItemsWithTitles: verbStringsForAnyString];
        break;
      case CriterionStringType:
        [cell addItemsWithTitles: verbStringsForString];
        break;
      case CriterionSelectionType:
        [cell addItemsWithTitles: verbStringsForSelection];
        break;
      case CriterionContentType:
	[cell addItemsWithTitles: verbStringsForContent];
	break;
      case CriterionDateType:
        [cell addItemsWithTitles: verbStringsForDate];
        break;
      case CriterionNumberType:
        [cell addItemsWithTitles: verbStringsForNumber];
        break;
      default:
	NSLog(@"Internal Error: Invalide CriterionType");
    }
}

- (void) _updateObjectView: (CriterionObjectView *) objectView
          accordingToSubject: (NSString *) subject
	                verb: (CriterionVerb) verbType
{
  CriterionType type = [dataSource criterionView: self
	                           typeOfSubject: subject];
  switch(verbType)
    {
      case CriterionIsVerb:
      case CriterionIsNotVerb:
        {
	  if (type == CriterionSelectionType)
	    {
	      NSArray *array = [dataSource criterionView: self
		                       availableSelectionsForSubject: subject];
              [objectView setType: CriterionObjectViewIsSelectionType];
              [objectView setSelectionPopUpButtonTitles: array];
	      break;
	    }
        }
      case CriterionContainsVerb:
      case CriterionDoesNotContainVerb:
      case CriterionStartsWithVerb:
      case CriterionEndsWithVerb:
      case CriterionMatchRegularExpressionVerb:
      case CriterionIsAfterVerb:
      case CriterionIsBeforeVerb:
      case CriterionIsGreaterThanVerb:
      case CriterionIsLessThanVerb:
	[objectView setType: CriterionObjectViewTextFieldType];
	break;
      case CriterionIsInTheLastVerb:
      case CriterionIsNotInTheLastVerb:
	[objectView setType: CriterionObjectViewInTheLastType];
	break;
      case CriterionIsInTheRangeVerb:
	[objectView setType: CriterionObjectViewInTheRangeType];
	break;
    }
}

- (NSRect) _frameOfSubjectAtIndex: (unsigned int) index
{
  float x, y, w, h;
  w = SubjectPopUpButtonCellWidth;
  h = cell_height;
  x = (isBordered ? PaddingX : 0);
  y = (isBordered ? PaddingY : 0)+index*(h+(isSmallControlSize ? SpaceBetweenCellSmall : SpaceBetweenCell));
  return NSMakeRect(x,y,w,h);
}

- (NSRect) _frameOfVerbAtIndex: (unsigned int) index
{
  float x, y, w, h;
  NSRect subjectFrame = [self _frameOfSubjectAtIndex: index];
  w = VerbPopUpButtonCellWidth;
  h = cell_height;
  x = NSMaxX(subjectFrame)+SpaceBetweenCell;
  y = (isBordered ? PaddingY : 0)+index*(h+(isSmallControlSize ? SpaceBetweenCellSmall : SpaceBetweenCell));
  return NSMakeRect(x, y, w, h);
}

- (NSRect) _frameOfRemoveButtonAtIndex: (unsigned int) index
{
  float x, y, w, h;
  NSRect verbFrame = [self _frameOfVerbAtIndex: index];
  w = h = AddRemoveButtonWidth;
  float space = (cell_height-AddRemoveButtonWidth)/2;
  x = [self frame].size.width-AddRemoveButtonWidth*2-SpaceBetweenCell-(isBordered ? PaddingX : 0);
  y = (isBordered ? PaddingY : 0)+index*(cell_height+(isSmallControlSize ? SpaceBetweenCellSmall : SpaceBetweenCell))+space;
  return NSMakeRect(x,y,w,h);
}

- (NSRect) _frameOfAddButtonAtIndex: (unsigned int) index
{
  float x, y, w, h;
  w = h = AddRemoveButtonWidth;
  float space = (cell_height-AddRemoveButtonWidth)/2;
  x = [self frame].size.width-AddRemoveButtonWidth-(isBordered ? PaddingX : 0);
  y = (isBordered ? PaddingY : 0)+index*(cell_height+(isSmallControlSize ? SpaceBetweenCellSmall : SpaceBetweenCell))+space;
  return NSMakeRect(x,y,w,h);
}

- (NSRect) _frameOfObjectAtIndex: (unsigned int) index
{
  float x, y, w, h;
  NSRect verbFrame = [self _frameOfVerbAtIndex: index];
  NSRect removeFrame = [self _frameOfRemoveButtonAtIndex: index];
  w = NSMinX(removeFrame)-NSMaxX(verbFrame)-SpaceBetweenCell*2;
  h = cell_height;
  x = NSMaxX(verbFrame)+SpaceBetweenCell;
  y = (isBordered ? PaddingY : 0)+index*(h+(isSmallControlSize ? SpaceBetweenCellSmall : SpaceBetweenCell));
  if (isSmallControlSize)
    {
      y += 1;
      h -= 4;
    }
  return NSMakeRect(x, y, w, h);
}

- (void) _setCriterionAtIndex: (unsigned int) index
{
  if ((index == NSNotFound) || (index >= [subjectCellArray count]))
    return;

  NSString *subject, *verbString;
  CriterionVerb verb;
  id object;

  subject = [[subjectCellArray objectAtIndex: index] titleOfSelectedItem];
  verbString = [[verbCellArray objectAtIndex: index] titleOfSelectedItem];
  verb = [self _verbFromString: verbString];
  object = [[objectViewArray objectAtIndex: index] objectValue];
  //NSLog(@"%@ %@ %@", subject, [self _stringFromVerb:verb], object);
  [dataSource setCriterionWithSubject: subject
	                         verb: verb
			       object: object
			      atIndex: index];
  if ([delegate respondsToSelector: @selector(criterionDidChangeInView:)] == YES)
    {
      [delegate criterionDidChangeInView: self];
    }
}

- (NSString *) _stringFromNumber: (NSNumber *) number
{
  NSString *description = [number description];
  if (numberType == RegExCommaFloatNumber)
    {
      NSRange range = [description rangeOfString: @"."];
      if (range.location != NSNotFound)
        {
          description = [NSString stringWithFormat: @"%@,%@", [description substringToIndex: range.location], [description substringFromIndex: range.location+1]];
        }
    }
  return description;
}

/* from Criterions to CriterionObjectView.
 * CriterionObjectView only accept NSString
 * except date unit is NSNumber
 */
- (void) _setObjectValue: (id) object 
            inObjectView: (CriterionObjectView *) view
{
  /* Check validation */
  CriterionObjectViewType type = [view type];
  Class stringClass, numberClass, dateClass, calendarDateClass, arrayClass;
  id first, second;
  id t1, t2;

  //NSLog(@"_setObjectValue:inObjectValue");

  stringClass = [NSString class];
  numberClass = [NSNumber class];
  dateClass = [NSDate class];
  calendarDateClass = [NSCalendarDate class];
  arrayClass = [NSArray class];

  switch(type)
    {
      case CriterionObjectViewTextFieldType:
	if ([object isKindOfClass: stringClass])
	  {
	    [view setObjectValue: AUTORELEASE([object copy])];
	  }
	else if ([object isKindOfClass: numberClass])
	  {
            [view setObjectValue: [self _stringFromNumber: object]];
	  }
	else if ([object isKindOfClass: dateClass])
	  {
	    [view setObjectValue: [object descriptionWithCalendarFormat: CalendarDateDisplayFormat timeZone: nil locale: nil]];
	  }
	else if ([object isKindOfClass: calendarDateClass])
	  {
	    [view setObjectValue: [object descriptionWithCalendarFormat: CalendarDateDisplayFormat]];
	  }
	else
	  {
	    NSLog(@"Internal Error: invalide class for CriterionObjectView");
	  }
	break;
      case CriterionObjectViewInTheRangeType:
	if ([object isKindOfClass: arrayClass] == NO)
	  {
	    NSLog(@"Internal Error: invalide class for CriterionObjectView");
	  }
	else
	  {
	    first = [object objectAtIndex: 0];
	    second = [object lastObject];
	    if (([first isKindOfClass: stringClass]) &&
		([second isKindOfClass: stringClass]))
	      {
	        [view setObjectValue: [NSArray arrayWithObjects: first, second, nil]];
	      }
	    else if (([first isKindOfClass: numberClass]) &&
		  ([second isKindOfClass: numberClass]))
	      {
	        [view setObjectValue: [NSArray arrayWithObjects: [self _stringFromNumber: first], [self _stringFromNumber: second], nil]];
	      }
	    else if (([first isKindOfClass: dateClass]) &&
		  ([second isKindOfClass: dateClass]))
	      {
		t1 = [first descriptionWithCalendarFormat: CalendarDateDisplayFormat timeZone: nil locale: nil];
		t2 = [first descriptionWithCalendarFormat: CalendarDateDisplayFormat timeZone: nil locale: nil];
		[view setObjectValue: [NSArray arrayWithObjects: t1, t2, nil]];
	      }
	    else if (([first isKindOfClass: calendarDateClass]) &&
		  ([second isKindOfClass: calendarDateClass]))
	      {
		t1 = [first descriptionWithCalendarFormat: CalendarDateDisplayFormat];
		t2 = [first descriptionWithCalendarFormat: CalendarDateDisplayFormat];
		[view setObjectValue: [NSArray arrayWithObjects: t1, t2, nil]];
	      }
	    else
	      {
	        NSLog(@"Internal Error: invalide class for CriterionObjectView");
	      }
	  }
	break;
      case CriterionObjectViewInTheLastType:
	if ([object isKindOfClass: arrayClass] == NO)
	  {
	    NSLog(@"Internal Error: invalide class for CriterionObjectView");
	  }
	else
	  {
	    first = [object objectAtIndex: 0];
	    second = [object lastObject];
	    if ([second isKindOfClass: numberClass] == NO)
	      {
	        NSLog(@"Internal Error: invalide class for CriterionObjectView");
	      }
	    else
	      {
	        if ([first isKindOfClass: stringClass])
	  	  {
	            [view setObjectValue: [NSArray arrayWithObjects: first, second, nil]];
		  }
		else if ([first isKindOfClass: numberClass])
		  {
	            [view setObjectValue: [NSArray arrayWithObjects: [self _stringFromNumber: first], second, nil]];
		  }
		else if ([first isKindOfClass: dateClass])
		  {
	            [view setObjectValue: [NSArray arrayWithObjects: [first descriptionWithCalendarFormat: CalendarDateDisplayFormat timeZone: nil locale: nil], second, nil]];
		  }
		else if ([first isKindOfClass: calendarDateClass])
		  {
	            [view setObjectValue: [NSArray arrayWithObjects: [first descriptionWithCalendarFormat: CalendarDateDisplayFormat], second, nil]];
		  }
		else
	          {
	            NSLog(@"Internal Error: invalide class for CriterionObjectView");
	          }
	      }
	  }
	break;
      case CriterionObjectViewIsSelectionType:
	if ([object isKindOfClass: stringClass])
	  {
	    [view setObjectValue: AUTORELEASE([object copy])];
	  }
	else
	  {
	    NSLog(@"Internal Error: invalide class for CriterionObjectView");
	  }
	break;
    }
}

- (void) criterionObjectViewAction: (id) sender
{
  /* Check validation */
  id result;
  id o1, o2, t1, t2;
  unsigned int index = [objectViewArray indexOfObject: sender];
  NSString *subject = [dataSource criterionView: self
	                         subjectAtIndex: index];
  CriterionType type = [dataSource criterionView: self
	                           typeOfSubject: subject];
  CriterionVerb verb = [dataSource criterionView: self
	                             verbAtIndex: index];
  RegExType regexType, regexType1;

  switch(type)
    {
      case CriterionAnyStringType:
      case CriterionStringType:
      case CriterionSelectionType:
      case CriterionContentType:
	o1= [sender objectValue];
	if ([o1 isKindOfClass: [NSString class]] == NO)
  	  {
	    NSLog(@"Internal Error: CriterionObjectView return object with wrong class");
	    return;
	  }
	result = AUTORELEASE([o1 copy]);
        break;
      case CriterionDateType:
	o1= [sender objectValue];
	if ([o1 isKindOfClass: [NSString class]])
	  {
	    t1 = objectFromRegExParsedString(o1, numberType, dateType, &regexType);
	    if (regexType != RegExCalendarDateType)
	      {
	        /* Wrong type */
		NSBeep();
		[self _setObjectValue: @"" inObjectView: sender];
		return;
	      }
	    else
	      {
		[self _setObjectValue: t1 inObjectView: sender];
	        result = AUTORELEASE([t1 copy]);
		break;
	      }
	  }
	else if ([o1 isKindOfClass: [NSArray class]])
	  {
	    if ([o1 count] < 2)
	      {
	        NSLog(@"Internal Error: CriterionObjectView return invalid NSArray object");
		return;
	      }

	    if ((verb == CriterionIsInTheLastVerb) ||
		(verb == CriterionIsNotInTheLastVerb))
	      {
	        t1 = AUTORELEASE([[o1 lastObject] copy]);
		t2 = objectFromRegExParsedString([o1 objectAtIndex: 0],
				                   numberType, dateType,
						   &regexType);
		if (regexType != RegExIntegerType)
		  {
		    NSBeep();
		    [self _setObjectValue: [NSArray arrayWithObjects: @"", t1, nil] inObjectView: sender];
		    return;
		  }
		else
		  {
		    result = [NSArray arrayWithObjects: AUTORELEASE([t2 copy]), t1, nil];
		    break;
		  }
	      }
	    else if (verb == CriterionIsInTheRangeVerb)
	      {
	        o2 = [o1 lastObject];
		o1 = [o1 objectAtIndex: 0];
		t1 = objectFromRegExParsedString(o1, numberType, 
				                    dateType, &regexType1);
		t2 = objectFromRegExParsedString(o2, numberType, 
				                   dateType, &regexType);
		if ((regexType != RegExCalendarDateType) &&
		    (regexType1 != RegExCalendarDateType))
		  {
		    NSBeep();
		    [self _setObjectValue: [NSArray arrayWithObjects: @"", @"", nil] inObjectView: sender];
		    return;
		  }
		else if (regexType != RegExCalendarDateType)
		  {
		    NSBeep();
		    [self _setObjectValue: [NSArray arrayWithObjects: o1, @"", nil] inObjectView: sender];
		    return;
		  }
		else if (regexType1 != RegExCalendarDateType)
		  {
		    NSBeep();
		    [self _setObjectValue: [NSArray arrayWithObjects: @"", o2, nil] inObjectView: sender];
		    return;
		  }
		else
	  	  {
		    [self _setObjectValue: [NSArray arrayWithObjects: t1, t2, nil] inObjectView: sender];
		  }

		result = [NSArray arrayWithObjects: 
			            AUTORELEASE([t1 copy]), 
		                    AUTORELEASE([t2 copy]), nil];
	        break;
	      }
	    else
	      {
	        NSLog(@"Internal Error: CriterionObjectView should not return NSArray object");
		return;
	      }
	  }
	else
  	  {
	    NSLog(@"Internal Error: CriterionObjectView return object with wrong class");
	    return;
	  }
	break;
      case CriterionNumberType:
	o1 = [sender objectValue];
	if ([o1 isKindOfClass: [NSString class]])
	  {
	    t1 = objectFromRegExParsedString(o1, numberType, dateType, &regexType);
	    if ((regexType == RegExIntegerType) ||
		(regexType == RegExFloatNumberType))
	      {
		[self _setObjectValue: t1 inObjectView: sender];
	        result = AUTORELEASE([t1 copy]);
		break;
	      }
	    else
	      {
		NSBeep();
		[self _setObjectValue: @"" inObjectView: sender];
		return;
	      }
	  }
	else if ([o1 isKindOfClass: [NSArray class]])
	  {
	    if ([o1 count] < 2)
	      {
	        NSLog(@"Internal Error: CriterionObjectView return invalid NSArray object");
		return;
	      }

	    if (verb == CriterionIsInTheRangeVerb)
	      {
	        o2 = [o1 lastObject];
		o1 = [o1 objectAtIndex: 0];
		t1 = objectFromRegExParsedString(o1, numberType, 
				                    dateType, &regexType1);
		t2 = objectFromRegExParsedString(o2, numberType, 
				                   dateType, &regexType);
		if ((regexType != RegExIntegerType) &&
		    (regexType != RegExFloatNumberType) &&
		    (regexType1 != RegExIntegerType) &&
		    (regexType1 != RegExFloatNumberType))
		  {
		    NSBeep();
		    [self _setObjectValue: [NSArray arrayWithObjects: @"", @"", nil] inObjectView: sender];
		    return;
		  }
		else if ((regexType != RegExIntegerType) &&
		         (regexType != RegExFloatNumberType))
		  {
		    NSBeep();
		    [self _setObjectValue: [NSArray arrayWithObjects: o1, @"", nil] inObjectView: sender];
		    return;
		  }
		else if ((regexType1 != RegExIntegerType) &&
		         (regexType1 != RegExFloatNumberType))
		  {
		    NSBeep();
		    [self _setObjectValue: [NSArray arrayWithObjects: @"", o2, nil] inObjectView: sender];
		    return;
		  }
		else
		  {
		    [self _setObjectValue: [NSArray arrayWithObjects: t1, t2, nil] inObjectView: sender];
		  }

		result = [NSArray arrayWithObjects: 
			            AUTORELEASE([t1 copy]), 
		                    AUTORELEASE([t2 copy]), nil];
	        break;
	      }
	    else
	      {
	        NSLog(@"Internal Error: CriterionObjectView should not return NSArray object");
		return;
	      }
	  }
	else
  	  {
	    NSLog(@"Internal Error: CriterionObjectView return object with wrong class");
	    return;
	  }
	break;
    }
  //NSLog(@"result %@", result);

  [dataSource setCriterionWithSubject: subject
	                         verb: verb
			       object: result
			      atIndex: index];
  if ([delegate respondsToSelector: @selector(criterionDidChangeInView:)] == YES)
    {
      [delegate criterionDidChangeInView: self];
    }
}

- (void) reloadSubjects
{
  unsigned int i, count = [subjectCellArray count];
  NSPopUpButtonCell *cell;
  NSString *title;
  unsigned int index;
  for(i = 0; i < count; i++)
    {
      cell = [subjectCellArray objectAtIndex: i];
      title = [cell titleOfSelectedItem];
      [cell removeAllItems];
      [cell addItemsWithTitles: [dataSource availableSubjectsOfCriterionsInView: self]];
      index = [cell indexOfItemWithTitle: title];
      if (index != NSNotFound)
        [cell selectItemAtIndex: index];
      else
	[cell selectItemAtIndex: 0];

      [cell synchronizeTitleAndSelectedItem];
    }
}

- (void) reloadSelections
{
  unsigned int i, count;
  CriterionObjectView *objectView;
  CriterionType type;
  NSArray *selections;
  NSString *subject;

  count = [subjectCellArray count];                 
  for(i = 0; i < count; i++)
    {
      subject = [[subjectCellArray objectAtIndex: i] titleOfSelectedItem];
      type = [dataSource criterionView: self typeOfSubject: subject]; 
      if (type == CriterionSelectionType)
        {
          selections = [dataSource criterionView: self
		                   availableSelectionsForSubject: subject];
	  objectView = [objectViewArray objectAtIndex: i];
	  [objectView setSelectionPopUpButtonTitles: selections];
	  [objectView display];
        } 
    }
}

- (void) reloadData
{
  NSPopUpButtonCell *popupCell; 
  CriterionType type;
  CriterionVerb verb;
  CriterionObjectView *objectView;
  unsigned int i, count = [dataSource numberOfCriterionsInView: self];
  NSString *subject;
  NSRect rect;
  id object;
  float font_size;
  if (isSmallControlSize)
    font_size = [NSFont smallSystemFontSize];
  else
    font_size = [NSFont systemFontSize];

  //NSLog(@"reloadData %d", [dataSource numberOfCriterionsInView: self]);

  for(i = 0; i < count; i++)
    {
      /* Update all subjects */
      if ([subjectCellArray count] < i+1)
        {
	  popupCell = [[NSPopUpButtonCell alloc] initTextCell: @"" pullsDown: NO];
	  if (isSmallControlSize)
	    {
	      [popupCell setControlSize: NSSmallControlSize];
	      [popupCell setFont: [NSFont controlContentFontOfSize: font_size]];
	    }
          [subjectCellArray addObject: popupCell];
	  RELEASE(popupCell);
        }
      else
        {
          popupCell = [subjectCellArray objectAtIndex: i];
	}
      [popupCell removeAllItems];
      [popupCell addItemsWithTitles: [dataSource availableSubjectsOfCriterionsInView: self]];
      subject = [dataSource criterionView: self 
	                    subjectAtIndex: i];
      /* put back selection */
      [popupCell selectItemWithTitle: subject];
      [popupCell synchronizeTitleAndSelectedItem];
      cell_height = [popupCell cellSize].height;

      if ([verbCellArray count] < i+1)
        {
	  popupCell = [[NSPopUpButtonCell alloc] initTextCell: @"" pullsDown: NO];
	  if (isSmallControlSize)
	    {
	      [popupCell setControlSize: NSSmallControlSize];
	      [popupCell setFont: [NSFont controlContentFontOfSize: font_size]];
	    }
	  [verbCellArray addObject: popupCell];
	  RELEASE(popupCell);
	}
      else
        {
          popupCell = [verbCellArray objectAtIndex: i];
	}
      [self _updateVerbPopUpCell: popupCell
	     accordingToSubject: subject];
      verb = [dataSource criterionView: self
	                   verbAtIndex: i];
      [popupCell selectItemWithTitle: [self _stringFromVerb: verb]];
      [popupCell synchronizeTitleAndSelectedItem];

      rect = [self _frameOfObjectAtIndex: i];
      if ([objectViewArray count] < i+1)
        {
	  //NSLog(@"%@", NSStringFromRect(rect));
	  objectView = [[CriterionObjectView alloc] initWithFrame: rect];
	  if (isSmallControlSize)
	    {
	      [objectView setSmallControlSize: YES];
	    }
	  [objectView setTarget: self];
	  [objectView setAction: @selector(criterionObjectViewAction:)];
	  [objectViewArray addObject: objectView];
	  [self addSubview: [objectViewArray objectAtIndex: i]];
        }
      else
        {
	  objectView = [objectViewArray objectAtIndex: i];
	  [objectView setFrame: rect];
        }

      [self _updateObjectView: objectView
  	     accordingToSubject: subject
	                   verb: verb];

      object = [dataSource criterionView: self
                           objectAtIndex: i];
      //[objectView setObjectValue: object];
      [self _setObjectValue: object inObjectView: objectView];
    }

  //NSLog(@"%d %d %d %d", count, [subjectCellArray count], [verbCellArray count], [objectViewArray count]);
  /* remove extra */
  NSRange range = NSMakeRange(count, [subjectCellArray count]-count);
  [subjectCellArray removeObjectsInRange: range];
  range = NSMakeRange(count, [verbCellArray count]-count);
  [verbCellArray removeObjectsInRange: range];
  i = [objectViewArray count]-1;
  for (i; i >= count; i--) 
    {
      objectView = [objectViewArray objectAtIndex: i];
      [objectView removeFromSuperview];
      [objectViewArray removeObjectAtIndex: i];
    }

  if (([subjectCellArray count] != [verbCellArray count]) ||
      ([verbCellArray count] != [objectViewArray count]) ||
      ([objectViewArray count] != [subjectCellArray count]))
    {
      NSLog(@"Internal Error: subjectCellArray, verbCellArray and objectViewArray are not synchronized.");
      NSLog(@"%d %d %d", [subjectCellArray count], [verbCellArray count], [objectViewArray count]);
    }

}

#ifdef GNUSTEP
- (void) _handleNotification: (NSNotification*) not
{
  NSString      *name = [not name];
  if ([name isEqual: NSMenuDidSendActionNotification] == YES)
    {
      [clickedPopUpCell dismissPopUp];
      [clickedPopUpCell synchronizeTitleAndSelectedItem];
      [self setNeedsDisplay: YES];
    }
}
#endif

- (void) setFrameSize: (NSSize) newSize
{
  /* reset object size */
  unsigned int i, count = [objectViewArray count];
  NSRect frame;
  for(i = 0; i < count; i++)
    {
      frame = [self _frameOfObjectAtIndex: i];
      //NSLog(@"frame %@", NSStringFromRect(frame));
      [[objectViewArray objectAtIndex: i] setFrame: frame];
    }
  [super setFrameSize: newSize];
}

- (void) mouseDown: (NSEvent *) event
{
  NSPoint initialLocation = [event locationInWindow];
  NSPoint p = [self convertPoint: initialLocation fromView: nil];
  NSRect subjectFrame, verbFrame, addFrame, removeFrame;
  unsigned int index = (p.y-(isBordered ? PaddingY : 0))/(cell_height+(isSmallControlSize ? SpaceBetweenCellSmall : SpaceBetweenCell));
  NSPopUpButtonCell *subjectCell, *verbCell;
  CriterionObjectView *objectView;
  CriterionType oldType, newType;
  CriterionVerb verbType;

  if (index >= [subjectCellArray count]) return;
  if ([subjectCellArray count] < 1) return;

  subjectFrame = [self _frameOfSubjectAtIndex: index];
  verbFrame = [self _frameOfVerbAtIndex: index];
  addFrame = [self _frameOfAddButtonAtIndex: index];
  removeFrame = [self _frameOfRemoveButtonAtIndex: index];
  if (NSMouseInRect(p, subjectFrame, YES))
    {
      NSString *newSubject;
      subjectCell = [subjectCellArray objectAtIndex: index];
      oldType =[dataSource criterionView: self
	                   typeOfSubject: [subjectCell titleOfSelectedItem]];
#ifdef GNUSTEP
      clickedPopUpCell = subjectCell;
      [subjectCell attachPopUpWithFrame: subjectFrame
                                 inView: self];
      NSWindow *menuWindow = [[[subjectCell menu] menuRepresentation] window];
      NSPoint p = [menuWindow convertScreenToBase: [[self window] convertBaseToScreen: [event locationInWindow]]];
      NSEvent *e = [NSEvent mouseEventWithType: [event type]
                                     location: p
                                modifierFlags: [event modifierFlags]
                                    timestamp: [event timestamp]
                                 windowNumber: [menuWindow windowNumber]
                                      context: [event context]
                                  eventNumber: [event eventNumber]
                                   clickCount: [event clickCount] 
                                     pressure: [event pressure]];
      [NSApp sendEvent: e];
      //NSLog(@"popup %@", [subjectCell objectValue]);
#else
      BOOL result = [subjectCell trackMouse: event inRect: subjectFrame
                                 ofView: self untilMouseUp: YES];
      //NSLog(@"popup %@", [subjectCell objectValue]);
#endif
      /* Change verb */
      newSubject = [subjectCell titleOfSelectedItem];
      newType =[dataSource criterionView: self
	                   typeOfSubject: newSubject];
      /* Update verb */
      if (oldType != newType)
        {
          verbCell = [verbCellArray objectAtIndex: index];
	  NSLog(@"update verb");
          [self _updateVerbPopUpCell: verbCell
	          accordingToSubject: newSubject];
          [verbCell synchronizeTitleAndSelectedItem];

	  objectView = [objectViewArray objectAtIndex: index];
	  verbType = [self _verbFromString: [verbCell titleOfSelectedItem]];
	  NSLog(@"update object view");
	  [self _updateObjectView: objectView
		 accordingToSubject: newSubject
		               verb: verbType];
        }

      /* Set criterion */
      [self _setCriterionAtIndex: index];
    }
  else if (NSMouseInRect(p, verbFrame, YES))
    {
      NSString *subject = [[subjectCellArray objectAtIndex: index] titleOfSelectedItem];
      verbCell = [verbCellArray objectAtIndex: index];
#ifdef GNUSTEP
      clickedPopUpCell = verbCell;
      [verbCell attachPopUpWithFrame: verbFrame
                              inView: self];
      NSWindow *menuWindow = [[[verbCell menu] menuRepresentation] window];
      NSPoint p = [menuWindow convertScreenToBase: [[self window] convertBaseToScreen: [event locationInWindow]]];
      NSEvent *e = [NSEvent mouseEventWithType: [event type]
                                     location: p
                                modifierFlags: [event modifierFlags]
                                    timestamp: [event timestamp]
                                 windowNumber: [menuWindow windowNumber]
                                      context: [event context]
                                  eventNumber: [event eventNumber]
                                   clickCount: [event clickCount] 
                                     pressure: [event pressure]];
      [NSApp sendEvent: e];
      //NSLog(@"popup %@", [verbCell objectValue]);
#else
      BOOL result = [verbCell trackMouse: event inRect: verbFrame
                              ofView: self untilMouseUp: YES];
      //NSLog(@"popup %@", [verbCell objectValue]);
#endif
      objectView = [objectViewArray objectAtIndex: index];
      verbType = [self _verbFromString: [verbCell titleOfSelectedItem]];
      [self _updateObjectView: objectView
   	     accordingToSubject: subject
	                   verb: verbType];
      [self _setCriterionAtIndex: index];
    }
  else if (NSMouseInRect(p, addFrame, YES))
    {
      //NSLog(@"Add criterion at index %d", index);
      /* End editing */
      [[self window] endEditingFor: nil];

      /* check over limite */
      unsigned int count = [dataSource numberOfCriterionsInView: self];
      if (count >= maxCriterions) return;

      NSString *subject = [[dataSource availableSubjectsOfCriterionsInView: self] objectAtIndex: 0];
      [dataSource insertCriterionWithSubject: subject
	                                verb: CriterionIsVerb
			              object: @""
				     atIndex: (index+1)];
      /* deal with objectView */
      NSRect rect = [self _frameOfObjectAtIndex: (index+1)];
      CriterionObjectView *object = [[CriterionObjectView alloc] initWithFrame: rect];
      if (isSmallControlSize)
        {
          [object setSmallControlSize: YES];
	}
      [object setTarget: self];
      [object setAction: @selector(criterionObjectViewAction:)];
      if (index >= [objectViewArray count])
        {
	  [objectViewArray addObject: object];
	}
      else
        {
	  [objectViewArray insertObject: object atIndex: (index+1)];
        }
      [self addSubview: object];
      RELEASE(object);

      [self reloadData];
      //NSLog(@"objectViewArray %@", objectViewArray);
      
      if ([delegate respondsToSelector: @selector(criterionViewProposeResizing:toSize:)] == YES)
        {
	  NSRect frame = [self frame];
	  float optimal_height = [self optimalHeight];
          [delegate criterionViewProposeResizing: self
                    toSize: NSMakeSize(frame.size.width, optimal_height)];
        }
    }
  else if (NSMouseInRect(p, removeFrame, YES))
    {
      //NSLog(@"Remove criterion at index %d", index);
      /* End editing */
      [[self window] endEditingFor: nil];
      /* Dont remove last one */
      if ([dataSource numberOfCriterionsInView: self] > 1)
        {
          [dataSource removeCriterionAtIndex: index];
	  objectView = [objectViewArray objectAtIndex: index];
	  [objectView removeFromSuperview];
          [objectViewArray removeObjectAtIndex: index];
	  [subjectCellArray removeObjectAtIndex: index];
	  [verbCellArray removeObjectAtIndex: index];
          [self reloadData];
	  //NSLog(@"objectViewArray %@", objectViewArray);
	}
      if ([delegate respondsToSelector: @selector(criterionViewProposeResizing:toSize:)] == YES)
        {
	  NSRect frame = [self frame];
	  float optimal_height = [self optimalHeight];
          [delegate criterionViewProposeResizing: self
                    toSize: NSMakeSize(frame.size.width, optimal_height)];
        }
      if ([delegate respondsToSelector: @selector(criterionDidChangeInView:)] == YES)
        {
          [delegate criterionDidChangeInView: self];
        }
    }
  else
    {
      [super mouseDown: event];
    }
}

- (void) drawRect: (NSRect) frame
{
  //NSLog(@"drawRect %@", NSStringFromRect(frame));
  [super drawRect: frame];

  if (isBordered)
    NSFrameRect([self bounds]);

  unsigned int x, y, h, w;
  NSRect rect;
  NSString *subject;
  NSPopUpButtonCell *subjectCell, *verbCell;

  rect = [self bounds];
  [self setFrameSize: rect.size];

  unsigned int i, count = [dataSource numberOfCriterionsInView: self];
  if ([subjectCellArray count] == 0) return;
  for(i = 0; i < count; i++)
    {
      /* Draw subject */
      subjectCell = [subjectCellArray objectAtIndex: i];
      rect = [self _frameOfSubjectAtIndex: i];
      [self lockFocus];
      [subjectCell drawWithFrame: rect inView: self];
      [self unlockFocus];

      /* Draw verb */
      verbCell = [verbCellArray objectAtIndex: i];
      rect = [self _frameOfVerbAtIndex: i];
      [self lockFocus];
      [verbCell drawWithFrame: rect inView: self];
      [self unlockFocus];

      NSSize imageSize;
#ifndef GNUSTEP
      if (count < maxCriterions)
        {
          /* Draw add */
          NSImage *add = [NSImage imageNamed: @"Add20.png"];
          imageSize = [add size];
          rect = [self _frameOfAddButtonAtIndex: i];
          [add drawInRect: rect 
                 fromRect: NSMakeRect(0, 0, imageSize.width, imageSize.height)
	        operation: NSCompositeSourceOver
	         fraction: 1.0];
	}
      /* Draw remove */
      if (count > 1)
        {
          NSImage *remove = [NSImage imageNamed: @"Remove20.png"];
          imageSize = [remove size];
          rect = [self _frameOfRemoveButtonAtIndex: i];
          [remove drawInRect: rect 
                  fromRect: NSMakeRect(0, 0, imageSize.width, imageSize.height)
	          operation: NSCompositeSourceOver
	          fraction: 1.0];
	}
#else
      /* Draw add */
      if (count < maxCriterions)
        {
          NSImage *add = [NSImage imageNamed: @"Add20.png"];
          imageSize = [add size];
          rect = [self _frameOfAddButtonAtIndex: i];
          if ([self isFlipped])
            rect.origin.y += rect.size.height;
          [add compositeToPoint: rect.origin
                      operation: NSCompositeSourceOver];
	}
      /* Draw remove */
      if (count > 1)
        {
          NSImage *remove = [NSImage imageNamed: @"Remove20.png"];
          imageSize = [remove size];
          rect = [self _frameOfRemoveButtonAtIndex: i];
          if ([self isFlipped])
            rect.origin.y += rect.size.height;
          [remove compositeToPoint: rect.origin
                         operation: NSCompositeSourceOver];
	}
#endif
    }
}

/* initiate and dealloc */
- (id) initWithFrame: (NSRect) frame
{
  unsigned int i, count;

  self = [super initWithFrame: frame];

  verbsForAnyString = [[NSArray alloc] initWithObjects: 
                         [NSNumber numberWithInt: CriterionContainsVerb],
		         nil];
  count = [verbsForAnyString count];
  verbStringsForAnyString = [[NSMutableArray alloc] init];
  for(i = 0; i < count; i++)
    {
      [verbStringsForAnyString addObject: [self _stringFromVerb: [[verbsForAnyString objectAtIndex: i] intValue]]];
    }

  verbsForString = [[NSArray alloc] initWithObjects: 
                 [NSNumber numberWithInt: CriterionContainsVerb],
                 [NSNumber numberWithInt: CriterionDoesNotContainVerb],
                 [NSNumber numberWithInt: CriterionIsVerb],
                 [NSNumber numberWithInt: CriterionIsNotVerb],
                 [NSNumber numberWithInt: CriterionStartsWithVerb],
                 [NSNumber numberWithInt: CriterionEndsWithVerb],
                 [NSNumber numberWithInt: CriterionMatchRegularExpressionVerb],
		 nil];
  count = [verbsForString count];
  verbStringsForString = [[NSMutableArray alloc] init];
  for(i = 0; i < count; i++)
    {
      [verbStringsForString addObject: [self _stringFromVerb: [[verbsForString objectAtIndex: i] intValue]]];
    }

  verbsForDate = [[NSArray alloc] initWithObjects: 
                 [NSNumber numberWithInt: CriterionIsVerb],
                 [NSNumber numberWithInt: CriterionIsNotVerb],
                 [NSNumber numberWithInt: CriterionIsAfterVerb],
                 [NSNumber numberWithInt: CriterionIsBeforeVerb],
                 [NSNumber numberWithInt: CriterionIsInTheLastVerb],
                 [NSNumber numberWithInt: CriterionIsNotInTheLastVerb],
                 [NSNumber numberWithInt: CriterionIsInTheRangeVerb],
		 nil];
  count = [verbsForDate count];
  verbStringsForDate = [[NSMutableArray alloc] init];
  for(i = 0; i < count; i++)
    {
      [verbStringsForDate addObject: [self _stringFromVerb: [[verbsForDate objectAtIndex: i] intValue]]];
    }

  verbsForNumber = [[NSArray alloc] initWithObjects: 
                 [NSNumber numberWithInt: CriterionIsVerb],
                 [NSNumber numberWithInt: CriterionIsNotVerb],
                 [NSNumber numberWithInt: CriterionIsInTheRangeVerb],
                 [NSNumber numberWithInt: CriterionIsGreaterThanVerb],
                 [NSNumber numberWithInt: CriterionIsLessThanVerb],
		 nil];
  count = [verbsForNumber count];
  verbStringsForNumber = [[NSMutableArray alloc] init];
  for(i = 0; i < count; i++)
    {
      [verbStringsForNumber addObject: [self _stringFromVerb: [[verbsForNumber objectAtIndex: i] intValue]]];
    }

  verbsForSelection = [[NSArray alloc] initWithObjects: 
                 [NSNumber numberWithInt: CriterionIsVerb],
                 [NSNumber numberWithInt: CriterionIsNotVerb],
		 nil];
  count = [verbsForSelection count];
  verbStringsForSelection = [[NSMutableArray alloc] init];
  for(i = 0; i < count; i++)
    {
      [verbStringsForSelection addObject: [self _stringFromVerb: [[verbsForSelection objectAtIndex: i] intValue]]];
    }

  verbsForContent = [[NSArray alloc] initWithObjects: 
                 [NSNumber numberWithInt: CriterionContainsVerb],
		 nil];
  count = [verbsForContent count];
  verbStringsForContent = [[NSMutableArray alloc] init];
  for(i = 0; i < count; i++)
    {
      [verbStringsForContent addObject: [self _stringFromVerb: [[verbsForContent objectAtIndex: i] intValue]]];
    }
  /* Cell */
  subjectCellArray = [[NSMutableArray alloc] init];
  verbCellArray = [[NSMutableArray alloc] init];
  objectViewArray = [[NSMutableArray alloc] init];

  cell_height = 22;
  isBordered = YES;
  isSmallControlSize = NO;
  maxCriterions = 100;

  /* Cache */
  allSubjects = [[NSMutableArray alloc] init];

  numberType = RegExDotFloatNumber;
  dateType = RegExMMDDYYYYDate;

  return self;
}

- (void) dealloc
{
  RELEASE(dataSource);
  RELEASE(delegate);
  RELEASE(verbsForAnyString);
  RELEASE(verbsForString);
  RELEASE(verbsForDate);
  RELEASE(verbsForNumber);
  RELEASE(verbStringsForAnyString);
  RELEASE(verbStringsForString);
  RELEASE(verbStringsForDate);
  RELEASE(verbStringsForNumber);
  RELEASE(subjectCellArray);
  RELEASE(verbCellArray);

  RELEASE(allSubjects);
  [super dealloc];
}

/* basic methods */
- (void) setDataSource: (id) object
{
  ASSIGN(dataSource, object);
  /* Check data source methods */
  if ([dataSource respondsToSelector: @selector(numberOfCriterionsInView:)] == NO)
    {
      NSLog(@"Data source of %@ does not respond to numberOfCriterionsInView:", self);
    }
  if ([dataSource respondsToSelector: @selector(availableSubjectsOfCriterionsInView:)] == NO)
    {
      NSLog(@"Data source of %@ does not respond to availableSubjectsOfCriterionsInView:", self);
    }
  if ([dataSource respondsToSelector: @selector(criterionView:subjectAtIndex:)] == NO)
    {
      NSLog(@"Data source of %@ does not respond to criterionView:subjectOfCriterionAtIndex:", self);
    }
  if ([dataSource respondsToSelector: @selector(criterionView:typeOfSubject:)] == NO)
    {
      NSLog(@"Data source of %@ does not respond to criterionView:typeOfSubject:", self);
    }
}

- (id) dataSource
{
  return dataSource;
}

- (void) setDelegate: (id) object
{
  ASSIGN(delegate, object);
}

- (id) delegate
{
  return delegate;
}

- (BOOL) isFlipped
{
  return YES;
}

- (float) optimalHeight
{
  unsigned int count = [subjectCellArray count];
  float space = (isSmallControlSize ? SpaceBetweenCellSmall : SpaceBetweenCell);
  return (cell_height+space)*count-space+2*(isBordered ? PaddingY : 0);
}

- (void) setFloatNumberRegExType: (RegExFloatNumber) type
{
  numberType = type;
  NSLog(@"type %d", type);

  /* Update all CriterionObjectView with NSNumber */
  CriterionObjectView *view;
  NSString *title;
  id object;
  unsigned int i, count = [dataSource numberOfCriterionsInView: self];
  for(i = 0; i < count; i++)
   {
      title = [dataSource criterionView: self
	                  subjectAtIndex: i];
      CriterionType type = [dataSource criterionView: self
	                               typeOfSubject: title];
      if (type == CriterionNumberType)
        {
	  view = [objectViewArray objectAtIndex: i];
	  object = [dataSource criterionView: self
		               objectAtIndex: i];
	  [self _setObjectValue: object
	 	   inObjectView: view];
        }
   }
}

- (void) setDateRegExType: (RegExDate) type
{
  dateType = type;
}

- (void) setBordered: (BOOL) flag
{
  isBordered = flag;
}

- (BOOL) isBordered
{
  return isBordered;
}

- (void) setSmallControlSize: (BOOL) flag
{
  isSmallControlSize = flag;
}

- (BOOL) isSmallControlSize;
{
  return isSmallControlSize;
}

- (void) setMaxCriterions: (unsigned int) number
{
  maxCriterions = number;
}

- (unsigned int) maxCriterions
{
  return maxCriterions;
}


@end
