/* ** CriterionView.m ** ** Copyright (c) 2004 ** ** Author: Yen-Ju Chen ** ** 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, ®exType); 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, ®exType); 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, ®exType1); t2 = objectFromRegExParsedString(o2, numberType, dateType, ®exType); 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, ®exType); 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, ®exType1); t2 = objectFromRegExParsedString(o2, numberType, dateType, ®exType); 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