![]() |
API 0.9.5
|
00001 /* 00002 * CPRuleEditor.j 00003 * AppKit 00004 * 00005 * Created by cacaodev. 00006 * Copyright 2011, cacaodev. 00007 * 00008 * This library is free software; you can redistribute it and/or 00009 * modify it under the terms of the GNU Lesser General Public 00010 * License as published by the Free Software Foundation; either 00011 * version 2.1 of the License, or (at your option) any later version. 00012 * 00013 * This library is distributed in the hope that it will be useful, 00014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00016 * Lesser General Public License for more details. 00017 * 00018 * You should have received a copy of the GNU Lesser General Public 00019 * License along with this library; if not, write to the Free Software 00020 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 00021 */ 00022 00023 00024 00025 CPRuleEditorPredicateLeftExpression = "CPRuleEditorPredicateLeftExpression"; 00026 CPRuleEditorPredicateRightExpression = "CPRuleEditorPredicateRightExpression"; 00027 CPRuleEditorPredicateComparisonModifier = "CPRuleEditorPredicateComparisonModifier"; 00028 CPRuleEditorPredicateOptions = "CPRuleEditorPredicateOptions"; 00029 CPRuleEditorPredicateOperatorType = "CPRuleEditorPredicateOperatorType"; 00030 CPRuleEditorPredicateCustomSelector = "CPRuleEditorPredicateCustomSelector"; 00031 CPRuleEditorPredicateCompoundType = "CPRuleEditorPredicateCompoundType"; 00032 00033 CPRuleEditorRowsDidChangeNotification = "CPRuleEditorRowsDidChangeNotification"; 00034 CPRuleEditorRulesDidChangeNotification = "CPRuleEditorRulesDidChangeNotification"; 00035 00036 CPRuleEditorNestingModeSingle = 0; // Only a single row is allowed. Plus/minus buttons will not be shown 00037 CPRuleEditorNestingModeList = 1; // Allows a single list, with no nesting and no compound rows 00038 CPRuleEditorNestingModeCompound = 2; // Unlimited nesting and compound rows; this is the default 00039 CPRuleEditorNestingModeSimple = 3; // One compound row at the top with subrows beneath it, and no further nesting allowed 00040 00041 CPRuleEditorRowTypeSimple = 0; 00042 CPRuleEditorRowTypeCompound = 1; 00043 00044 var CPRuleEditorItemPBoardType = @"CPRuleEditorItemPBoardType"; 00045 00046 var itemsContext = "items", 00047 valuesContext = "values", 00048 subrowsContext = "subrows_array", 00049 boundArrayContext = "bound_array"; 00050 00071 @implementation CPRuleEditor : CPControl 00072 { 00073 BOOL _suppressKeyDownHandling; 00074 BOOL _allowsEmptyCompoundRows; 00075 BOOL _disallowEmpty; 00076 BOOL _delegateWantsValidation; 00077 BOOL _editable; 00078 00079 Class _rowClass; 00080 00081 CPIndexSet _draggingRows; 00082 CPInteger _subviewIndexOfDropLine; 00083 CPView _dropLineView; 00084 00085 CPMutableArray _rowCache; 00086 CPMutableArray _slices; 00087 00088 CPPredicate _predicate; 00089 00090 CPString _itemsKeyPath; 00091 CPString _subrowsArrayKeyPath; 00092 CPString _typeKeyPath; 00093 CPString _valuesKeyPath; 00094 CPString _boundArrayKeyPath; 00095 00096 CPView _slicesHolder; 00097 CPViewAnimation _currentAnimation; 00098 00099 CPInteger _lastRow; 00100 CPInteger _nestingMode; 00101 00102 float _alignmentGridWidth; 00103 float _sliceHeight; 00104 00105 id _ruleDataSource; 00106 id _ruleDelegate; 00107 id _boundArrayOwner; 00108 00109 CPString _stringsFilename; 00110 00111 BOOL _isKeyDown; 00112 BOOL _nestingModeDidChange; 00113 00114 id _standardLocalizer; 00115 id _itemsAndValuesToAddForRowType; 00116 } 00117 00120 + (CPString)defaultThemeClass 00121 { 00122 return @"rule-editor"; 00123 } 00124 00125 + (id)themeAttributes 00126 { 00127 return [CPDictionary dictionaryWithObjects:[[CPNull null], [CPNull null], [CPNull null], [CPNull null], [CPNull null], [CPNull null], [CPNull null], [CPNull null]] 00128 forKeys:[@"alternating-row-colors", @"selected-color", @"slice-top-border-color", @"slice-bottom-border-color", @"slice-last-bottom-border-color", @"font", @"add-image", @"remove-image"]]; 00129 } 00130 00131 - (id)initWithFrame:(CGRect)frame 00132 { 00133 self = [super initWithFrame:frame]; 00134 if (self != nil) 00135 { 00136 _slices = [[CPMutableArray alloc] init]; 00137 00138 _sliceHeight = 26.; 00139 _nestingMode = CPRuleEditorNestingModeSimple; // 10.5 default is CPRuleEditorNestingModeCompound 00140 _editable = YES; 00141 _allowsEmptyCompoundRows = NO; 00142 _disallowEmpty = NO; 00143 00144 [self setCriteriaKeyPath:@"criteria"]; 00145 [self setSubrowsKeyPath:@"subrows"]; 00146 [self setRowTypeKeyPath:@"rowType"]; 00147 [self setDisplayValuesKeyPath:@"displayValues"]; 00148 [self setBoundArrayKeyPath:@"boundArray"]; 00149 00150 _slicesHolder = [[_CPRuleEditorViewSliceHolder alloc] initWithFrame:[self bounds]]; 00151 [self addSubview:_slicesHolder]; 00152 00153 _boundArrayOwner = [[_CPRuleEditorViewUnboundRowHolder alloc] init]; 00154 00155 [self _initRuleEditorShared]; 00156 } 00157 00158 return self; 00159 } 00160 00161 - (void)_initRuleEditorShared 00162 { 00163 _rowCache = [[CPMutableArray alloc] init]; 00164 _rowClass = [RowObject class]; 00165 _isKeyDown = NO; 00166 _subviewIndexOfDropLine = CPNotFound; 00167 _lastRow = 0; 00168 _delegateWantsValidation = YES; 00169 _suppressKeyDownHandling = NO; 00170 _nestingModeDidChange = NO; 00171 _itemsAndValuesToAddForRowType = {}; 00172 var animation = [[CPViewAnimation alloc] initWithDuration:0.5 animationCurve:CPAnimationEaseInOut]; 00173 [self setAnimation:animation]; 00174 00175 [_slicesHolder setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; 00176 00177 _dropLineView = [self _createSliceDropSeparator]; 00178 [_slicesHolder addSubview:_dropLineView]; 00179 00180 [self registerForDraggedTypes:[CPArray arrayWithObjects:CPRuleEditorItemPBoardType,nil]]; 00181 [_boundArrayOwner addObserver:self forKeyPath:_boundArrayKeyPath options:CPKeyValueObservingOptionOld|CPKeyValueObservingOptionNew context:boundArrayContext]; 00182 } 00183 00195 - (id)delegate 00196 { 00197 return _ruleDelegate; 00198 } 00199 00206 - (void)setDelegate:(id)aDelegate 00207 { 00208 if (_ruleDelegate === aDelegate) 00209 return; 00210 00211 var nc = [CPNotificationCenter defaultCenter]; 00212 if (_ruleDelegate) 00213 [nc removeObserver:_ruleDelegate name:nil object:self]; 00214 00215 _ruleDelegate = aDelegate; 00216 00217 if ([_ruleDelegate respondsToSelector:@selector(ruleEditorRowsDidChange:)]) 00218 [nc addObserver:_ruleDelegate selector:@selector(ruleEditorRowsDidChange:) name:CPRuleEditorRowsDidChangeNotification object:nil]; 00219 } 00226 - (BOOL)isEditable 00227 { 00228 return _editable; 00229 } 00230 00236 - (void)setEditable:(BOOL)editable 00237 { 00238 if (editable == _editable) 00239 return; 00240 00241 _editable = editable; 00242 00243 if (!_editable) 00244 [self _deselectAll]; 00245 00246 [_slices makeObjectsPerformSelector:@selector(setEditable:) withObject:_editable]; 00247 } 00248 00254 - (CPRuleEditorNestingMode)nestingMode 00255 { 00256 return _nestingMode; 00257 } 00258 00266 - (void)setNestingMode:(CPRuleEditorNestingMode)mode 00267 { 00268 if (mode != _nestingMode) 00269 { 00270 _nestingMode = mode; 00271 if ([self numberOfRows] > 0) 00272 _nestingModeDidChange = YES; 00273 } 00274 } 00275 00281 - (BOOL)canRemoveAllRows 00282 { 00283 return !_disallowEmpty; 00284 } 00285 00291 - (void)setCanRemoveAllRows:(BOOL)canRemove 00292 { 00293 _disallowEmpty = !canRemove; 00294 [self _updateButtonVisibilities]; 00295 } 00296 00302 - (BOOL)allowsEmptyCompoundRows 00303 { 00304 return _allowsEmptyCompoundRows; 00305 } 00306 00312 - (void)setAllowsEmptyCompoundRows:(BOOL)allows 00313 { 00314 _allowsEmptyCompoundRows = allows; 00315 [self _updateButtonVisibilities]; 00316 } 00317 00323 - (CPInteger)rowHeight 00324 { 00325 return _sliceHeight; 00326 } 00327 00333 - (void)setRowHeight:(float)height 00334 { 00335 if (height == _sliceHeight) 00336 return; 00337 00338 _sliceHeight = MAX([self _minimumFrameHeight], height); 00339 [self _reconfigureSubviewsAnimate:NO]; 00340 } 00341 00352 - (CPDictionary)formattingDictionary 00353 { 00354 return [_standardLocalizer dictionary]; 00355 } 00356 00364 - (void)setFormattingDictionary:(CPDictionary)dictionary 00365 { 00366 [_standardLocalizer setDictionary:dictionary]; 00367 _stringsFilename = nil; 00368 } 00369 00375 - (CPString)formattingStringsFilename 00376 { 00377 return _stringsFilename; 00378 } 00379 00387 - (void)setFormattingStringsFilename:(CPString)stringsFilename 00388 { 00389 // Can we set _stringsFilename to nil in cocoa ? 00390 if (_standardLocalizer == nil) 00391 _standardLocalizer = [_CPRuleEditorLocalizer new]; 00392 00393 if (_stringsFilename != stringsFilename) 00394 { 00395 _stringsFilename = stringsFilename; 00396 00397 if (stringsFilename !== nil) 00398 { 00399 if (![stringsFilename hasSuffix:@".strings"]) 00400 stringsFilename = stringsFilename + @".strings"; 00401 var path = [[CPBundle mainBundle] pathForResource:stringsFilename]; 00402 if (path !=nil) 00403 [_standardLocalizer loadContentOfURL:[CPURL URLWithString:path]]; 00404 } 00405 } 00406 } 00407 00416 - (void)reloadCriteria 00417 { 00418 var current_rows = [_boundArrayOwner valueForKey:_boundArrayKeyPath]; 00419 [self _stopObservingRowObjectsRecursively:current_rows]; 00420 [_boundArrayOwner setValue:[CPArray arrayWithArray:current_rows] forKey:_boundArrayKeyPath]; 00421 } 00422 00431 - (void)setCriteria:(CPArray)criteria andDisplayValues:(CPArray)values forRowAtIndex:(int)rowIndex 00432 { 00433 // TODO: reload from the delegate if criteria is an empty array. 00434 if (criteria == nil || values == nil) 00435 [CPException raise:CPInvalidArgumentException reason:_cmd + @". criteria and values parameters must not be nil."]; 00436 00437 if (rowIndex < 0 || rowIndex >= [self numberOfRows]) 00438 [CPException raise:CPRangeException reason:_cmd + @". rowIndex is out of bounds."]; 00439 00440 var rowObject = [[self _rowCacheForIndex:rowIndex] rowObject]; 00441 00442 [rowObject setValue:criteria forKey:_itemsKeyPath]; 00443 [rowObject setValue:values forKey:_valuesKeyPath]; 00444 00445 [self reloadCriteria]; 00446 } 00447 00453 - (id)criteriaForRow:(int)row 00454 { 00455 var rowcache = [self _rowCacheForIndex:row]; 00456 if (rowcache) 00457 return [[rowcache rowObject] valueForKey:_itemsKeyPath]; 00458 00459 return nil; 00460 } 00461 00472 - (CPMutableArray)displayValuesForRow:(int)row 00473 { 00474 var rowcache = [self _rowCacheForIndex:row]; 00475 if (rowcache) 00476 return [[rowcache rowObject] valueForKey:_valuesKeyPath]; 00477 00478 return nil; 00479 } 00480 00485 - (int)numberOfRows 00486 { 00487 return [_slices count]; 00488 } 00489 00495 - (int)parentRowForRow:(int)rowIndex 00496 { 00497 if (rowIndex < 0 || rowIndex >= [self numberOfRows]) 00498 [CPException raise:CPRangeException reason:_cmd+@" row " + rowIndex + " is out of range"]; 00499 00500 var targetObject = [[self _rowCacheForIndex:rowIndex] rowObject]; 00501 00502 for (var current_index = 0; current_index < rowIndex; current_index++) 00503 { 00504 if ([self rowTypeForRow:current_index] == CPRuleEditorRowTypeCompound) 00505 { 00506 var candidate = [[self _rowCacheForIndex:current_index] rowObject], 00507 subObjects = [[self _subrowObjectsOfObject:candidate] _representedObject]; 00508 00509 if ([subObjects indexOfObjectIdenticalTo:targetObject] != CPNotFound) 00510 return current_index; 00511 } 00512 } 00513 00514 return -1; 00515 } 00516 00517 /* 00518 TODO: implement 00519 Returns the index of the row containing a given value. 00520 00521 displayValue The display value (string, view, or menu item) of an item in the receiver. This value must not be nil. 00522 00523 The index of the row containing displayValue, or CPNotFound. 00524 00525 This method searches each row via objects equality for the given display value, which may be present as an alternative in a popup menu for that row. 00526 00527 - (CPInteger)rowForDisplayValue:(id)displayValue 00528 */ 00529 00536 - (CPRuleEditorRowType)rowTypeForRow:(int)rowIndex 00537 { 00538 if (rowIndex < 0 || rowIndex > [self numberOfRows]) 00539 [CPException raise:CPRangeException reason:_cmd+@"row " + rowIndex + " is out of range"]; 00540 00541 var rowcache = [self _rowCacheForIndex:rowIndex]; 00542 if (rowcache) 00543 { 00544 var rowobject = [rowcache rowObject]; 00545 return [rowobject valueForKey:_typeKeyPath]; 00546 } 00547 00548 return CPNotFound; 00549 } 00550 00557 - (CPIndexSet)subrowIndexesForRow:(int)rowIndex 00558 { 00559 var object; 00560 if (rowIndex == -1) 00561 object = _boundArrayOwner; 00562 else 00563 object = [[self _rowCacheForIndex:rowIndex] rowObject]; 00564 00565 var subobjects = [self _subrowObjectsOfObject:object], 00566 objectsCount = [subobjects count], 00567 indexes = [CPMutableIndexSet indexSet], 00568 count = [self numberOfRows]; 00569 00570 for (var i = rowIndex + 1; i < count; i++) 00571 { 00572 var candidate = [[self _rowCacheForIndex:i] rowObject], 00573 indexInSubrows = [[subobjects _representedObject] indexOfObjectIdenticalTo:candidate]; 00574 00575 if (indexInSubrows != CPNotFound) 00576 { 00577 [indexes addIndex:i]; 00578 objectsCount --; 00579 // [buffer removeObjectAtIndex:indexInSubrows]; 00580 if ([self rowTypeForRow:i] == CPRuleEditorRowTypeCompound) 00581 i += [[self subrowIndexesForRow:i] count]; 00582 } 00583 00584 if (objectsCount == 0) 00585 break; 00586 } 00587 00588 return indexes; 00589 } 00590 00595 - (CPIndexSet)selectedRowIndexes 00596 { 00597 return [self _selectedSliceIndices]; 00598 } 00599 00605 - (void)selectRowIndexes:(CPIndexSet)indexes byExtendingSelection:(BOOL)extend 00606 { 00607 var count = [_slices count], 00608 lastSelected = [indexes lastIndex]; 00609 00610 if (lastSelected >= [self numberOfRows]) 00611 [CPException raise:CPRangeException reason:@"row indexes " + indexes + " are out of range"]; 00612 00613 if (!extend) 00614 [self _deselectAll]; 00615 00616 while (count--) 00617 { 00618 var slice = _slices[count], 00619 rowIndex = [slice rowIndex], 00620 contains = [indexes containsIndex:rowIndex]; 00621 shouldSelect = (contains && !(extend && [slice _isSelected])); 00622 00623 if (contains) 00624 [slice _setSelected:shouldSelect]; 00625 [slice _setLastSelected:(rowIndex == lastSelected)]; 00626 [slice setNeedsDisplay:YES]; 00627 } 00628 } 00629 00639 - (void)addRow:(id)sender 00640 { 00641 var parentRowIndex = -1, 00642 rowtype, 00643 numberOfRows = [self numberOfRows], 00644 hasRows = (numberOfRows > 0), 00645 nestingMode = [self _applicableNestingMode]; 00646 00647 switch (nestingMode) 00648 { 00649 case CPRuleEditorNestingModeSimple: 00650 rowtype = hasRows ? CPRuleEditorRowTypeSimple : CPRuleEditorRowTypeCompound; 00651 if (hasRows) 00652 parentRowIndex = 0; 00653 break; 00654 case CPRuleEditorNestingModeSingle: 00655 if (hasRows) 00656 return; 00657 case CPRuleEditorNestingModeList: 00658 rowtype = CPRuleEditorRowTypeSimple; 00659 break; 00660 case CPRuleEditorNestingModeCompound: 00661 rowtype = CPRuleEditorRowTypeCompound; 00662 if (hasRows) 00663 parentRowIndex = 0; 00664 break; 00665 default: 00666 [CPException raise:CPInvalidArgumentException reason:@"Not supported CPRuleEditorNestingMode " + nestingMode]; 00667 // Compound mode: parentRowIndex=(lastRowType == CPRuleEditorRowTypeCompound)?lastRow :[self parentRowForRow:lastRow]; break; 00668 } 00669 00670 [self insertRowAtIndex:numberOfRows withType:rowtype asSubrowOfRow:parentRowIndex animate:YES]; 00671 } 00672 00682 - (void)insertRowAtIndex:(int)rowIndex withType:(unsigned int)rowType asSubrowOfRow:(int)parentRow animate:(BOOL)shouldAnimate 00683 { 00684 /* 00685 TODO: raise exceptions if parentRow is greater than or equal to rowIndex, or if rowIndex would fall amongst the children of some other parent, or if the nesting mode forbids this configuration. 00686 */ 00687 var newObject = [self _insertNewRowAtIndex:rowIndex ofType:rowType withParentRow:parentRow]; 00688 00689 if (rowType == CPRuleEditorRowTypeCompound && !_allowsEmptyCompoundRows) 00690 { 00691 var subrow = [self _insertNewRowAtIndex:(rowIndex + 1) ofType:CPRuleEditorRowTypeSimple withParentRow:rowIndex]; 00692 } 00693 } 00694 00701 - (void)removeRowAtIndex:(int)rowIndex 00702 { 00703 // TO DO : Any subrows of the deleted row are adopted by the parent of the deleted row, or are made root rows. 00704 00705 if (rowIndex < 0 || rowIndex >= [self numberOfRows]) 00706 [CPException raise:CPRangeException reason:@"row " + rowIndex + " is out of range"]; 00707 00708 [self removeRowsAtIndexes:[CPIndexSet indexSetWithIndex:rowIndex] includeSubrows:NO]; 00709 } 00710 00718 - (void)removeRowsAtIndexes:(CPIndexSet)rowIndexes includeSubrows:(BOOL)includeSubrows 00719 { 00720 if ([rowIndexes count] == 0) 00721 return; 00722 00723 if ([rowIndexes lastIndex] >= [self numberOfRows]) 00724 [CPException raise:CPRangeException reason:@"rows indexes " + rowIndexes + " are out of range"]; 00725 00726 var current_index = [rowIndexes firstIndex], 00727 parentRowIndex = [self parentRowForRow:current_index], 00728 childsIndexes = [CPMutableIndexSet indexSet], 00729 subrows; 00730 00731 if (parentRowIndex == -1) 00732 subrows = [self _rootRowsArray]; 00733 else 00734 { 00735 var parentRowObject = [[self _rowCacheForIndex:parentRowIndex] rowObject]; 00736 subrows = [self _subrowObjectsOfObject:parentRowObject]; 00737 } 00738 00739 while (current_index != CPNotFound) 00740 { 00741 var rowObject = [[self _rowCacheForIndex:current_index] rowObject], 00742 relativeChildIndex = [[subrows _representedObject] indexOfObjectIdenticalTo:rowObject]; 00743 00744 if (relativeChildIndex != CPNotFound) 00745 [childsIndexes addIndex:relativeChildIndex]; 00746 00747 if (includeSubrows && [self rowTypeForRow:current_index] == CPRuleEditorRowTypeCompound) 00748 { 00749 var more_childs = [self subrowIndexesForRow:current_index]; 00750 [self removeRowsAtIndexes:more_childs includeSubrows:includeSubrows]; 00751 } 00752 00753 current_index = [rowIndexes indexGreaterThanIndex:current_index]; 00754 } 00755 00756 [subrows removeObjectsAtIndexes:childsIndexes]; 00757 } 00758 00768 - (CPPredicate)predicate 00769 { 00770 return _predicate; 00771 } 00772 00777 - (void)reloadPredicate 00778 { 00779 [self _updatePredicate]; 00780 } 00781 00788 - (CPPredicate)predicateForRow:(CPInteger)aRow 00789 { 00790 var predicateParts = [CPDictionary dictionary], 00791 items = [self criteriaForRow:aRow], 00792 count = [items count], 00793 predicate, 00794 i; 00795 00796 for (i = 0; i < count; i++) 00797 { 00798 var item = [items objectAtIndex:i], 00799 //var displayValue = [self _queryValueForItem:item inRow:aRow]; A voir. On peut aussi prendre la valeur affichée dans le row cache. 00800 displayValue = [[self displayValuesForRow:aRow] objectAtIndex:i], 00801 predpart = [_ruleDelegate ruleEditor:self predicatePartsForCriterion:item withDisplayValue:displayValue inRow:aRow]; 00802 00803 if (predpart) 00804 [predicateParts addEntriesFromDictionary:predpart]; 00805 } 00806 00807 if ([self rowTypeForRow:aRow] == CPRuleEditorRowTypeCompound) 00808 { 00809 var compoundPredicate, 00810 subpredicates = [CPMutableArray array], 00811 subrowsIndexes = [self subrowIndexesForRow:aRow]; 00812 00813 if ([subrowsIndexes count] == 0) 00814 return nil; 00815 00816 var current_index = [subrowsIndexes firstIndex]; 00817 while (current_index != CPNotFound) 00818 { 00819 var subpredicate = [self predicateForRow:current_index]; 00820 if (subpredicate != nil) 00821 [subpredicates addObject:subpredicate]; 00822 00823 current_index = [subrowsIndexes indexGreaterThanIndex:current_index]; 00824 } 00825 00826 var compoundType = [predicateParts objectForKey:CPRuleEditorPredicateCompoundType]; 00827 00828 if ([subpredicates count] == 0) 00829 return nil; 00830 else 00831 { 00832 try 00833 { 00834 compoundPredicate = [[CPCompoundPredicate alloc ] initWithType:compoundType subpredicates:subpredicates]; 00835 } 00836 catch(error) 00837 { 00838 CPLogConsole(@"Compound predicate error: [%@]\npredicateType:%i",[error description],compoundType); 00839 compoundPredicate = nil; 00840 } 00841 finally 00842 { 00843 return compoundPredicate; 00844 } 00845 00846 } 00847 } 00848 00849 var lhs = [predicateParts objectForKey:CPRuleEditorPredicateLeftExpression], 00850 rhs = [predicateParts objectForKey:CPRuleEditorPredicateRightExpression], 00851 operator = [predicateParts objectForKey:CPRuleEditorPredicateOperatorType], 00852 options = [predicateParts objectForKey:CPRuleEditorPredicateOptions], 00853 modifier = [predicateParts objectForKey:CPRuleEditorPredicateComparisonModifier], 00854 selector = CPSelectorFromString([predicateParts objectForKey:CPRuleEditorPredicateCustomSelector]); 00855 00856 if (lhs == nil){ CPLogConsole(@"missing left expression in predicate parts dictionary"); return NULL;} 00857 if (rhs == nil){ CPLogConsole(@"missing right expression in predicate parts dictionary"); return NULL;} 00858 if (selector == nil && operator == nil){ CPLogConsole(@"missing operator and selector in predicate parts dictionary"); return NULL;} 00859 00860 if (modifier == nil) CPLogConsole(@"missing modifier in predicate parts dictionary. Setting default: CPDirectPredicateModifier"); 00861 if (options == nil) CPLogConsole(@"missing options in predicate parts dictionary. Setting default: CPCaseInsensitivePredicateOption"); 00862 00863 try 00864 { 00865 if (selector != nil) 00866 predicate = [CPComparisonPredicate 00867 predicateWithLeftExpression:lhs 00868 rightExpression:rhs 00869 customSelector:selector 00870 ]; 00871 else 00872 predicate = [CPComparisonPredicate 00873 predicateWithLeftExpression:lhs 00874 rightExpression:rhs 00875 modifier:(modifier || CPDirectPredicateModifier) 00876 type:operator 00877 options:(options || CPCaseInsensitivePredicateOption) 00878 ]; 00879 } 00880 catch(error) 00881 { 00882 CPLogConsole(@"Row predicate error: ["+[error description]+"] for row "+aRow); 00883 predicate = nil; 00884 } 00885 finally 00886 { 00887 return predicate; 00888 } 00889 } 00890 00900 - (Class)rowClass 00901 { 00902 return _rowClass; 00903 } 00904 00910 - (void)setRowClass:(Class)rowClass 00911 { 00912 if (rowClass == [CPMutableDictionary class]) 00913 rowClass = [RowObject class]; 00914 00915 _rowClass = rowClass; 00916 } 00917 00925 - (CPString)rowTypeKeyPath 00926 { 00927 return _typeKeyPath; 00928 } 00929 00935 - (void)setRowTypeKeyPath:(CPString)keyPath 00936 { 00937 if (_typeKeyPath !== keyPath) 00938 _typeKeyPath = keyPath; 00939 } 00940 00948 - (CPString)subrowsKeyPath 00949 { 00950 return _subrowsArrayKeyPath; 00951 } 00952 00958 - (void)setSubrowsKeyPath:(CPString)keyPath 00959 { 00960 if (_subrowsArrayKeyPath !== keyPath) 00961 _subrowsArrayKeyPath = keyPath; 00962 } 00963 00971 - (CPString)criteriaKeyPath 00972 { 00973 return _itemsKeyPath; 00974 } 00975 00981 - (void)setCriteriaKeyPath:(CPString)keyPath 00982 { 00983 if (_itemsKeyPath !== keyPath) 00984 _itemsKeyPath = keyPath; 00985 } 00986 00994 - (CPString)displayValuesKeyPath 00995 { 00996 return _valuesKeyPath; 00997 } 00998 01004 - (void)setDisplayValuesKeyPath:(CPString)keyPath 01005 { 01006 if (_valuesKeyPath !== keyPath) 01007 _valuesKeyPath = keyPath; 01008 } 01009 01019 - (id)animation 01020 { 01021 return _currentAnimation; 01022 } 01023 01030 - (void)setAnimation:(CPViewAnimation)animation 01031 { 01032 _currentAnimation = animation; 01033 [_currentAnimation setDelegate:self]; 01034 } 01035 01036 // TODO: delegate methods are implemented by the delegate. How do you document them in doxygen without implementing them here ? @fn not working ? 01082 - (BOOL)acceptsFirstResponder 01083 { 01084 return YES; 01085 } 01086 01087 - (void)keyDown:(CPEvent)event 01088 { 01089 if (!_suppressKeyDownHandling && [self _applicableNestingMode] == CPRuleEditorNestingModeCompound && !_isKeyDown && ([event modifierFlags] & CPAlternateKeyMask)) 01090 { 01091 [_slices makeObjectsPerformSelector:@selector(_configurePlusButtonByRowType:) withObject:CPRuleEditorRowTypeCompound]; 01092 } 01093 01094 _isKeyDown = YES; 01095 } 01096 01097 - (void)keyUp:(CPEvent)event 01098 { 01099 if (!_suppressKeyDownHandling) 01100 { 01101 [_slices makeObjectsPerformSelector:@selector(_configurePlusButtonByRowType:) withObject:CPRuleEditorRowTypeSimple]; 01102 } 01103 01104 _isKeyDown = NO; 01105 } 01106 01107 - (_CPRuleEditorViewSliceDropSeparator)_createSliceDropSeparator 01108 { 01109 var view = [[_CPRuleEditorViewSliceDropSeparator alloc] initWithFrame:CGRectMake(0,-10, [self frame].size.width, 2)]; 01110 [view setAutoresizingMask:CPViewWidthSizable]; 01111 #if PLATFORM(DOM) 01112 view._DOMElement.style.webkitTransition = "opacity 300ms ease-in"; 01113 #endif 01114 return view; 01115 } 01116 01117 - (BOOL)_suppressKeyDownHandling 01118 { 01119 return _suppressKeyDownHandling; 01120 } 01121 01122 - (BOOL)_wantsRowAnimations 01123 { 01124 return (_currentAnimation != nil); 01125 } 01126 01127 - (void)_updateButtonVisibilities 01128 { 01129 [_slices makeObjectsPerformSelector:@selector(_updateButtonVisibilities)]; 01130 } 01131 01132 - (float)_alignmentGridWidth 01133 { 01134 return _alignmentGridWidth; 01135 } 01136 01137 - (float)_minimumFrameHeight 01138 { 01139 return 26.; 01140 } 01141 01142 - (CPRuleEditorNestingMode)_applicableNestingMode 01143 { 01144 if (!_nestingModeDidChange) 01145 return _nestingMode; 01146 01147 var a = (_nestingMode == CPRuleEditorNestingModeCompound || _nestingMode == CPRuleEditorNestingModeSimple); 01148 var b = ([self rowTypeForRow:0] == CPRuleEditorRowTypeCompound); 01149 01150 if (a == b) 01151 return _nestingMode; 01152 01153 return a ? CPRuleEditorNestingModeList : CPRuleEditorNestingModeSimple; 01154 } 01155 01156 - (BOOL)_shouldHideAddButtonForSlice:(id)slice 01157 { 01158 return (!_editable || [self _applicableNestingMode] == CPRuleEditorNestingModeSingle); 01159 } 01160 01161 - (BOOL)_shouldHideSubtractButtonForSlice:(id)slice 01162 { 01163 if (!_editable) 01164 return YES; 01165 01166 if (!_disallowEmpty) 01167 return NO; 01168 01169 var shouldHide, 01170 rowIndex = [slice rowIndex], 01171 parentIndex = [self parentRowForRow:rowIndex], 01172 subrowsIndexes = [self subrowIndexesForRow:parentIndex], 01173 nestingMode = [self _applicableNestingMode]; 01174 01175 switch (nestingMode) 01176 { 01177 case CPRuleEditorNestingModeCompound: 01178 case CPRuleEditorNestingModeSimple: shouldHide = ([subrowsIndexes count] == 1 && !_allowsEmptyCompoundRows) || parentIndex == -1; 01179 break; 01180 case CPRuleEditorNestingModeList: shouldHide = ([self numberOfRows] == 1); 01181 break; 01182 case CPRuleEditorNestingModeSingle: shouldHide = YES; 01183 break; 01184 default: shouldHide = NO; 01185 } 01186 01187 return shouldHide; 01188 } 01189 01190 #pragma mark Rows management 01191 01192 - (id)_rowCacheForIndex:(int)index 01193 { 01194 return [_rowCache objectAtIndex:index]; 01195 } 01196 01197 - (id)_searchCacheForRowObject:(id)rowObject 01198 { 01199 var count = [_rowCache count], 01200 i; 01201 01202 for (i = 0; i < count; i++) 01203 { 01204 var cache = _rowCache[i]; 01205 if ([cache rowObject] === rowObject) 01206 return cache; 01207 } 01208 01209 return nil; 01210 } 01211 01212 - (int)_rowIndexForRowObject:(id)rowobject 01213 { 01214 if (rowobject == _boundArrayOwner) 01215 return -1; 01216 01217 return [[self _searchCacheForRowObject:rowobject] rowIndex]; // Pas bon car le rowIndex du row cache n'est pas synchro avec la position dans _rowCache. 01218 } 01219 01220 - (CPMutableArray)_subrowObjectsOfObject:(id)object 01221 { 01222 if (object === _boundArrayOwner) 01223 return [self _rootRowsArray]; 01224 01225 return [object mutableArrayValueForKey:_subrowsArrayKeyPath]; 01226 } 01227 01228 - (CPIndexSet)_childlessParentsIfSlicesWereDeletedAtIndexes:(id)indexes 01229 { 01230 var childlessParents = [CPIndexSet indexSet], 01231 current_index = [indexes firstIndex]; 01232 01233 while (current_index != CPNotFound) 01234 { 01235 var parentIndex = [self parentRowForRow:current_index]; 01236 01237 var subrowsIndexes = [self subrowIndexesForRow:parentIndex]; 01238 if ([subrowsIndexes count]==1) 01239 { 01240 if (parentIndex != -1) 01241 return [CPIndexSet indexSetWithIndex:0]; 01242 01243 var childlessGranPa = [self _childlessParentsIfSlicesWereDeletedAtIndexes:[CPIndexSet indexSetWithIndex:parentIndex]]; 01244 [childlessParents addIndexes:childlessGranPa]; 01245 } 01246 01247 current_index = [indexes indexGreaterThanIndex:current_index]; 01248 } 01249 01250 return childlessParents; 01251 // (id)-[RuleEditor _includeSubslicesForSlicesAtIndexes:] 01252 } 01253 01254 - (CPIndexSet)_includeSubslicesForSlicesAtIndexes:(CPIndexSet)indexes 01255 { 01256 var subindexes = [indexes copy], 01257 current_index = [indexes firstIndex]; 01258 01259 while (current_index != CPNotFound) 01260 { 01261 var sub = [self subrowIndexesForRow:current_index]; 01262 [subindexes addIndexes:[self _includeSubslicesForSlicesAtIndexes:sub]]; 01263 current_index = [indexes indexGreaterThanIndex:current_index]; 01264 } 01265 01266 return subindexes; 01267 } 01268 01269 - (void)_deleteSlice:(id)slice 01270 { 01271 var rowindexes = [CPIndexSet indexSetWithIndex:[slice rowIndex]]; 01272 01273 if (!_allowsEmptyCompoundRows) 01274 { 01275 var childlessIndexes = [self _childlessParentsIfSlicesWereDeletedAtIndexes:rowindexes]; 01276 if ([childlessIndexes count] > 0) 01277 rowindexes = childlessIndexes; 01278 } 01279 01280 [self removeRowsAtIndexes:rowindexes includeSubrows:YES]; 01281 [self _postRowCountChangedNotificationOfType:CPRuleEditorRowsDidChangeNotification indexes:rowindexes]; // indexes should include childs 01282 } 01283 01284 - (CPArray)_rootRowsArray 01285 { 01286 return [_boundArrayOwner mutableArrayValueForKey:_boundArrayKeyPath]; 01287 } 01288 01289 - (BOOL)_nextUnusedItems:({CPArray})items andValues:({CPArray})values forRow:(int)rowIndex forRowType:(unsigned int)type 01290 { 01291 var parentItem = [items lastObject], // if empty items array, this is NULL aka the root item; 01292 childrenCount = [self _queryNumberOfChildrenOfItem:parentItem withRowType:type], 01293 foundIndex = CPNotFound; 01294 01295 if (childrenCount == 0) 01296 return NO; 01297 01298 var current_criterions = [CPMutableArray array], 01299 count = [self numberOfRows], 01300 row; 01301 01302 for (row = 0; row < count; row++) // num of rows should be num of siblings of parentItem 01303 { 01304 var aCriteria = [self criteriaForRow:row], 01305 itemIndex = [items count]; 01306 01307 if ([self rowTypeForRow:row] == type && itemIndex < [aCriteria count]) 01308 { 01309 var crit = [aCriteria objectAtIndex:itemIndex]; 01310 [current_criterions addObject:crit]; 01311 } 01312 } 01313 01314 while (foundIndex == CPNotFound) 01315 { 01316 var buffer = [CPMutableArray arrayWithArray:current_criterions], 01317 i; 01318 for (i = 0; i < childrenCount; i++) 01319 { 01320 var child = [self _queryChild:i ofItem:parentItem withRowType:type]; 01321 if ([current_criterions indexOfObject:child] == CPNotFound) 01322 { 01323 foundIndex = i; 01324 break; 01325 } 01326 } 01327 01328 if (foundIndex == CPNotFound) 01329 { 01330 for (var k = 0; k < childrenCount; k++) 01331 { 01332 var anobject = [self _queryChild:k ofItem:parentItem withRowType:type], 01333 index = [buffer indexOfObject:anobject]; 01334 if (index != CPNotFound) 01335 [buffer removeObjectAtIndex:index]; 01336 } 01337 01338 current_criterions = buffer; 01339 } 01340 } 01341 01342 var foundItem = [self _queryChild:foundIndex ofItem:parentItem withRowType:type], 01343 foundValue = [self _queryValueForItem:foundItem inRow:rowIndex]; 01344 01345 [items addObject:foundItem]; 01346 [values addObject:foundValue]; 01347 01348 return YES; 01349 } 01350 01351 - (CPMutableArray)_getItemsAndValuesToAddForRow:(int)rowIndex ofType:(CPRuleEditorRowType)type 01352 { 01353 //var cachedItemsAndValues = _itemsAndValuesToAddForRowType[type]; 01354 //if (cachedItemsAndValues) 01355 // return cachedItemsAndValues; 01356 01357 var itemsAndValues = [CPMutableArray array], 01358 items = [CPMutableArray array], 01359 values = [CPMutableArray array], 01360 unusedItems = YES; 01361 01362 while (unusedItems) 01363 unusedItems = [self _nextUnusedItems:items andValues:values forRow:rowIndex forRowType:type]; 01364 01365 var count = [items count]; 01366 01367 for (var i = 0; i < count; i++) 01368 { 01369 var item = [items objectAtIndex:i], 01370 value = [values objectAtIndex:i], 01371 itemAndValue = [CPDictionary dictionaryWithObjects:[item,value] forKeys:["item","value"]]; 01372 01373 [itemsAndValues addObject:itemAndValue]; 01374 } 01375 01376 return itemsAndValues; 01377 } 01378 01379 - (void)_addOptionFromSlice:(id)slice ofRowType:(unsigned int)type 01380 { 01381 // for CPRuleEditorNestingModeSimple only 01382 01383 var rowIndexEvent = [slice rowIndex], 01384 rowTypeEvent = [self rowTypeForRow:rowIndexEvent]; 01385 01386 var parentRowIndex = (rowTypeEvent == CPRuleEditorRowTypeCompound) ? rowIndexEvent:[self parentRowForRow:rowIndexEvent]; 01387 01388 [self insertRowAtIndex:rowIndexEvent + 1 withType:type asSubrowOfRow:parentRowIndex animate:YES]; 01389 01390 // [self _reconfigureSubviewsAnimate:YES]; 01391 // [self _updatePredicate]; 01392 } 01393 01394 - (id)_insertNewRowAtIndex:(int)insertIndex ofType:(CPRuleEditorRowType)rowtype withParentRow:(int)parentRowIndex 01395 { 01396 var row = [[[self rowClass] alloc] init]; 01397 01398 var itemsandvalues = [self _getItemsAndValuesToAddForRow:insertIndex ofType:rowtype], 01399 newitems = [itemsandvalues valueForKey:@"item"], 01400 newvalues = [itemsandvalues valueForKey:@"value"]; 01401 01402 [row setValue:newitems forKey:_itemsKeyPath]; 01403 [row setValue:newvalues forKey:_valuesKeyPath]; 01404 [row setValue:rowtype forKey:_typeKeyPath]; 01405 [row setValue:[CPMutableArray array] forKey:_subrowsArrayKeyPath]; 01406 01407 var subrowsObjects; 01408 if (parentRowIndex == -1 || [self _applicableNestingMode] == CPRuleEditorNestingModeList) 01409 subrowsObjects = [self _rootRowsArray]; 01410 else 01411 { 01412 var parentRowObject = [[self _rowCacheForIndex:parentRowIndex] rowObject]; 01413 subrowsObjects = [self _subrowObjectsOfObject:parentRowObject]; 01414 } 01415 01416 var relInsertIndex = insertIndex - parentRowIndex - 1; 01417 [subrowsObjects insertObject:row atIndex:relInsertIndex]; 01418 01419 return row; 01420 } 01421 01422 #pragma mark Key value observing 01423 01424 - (void)_startObservingRowObjectsRecursively:(CPArray)rowObjects 01425 { 01426 [_boundArrayOwner addObserver:self forKeyPath:_boundArrayKeyPath options:CPKeyValueObservingOptionOld|CPKeyValueObservingOptionNew context:boundArrayContext]; 01427 01428 var count = [rowObjects count]; 01429 01430 for (var i = 0; i < count; i++) 01431 { 01432 var rowObject = [rowObjects objectAtIndex:i]; 01433 01434 [rowObject addObserver:self forKeyPath:_itemsKeyPath options:CPKeyValueObservingOptionOld|CPKeyValueObservingOptionNew context:itemsContext]; 01435 [rowObject addObserver:self forKeyPath:_valuesKeyPath options:CPKeyValueObservingOptionOld|CPKeyValueObservingOptionNew context:valuesContext]; 01436 [rowObject addObserver:self forKeyPath:_subrowsArrayKeyPath options:CPKeyValueObservingOptionOld|CPKeyValueObservingOptionNew context:subrowsContext]; 01437 01438 var subrows = [self _subrowObjectsOfObject:rowObject]; 01439 if ([subrows count] > 0) 01440 [self _startObservingRowObjectsRecursively:subrows]; 01441 } 01442 // ORIG IMPL : calls +keyPathsForValuesAffectingValueForKey: for all keys 01443 } 01444 01445 - (void)_stopObservingRowObjectsRecursively:(CPArray)rowObjects 01446 { 01447 [_boundArrayOwner removeObserver:self forKeyPath:_boundArrayKeyPath]; 01448 01449 var count = [rowObjects count]; 01450 01451 for (var i = 0; i < count; i++) 01452 { 01453 var rowObject = [rowObjects objectAtIndex:i]; 01454 [rowObject removeObserver:self forKeyPath:_itemsKeyPath]; 01455 [rowObject removeObserver:self forKeyPath:_valuesKeyPath]; 01456 [rowObject removeObserver:self forKeyPath:_subrowsArrayKeyPath]; 01457 01458 var subrows = [rowObject valueForKey:_subrowsArrayKeyPath]; 01459 if ([subrows count] > 0) 01460 [self _stopObservingRowObjectsRecursively:subrows]; 01461 } 01462 } 01463 01464 - (void)observeValueForKeyPath:(CPString)keypath ofObject:(id)object change:(CPDictionary)change context:(void)context 01465 { 01466 var changeKind = [change objectForKey:CPKeyValueChangeKindKey], 01467 changeNewValue = [change objectForKey:CPKeyValueChangeNewKey], 01468 changeOldValue = [change objectForKey:CPKeyValueChangeOldKey], 01469 newRows, 01470 oldRows; 01471 01472 if (context == boundArrayContext || context == subrowsContext) 01473 { 01474 if (changeKind == CPKeyValueChangeSetting) 01475 { 01476 newRows = changeNewValue; 01477 oldRows = changeOldValue; 01478 01479 } 01480 else if (changeKind == CPKeyValueChangeInsertion) 01481 { 01482 newRows = [self _subrowObjectsOfObject:object]; 01483 oldRows = [CPArray arrayWithArray:newRows]; 01484 [oldRows removeObjectsInArray:changeNewValue]; 01485 } 01486 else if (changeKind == CPKeyValueChangeRemoval) 01487 { 01488 newRows = [self _subrowObjectsOfObject:object]; 01489 oldRows = [CPArray arrayWithArray:newRows]; 01490 var delIndexes = [change objectForKey:CPKeyValueChangeIndexesKey]; 01491 [oldRows insertObjects:delObjects atIndexes:changeOldValue]; // Pas sur que ce soit bon 01492 } 01493 01494 [self _changedRowArray:newRows withOldRowArray:oldRows forParent:object]; 01495 01496 [self _reconfigureSubviewsAnimate:[self _wantsRowAnimations]]; 01497 [self _postRowCountChangedNotificationOfType:CPRuleEditorRowsDidChangeNotification indexes:[change objectForKey:CPKeyValueChangeIndexesKey]]; 01498 01499 } 01500 else if (context == itemsContext) 01501 { 01502 } 01503 else if (context == valuesContext) 01504 { 01505 } 01506 } 01507 01508 - (void)_changedItem:(id)fromItem toItem:(id)toItem inRow:(int)aRow atCriteriaIndex:(int)fromItemIndex 01509 { 01510 var criteria = [self criteriaForRow:aRow], 01511 displayValues = [self displayValuesForRow:aRow], 01512 rowType = [self rowTypeForRow:aRow], 01513 anItem = toItem; 01514 //fromItemIndex = [criteria indexOfObjectIdenticalTo:fromItem]; 01515 01516 var items = [criteria subarrayWithRange:CPMakeRange(0, fromItemIndex)], 01517 values = [displayValues subarrayWithRange:CPMakeRange(0, fromItemIndex)]; 01518 01519 _lastRow = aRow; 01520 01521 while (YES) 01522 { 01523 [items addObject:anItem]; 01524 var value = [self _queryValueForItem:anItem inRow:aRow]; 01525 [values addObject:value]; 01526 01527 if (![self _queryNumberOfChildrenOfItem:anItem withRowType:rowType]) 01528 break; 01529 01530 anItem = [self _queryChild:0 ofItem:anItem withRowType:rowType]; 01531 } 01532 01533 var object = [[self _rowCacheForIndex:aRow] rowObject]; 01534 [object setValue:items forKey:_itemsKeyPath]; 01535 [object setValue:values forKey:_valuesKeyPath]; 01536 01537 var slice = [_slices objectAtIndex:aRow]; 01538 [slice _reconfigureSubviews]; 01539 [self _sendRuleAction]; 01540 [self _postRuleOptionChangedNotification]; 01541 } 01542 01543 - (void)_changedRowArray:(CPArray)newRows withOldRowArray:(CPArray)oldRows forParent:(id)parentRowObject 01544 { 01545 var newRowCount = [newRows count], 01546 oldRowCount = [oldRows count], 01547 deltaCount = newRowCount - oldRowCount, 01548 minusCount = MIN(newRowCount, oldRowCount), 01549 maxCount = MAX(newRowCount, oldRowCount), 01550 01551 insertCacheIndexes = [CPIndexSet indexSet], 01552 newCaches = [CPArray array], 01553 01554 parentCacheIndentation, 01555 parentCacheIndex = [self _rowIndexForRowObject:parentRowObject], 01556 01557 newRowCacheIndex = 0, 01558 changeStartIndex = 0; 01559 01560 [self _stopObservingRowObjectsRecursively:oldRows]; 01561 [self _startObservingRowObjectsRecursively:newRows]; 01562 01563 //var gindexes = [self _globalIndexesForSubrowIndexes:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0,oldRowCount)] ofParentObject:parentRowObject]; 01564 01565 if (parentCacheIndex == -1) 01566 parentCacheIndentation = -1; 01567 else 01568 parentCacheIndentation = [[self _rowCacheForIndex:parentCacheIndex] indentation]; 01569 01570 for (; newRowCacheIndex < newRowCount; newRowCacheIndex++) 01571 { 01572 var newCacheGlobalIndex = (parentCacheIndex + 1) + newRowCacheIndex, 01573 obj = [newRows objectAtIndex:newRowCacheIndex], 01574 newRowType = [obj valueForKey:_typeKeyPath]; 01575 01576 var cache = [[_CPRuleEditorCache alloc] init]; 01577 [cache setRowObject:obj]; 01578 [cache setRowIndex:newCacheGlobalIndex]; 01579 [cache setIndentation:parentCacheIndentation + 1]; 01580 01581 [insertCacheIndexes addIndex:newCacheGlobalIndex]; 01582 [newCaches addObject:cache]; 01583 } 01584 01585 //var lastCacheIndex = [self _rowIndexForRowObject:[oldRows lastObject]]; 01586 [_rowCache removeObjectsInRange:CPMakeRange(parentCacheIndex + 1, [oldRows count])]; 01587 [_rowCache insertObjects:newCaches atIndexes:insertCacheIndexes]; 01588 01589 for (; changeStartIndex < minusCount; changeStartIndex++) 01590 { 01591 var oldrow = [oldRows objectAtIndex:changeStartIndex], 01592 newrow = [newRows objectAtIndex:changeStartIndex]; 01593 01594 if (newrow != oldrow) 01595 break; 01596 } 01597 01598 var replaceCount = (deltaCount == 0) ? maxCount : maxCount - minusCount; 01599 var startIndex = parentCacheIndex + changeStartIndex + 1; 01600 01601 if (deltaCount <= 0) 01602 { 01603 var removeIndexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(startIndex, replaceCount)]; 01604 var removeSlices = [_slices objectsAtIndexes:removeIndexes]; 01605 [removeSlices makeObjectsPerformSelector:@selector(removeFromSuperview)]; 01606 [_slices removeObjectsAtIndexes:removeIndexes]; 01607 } 01608 01609 if (deltaCount >= 0) 01610 { 01611 var newIndentation = parentCacheIndentation + 1, 01612 newIndex = startIndex; 01613 01614 for (; newIndex < startIndex + replaceCount; newIndex++) 01615 { 01616 var newslice = [self _newSlice], 01617 rowType = [self rowTypeForRow:newIndex]; 01618 01619 [newslice setRowIndex:newIndex]; 01620 [newslice setIndentation:newIndentation]; 01621 [newslice _setRowType:rowType]; 01622 [newslice _configurePlusButtonByRowType:CPRuleEditorRowTypeSimple]; 01623 01624 [_slices insertObject:newslice atIndex:newIndex]; 01625 } 01626 } 01627 01628 var emptyArray = [CPArray array], 01629 count = [oldRows count], 01630 n; 01631 for (n = 0; n < count; n++) 01632 { 01633 var oldRow = [oldRows objectAtIndex:n], 01634 subOldRows = [self _subrowObjectsOfObject:oldRow]; 01635 01636 if ([subOldRows count] > 0) 01637 [self _changedRowArray:emptyArray withOldRowArray:subOldRows forParent:oldRow]; 01638 } 01639 01640 count = [newRows count]; 01641 for (n = 0; n < count; n++) 01642 { 01643 var newRow = [newRows objectAtIndex:n], 01644 subnewRows = [self _subrowObjectsOfObject:newRow]; 01645 01646 if ([subnewRows count] > 0) 01647 [self _changedRowArray:subnewRows withOldRowArray:emptyArray forParent:newRow]; 01648 } 01649 } 01650 01651 - (void)bind:(CPString)binding toObject:(id)observableController withKeyPath:(CPString)keyPath options:(CPDictionary)options 01652 { 01653 if (keyPath == nil || [observableController valueForKey:keyPath] == nil) 01654 { 01655 [CPException raise:CPInvalidArgumentException reason:"Keypath or bound object cannot be nil"]; 01656 return; 01657 } 01658 01659 if ([binding isEqualToString:@"rows"]) 01660 { 01661 if ([observableController respondsToSelector:@selector(objectClass)]) 01662 _rowClass = [observableController objectClass]; 01663 01664 [self _setBoundDataSource:observableController withKeyPath:keyPath options:options]; 01665 } 01666 else if ([binding isEqualToString:CPValueBinding]) 01667 [super bind:binding toObject:observableController withKeyPath:keyPath options:options]; 01668 else 01669 [CPException raise:CPInvalidArgumentException reason:"Keypath or bound object cannot be nil"]; 01670 } 01671 01672 - (void)unbind:(id)object 01673 { 01674 _rowClass = [RowObject class]; 01675 [super unbind:object]; 01676 } 01677 01678 - (void)_setBoundDataSource:(id)datasource withKeyPath:(CPString)keyPath options:(CPDictionary)options 01679 { 01680 if (_boundArrayOwner != nil) 01681 [_boundArrayOwner removeObserver:self forKeyPath:_boundArrayKeyPath]; 01682 01683 _boundArrayKeyPath = keyPath; 01684 _boundArrayOwner = datasource; 01685 01686 var boundRows = [_boundArrayOwner valueForKey:_boundArrayKeyPath]; 01687 01688 [_boundArrayOwner addObserver:self forKeyPath:_boundArrayKeyPath options:CPKeyValueObservingOptionOld|CPKeyValueObservingOptionNew context:boundArrayContext]; 01689 01690 if ([boundRows isKindOfClass:[CPArray class]] && [boundRows count] > 0) 01691 [_boundArrayOwner setValue:boundRows forKey:_boundArrayKeyPath]; 01692 } 01693 01694 - (void)_setPredicate:(CPPredicate)predicate 01695 { 01696 if (_predicate !== predicate) 01697 _predicate = predicate; 01698 } 01699 01700 - (void)_updatePredicate 01701 { 01702 if (_delegateWantsValidation) 01703 { 01704 var selector = @selector(ruleEditor:predicatePartsForCriterion:withDisplayValue:inRow:); 01705 if (![_ruleDelegate respondsToSelector:selector]) 01706 return; 01707 01708 _delegateWantsValidation = NO; 01709 } 01710 01711 var subpredicates = [CPMutableArray array], 01712 subindexes = [self subrowIndexesForRow:-1], 01713 current_index = [subindexes firstIndex]; 01714 01715 while (current_index != CPNotFound) 01716 { 01717 var subpredicate = [self predicateForRow:current_index]; 01718 01719 if (subpredicate != nil) 01720 [subpredicates addObject:subpredicate]; 01721 01722 current_index = [subindexes indexGreaterThanIndex:current_index]; 01723 } 01724 01725 var new_predicate = [[CPCompoundPredicate alloc] initWithType:CPOrPredicateType subpredicates:subpredicates]; 01726 01727 [self _setPredicate:new_predicate]; 01728 } 01729 01730 - (_CPRuleEditorViewSliceRow)_newSlice 01731 { 01732 var sliceRect = CGRectMake(0, 0, CGRectGetWidth([self frame]), 0), 01733 slice = [self _createNewSliceWithFrame:sliceRect ruleEditorView:self]; 01734 01735 return slice; 01736 } 01737 01738 - (_CPRuleEditorViewSliceRow)_createNewSliceWithFrame:(CGRect)frame ruleEditorView:(CPRuleEditor)editor 01739 { 01740 return [[_CPRuleEditorViewSliceRow alloc] initWithFrame:frame ruleEditorView:editor]; 01741 } 01742 01743 - (void)_reconfigureSubviewsAnimate:(BOOL)animate 01744 { 01745 [self _updateSliceRows]; 01746 01747 var viewAnimations = [CPMutableArray array], 01748 added_slices = [CPMutableArray array], 01749 count = [_slices count]; 01750 01751 for (var i = 0; i < count; i++) 01752 { 01753 var aslice = [_slices objectAtIndex:i], 01754 targetRect = [aslice _animationTargetRect], 01755 startRect = [aslice frame], 01756 startIndex = [aslice rowIndex] - 1; 01757 01758 if ([aslice superview] == nil) 01759 { 01760 startRect = CGRectMake(0, startIndex * _sliceHeight, CGRectGetWidth(startRect), _sliceHeight); 01761 [aslice _reconfigureSubviews]; 01762 [added_slices addObject:aslice]; 01763 } 01764 01765 if (animate) 01766 { 01767 var animation = [CPDictionary dictionary]; 01768 [animation setObject:aslice forKey:CPViewAnimationTargetKey]; 01769 [animation setObject:startRect forKey:CPViewAnimationStartFrameKey]; 01770 [animation setObject:targetRect forKey:CPViewAnimationEndFrameKey]; 01771 01772 [viewAnimations insertObject:animation atIndex:0]; 01773 } 01774 else 01775 [aslice setFrame:targetRect]; 01776 } 01777 01778 var addcount = [added_slices count]; 01779 for (var i = 0; i < addcount; i++) 01780 [_slicesHolder addSubview:added_slices[i] positioned:CPWindowBelow relativeTo:nil]; 01781 01782 if (animate) 01783 { 01784 [_currentAnimation setViewAnimations:viewAnimations]; 01785 [_currentAnimation startAnimation]; 01786 } 01787 01788 _lastRow = [self numberOfRows] - 1; 01789 01790 if (_lastRow == -1) 01791 _nestingModeDidChange = NO; 01792 01793 [self setNeedsDisplay:YES]; 01794 [_slices makeObjectsPerformSelector:@selector(_updateButtonVisibilities)]; 01795 } 01796 01797 - (void)animationDidEnd:(CPViewAnimation)animation 01798 { 01799 // var nextSimple = [self _getItemsAndValuesToAddForRow:0 ofType:CPRuleEditorRowTypeSimple], 01800 // nextCompound = [self _getItemsAndValuesToAddForRow:0 ofType:CPRuleEditorRowTypeCompound]; 01801 01802 // _itemsAndValuesToAddForRowType = {CPRuleEditorRowTypeSimple:nextSimple, CPRuleEditorRowTypeCompound:nextCompound}; 01803 } 01804 01805 - (void)_updateSliceRows 01806 { 01807 var width = [self frame].size.width, 01808 count = [_slices count]; 01809 01810 for (var i = 0; i < count; i++) 01811 { 01812 var slice = [_slices objectAtIndex:i], 01813 targetRect = CGRectMake(0, i * _sliceHeight, width, _sliceHeight); 01814 01815 [slice setRowIndex:i]; 01816 [slice _setAnimationTargetRect:targetRect]; 01817 } 01818 } 01819 01820 - (CPArray)_backgroundColors 01821 { 01822 return [self valueForThemeAttribute:@"alternating-row-colors"]; 01823 } 01824 01825 - (CPColor)_selectedRowColor 01826 { 01827 return [self valueForThemeAttribute:@"selected-color"]; 01828 } 01829 01830 - (CPColor)_sliceTopBorderColor 01831 { 01832 return [self valueForThemeAttribute:@"slice-top-border-color"]; 01833 } 01834 01835 - (CPColor)_sliceBottomBorderColor 01836 { 01837 return [self valueForThemeAttribute:@"slice-bottom-border-color"]; 01838 } 01839 01840 - (CPColor)_sliceLastBottomBorderColor 01841 { 01842 return [self valueForThemeAttribute:@"slice-last-bottom-border-color"]; 01843 } 01844 01845 - (CPFont)font 01846 { 01847 return [self valueForThemeAttribute:@"font"]; 01848 } 01849 01850 - (CPImage)_addImage 01851 { 01852 return [self valueForThemeAttribute:@"add-image"]; 01853 } 01854 01855 - (CPImage)_removeImage 01856 { 01857 return [self valueForThemeAttribute:@"remove-image"]; 01858 } 01859 01860 - (CPString)_toolTipForAddCompoundRowButton 01861 { 01862 return @"Add Compound row"; 01863 } 01864 01865 - (CPString)_toolTipForAddSimpleRowButton 01866 { 01867 return @"Add row"; 01868 } 01869 01870 - (CPString)_toolTipForDeleteRowButton 01871 { 01872 return @"Delete row"; 01873 } 01874 01875 - (void)_updateSliceIndentations 01876 { 01877 [self _updateSliceIndentationAtIndex:0 toIndentation:0 withIndexSet:[self subrowIndexesForRow:0]]; 01878 } 01879 01880 - (void)_updateSliceIndentationAtIndex:(int)index toIndentation:(int)indentation withIndexSet:(id)indexes 01881 { 01882 var current_index = [indexes firstIndex]; 01883 01884 while (current_index !=CPNotFound) 01885 { 01886 var subindexes = [self subrowIndexesForRow:index]; 01887 [self _updateSliceIndentationAtIndex:current_index toIndentation:indentation + 1 withIndexSet:subindexes]; 01888 current_index = [indexes indexGreaterThanIndex:current_index]; 01889 } 01890 01891 [[_slices objectAtIndex:index] setIndentation:indentation]; 01892 } 01893 01894 - (CPArray)_selectedSlices 01895 { 01896 var _selectedSlices = [CPMutableArray array], 01897 count = [_slices count], 01898 i; 01899 01900 for (i = 0; i < count; i++) 01901 { 01902 var slice = _slices[i]; 01903 if ([slice _isSelected]) 01904 [_selectedSlices addObject:slice]; 01905 } 01906 01907 return _selectedSlices; 01908 } 01909 01910 - (int)_lastSelectedSliceIndex 01911 { 01912 var lastIndex = -1, 01913 count = [_slices count], 01914 i; 01915 01916 for (i = 0; i < count; i++) 01917 { 01918 var slice = _slices[i]; 01919 if ([slice _isLastSelected]) 01920 return [slice rowIndex]; 01921 } 01922 01923 return CPNotFound; 01924 } 01925 01926 - (void)_mouseUpOnSlice:(id)slice withEvent:(CPEvent)event 01927 { 01928 if ([slice _rowType] != CPRuleEditorRowTypeSimple) 01929 return; 01930 01931 var modifierFlags = [event modifierFlags], 01932 extend = (modifierFlags & CPCommandKeyMask) || (modifierFlags & CPShiftKeyMask), 01933 rowIndexes = [CPIndexSet indexSetWithIndex:[slice rowIndex]]; 01934 01935 [self selectRowIndexes:rowIndexes byExtendingSelection:extend]; 01936 } 01937 01938 - (void)_mouseDownOnSlice:(id)slice withEvent:(CPEvent)event 01939 { 01940 } 01941 01942 - (void)_rightMouseDownOnSlice:(_CPRuleEditorViewSlice)slice withEvent:(CPEvent)event 01943 { 01944 } 01945 01946 - (void)_performClickOnSlice:(id)slice withEvent:(CPEvent)event 01947 { 01948 } 01949 01950 - (void)_setSuppressKeyDownHandling:(BOOL)flag 01951 { 01952 _suppressKeyDownHandling = flag; 01953 } 01954 01955 - (void)selectAll:(id)sender 01956 { 01957 var count = [_slices count]; 01958 01959 while (count--) 01960 { 01961 var slice = _slices[count]; 01962 [slice _setSelected:YES]; 01963 [slice setNeedsDisplay:YES]; 01964 } 01965 } 01966 01967 - (void)_deselectAll 01968 { 01969 var count = [_slices count]; 01970 01971 while (count--) 01972 { 01973 var slice = _slices[count]; 01974 [slice _setSelected:NO]; 01975 [slice _setLastSelected:NO]; 01976 [slice setNeedsDisplay:YES]; 01977 } 01978 } 01979 01980 - (int)_queryNumberOfChildrenOfItem:(id)item withRowType:(CPRuleEditorRowType)type 01981 { 01982 return [_ruleDelegate ruleEditor:self numberOfChildrenForCriterion:item withRowType:type]; 01983 } 01984 01985 - (id)_queryChild:(int)childIndex ofItem:(id)item withRowType:(CPRuleEditorRowType)type 01986 { 01987 return [_ruleDelegate ruleEditor:self child:childIndex forCriterion:item withRowType:type]; 01988 } 01989 01990 - (id)_queryValueForItem:(id)item inRow:(int)row 01991 { 01992 return [_ruleDelegate ruleEditor:self displayValueForCriterion:item inRow:row]; 01993 } 01994 01995 - (int)_lastRow 01996 { 01997 return _lastRow; 01998 } 01999 02000 - (int)_countOfRowsStartingAtObject:(id)object 02001 { 02002 var index = [self _rowIndexForRowObject:object]; 02003 return ([self numberOfRows] - index); 02004 } 02005 02006 - (void)_setAlignmentGridWidth:(float)width 02007 { 02008 _alignmentGridWidth = width; 02009 } 02010 02011 - (BOOL)_validateItem:(id)item value:(id)value inRow:(int)row 02012 { 02013 return [self _queryCanSelectItem:item displayValue:value inRow:row]; 02014 } 02015 02016 - (BOOL)_queryCanSelectItem:(id)item displayValue:(id)value inRow:(int)row 02017 { 02018 return YES; 02019 } 02020 02021 - (void)_windowChangedKeyState 02022 { 02023 [self setNeedsDisplay:YES]; 02024 } 02025 02026 - (void)setNeedsDisplay:(BOOL)flag 02027 { 02028 [_slices makeObjectsPerformSelector:@selector(setNeedsDisplay:) withObject:flag]; 02029 [super setNeedsDisplay:flag]; 02030 } 02031 02032 - (void)setFrameSize:(CPSize)size 02033 { 02034 [self setNeedsDisplay:YES]; 02035 02036 if (CGRectGetWidth([self frame]) != size.width) 02037 [_slices makeObjectsPerformSelector:@selector(setNeedsLayout)]; 02038 02039 [super setFrameSize:size]; 02040 } 02041 02042 - (CPIndexSet)_selectedSliceIndices 02043 { 02044 var selectedIndices = [CPMutableIndexSet indexSet], 02045 count = [_slices count], 02046 i; 02047 02048 for (i = 0; i < count; i++) 02049 { 02050 var slice = _slices[i]; 02051 if ([slice _isSelected]) 02052 [selectedIndices addIndex:[slice rowIndex]]; 02053 } 02054 02055 return selectedIndices; 02056 } 02057 02058 - (void)mouseDragged:(CPEvent)event 02059 { 02060 if (!_editable) 02061 return; 02062 02063 var point = [self convertPoint:[event locationInWindow] fromView:nil], 02064 view = [_slices objectAtIndex:FLOOR(point.y / _sliceHeight)]; 02065 02066 if ([self _dragShouldBeginFromMouseDown:view]) 02067 [self _performDragForSlice:view withEvent:event]; 02068 } 02069 02070 - (BOOL)_dragShouldBeginFromMouseDown:(CPView)view 02071 { 02072 return (([self nestingMode] == CPRuleEditorNestingModeList || [view rowIndex] != 0) && _editable && [view isKindOfClass:[_CPRuleEditorViewSliceRow class]] && _draggingRows == nil); 02073 } 02074 02075 - (BOOL)_performDragForSlice:(id)slice withEvent:(CPEvent)event 02076 { 02077 var dragPoint, 02078 mainRowIndex = [slice rowIndex], 02079 draggingRows = [CPIndexSet indexSetWithIndex:mainRowIndex], 02080 selected_indices = [self _selectedSliceIndices], 02081 pasteboard = [CPPasteboard pasteboardWithName: CPDragPboard]; 02082 02083 [pasteboard declareTypes:[CPArray arrayWithObjects: CPRuleEditorItemPBoardType, nil] owner: self]; 02084 02085 if ([selected_indices containsIndex:mainRowIndex]) 02086 [draggingRows addIndexes:selected_indices]; 02087 _draggingRows = [self _includeSubslicesForSlicesAtIndexes:draggingRows]; 02088 02089 var firstIndex = [_draggingRows firstIndex], 02090 firstSlice = [_slices objectAtIndex:firstIndex], 02091 dragview = [[CPView alloc] initWithFrame:[firstSlice frame]]; 02092 02093 #if PLATFORM(DOM) 02094 var html = firstSlice._DOMElement.innerHTML; 02095 dragview._DOMElement.innerHTML = [html copy]; 02096 #endif 02097 [dragview setBackgroundColor:[firstSlice backgroundColor]]; 02098 [dragview setAlphaValue:0.7]; 02099 02100 dragPoint = CPMakePoint(0, firstIndex * _sliceHeight); 02101 02102 [self dragView:dragview 02103 at:dragPoint 02104 offset:CGSizeMake(0, _sliceHeight) 02105 event:event 02106 pasteboard:pasteboard 02107 source:self 02108 slideBack:YES]; 02109 02110 return YES; 02111 } 02112 02113 - (CPDragOperation)draggingEntered:(id < CPDraggingInfo >)sender 02114 { 02115 if ([sender draggingSource] == self) 02116 { 02117 [self _clearDropLine]; 02118 return CPDragOperationMove; 02119 } 02120 02121 return CPDragOperationNone; 02122 } 02123 02124 - (void)draggingExited:(id)sender 02125 { 02126 [self _clearDropLine]; 02127 [self setNeedsDisplay:YES]; 02128 } 02129 02130 - (void)_clearDropLine 02131 { 02132 [_dropLineView setAlphaValue:0]; 02133 02134 if (_subviewIndexOfDropLine != CPNotFound && _subviewIndexOfDropLine < _lastRow) 02135 { 02136 var previousBelowSlice = [_slices objectAtIndex:_subviewIndexOfDropLine]; 02137 [previousBelowSlice setFrameOrigin:CGPointMake(0, [previousBelowSlice rowIndex] * _sliceHeight)]; 02138 } 02139 02140 _subviewIndexOfDropLine = CPNotFound; 02141 } 02142 02143 - (CPDragOperation)draggingUpdated:(id <CPDraggingInfo>)sender 02144 { 02145 var point = [self convertPoint:[sender draggingLocation] fromView:nil], 02146 y = point.y + _sliceHeight /2, 02147 indexOfDropLine = FLOOR(y / _sliceHeight), 02148 numberOfRows = [self numberOfRows]; 02149 02150 if (indexOfDropLine < 0 || indexOfDropLine > numberOfRows || (indexOfDropLine >= [_draggingRows firstIndex] && indexOfDropLine <= [_draggingRows lastIndex] + 1)) 02151 { 02152 if (_subviewIndexOfDropLine != CPNotFound && indexOfDropLine != _subviewIndexOfDropLine) 02153 [self _clearDropLine]; 02154 return CPDragOperationNone; 02155 } 02156 02157 if (_subviewIndexOfDropLine != indexOfDropLine) 02158 { 02159 if (_subviewIndexOfDropLine != CPNotFound && _subviewIndexOfDropLine < numberOfRows) 02160 { 02161 var previousBelowSlice = [_slices objectAtIndex:_subviewIndexOfDropLine]; 02162 [previousBelowSlice setFrameOrigin:CPMakePoint(0, [previousBelowSlice rowIndex] * _sliceHeight)]; 02163 } 02164 02165 if (indexOfDropLine <= _lastRow && indexOfDropLine < numberOfRows) 02166 { 02167 var belowSlice = [_slices objectAtIndex:indexOfDropLine]; 02168 [belowSlice setFrameOrigin:CGPointMake(0, [belowSlice rowIndex] * _sliceHeight + 2)]; 02169 } 02170 02171 [_dropLineView setAlphaValue:1]; 02172 [_dropLineView setFrameOrigin:CGPointMake(CGRectGetMinX([_dropLineView frame]), indexOfDropLine * _sliceHeight)]; 02173 02174 _subviewIndexOfDropLine = indexOfDropLine; 02175 } 02176 02177 return CPDragOperationMove; 02178 } 02179 02180 - (BOOL)prepareForDragOperation:(id < CPDraggingInfo >)sender 02181 { 02182 return (_subviewIndexOfDropLine != CPNotFound); 02183 } 02184 02185 - (BOOL)performDragOperation:(id < CPDraggingInfo >)info 02186 { 02187 var aboveInsertIndexCount = 0, 02188 object, 02189 removeIndex; 02190 02191 var rowObjects = [_rowCache valueForKey:@"rowObject"], 02192 index = [_draggingRows lastIndex]; 02193 02194 var parentRowIndex = [self parentRowForRow:index]; // first index of draggingrows 02195 var parentRowObject = (parentRowIndex == -1) ? _boundArrayOwner : [[self _rowCacheForIndex:parentRowIndex] rowObject]; 02196 var insertIndex = _subviewIndexOfDropLine; 02197 02198 while (index != CPNotFound) 02199 { 02200 if (index >= insertIndex) 02201 { 02202 removeIndex = index + aboveInsertIndexCount; 02203 aboveInsertIndexCount += 1; 02204 } 02205 else 02206 { 02207 removeIndex = index; 02208 insertIndex -= 1; 02209 } 02210 02211 object = [rowObjects objectAtIndex:removeIndex]; 02212 [self removeRowAtIndex:removeIndex]; 02213 [[self _subrowObjectsOfObject:parentRowObject] insertObject:object atIndex:insertIndex - parentRowIndex - 1]; 02214 02215 index = [_draggingRows indexLessThanIndex:index]; 02216 } 02217 02218 [self _clearDropLine]; 02219 _draggingRows = nil; 02220 return YES; 02221 } 02222 02223 - (CPIndexSet)_draggingTypes 02224 { 02225 return [CPIndexSet indexSetWithIndex:CPDragOperationMove]; 02226 } 02227 02228 - (void)draggedView:(CPView)dragView endedAt:(CPPoint)aPoint operation:(CPDragOperation)operation 02229 { 02230 _draggingRows = nil; 02231 } 02232 02233 - (BOOL)wantsPeriodicDraggingUpdates 02234 { 02235 return NO; 02236 } 02237 02238 - (void)pasteboard:(CPPasteboard)pasteboard provideDataForType:(int)type 02239 { 02240 } 02241 02242 - (void)_setWindow:(id)window 02243 { 02244 [super _setWindow:window]; 02245 } 02246 02247 - (void)_windowUpdate:(id)sender 02248 { 02249 [super _windowUpdate:sender]; 02250 } 02251 02252 - (void)_postRuleOptionChangedNotification 02253 { 02254 [self reloadPredicate]; 02255 [self _sendRuleAction]; 02256 [[CPNotificationCenter defaultCenter] postNotificationName:CPRuleEditorRulesDidChangeNotification object:self]; 02257 } 02258 02259 - (void)_postRowCountChangedNotificationOfType:(CPString)notificationName indexes:indexes 02260 { 02261 [self reloadPredicate]; 02262 [self _sendRuleAction]; 02263 [[CPNotificationCenter defaultCenter] postNotificationName:notificationName object:self userInfo:[CPDictionary dictionaryWithObject:indexes forKey:"indexes"]]; 02264 } 02265 02266 - (CPIndexSet)_globalIndexesForSubrowIndexes:(CPIndexSet)indexes ofParentObject:(id)parentRowObject 02267 { 02268 var _subrows = [self _subrowObjectsOfObject:parentRowObject], 02269 parentRowIndex = [self _rowIndexForRowObject:parentRowObject], 02270 02271 globalIndexes = [CPMutableIndexSet indexSet], 02272 current_index = [indexes firstIndex], 02273 numberOfChildrenOfPreviousBrother = 0; 02274 02275 while (current_index != CPNotFound) 02276 { 02277 var globalChildIndex = current_index + parentRowIndex + 1 + numberOfChildrenOfPreviousBrother; 02278 [globalIndexes addIndex:globalChildIndex]; 02279 02280 if ([self rowTypeForRow:globalChildIndex] == CPRuleEditorRowTypeCompound) 02281 { 02282 var rowObject = [[self _rowCacheForIndex:current_index] rowObject], 02283 subrows = [self _subrowObjectsOfObject:rowObject]; 02284 02285 var subIndexes = [self _globalIndexesForSubrowIndexes:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0,[subrows count])] ofParentObject:rowObject]; 02286 numberOfChildrenOfPreviousBrother = [subIndexes count]; 02287 } 02288 02289 current_index = [indexes indexGreaterThanIndex:current_index]; 02290 } 02291 02292 return globalIndexes; 02293 } 02294 02295 - (void)_sendRuleAction 02296 { 02297 var action = [self action], 02298 target = [self target]; 02299 02300 if (action && target) 02301 [self sendAction:[self action] to:[self target]]; 02302 } 02303 02304 - (BOOL)_sendsActionOnIncompleteTextChange 02305 { 02306 return YES; 02307 } 02308 02309 - (void)_getAllAvailableItems:(id)items values:(id)values asChildrenOfItem:(id)parentItem inRow:(int)aRow 02310 { 02311 var type, 02312 indexofCriterion, 02313 numOfChildren; 02314 02315 var availItems = [CPMutableArray array], 02316 availValues = [CPMutableArray array]; 02317 02318 var criterion = nil, 02319 value = nil; 02320 02321 _lastRow = aRow; 02322 type = [self rowTypeForRow:aRow]; 02323 numOfChildren = [self _queryNumberOfChildrenOfItem:parentItem withRowType:type]; 02324 02325 var criteria = [self criteriaForRow:aRow]; 02326 indexofCriterion = [criteria indexOfObject:criterion]; 02327 02328 if (parentItem != nil 02329 && indexofCriterion != CPNotFound 02330 && indexofCriterion < [criteria count] - 1) 02331 { 02332 var next = indexofCriterion + 1; 02333 02334 criterion = [criteria objectAtIndex:next]; 02335 var values = [self displayValuesForRow:aRow]; 02336 value = [values objectAtIndex:next]; 02337 } 02338 02339 for (var i = 0; i < numOfChildren; ++i) 02340 { 02341 var aChild = [self _queryChild:i ofItem:parentItem withRowType:type]; 02342 var availChild = aChild, 02343 availValue = value; 02344 02345 if ( criterion != aChild ) 02346 availValue = [self _queryValueForItem:aChild inRow:aRow]; 02347 02348 if ( !availValue ) 02349 availValue = [self _queryValueForItem:availChild inRow:aRow]; 02350 02351 [availItems addObject:availChild]; 02352 [availValues addObject:availValue]; 02353 } 02354 02355 [items addObjectsFromArray:availItems]; 02356 [values addObjectsFromArray:availValues]; 02357 } 02358 02359 @end 02360 02361 var CPRuleEditorAlignmentGridWidthKey = @"CPRuleEditorAlignmentGridWidth", 02362 CPRuleEditorSliceHeightKey = @"CPRuleEditorSliceHeight", 02363 CPRuleEditorStringsFilenameKey = @"CPRuleEditorStringsFilename", 02364 CPRuleEditorEditableKey = @"CPRuleEditorEditable", 02365 CPRuleEditorAllowsEmptyCompoundRowsKey = @"CPRuleEditorAllowsEmptyCompoundRows", 02366 CPRuleEditorDisallowEmptyKey = @"CPRuleEditorDisallowEmpty", 02367 CPRuleEditorNestingModeKey = @"CPRuleEditorNestingMode", 02368 CPRuleEditorRowTypeKeyPathKey = @"CPRuleEditorRowTypeKeyPath", 02369 CPRuleEditorItemsKeyPathKey = @"CPRuleEditorItemsKeyPath", 02370 CPRuleEditorValuesKeyPathKey = @"CPRuleEditorValuesKeyPath", 02371 CPRuleEditorSubrowsArrayKeyPathKey = @"CPRuleEditorSubrowsArrayKeyPath", 02372 CPRuleEditorBoundArrayKeyPathKey = @"CPRuleEditorBoundArrayKeyPath", 02373 CPRuleEditorRowClassKey = @"CPRuleEditorRowClass", 02374 CPRuleEditorSlicesHolderKey = @"CPRuleEditorSlicesHolder", 02375 CPRuleEditorSlicesKey = @"CPRuleEditorSlices", 02376 CPRuleEditorDelegateKey = @"CPRuleEditorDelegate", 02377 CPRuleEditorBoundArrayOwnerKey = @"CPRuleEditorBoundArrayOwner"; 02378 02379 @implementation CPRuleEditor (CPCoding) 02380 02381 - (id)initWithCoder:(CPCoder)coder 02382 { 02383 self = [super initWithCoder:coder]; 02384 if (self != nil) 02385 { 02386 [self setFormattingStringsFilename:[coder decodeObjectForKey:CPRuleEditorStringsFilenameKey]]; 02387 _alignmentGridWidth = [coder decodeFloatForKey:CPRuleEditorAlignmentGridWidthKey]; 02388 _sliceHeight = [coder decodeDoubleForKey:CPRuleEditorSliceHeightKey]; 02389 _editable = [coder decodeBoolForKey:CPRuleEditorEditableKey]; 02390 _allowsEmptyCompoundRows = [coder decodeBoolForKey:CPRuleEditorAllowsEmptyCompoundRowsKey]; 02391 _disallowEmpty = [coder decodeBoolForKey:CPRuleEditorDisallowEmptyKey]; 02392 _nestingMode = [coder decodeIntForKey:CPRuleEditorNestingModeKey]; 02393 _typeKeyPath = [coder decodeObjectForKey:CPRuleEditorRowTypeKeyPathKey]; 02394 _itemsKeyPath = [coder decodeObjectForKey:CPRuleEditorItemsKeyPathKey]; 02395 _valuesKeyPath = [coder decodeObjectForKey:CPRuleEditorValuesKeyPathKey]; 02396 _subrowsArrayKeyPath = [coder decodeObjectForKey:CPRuleEditorSubrowsArrayKeyPathKey]; 02397 _boundArrayKeyPath = [coder decodeObjectForKey:CPRuleEditorBoundArrayKeyPathKey]; 02398 02399 _slicesHolder = [[self subviews] objectAtIndex:0]; 02400 _boundArrayOwner = [coder decodeObjectForKey:CPRuleEditorBoundArrayOwnerKey]; 02401 _slices = [coder decodeObjectForKey:CPRuleEditorSlicesKey]; 02402 _ruleDelegate = [coder decodeObjectForKey:CPRuleEditorDelegateKey]; 02403 02404 [self _initRuleEditorShared]; 02405 } 02406 02407 return self; 02408 } 02409 02410 - (void)encodeWithCoder:(id)coder 02411 { 02412 [super encodeWithCoder:coder]; 02413 02414 [coder encodeBool:_editable forKey:CPRuleEditorEditableKey]; 02415 [coder encodeBool:_allowsEmptyCompoundRows forKey:CPRuleEditorAllowsEmptyCompoundRowsKey]; 02416 [coder encodeBool:_disallowEmpty forKey:CPRuleEditorDisallowEmptyKey]; 02417 02418 [coder encodeFloat:_alignmentGridWidth forKey:CPRuleEditorAlignmentGridWidthKey]; 02419 [coder encodeDouble:_sliceHeight forKey:CPRuleEditorSliceHeightKey]; 02420 [coder encodeInt:_nestingMode forKey:CPRuleEditorNestingModeKey]; 02421 02422 [coder encodeObject:_stringsFilename forKey:CPRuleEditorStringsFilenameKey]; 02423 [coder encodeObject:_typeKeyPath forKey:CPRuleEditorRowTypeKeyPathKey]; 02424 [coder encodeObject:_itemsKeyPath forKey:CPRuleEditorItemsKeyPathKey]; 02425 [coder encodeObject:_valuesKeyPath forKey:CPRuleEditorValuesKeyPathKey]; 02426 [coder encodeObject:_boundArrayKeyPath forKey:CPRuleEditorBoundArrayKeyPathKey]; 02427 [coder encodeObject:_subrowsArrayKeyPath forKey:CPRuleEditorSubrowsArrayKeyPathKey]; 02428 02429 [coder encodeConditionalObject:_slicesHolder forKey:CPRuleEditorSlicesHolderKey]; 02430 [coder encodeObject:_slices forKey:CPRuleEditorSlicesKey]; 02431 [coder encodeObject:_boundArrayOwner forKey:CPRuleEditorBoundArrayOwnerKey]; 02432 } 02433 02434 @end 02435 02436 var CriteriaKey = @"criteria", 02437 SubrowsKey = @"subrows", 02438 DisplayValuesKey = @"displayValues", 02439 RowTypeKey = @"rowType"; 02440 02441 @implementation RowObject : CPObject 02442 { 02443 CPArray subrows; 02444 CPArray criteria; 02445 CPArray displayValues; 02446 CPInteger rowType; 02447 } 02448 02449 - (id)copy 02450 { 02451 var copy = [[RowObject alloc] init]; 02452 [copy setSubrows:[[CPArray alloc] initWithArray:subrows copyItems:YES]]; 02453 [copy setCriteria:[[CPArray alloc] initWithArray:criteria copyItems:YES]]; 02454 [copy setDisplayValues:[[CPArray alloc] initWithArray:displayValues copyItems:YES]]; 02455 [copy setRowType:rowType]; 02456 02457 return copy; 02458 } 02459 02460 - (CPString)description 02461 { 02462 return "<RowObject>\nsubrows = " + [subrows description] + "\ncriteria = " + [criteria description] + "\ndisplayValues = " + [displayValues description]; 02463 } 02464 02465 - (id)initWithCoder:(id)coder 02466 { 02467 self = [super init]; 02468 if (self != nil) 02469 { 02470 subrows = [coder decodeObjectForKey:SubrowsKey]; 02471 criteria = [coder decodeObjectForKey:CriteriaKey]; 02472 displayValues = [coder decodeObjectForKey:DisplayValuesKey]; 02473 rowType = [coder decodeIntForKey:RowTypeKey]; 02474 } 02475 02476 return self; 02477 } 02478 02479 - (void)encodeWithCoder:(id)coder 02480 { 02481 [coder encodeObject:subrows forKey:SubrowsKey]; 02482 [coder encodeObject:criteria forKey:CriteriaKey]; 02483 [coder encodeObject:displayValues forKey:DisplayValuesKey]; 02484 [coder encodeInt:rowType forKey:RowTypeKey]; 02485 } 02486 02487 @end 02488 02489 @implementation _CPRuleEditorCache : CPObject 02490 { 02491 CPDictionary rowObject; 02492 CPInteger rowIndex; 02493 CPInteger indentation; 02494 } 02495 02496 - (CPString)description 02497 { 02498 return [CPString stringWithFormat:@"<%d object:%d rowIndex:%d indentation:%d>",[self hash], [rowObject hash], rowIndex, indentation]; 02499 } 02500 02501 @end 02502 02503 var CPBoundArrayKey = @"CPBoundArray"; 02504 02505 @implementation _CPRuleEditorViewUnboundRowHolder : CPObject 02506 { 02507 CPArray boundArray; 02508 } 02509 02510 - (id)init 02511 { 02512 if (self = [super init]) 02513 boundArray = [[CPArray alloc] init]; 02514 02515 return self; 02516 } 02517 02518 - (id)initWithCoder:(id)coder 02519 { 02520 if (self = [super init]) 02521 boundArray = [coder decodeObjectForKey:CPBoundArrayKey]; 02522 02523 return self; 02524 } 02525 02526 - (void)encodeWithCoder:(id)coder 02527 { 02528 [coder encodeObject:boundArray forKey:CPBoundArrayKey]; 02529 } 02530 02531 @end 02532 @implementation _CPRuleEditorViewSliceHolder : CPView 02533 { 02534 id __doxygen__; 02535 } 02536 02537 - (void)addSubview:(CPView)subview 02538 { 02539 [self setNeedsDisplay:YES]; 02540 [super addSubview:subview]; 02541 } 02542 02543 @end 02544 02545 var dropSeparatorColor = [CPColor colorWithHexString:@"4886ca"]; 02546 @implementation _CPRuleEditorViewSliceDropSeparator : CPView 02547 { 02548 id __doxygen__; 02549 } 02550 02551 - (void)drawRect:(CPRect)rect 02552 { 02553 var context = [[CPGraphicsContext currentContext] graphicsPort]; 02554 CGContextSetFillColor(context, dropSeparatorColor); 02555 CGContextFillRect(context, [self bounds]); 02556 } 02557 02558 @end 02559 02560 @implementation CPObject (CPRuleEditorSliceRow) 02561 02562 - (int)valueType 02563 { 02564 var result = 0; 02565 02566 var isString = [self isKindOfClass:[CPString class]]; 02567 if ( !isString ) 02568 { 02569 var isView = [self isKindOfClass:[CPView class]]; 02570 result = 1; 02571 if ( !isView ) 02572 { 02573 var ismenuItem = [self isKindOfClass:[CPMenuItem class]]; 02574 result = 2; 02575 if ( !ismenuItem ) 02576 { 02577 [CPException raise:CPGenericException reason:@"Unknown Type For " + self]; 02578 result = -1; 02579 } 02580 } 02581 } 02582 02583 return result; 02584 } 02585 02586 @end 02589 @implementation CPRuleEditor (CPSynthesizedAccessors) 02590 02594 - (CPString)boundArrayKeyPath 02595 { 02596 return _boundArrayKeyPath; 02597 } 02598 02602 - (void)setBoundArrayKeyPath:(CPString)aValue 02603 { 02604 _boundArrayKeyPath = aValue; 02605 } 02606 02610 - (id)standardLocalizer 02611 { 02612 return _standardLocalizer; 02613 } 02614 02618 - (void)setStandardLocalizer:(id)aValue 02619 { 02620 _standardLocalizer = aValue; 02621 } 02622 02623 @end 02624 02625 @implementation RowObject (CPSynthesizedAccessors) 02626 02627 @end