API 0.9.5
AppKit/CPRuleEditor/CPRuleEditor.j
Go to the documentation of this file.
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
 All Classes Files Functions Variables Defines