API  0.9.6
 All Classes Files Functions Variables Macros Groups Pages
CPRuleEditor.j
Go to the documentation of this file.
1 /*
2  * CPRuleEditor.j
3  * AppKit
4  *
5  * Created by cacaodev.
6  * Copyright 2011, cacaodev.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22 
23 
24 
25 CPRuleEditorPredicateLeftExpression = "CPRuleEditorPredicateLeftExpression";
26 CPRuleEditorPredicateRightExpression = "CPRuleEditorPredicateRightExpression";
27 CPRuleEditorPredicateComparisonModifier = "CPRuleEditorPredicateComparisonModifier";
28 CPRuleEditorPredicateOptions = "CPRuleEditorPredicateOptions";
29 CPRuleEditorPredicateOperatorType = "CPRuleEditorPredicateOperatorType";
30 CPRuleEditorPredicateCustomSelector = "CPRuleEditorPredicateCustomSelector";
31 CPRuleEditorPredicateCompoundType = "CPRuleEditorPredicateCompoundType";
32 
33 CPRuleEditorRowsDidChangeNotification = "CPRuleEditorRowsDidChangeNotification";
34 CPRuleEditorRulesDidChangeNotification = "CPRuleEditorRulesDidChangeNotification";
35 
36 CPRuleEditorNestingModeSingle = 0; // Only a single row is allowed. Plus/minus buttons will not be shown
37 CPRuleEditorNestingModeList = 1; // Allows a single list, with no nesting and no compound rows
38 CPRuleEditorNestingModeCompound = 2; // Unlimited nesting and compound rows; this is the default
39 CPRuleEditorNestingModeSimple = 3; // One compound row at the top with subrows beneath it, and no further nesting allowed
40 
43 
44 var CPRuleEditorItemPBoardType = @"CPRuleEditorItemPBoardType",
45  itemsContext = "items",
46  valuesContext = "values",
47  subrowsContext = "subrows_array",
48  boundArrayContext = "bound_array";
49 
70 @implementation CPRuleEditor : CPControl
71 {
72  BOOL _suppressKeyDownHandling;
73  BOOL _allowsEmptyCompoundRows;
74  BOOL _disallowEmpty;
75  BOOL _delegateWantsValidation;
76  BOOL _editable;
77  BOOL _sendAction;
78 
79  Class _rowClass;
80 
81  CPIndexSet _draggingRows;
82  CPInteger _subviewIndexOfDropLine;
83  CPView _dropLineView;
84 
85  CPMutableArray _rowCache;
86  CPMutableArray _slices;
87 
88  CPPredicate _predicate;
89 
90  CPString _itemsKeyPath;
91  CPString _subrowsArrayKeyPath;
92  CPString _typeKeyPath;
93  CPString _valuesKeyPath;
94  CPString _boundArrayKeyPath;
95 
96  CPView _slicesHolder;
97  CPViewAnimation _currentAnimation;
98 
99  CPInteger _lastRow;
100  CPInteger _nestingMode;
101 
102  float _alignmentGridWidth;
103  float _sliceHeight;
104 
105  id _ruleDataSource;
106  id _ruleDelegate;
107  id _boundArrayOwner;
108 
109  CPString _stringsFilename;
110 
111  BOOL _isKeyDown;
112  BOOL _nestingModeDidChange;
113 
114  _CPRuleEditorLocalizer _standardLocalizer;
115  CPDictionary _itemsAndValuesToAddForRowType;
116 }
117 
120 + (CPString)defaultThemeClass
121 {
122  return @"rule-editor";
123 }
124 
125 + (id)themeAttributes
126 {
128  forKeys:[@"alternating-row-colors", @"selected-color", @"slice-top-border-color", @"slice-bottom-border-color", @"slice-last-bottom-border-color", @"font", @"add-image", @"remove-image"]];
129 }
130 
131 - (id)initWithFrame:(CGRect)frame
132 {
133  self = [super initWithFrame:frame];
134  if (self !== nil)
135  {
136  _slices = [[CPMutableArray alloc] init];
137 
138  _sliceHeight = 26.;
139  _nestingMode = CPRuleEditorNestingModeSimple; // 10.5 default is CPRuleEditorNestingModeCompound
140  _editable = YES;
141  _allowsEmptyCompoundRows = NO;
142  _disallowEmpty = NO;
143 
144  [self setFormattingStringsFilename:nil];
145  [self setCriteriaKeyPath:@"criteria"];
146  [self setSubrowsKeyPath:@"subrows"];
147  [self setRowTypeKeyPath:@"rowType"];
148  [self setDisplayValuesKeyPath:@"displayValues"];
149  [self setBoundArrayKeyPath:@"boundArray"];
150 
151  _slicesHolder = [[_CPRuleEditorViewSliceHolder alloc] initWithFrame:[self bounds]];
152  [self addSubview:_slicesHolder];
153 
154  _boundArrayOwner = [[_CPRuleEditorViewUnboundRowHolder alloc] init];
155 
156  [self _initRuleEditorShared];
157  }
158 
159  return self;
160 }
161 
162 - (void)_initRuleEditorShared
163 {
164  _rowCache = [[CPMutableArray alloc] init];
165  _rowClass = [_CPRuleEditorRowObject class];
166  _isKeyDown = NO;
167  _subviewIndexOfDropLine = CPNotFound;
168  _lastRow = 0;
169  _delegateWantsValidation = YES;
170  _suppressKeyDownHandling = NO;
171  _nestingModeDidChange = NO;
172  _sendAction = YES;
173  _itemsAndValuesToAddForRowType = {};
174  var animation = [[CPViewAnimation alloc] initWithDuration:0.5 animationCurve:CPAnimationEaseInOut];
175  [self setAnimation:animation];
176 
177  [_slicesHolder setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
178 
179  _dropLineView = [self _createSliceDropSeparator];
180  [_slicesHolder addSubview:_dropLineView];
181 
182  [self registerForDraggedTypes:[CPArray arrayWithObjects:CPRuleEditorItemPBoardType,nil]];
183  [_boundArrayOwner addObserver:self forKeyPath:_boundArrayKeyPath options:CPKeyValueObservingOptionOld | CPKeyValueObservingOptionNew context:boundArrayContext];
184 }
185 
197 - (id)delegate
198 {
199  return _ruleDelegate;
200 }
201 
208 - (void)setDelegate:(id)aDelegate
209 {
210  if (_ruleDelegate === aDelegate)
211  return;
212 
214  if (_ruleDelegate)
215  [nc removeObserver:_ruleDelegate name:nil object:self];
216 
217  _ruleDelegate = aDelegate;
218 
219  if ([_ruleDelegate respondsToSelector:@selector(ruleEditorRowsDidChange:)])
220  [nc addObserver:_ruleDelegate selector:@selector(ruleEditorRowsDidChange:) name:CPRuleEditorRowsDidChangeNotification object:nil];
221 }
228 - (BOOL)isEditable
229 {
230  return _editable;
231 }
232 
238 - (void)setEditable:(BOOL)editable
239 {
240  if (editable === _editable)
241  return;
242 
243  _editable = editable;
244 
245  if (!_editable)
246  [self _deselectAll];
247 
248  [_slices makeObjectsPerformSelector:@selector(setEditable:) withObject:_editable];
249 }
250 
256 - (CPRuleEditorNestingMode)nestingMode
257 {
258  return _nestingMode;
259 }
260 
268 - (void)setNestingMode:(CPRuleEditorNestingMode)mode
269 {
270  if (mode !== _nestingMode)
271  {
272  _nestingMode = mode;
273  if ([self numberOfRows] > 0)
274  _nestingModeDidChange = YES;
275  }
276 }
277 
283 - (BOOL)canRemoveAllRows
284 {
285  return !_disallowEmpty;
286 }
287 
293 - (void)setCanRemoveAllRows:(BOOL)canRemove
294 {
295  _disallowEmpty = !canRemove;
296  [self _updateButtonVisibilities];
297 }
298 
304 - (BOOL)allowsEmptyCompoundRows
305 {
306  return _allowsEmptyCompoundRows;
307 }
308 
314 - (void)setAllowsEmptyCompoundRows:(BOOL)allows
315 {
316  _allowsEmptyCompoundRows = allows;
317  [self _updateButtonVisibilities];
318 }
319 
325 - (CPInteger)rowHeight
326 {
327  return _sliceHeight;
328 }
329 
335 - (void)setRowHeight:(float)height
336 {
337  if (height === _sliceHeight)
338  return;
339 
340  _sliceHeight = MAX([self _minimumFrameHeight], height);
341  [self _reconfigureSubviewsAnimate:NO];
342 }
343 
354 - (CPDictionary)formattingDictionary
355 {
356  return [_standardLocalizer dictionary];
357 }
358 
366 - (void)setFormattingDictionary:(CPDictionary)dictionary
367 {
368  [_standardLocalizer setDictionary:dictionary];
369  _stringsFilename = nil;
370 }
371 
377 - (CPString)formattingStringsFilename
378 {
379  return _stringsFilename;
380 }
381 
389 - (void)setFormattingStringsFilename:(CPString)stringsFilename
390 {
391  if (_standardLocalizer === nil)
392  _standardLocalizer = [_CPRuleEditorLocalizer new];
393 
394  if (_stringsFilename !== stringsFilename)
395  {
396  // Convert an empty string to nil
397  _stringsFilename = stringsFilename || nil;
398 
399  if (stringsFilename !== nil)
400  {
401  if (![stringsFilename hasSuffix:@".strings"])
402  stringsFilename = stringsFilename + @".strings";
403 
404  var path = [[CPBundle mainBundle] pathForResource:stringsFilename];
405 
406  if (path !== nil)
407  [_standardLocalizer loadContentOfURL:[CPURL URLWithString:path]];
408  }
409  }
410 }
411 
420 - (void)reloadCriteria
421 {
422  var current_rows = [_boundArrayOwner valueForKey:_boundArrayKeyPath];
423  [self _stopObservingRowObjectsRecursively:current_rows];
424  [_boundArrayOwner setValue:[CPArray arrayWithArray:current_rows] forKey:_boundArrayKeyPath];
425 }
426 
435 - (void)setCriteria:(CPArray)criteria andDisplayValues:(CPArray)values forRowAtIndex:(int)rowIndex
436 {
437  if (criteria === nil || values === nil)
438  [CPException raise:CPInvalidArgumentException reason:_cmd + @". criteria and values parameters must not be nil."];
439 
440  if (rowIndex < 0 || rowIndex >= [self numberOfRows])
441  [CPException raise:CPRangeException reason:_cmd + @". rowIndex is out of bounds."];
442 
443  var rowObject = [[self _rowCacheForIndex:rowIndex] rowObject];
444 
445  [rowObject setValue:criteria forKey:_itemsKeyPath];
446  [rowObject setValue:values forKey:_valuesKeyPath];
447 
448  [self reloadCriteria];
449 }
450 
456 - (id)criteriaForRow:(int)row
457 {
458  var rowcache = [self _rowCacheForIndex:row];
459  if (rowcache)
460  return [[rowcache rowObject] valueForKey:_itemsKeyPath];
461 
462  return nil;
463 }
464 
475 - (CPMutableArray)displayValuesForRow:(int)row
476 {
477  var rowcache = [self _rowCacheForIndex:row];
478  if (rowcache)
479  return [[rowcache rowObject] valueForKey:_valuesKeyPath];
480 
481  return nil;
482 }
483 
488 - (int)numberOfRows
489 {
490  return [_slices count];
491 }
492 
498 - (int)parentRowForRow:(int)rowIndex
499 {
500  if (rowIndex < 0 || rowIndex >= [self numberOfRows])
501  [CPException raise:CPRangeException reason:_cmd + @" row " + rowIndex + " is out of range"];
502 
503  var targetObject = [[self _rowCacheForIndex:rowIndex] rowObject];
504 
505  for (var current_index = 0; current_index < rowIndex; current_index++)
506  {
507  if ([self rowTypeForRow:current_index] === CPRuleEditorRowTypeCompound)
508  {
509  var candidate = [[self _rowCacheForIndex:current_index] rowObject],
510  subObjects = [[self _subrowObjectsOfObject:candidate] _representedObject];
511 
512  if ([subObjects indexOfObjectIdenticalTo:targetObject] !== CPNotFound)
513  return current_index;
514  }
515  }
516 
517  return -1;
518 }
519 
520 /*
521 TODO: implement
522  Returns the index of the row containing a given value.
523 
524  displayValue The display value (string, view, or menu item) of an item in the receiver. This value must not be nil.
525 
526  The index of the row containing displayValue, or CPNotFound.
527 
528  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.
529 
530 - (CPInteger)rowForDisplayValue:(id)displayValue
531 */
532 
539 - (CPRuleEditorRowType)rowTypeForRow:(int)rowIndex
540 {
541  if (rowIndex < 0 || rowIndex > [self numberOfRows])
542  [CPException raise:CPRangeException reason:_cmd + @"row " + rowIndex + " is out of range"];
543 
544  var rowcache = [self _rowCacheForIndex:rowIndex];
545  if (rowcache)
546  {
547  var rowobject = [rowcache rowObject];
548  return [rowobject valueForKey:_typeKeyPath];
549  }
550 
551  return CPNotFound;
552 }
553 
560 - (CPIndexSet)subrowIndexesForRow:(int)rowIndex
561 {
562  var object;
563 
564  if (rowIndex === -1)
565  object = _boundArrayOwner;
566  else
567  object = [[self _rowCacheForIndex:rowIndex] rowObject];
568 
569  var subobjects = [self _subrowObjectsOfObject:object],
570  objectsCount = [subobjects count],
571  indexes = [CPMutableIndexSet indexSet],
572  count = [self numberOfRows];
573 
574  for (var i = rowIndex + 1; i < count; i++)
575  {
576  var candidate = [[self _rowCacheForIndex:i] rowObject],
577  indexInSubrows = [[subobjects _representedObject] indexOfObjectIdenticalTo:candidate];
578 
579  if (indexInSubrows !== CPNotFound)
580  {
581  [indexes addIndex:i];
582  objectsCount--;
583 
584  if ([self rowTypeForRow:i] === CPRuleEditorRowTypeCompound)
585  i += [[self subrowIndexesForRow:i] count];
586  }
587 
588  if (objectsCount === 0)
589  break;
590  }
591 
592  return indexes;
593 }
594 
599 - (CPIndexSet)selectedRowIndexes
600 {
601  return [self _selectedSliceIndices];
602 }
603 
609 - (void)selectRowIndexes:(CPIndexSet)indexes byExtendingSelection:(BOOL)extend
610 {
611  var count = [_slices count],
612  lastSelected = [indexes lastIndex];
613 
614  if (lastSelected >= [self numberOfRows])
615  [CPException raise:CPRangeException reason:@"row indexes " + indexes + " are out of range"];
616 
617  if (!extend)
618  [self _deselectAll];
619 
620  while (count--)
621  {
622  var slice = _slices[count],
623  rowIndex = [slice rowIndex],
624  contains = [indexes containsIndex:rowIndex],
625  shouldSelect = (contains && !(extend && [slice _isSelected]));
626 
627  if (contains)
628  [slice _setSelected:shouldSelect];
629  [slice _setLastSelected:(rowIndex === lastSelected)];
630  [slice setNeedsDisplay:YES];
631  }
632 }
633 
643 - (void)addRow:(id)sender
644 {
645  var parentRowIndex = -1,
646  rowtype,
647  numberOfRows = [self numberOfRows],
648  hasRows = (numberOfRows > 0),
649  nestingMode = [self _applicableNestingMode];
650 
651  switch (nestingMode)
652  {
655  if (hasRows)
656  parentRowIndex = 0;
657  break;
659  if (hasRows)
660  return;
662  rowtype = CPRuleEditorRowTypeSimple;
663  break;
665  rowtype = CPRuleEditorRowTypeCompound;
666  if (hasRows)
667  parentRowIndex = 0;
668  break;
669  default:
670  [CPException raise:CPInvalidArgumentException reason:@"Not supported CPRuleEditorNestingMode " + nestingMode];
671  // Compound mode: parentRowIndex=(lastRowType === CPRuleEditorRowTypeCompound)?lastRow :[self parentRowForRow:lastRow]; break;
672  }
673 
674  [self insertRowAtIndex:numberOfRows withType:rowtype asSubrowOfRow:parentRowIndex animate:YES];
675 }
676 
686 - (void)insertRowAtIndex:(int)rowIndex withType:(unsigned int)rowType asSubrowOfRow:(int)parentRow animate:(BOOL)shouldAnimate
687 {
688 /*
689  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.
690 */
691  var newObject = [self _insertNewRowAtIndex:rowIndex ofType:rowType withParentRow:parentRow];
692 
693  if (rowType === CPRuleEditorRowTypeCompound && !_allowsEmptyCompoundRows)
694  {
695  var subrow = [self _insertNewRowAtIndex:(rowIndex + 1) ofType:CPRuleEditorRowTypeSimple withParentRow:rowIndex];
696  }
697 }
698 
705 - (void)removeRowAtIndex:(int)rowIndex
706 {
707  // TO DO : Any subrows of the deleted row are adopted by the parent of the deleted row, or are made root rows.
708 
709  if (rowIndex < 0 || rowIndex >= [self numberOfRows])
710  [CPException raise:CPRangeException reason:@"row " + rowIndex + " is out of range"];
711 
713 }
714 
722 - (void)removeRowsAtIndexes:(CPIndexSet)rowIndexes includeSubrows:(BOOL)includeSubrows
723 {
724  if ([rowIndexes count] === 0)
725  return;
726 
727  if ([rowIndexes lastIndex] >= [self numberOfRows])
728  [CPException raise:CPRangeException reason:@"rows indexes " + rowIndexes + " are out of range"];
729 
730  var current_index = [rowIndexes firstIndex],
731  parentRowIndex = [self parentRowForRow:current_index],
732  childsIndexes = [CPMutableIndexSet indexSet],
733  subrows;
734 
735  if (parentRowIndex === -1)
736  subrows = [self _rootRowsArray];
737  else
738  {
739  var parentRowObject = [[self _rowCacheForIndex:parentRowIndex] rowObject];
740  subrows = [self _subrowObjectsOfObject:parentRowObject];
741  }
742 
743  while (current_index !== CPNotFound)
744  {
745  var rowObject = [[self _rowCacheForIndex:current_index] rowObject],
746  relativeChildIndex = [[subrows _representedObject] indexOfObjectIdenticalTo:rowObject];
747 
748  if (relativeChildIndex !== CPNotFound)
749  [childsIndexes addIndex:relativeChildIndex];
750 
751  if (includeSubrows && [self rowTypeForRow:current_index] === CPRuleEditorRowTypeCompound)
752  {
753  var more_childs = [self subrowIndexesForRow:current_index];
754  [self removeRowsAtIndexes:more_childs includeSubrows:includeSubrows];
755  }
756 
757  current_index = [rowIndexes indexGreaterThanIndex:current_index];
758  }
759 
760  [subrows removeObjectsAtIndexes:childsIndexes];
761 }
762 
772 - (CPPredicate)predicate
773 {
774  return _predicate;
775 }
776 
781 - (void)reloadPredicate
782 {
783  [self _updatePredicate];
784 }
785 
792 - (CPPredicate)predicateForRow:(CPInteger)aRow
793 {
794  var predicateParts = [CPDictionary dictionary],
795  items = [self criteriaForRow:aRow],
796  count = [items count],
797  predicate,
798  i;
799 
800  for (i = 0; i < count; i++)
801  {
802  var item = [items objectAtIndex:i],
803  //var displayValue = [self _queryValueForItem:item inRow:aRow]; Ask the delegate or get cached value ?.
804  displayValue = [[self displayValuesForRow:aRow] objectAtIndex:i],
805  predpart = [_ruleDelegate ruleEditor:self predicatePartsForCriterion:item withDisplayValue:displayValue inRow:aRow];
806 
807  if (predpart)
808  [predicateParts addEntriesFromDictionary:predpart];
809  }
810 
811  if ([self rowTypeForRow:aRow] === CPRuleEditorRowTypeCompound)
812  {
813  var compoundPredicate,
814  subpredicates = [CPMutableArray array],
815  subrowsIndexes = [self subrowIndexesForRow:aRow];
816 
817  if ([subrowsIndexes count] === 0)
818  return nil;
819 
820  var current_index = [subrowsIndexes firstIndex];
821  while (current_index !== CPNotFound)
822  {
823  var subpredicate = [self predicateForRow:current_index];
824  if (subpredicate !== nil)
825  [subpredicates addObject:subpredicate];
826 
827  current_index = [subrowsIndexes indexGreaterThanIndex:current_index];
828  }
829 
830  var compoundType = [predicateParts objectForKey:CPRuleEditorPredicateCompoundType];
831 
832  if ([subpredicates count] === 0)
833  return nil;
834  else
835  {
836  try
837  {
838  compoundPredicate = [[CPCompoundPredicate alloc ] initWithType:compoundType subpredicates:subpredicates];
839  }
840  catch(error)
841  {
842  CPLogConsole(@"Compound predicate error: [%@]\npredicateType:%i", [error description], compoundType);
843  compoundPredicate = nil;
844  }
845  finally
846  {
847  return compoundPredicate;
848  }
849 
850  }
851  }
852 
853  var lhs = [predicateParts objectForKey:CPRuleEditorPredicateLeftExpression],
854  rhs = [predicateParts objectForKey:CPRuleEditorPredicateRightExpression],
855  operator = [predicateParts objectForKey:CPRuleEditorPredicateOperatorType],
856  options = [predicateParts objectForKey:CPRuleEditorPredicateOptions],
857  modifier = [predicateParts objectForKey:CPRuleEditorPredicateComparisonModifier],
858  selector = CPSelectorFromString([predicateParts objectForKey:CPRuleEditorPredicateCustomSelector]);
859 
860  if (lhs === nil)
861  {
862  CPLogConsole(@"missing left expression in predicate parts dictionary");
863  return NULL;
864  }
865 
866  if (rhs === nil)
867  {
868  CPLogConsole(@"missing right expression in predicate parts dictionary");
869  return NULL;
870  }
871 
872  if (selector === nil && operator === nil)
873  {
874  CPLogConsole(@"missing operator and selector in predicate parts dictionary");
875  return NULL;
876  }
877 
878  if (modifier === nil)
879  CPLogConsole(@"missing modifier in predicate parts dictionary. Setting default: CPDirectPredicateModifier");
880 
881  if (options === nil)
882  CPLogConsole(@"missing options in predicate parts dictionary. Setting default: CPCaseInsensitivePredicateOption");
883 
884  try
885  {
886  if (selector !== nil)
888  rightExpression:rhs
889  customSelector:selector
890  ];
891  else
893  rightExpression:rhs
894  modifier:(modifier || CPDirectPredicateModifier)
895  type:operator
896  options:(options || CPCaseInsensitivePredicateOption)
897  ];
898  }
899  catch(error)
900  {
901  CPLogConsole(@"Row predicate error: [" + [error description] + "] for row " + aRow);
902  predicate = nil;
903  }
904  finally
905  {
906  return predicate;
907  }
908 }
909 
919 - (Class)rowClass
920 {
921  return _rowClass;
922 }
923 
929 - (void)setRowClass:(Class)rowClass
930 {
931  if (rowClass === [CPMutableDictionary class])
932  rowClass = [_CPRuleEditorRowObject class];
933 
934  _rowClass = rowClass;
935 }
936 
944 - (CPString)rowTypeKeyPath
945 {
946  return _typeKeyPath;
947 }
948 
954 - (void)setRowTypeKeyPath:(CPString)keyPath
955 {
956  if (_typeKeyPath !== keyPath)
957  _typeKeyPath = keyPath;
958 }
959 
967 - (CPString)subrowsKeyPath
968 {
969  return _subrowsArrayKeyPath;
970 }
971 
977 - (void)setSubrowsKeyPath:(CPString)keyPath
978 {
979  if (_subrowsArrayKeyPath !== keyPath)
980  _subrowsArrayKeyPath = keyPath;
981 }
982 
990 - (CPString)criteriaKeyPath
991 {
992  return _itemsKeyPath;
993 }
994 
1000 - (void)setCriteriaKeyPath:(CPString)keyPath
1001 {
1002  if (_itemsKeyPath !== keyPath)
1003  _itemsKeyPath = keyPath;
1004 }
1005 
1013 - (CPString)displayValuesKeyPath
1014 {
1015  return _valuesKeyPath;
1016 }
1017 
1023 - (void)setDisplayValuesKeyPath:(CPString)keyPath
1024 {
1025  if (_valuesKeyPath !== keyPath)
1026  _valuesKeyPath = keyPath;
1027 }
1028 
1038 - (id)animation
1039 {
1040  return _currentAnimation;
1041 }
1042 
1049 - (void)setAnimation:(CPViewAnimation)animation
1050 {
1051  _currentAnimation = animation;
1052  [_currentAnimation setDelegate:self];
1053 }
1054 
1100 - (BOOL)acceptsFirstResponder
1101 {
1102  return YES;
1103 }
1104 
1105 - (void)keyDown:(CPEvent)event
1106 {
1107  if (!_suppressKeyDownHandling && [self _applicableNestingMode] === CPRuleEditorNestingModeCompound && !_isKeyDown && ([event modifierFlags] & CPAlternateKeyMask))
1108  {
1109  [_slices makeObjectsPerformSelector:@selector(_configurePlusButtonByRowType:) withObject:CPRuleEditorRowTypeCompound];
1110  }
1111 
1112  _isKeyDown = YES;
1113 }
1114 
1115 - (void)keyUp:(CPEvent)event
1116 {
1117  if (!_suppressKeyDownHandling)
1118  {
1119  [_slices makeObjectsPerformSelector:@selector(_configurePlusButtonByRowType:) withObject:CPRuleEditorRowTypeSimple];
1120  }
1121 
1122  _isKeyDown = NO;
1123 }
1124 
1125 - (_CPRuleEditorViewSliceDropSeparator)_createSliceDropSeparator
1126 {
1127  var view = [[_CPRuleEditorViewSliceDropSeparator alloc] initWithFrame:CGRectMake(0, -10, [self frame].size.width, 2)];
1128  [view setAutoresizingMask:CPViewWidthSizable];
1129  return view;
1130 }
1131 
1132 - (BOOL)_suppressKeyDownHandling
1133 {
1134  return _suppressKeyDownHandling;
1135 }
1136 
1137 - (BOOL)_wantsRowAnimations
1138 {
1139  return (_currentAnimation !== nil);
1140 }
1141 
1142 - (void)_updateButtonVisibilities
1143 {
1144  [_slices makeObjectsPerformSelector:@selector(_updateButtonVisibilities)];
1145 }
1146 
1147 - (float)_alignmentGridWidth
1148 {
1149  return _alignmentGridWidth;
1150 }
1151 
1152 - (float)_minimumFrameHeight
1153 {
1154  return 26.;
1155 }
1156 
1157 - (CPRuleEditorNestingMode)_applicableNestingMode
1158 {
1159  if (!_nestingModeDidChange)
1160  return _nestingMode;
1161 
1162  var a = (_nestingMode === CPRuleEditorNestingModeCompound || _nestingMode === CPRuleEditorNestingModeSimple),
1163  b = ([self rowTypeForRow:0] === CPRuleEditorRowTypeCompound);
1164 
1165  if (a === b)
1166  return _nestingMode;
1167 
1169 }
1170 
1171 - (BOOL)_shouldHideAddButtonForSlice:(id)slice
1172 {
1173  return (!_editable || [self _applicableNestingMode] === CPRuleEditorNestingModeSingle);
1174 }
1175 
1176 - (BOOL)_shouldHideSubtractButtonForSlice:(id)slice
1177 {
1178  if (!_editable)
1179  return YES;
1180 
1181  if (!_disallowEmpty)
1182  return NO;
1183 
1184  var shouldHide,
1185  rowIndex = [slice rowIndex],
1186  parentIndex = [self parentRowForRow:rowIndex],
1187  subrowsIndexes = [self subrowIndexesForRow:parentIndex],
1188  nestingMode = [self _applicableNestingMode];
1189 
1190  switch (nestingMode)
1191  {
1193  case CPRuleEditorNestingModeSimple: shouldHide = ([subrowsIndexes count] === 1 && !_allowsEmptyCompoundRows) || parentIndex === -1;
1194  break;
1195  case CPRuleEditorNestingModeList: shouldHide = ([self numberOfRows] === 1);
1196  break;
1197  case CPRuleEditorNestingModeSingle: shouldHide = YES;
1198  break;
1199  default: shouldHide = NO;
1200  }
1201 
1202  return shouldHide;
1203 }
1204 
1205 #pragma mark Rows management
1206 
1207 - (id)_rowCacheForIndex:(int)index
1208 {
1209  return [_rowCache objectAtIndex:index];
1210 }
1211 
1212 - (id)_searchCacheForRowObject:(id)rowObject
1213 {
1214  var count = [_rowCache count],
1215  i;
1216 
1217  for (i = 0; i < count; i++)
1218  {
1219  var cache = _rowCache[i];
1220  if ([cache rowObject] === rowObject)
1221  return cache;
1222  }
1223 
1224  return nil;
1225 }
1226 
1227 - (int)_rowIndexForRowObject:(id)rowobject
1228 {
1229  if (rowobject === _boundArrayOwner)
1230  return -1;
1231 
1232  return [[self _searchCacheForRowObject:rowobject] rowIndex]; // Pas bon car le rowIndex du row cache n'est pas synchro avec la position dans _rowCache.
1233 }
1234 
1235 - (CPMutableArray)_subrowObjectsOfObject:(id)object
1236 {
1237  if (object === _boundArrayOwner)
1238  return [self _rootRowsArray];
1239 
1240  return [object mutableArrayValueForKey:_subrowsArrayKeyPath];
1241 }
1242 
1243 - (CPIndexSet)_childlessParentsIfSlicesWereDeletedAtIndexes:(id)indexes
1244 {
1245  var childlessParents = [CPIndexSet indexSet],
1246  current_index = [indexes firstIndex];
1247 
1248  while (current_index !== CPNotFound)
1249  {
1250  var parentIndex = [self parentRowForRow:current_index],
1251  subrowsIndexes = [self subrowIndexesForRow:parentIndex];
1252 
1253  if ([subrowsIndexes count] === 1)
1254  {
1255  if (parentIndex !== -1)
1256  return [CPIndexSet indexSetWithIndex:0];
1257 
1258  var childlessGranPa = [self _childlessParentsIfSlicesWereDeletedAtIndexes:[CPIndexSet indexSetWithIndex:parentIndex]];
1259  [childlessParents addIndexes:childlessGranPa];
1260  }
1261 
1262  current_index = [indexes indexGreaterThanIndex:current_index];
1263  }
1264 
1265  return childlessParents;
1266  // (id)-[RuleEditor _includeSubslicesForSlicesAtIndexes:]
1267 }
1268 
1269 - (CPIndexSet)_includeSubslicesForSlicesAtIndexes:(CPIndexSet)indexes
1270 {
1271  var subindexes = [indexes copy],
1272  current_index = [indexes firstIndex];
1273 
1274  while (current_index !== CPNotFound)
1275  {
1276  var sub = [self subrowIndexesForRow:current_index];
1277  [subindexes addIndexes:[self _includeSubslicesForSlicesAtIndexes:sub]];
1278  current_index = [indexes indexGreaterThanIndex:current_index];
1279  }
1280 
1281  return subindexes;
1282 }
1283 
1284 - (void)_deleteSlice:(id)slice
1285 {
1286  var rowindexes = [CPIndexSet indexSetWithIndex:[slice rowIndex]];
1287 
1288  if (!_allowsEmptyCompoundRows)
1289  {
1290  var childlessIndexes = [self _childlessParentsIfSlicesWereDeletedAtIndexes:rowindexes];
1291  if ([childlessIndexes count] > 0)
1292  rowindexes = childlessIndexes;
1293  }
1294 
1295  [self removeRowsAtIndexes:rowindexes includeSubrows:YES];
1296 
1297  [self _updatePredicate];
1298  [self _sendRuleAction];
1299  [self _postRuleOptionChangedNotification];
1300  [self _postRowCountChangedNotificationOfType:CPRuleEditorRowsDidChangeNotification indexes:rowindexes];
1301 }
1302 
1303 - (CPArray)_rootRowsArray
1304 {
1305  return [_boundArrayOwner mutableArrayValueForKey:_boundArrayKeyPath];
1306 }
1307 
1308 - (BOOL)_nextUnusedItems:(CPArray)items andValues:(CPArray)values forRow:(int)rowIndex forRowType:(unsigned int)type
1309 {
1310  var parentItem = [items lastObject], // if empty items array, this is NULL aka the root item;
1311  childrenCount = [self _queryNumberOfChildrenOfItem:parentItem withRowType:type],
1312  foundIndex = CPNotFound;
1313 
1314  if (childrenCount === 0)
1315  return NO;
1316 
1317  var current_criterions = [CPMutableArray array],
1318  count = [self numberOfRows],
1319  row;
1320 
1321  for (row = 0; row < count; row++) // num of rows should be num of siblings of parentItem
1322  {
1323  var aCriteria = [self criteriaForRow:row],
1324  itemIndex = [items count];
1325 
1326  if ([self rowTypeForRow:row] === type && itemIndex < [aCriteria count])
1327  {
1328  var crit = [aCriteria objectAtIndex:itemIndex];
1329  [current_criterions addObject:crit];
1330  }
1331  }
1332 
1333  while (foundIndex === CPNotFound)
1334  {
1335  var buffer = [CPMutableArray arrayWithArray:current_criterions],
1336  i;
1337  for (i = 0; i < childrenCount; i++)
1338  {
1339  var child = [self _queryChild:i ofItem:parentItem withRowType:type];
1340  if ([current_criterions indexOfObject:child] === CPNotFound)
1341  {
1342  foundIndex = i;
1343  break;
1344  }
1345  }
1346 
1347  if (foundIndex === CPNotFound)
1348  {
1349  for (var k = 0; k < childrenCount; k++)
1350  {
1351  var anobject = [self _queryChild:k ofItem:parentItem withRowType:type],
1352  index = [buffer indexOfObject:anobject];
1353  if (index !== CPNotFound)
1354  [buffer removeObjectAtIndex:index];
1355  }
1356 
1357  current_criterions = buffer;
1358  }
1359  }
1360 
1361  var foundItem = [self _queryChild:foundIndex ofItem:parentItem withRowType:type],
1362  foundValue = [self _queryValueForItem:foundItem inRow:rowIndex];
1363 
1364  [items addObject:foundItem];
1365  [values addObject:foundValue];
1366 
1367  return YES;
1368 }
1369 
1370 - (CPMutableArray)_getItemsAndValuesToAddForRow:(int)rowIndex ofType:(CPRuleEditorRowType)type
1371 {
1372  //var cachedItemsAndValues = _itemsAndValuesToAddForRowType[type];
1373  //if (cachedItemsAndValues)
1374  // return cachedItemsAndValues;
1375 
1376  var itemsAndValues = [CPMutableArray array],
1377  items = [CPMutableArray array],
1378  values = [CPMutableArray array],
1379  unusedItems = YES;
1380 
1381  while (unusedItems)
1382  unusedItems = [self _nextUnusedItems:items andValues:values forRow:rowIndex forRowType:type];
1383 
1384  var count = [items count];
1385 
1386  for (var i = 0; i < count; i++)
1387  {
1388  var item = [items objectAtIndex:i],
1389  value = [values objectAtIndex:i],
1390  itemAndValue = [CPDictionary dictionaryWithObjects:[item, value] forKeys:["item", "value"]];
1391 
1392  [itemsAndValues addObject:itemAndValue];
1393  }
1394 
1395  return itemsAndValues;
1396 }
1397 
1398 - (void)_addOptionFromSlice:(id)slice ofRowType:(unsigned int)type
1399 {
1400  // for CPRuleEditorNestingModeSimple only
1401 
1402  var rowIndexEvent = [slice rowIndex],
1403  rowTypeEvent = [self rowTypeForRow:rowIndexEvent],
1404  insertIndex = rowIndexEvent + 1,
1405  parentRowIndex = (rowTypeEvent === CPRuleEditorRowTypeCompound) ? rowIndexEvent:[self parentRowForRow:rowIndexEvent];
1406 
1407  [self insertRowAtIndex:insertIndex withType:type asSubrowOfRow:parentRowIndex animate:YES];
1408 }
1409 
1410 - (id)_insertNewRowAtIndex:(int)insertIndex ofType:(CPRuleEditorRowType)rowtype withParentRow:(int)parentRowIndex
1411 {
1412  var row = [[[self rowClass] alloc] init],
1413  itemsandvalues = [self _getItemsAndValuesToAddForRow:insertIndex ofType:rowtype],
1414  newitems = [itemsandvalues valueForKey:@"item"],
1415  newvalues = [itemsandvalues valueForKey:@"value"];
1416 
1417  [row setValue:newitems forKey:_itemsKeyPath];
1418  [row setValue:newvalues forKey:_valuesKeyPath];
1419  [row setValue:rowtype forKey:_typeKeyPath];
1420  [row setValue:[CPMutableArray array] forKey:_subrowsArrayKeyPath];
1421 
1422  var subrowsObjects;
1423  if (parentRowIndex === -1 || [self _applicableNestingMode] === CPRuleEditorNestingModeList)
1424  subrowsObjects = [self _rootRowsArray];
1425  else
1426  {
1427  var parentRowObject = [[self _rowCacheForIndex:parentRowIndex] rowObject];
1428  subrowsObjects = [self _subrowObjectsOfObject:parentRowObject];
1429  }
1430 
1431  var relInsertIndex = insertIndex - parentRowIndex - 1;
1432  [subrowsObjects insertObject:row atIndex:relInsertIndex];
1433 
1434  [self _updatePredicate];
1435  [self _sendRuleAction];
1436  [self _postRuleOptionChangedNotification];
1437  [self _postRowCountChangedNotificationOfType:CPRuleEditorRowsDidChangeNotification indexes:[CPIndexSet indexSetWithIndex:insertIndex]];
1438 
1439  return row;
1440 }
1441 
1442 #pragma mark Key value observing
1443 
1444 - (void)_startObservingRowObjectsRecursively:(CPArray)rowObjects
1445 {
1446  [_boundArrayOwner addObserver:self forKeyPath:_boundArrayKeyPath options:CPKeyValueObservingOptionOld | CPKeyValueObservingOptionNew context:boundArrayContext];
1447 
1448  var count = [rowObjects count];
1449 
1450  for (var i = 0; i < count; i++)
1451  {
1452  var rowObject = [rowObjects objectAtIndex:i];
1453 
1454  [rowObject addObserver:self forKeyPath:_itemsKeyPath options:CPKeyValueObservingOptionOld | CPKeyValueObservingOptionNew context:itemsContext];
1455  [rowObject addObserver:self forKeyPath:_valuesKeyPath options:CPKeyValueObservingOptionOld | CPKeyValueObservingOptionNew context:valuesContext];
1456  [rowObject addObserver:self forKeyPath:_subrowsArrayKeyPath options:CPKeyValueObservingOptionOld | CPKeyValueObservingOptionNew context:subrowsContext];
1457 
1458  var subrows = [self _subrowObjectsOfObject:rowObject];
1459  if ([subrows count] > 0)
1460  [self _startObservingRowObjectsRecursively:subrows];
1461  }
1462  // ORIG IMPL : calls +keyPathsForValuesAffectingValueForKey: for all keys
1463 }
1464 
1465 - (void)_stopObservingRowObjectsRecursively:(CPArray)rowObjects
1466 {
1467  [_boundArrayOwner removeObserver:self forKeyPath:_boundArrayKeyPath];
1468 
1469  var count = [rowObjects count];
1470 
1471  for (var i = 0; i < count; i++)
1472  {
1473  var rowObject = [rowObjects objectAtIndex:i];
1474  [rowObject removeObserver:self forKeyPath:_itemsKeyPath];
1475  [rowObject removeObserver:self forKeyPath:_valuesKeyPath];
1476  [rowObject removeObserver:self forKeyPath:_subrowsArrayKeyPath];
1477 
1478  var subrows = [rowObject valueForKey:_subrowsArrayKeyPath];
1479  if ([subrows count] > 0)
1480  [self _stopObservingRowObjectsRecursively:subrows];
1481  }
1482 }
1483 
1484 - (void)observeValueForKeyPath:(CPString)keypath ofObject:(id)object change:(CPDictionary)change context:(void)context
1485 {
1486  var changeKind = [change objectForKey:CPKeyValueChangeKindKey],
1487  changeNewValue = [change objectForKey:CPKeyValueChangeNewKey],
1488  changeOldValue = [change objectForKey:CPKeyValueChangeOldKey],
1489  newRows,
1490  oldRows;
1491 
1492  if (context === boundArrayContext || context === subrowsContext)
1493  {
1494  if (changeKind === CPKeyValueChangeSetting)
1495  {
1496  newRows = changeNewValue;
1497  oldRows = changeOldValue;
1498 
1499  }
1500  else if (changeKind === CPKeyValueChangeInsertion)
1501  {
1502  newRows = [self _subrowObjectsOfObject:object];
1503  oldRows = [CPArray arrayWithArray:newRows];
1504  [oldRows removeObjectsInArray:changeNewValue];
1505  }
1506  else if (changeKind === CPKeyValueChangeRemoval)
1507  {
1508  newRows = [self _subrowObjectsOfObject:object];
1509  oldRows = [CPArray arrayWithArray:newRows];
1510  var delIndexes = [change objectForKey:CPKeyValueChangeIndexesKey];
1511  [oldRows insertObjects:delObjects atIndexes:changeOldValue]; // Pas sur que ce soit bon
1512  }
1513 
1514  [self _changedRowArray:newRows withOldRowArray:oldRows forParent:object];
1515  [self _reconfigureSubviewsAnimate:[self _wantsRowAnimations]];
1516  }
1517  else if (context === itemsContext)
1518  {
1519  }
1520  else if (context === valuesContext)
1521  {
1522  }
1523 }
1524 
1525 - (void)_changedItem:(id)fromItem toItem:(id)toItem inRow:(int)aRow atCriteriaIndex:(int)fromItemIndex
1526 {
1527  var criteria = [self criteriaForRow:aRow],
1528  displayValues = [self displayValuesForRow:aRow],
1529  rowType = [self rowTypeForRow:aRow],
1530  anItem = toItem,
1531 
1532  items = [criteria subarrayWithRange:CPMakeRange(0, fromItemIndex)],
1533  values = [displayValues subarrayWithRange:CPMakeRange(0, fromItemIndex)];
1534 
1535  _lastRow = aRow;
1536 
1537  while (YES)
1538  {
1539  [items addObject:anItem];
1540  var value = [self _queryValueForItem:anItem inRow:aRow];
1541  [values addObject:value];
1542 
1543  if (![self _queryNumberOfChildrenOfItem:anItem withRowType:rowType])
1544  break;
1545 
1546  anItem = [self _queryChild:0 ofItem:anItem withRowType:rowType];
1547  }
1548 
1549  var object = [[self _rowCacheForIndex:aRow] rowObject];
1550  [object setValue:items forKey:_itemsKeyPath];
1551  [object setValue:values forKey:_valuesKeyPath];
1552 
1553  var slice = [_slices objectAtIndex:aRow];
1554  [slice _reconfigureSubviews];
1555 
1556  [self _updatePredicate];
1557  [self _sendRuleAction];
1558  [self _postRuleOptionChangedNotification];
1559 }
1560 
1561 - (void)_changedRowArray:(CPArray)newRows withOldRowArray:(CPArray)oldRows forParent:(id)parentRowObject
1562 {
1563  var newRowCount = [newRows count],
1564  oldRowCount = [oldRows count],
1565  deltaCount = newRowCount - oldRowCount,
1566  minusCount = MIN(newRowCount, oldRowCount),
1567  maxCount = MAX(newRowCount, oldRowCount),
1568 
1569  insertCacheIndexes = [CPIndexSet indexSet],
1570  newCaches = [CPArray array],
1571 
1572  parentCacheIndentation,
1573  parentCacheIndex = [self _rowIndexForRowObject:parentRowObject],
1574 
1575  newRowCacheIndex = 0,
1576  changeStartIndex = 0;
1577 
1578  [self _stopObservingRowObjectsRecursively:oldRows];
1579  [self _startObservingRowObjectsRecursively:newRows];
1580 
1581  //var gindexes = [self _globalIndexesForSubrowIndexes:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0,oldRowCount)] ofParentObject:parentRowObject];
1582 
1583  if (parentCacheIndex === -1)
1584  parentCacheIndentation = -1;
1585  else
1586  parentCacheIndentation = [[self _rowCacheForIndex:parentCacheIndex] indentation];
1587 
1588  for (; newRowCacheIndex < newRowCount; newRowCacheIndex++)
1589  {
1590  var newCacheGlobalIndex = (parentCacheIndex + 1) + newRowCacheIndex,
1591  obj = [newRows objectAtIndex:newRowCacheIndex],
1592  newRowType = [obj valueForKey:_typeKeyPath],
1593  cache = [[_CPRuleEditorCache alloc] init];
1594 
1595  [cache setRowObject:obj];
1596  [cache setRowIndex:newCacheGlobalIndex];
1597  [cache setIndentation:parentCacheIndentation + 1];
1598 
1599  [insertCacheIndexes addIndex:newCacheGlobalIndex];
1600  [newCaches addObject:cache];
1601  }
1602 
1603  //var lastCacheIndex = [self _rowIndexForRowObject:[oldRows lastObject]];
1604  [_rowCache removeObjectsInRange:CPMakeRange(parentCacheIndex + 1, [oldRows count])];
1605  [_rowCache insertObjects:newCaches atIndexes:insertCacheIndexes];
1606 
1607  for (; changeStartIndex < minusCount; changeStartIndex++)
1608  {
1609  var oldrow = [oldRows objectAtIndex:changeStartIndex],
1610  newrow = [newRows objectAtIndex:changeStartIndex];
1611 
1612  if (newrow !== oldrow)
1613  break;
1614  }
1615 
1616  var replaceCount = (deltaCount === 0) ? maxCount : maxCount - minusCount,
1617  startIndex = parentCacheIndex + changeStartIndex + 1;
1618 
1619  if (deltaCount <= 0)
1620  {
1621  var removeIndexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(startIndex, replaceCount)],
1622  removeSlices = [_slices objectsAtIndexes:removeIndexes];
1623 
1624  [removeSlices makeObjectsPerformSelector:@selector(removeFromSuperview)];
1625  [_slices removeObjectsAtIndexes:removeIndexes];
1626  }
1627 
1628  if (deltaCount >= 0)
1629  {
1630  var newIndentation = parentCacheIndentation + 1,
1631  newIndex = startIndex;
1632 
1633  for (; newIndex < startIndex + replaceCount; newIndex++)
1634  {
1635  var newslice = [self _newSlice],
1636  rowType = [self rowTypeForRow:newIndex];
1637 
1638  [newslice setRowIndex:newIndex];
1639  [newslice setIndentation:newIndentation];
1640  [newslice _setRowType:rowType];
1641  [newslice _configurePlusButtonByRowType:CPRuleEditorRowTypeSimple];
1642 
1643  [_slices insertObject:newslice atIndex:newIndex];
1644  }
1645  }
1646 
1647  var emptyArray = [CPArray array],
1648  count = [oldRows count],
1649  n;
1650  for (n = 0; n < count; n++)
1651  {
1652  var oldRow = [oldRows objectAtIndex:n],
1653  subOldRows = [self _subrowObjectsOfObject:oldRow];
1654 
1655  if ([subOldRows count] > 0)
1656  [self _changedRowArray:emptyArray withOldRowArray:subOldRows forParent:oldRow];
1657  }
1658 
1659  count = [newRows count];
1660  for (n = 0; n < count; n++)
1661  {
1662  var newRow = [newRows objectAtIndex:n],
1663  subnewRows = [self _subrowObjectsOfObject:newRow];
1664 
1665  if ([subnewRows count] > 0)
1666  [self _changedRowArray:subnewRows withOldRowArray:emptyArray forParent:newRow];
1667  }
1668 }
1669 
1670 - (void)bind:(CPString)aBinding toObject:(id)observableController withKeyPath:(CPString)aKeyPath options:(CPDictionary)options
1671 {
1672  if ([aBinding isEqualToString:@"rows"])
1673  {
1674  [self unbind:aBinding];
1675  [self _setBoundDataSource:observableController withKeyPath:aKeyPath options:options];
1676 
1677  [_rowCache removeAllObjects];
1678  [_slices removeAllObjects];
1679 
1680  var newRows = [CPArray array],
1681  oldRows = [self _rootRowsArray];
1682 
1683  [self _changedRowArray:newRows withOldRowArray:oldRows forParent:_boundArrayOwner];
1684  }
1685  else
1686  [super bind:aBinding toObject:observableController withKeyPath:aKeyPath options:options];
1687 }
1688 
1689 - (void)unbind:(id)object
1690 {
1691  _rowClass = [_CPRuleEditorRowObject class];
1692  [super unbind:object];
1693 }
1694 
1695 - (void)_setBoundDataSource:(id)datasource withKeyPath:(CPString)keyPath options:(CPDictionary)options
1696 {
1697  if ([observableController respondsToSelector:@selector(objectClass)])
1698  _rowClass = [observableController objectClass];
1699 
1700  _boundArrayKeyPath = keyPath;
1701  _boundArrayOwner = datasource;
1702 
1703  //var boundRows = [_boundArrayOwner valueForKey:_boundArrayKeyPath];
1704 
1705  [_boundArrayOwner addObserver:self forKeyPath:_boundArrayKeyPath options:CPKeyValueObservingOptionOld | CPKeyValueObservingOptionNew context:boundArrayContext];
1706 
1707  //if ([boundRows isKindOfClass:[CPArray class]] && [boundRows count] > 0)
1708  // [_boundArrayOwner setValue:boundRows forKey:_boundArrayKeyPath];
1709 }
1710 
1711 - (void)_setPredicate:(CPPredicate)predicate
1712 {
1713  if (_predicate !== predicate)
1714  _predicate = predicate;
1715 }
1716 
1717 - (void)_updatePredicate
1718 {
1719  if (_delegateWantsValidation)
1720  {
1721  var selector = @selector(ruleEditor:predicatePartsForCriterion:withDisplayValue:inRow:);
1722  if (![_ruleDelegate respondsToSelector:selector])
1723  return;
1724 
1725  _delegateWantsValidation = NO;
1726  }
1727 
1728  var subpredicates = [CPMutableArray array],
1729  subindexes = [self subrowIndexesForRow:-1],
1730  current_index = [subindexes firstIndex];
1731 
1732  while (current_index !== CPNotFound)
1733  {
1734  var subpredicate = [self predicateForRow:current_index];
1735 
1736  if (subpredicate !== nil)
1737  [subpredicates addObject:subpredicate];
1738 
1739  current_index = [subindexes indexGreaterThanIndex:current_index];
1740  }
1741 
1742  var new_predicate = [[CPCompoundPredicate alloc] initWithType:CPOrPredicateType subpredicates:subpredicates];
1743 
1744  [self _setPredicate:new_predicate];
1745 }
1746 
1747 - (_CPRuleEditorViewSliceRow)_newSlice
1748 {
1749  var sliceRect = CGRectMake(0, 0, CGRectGetWidth([self frame]), 0),
1750  slice = [self _createNewSliceWithFrame:sliceRect ruleEditorView:self];
1751 
1752  return slice;
1753 }
1754 
1755 - (_CPRuleEditorViewSliceRow)_createNewSliceWithFrame:(CGRect)frame ruleEditorView:(CPRuleEditor)editor
1756 {
1757  return [[_CPRuleEditorViewSliceRow alloc] initWithFrame:frame ruleEditorView:editor];
1758 }
1759 
1760 - (void)_reconfigureSubviewsAnimate:(BOOL)animate
1761 {
1762  var viewAnimations = [CPMutableArray array],
1763  added_slices = [CPMutableArray array],
1764  count = [_slices count];
1765 
1766  [self _updateSliceRows];
1767 
1768  if ([[self superview] isKindOfClass:[CPClipView class]])
1769  [self setFrameSize:CGSizeMake(CGRectGetWidth([self frame]), count * _sliceHeight)];
1770 
1771  for (var i = 0; i < count; i++)
1772  {
1773  var aslice = [_slices objectAtIndex:i],
1774  targetRect = [aslice _animationTargetRect],
1775  startRect = [aslice frame],
1776  startIndex = [aslice rowIndex] - 1;
1777 
1778  if ([aslice superview] === nil)
1779  {
1780  startRect = CGRectMake(0, startIndex * _sliceHeight, CGRectGetWidth(startRect), _sliceHeight);
1781  [aslice _reconfigureSubviews];
1782  [added_slices addObject:aslice];
1783  }
1784 
1785  if (animate)
1786  {
1787  var animation = [CPDictionary dictionary];
1788  [animation setObject:aslice forKey:CPViewAnimationTargetKey];
1789  [animation setObject:startRect forKey:CPViewAnimationStartFrameKey];
1790  [animation setObject:targetRect forKey:CPViewAnimationEndFrameKey];
1791 
1792  [viewAnimations insertObject:animation atIndex:0];
1793  }
1794  else
1795  [aslice setFrame:targetRect];
1796  }
1797 
1798  var addcount = [added_slices count];
1799  for (var i = 0; i < addcount; i++)
1800  [_slicesHolder addSubview:added_slices[i] positioned:CPWindowBelow relativeTo:nil];
1801 
1802  if (animate)
1803  {
1804  [_currentAnimation setViewAnimations:viewAnimations];
1805  [_currentAnimation startAnimation];
1806  }
1807 
1808  _lastRow = [self numberOfRows] - 1;
1809 
1810  if (_lastRow === -1)
1811  _nestingModeDidChange = NO;
1812 
1813  [self setNeedsDisplay:YES];
1814  [_slices makeObjectsPerformSelector:@selector(_updateButtonVisibilities)];
1815 }
1816 
1817 - (void)animationDidEnd:(CPViewAnimation)animation
1818 {
1819 // var nextSimple = [self _getItemsAndValuesToAddForRow:0 ofType:CPRuleEditorRowTypeSimple],
1820 // nextCompound = [self _getItemsAndValuesToAddForRow:0 ofType:CPRuleEditorRowTypeCompound];
1821 
1822 // _itemsAndValuesToAddForRowType = {CPRuleEditorRowTypeSimple:nextSimple, CPRuleEditorRowTypeCompound:nextCompound};
1823 }
1824 
1825 - (void)_updateSliceRows
1826 {
1827  var width = [self frame].size.width,
1828  count = [_slices count];
1829 
1830  for (var i = 0; i < count; i++)
1831  {
1832  var slice = [_slices objectAtIndex:i],
1833  targetRect = CGRectMake(0, i * _sliceHeight, width, _sliceHeight);
1834 
1835  [slice setRowIndex:i];
1836  [slice _setAnimationTargetRect:targetRect];
1837  }
1838 }
1839 
1840 - (CPArray)_backgroundColors
1841 {
1842  return [self valueForThemeAttribute:@"alternating-row-colors"];
1843 }
1844 
1845 - (CPColor)_selectedRowColor
1846 {
1847  return [self valueForThemeAttribute:@"selected-color"];
1848 }
1849 
1850 - (CPColor)_sliceTopBorderColor
1851 {
1852  return [self valueForThemeAttribute:@"slice-top-border-color"];
1853 }
1854 
1855 - (CPColor)_sliceBottomBorderColor
1856 {
1857  return [self valueForThemeAttribute:@"slice-bottom-border-color"];
1858 }
1859 
1860 - (CPColor)_sliceLastBottomBorderColor
1861 {
1862  return [self valueForThemeAttribute:@"slice-last-bottom-border-color"];
1863 }
1864 
1865 - (CPFont)font
1866 {
1867  return [self valueForThemeAttribute:@"font"];
1868 }
1869 
1870 - (CPImage)_addImage
1871 {
1872  return [self valueForThemeAttribute:@"add-image"];
1873 }
1874 
1875 - (CPImage)_removeImage
1876 {
1877  return [self valueForThemeAttribute:@"remove-image"];
1878 }
1879 
1880 - (CPString)_toolTipForAddCompoundRowButton
1881 {
1882  return @"Add Compound row";
1883 }
1884 
1885 - (CPString)_toolTipForAddSimpleRowButton
1886 {
1887  return @"Add row";
1888 }
1889 
1890 - (CPString)_toolTipForDeleteRowButton
1891 {
1892  return @"Delete row";
1893 }
1894 
1895 - (void)_updateSliceIndentations
1896 {
1897  [self _updateSliceIndentationAtIndex:0 toIndentation:0 withIndexSet:[self subrowIndexesForRow:0]];
1898 }
1899 
1900 - (void)_updateSliceIndentationAtIndex:(int)index toIndentation:(int)indentation withIndexSet:(id)indexes
1901 {
1902  var current_index = [indexes firstIndex];
1903 
1904  while (current_index !== CPNotFound)
1905  {
1906  var subindexes = [self subrowIndexesForRow:index];
1907  [self _updateSliceIndentationAtIndex:current_index toIndentation:indentation + 1 withIndexSet:subindexes];
1908  current_index = [indexes indexGreaterThanIndex:current_index];
1909  }
1910 
1911  [[_slices objectAtIndex:index] setIndentation:indentation];
1912 }
1913 
1914 - (CPArray)_selectedSlices
1915 {
1916  var _selectedSlices = [CPMutableArray array],
1917  count = [_slices count],
1918  i;
1919 
1920  for (i = 0; i < count; i++)
1921  {
1922  var slice = _slices[i];
1923  if ([slice _isSelected])
1924  [_selectedSlices addObject:slice];
1925  }
1926 
1927  return _selectedSlices;
1928 }
1929 
1930 - (int)_lastSelectedSliceIndex
1931 {
1932  var lastIndex = -1,
1933  count = [_slices count],
1934  i;
1935 
1936  for (i = 0; i < count; i++)
1937  {
1938  var slice = _slices[i];
1939  if ([slice _isLastSelected])
1940  return [slice rowIndex];
1941  }
1942 
1943  return CPNotFound;
1944 }
1945 
1946 - (void)_mouseUpOnSlice:(id)slice withEvent:(CPEvent)event
1947 {
1948  if ([slice _rowType] !== CPRuleEditorRowTypeSimple)
1949  return;
1950 
1951  var modifierFlags = [event modifierFlags],
1952  extend = (modifierFlags & CPCommandKeyMask) || (modifierFlags & CPShiftKeyMask),
1953  rowIndexes = [CPIndexSet indexSetWithIndex:[slice rowIndex]];
1954 
1955  [self selectRowIndexes:rowIndexes byExtendingSelection:extend];
1956 }
1957 
1958 - (void)_mouseDownOnSlice:(id)slice withEvent:(CPEvent)event
1959 {
1960 }
1961 
1962 - (void)_rightMouseDownOnSlice:(_CPRuleEditorViewSlice)slice withEvent:(CPEvent)event
1963 {
1964 }
1965 
1966 - (void)_performClickOnSlice:(id)slice withEvent:(CPEvent)event
1967 {
1968 }
1969 
1970 - (void)_setSuppressKeyDownHandling:(BOOL)flag
1971 {
1972  _suppressKeyDownHandling = flag;
1973 }
1974 
1975 - (void)selectAll:(id)sender
1976 {
1977  var count = [_slices count];
1978 
1979  while (count--)
1980  {
1981  var slice = _slices[count];
1982  [slice _setSelected:YES];
1983  [slice setNeedsDisplay:YES];
1984  }
1985 }
1986 
1987 - (void)_deselectAll
1988 {
1989  var count = [_slices count];
1990 
1991  while (count--)
1992  {
1993  var slice = _slices[count];
1994  [slice _setSelected:NO];
1995  [slice _setLastSelected:NO];
1996  [slice setNeedsDisplay:YES];
1997  }
1998 }
1999 
2000 - (int)_queryNumberOfChildrenOfItem:(id)item withRowType:(CPRuleEditorRowType)type
2001 {
2002  return [_ruleDelegate ruleEditor:self numberOfChildrenForCriterion:item withRowType:type];
2003 }
2004 
2005 - (id)_queryChild:(int)childIndex ofItem:(id)item withRowType:(CPRuleEditorRowType)type
2006 {
2007  return [_ruleDelegate ruleEditor:self child:childIndex forCriterion:item withRowType:type];
2008 }
2009 
2010 - (id)_queryValueForItem:(id)item inRow:(int)row
2011 {
2012  return [_ruleDelegate ruleEditor:self displayValueForCriterion:item inRow:row];
2013 }
2014 
2015 - (int)_lastRow
2016 {
2017  return _lastRow;
2018 }
2019 
2020 - (int)_countOfRowsStartingAtObject:(id)object
2021 {
2022  var index = [self _rowIndexForRowObject:object];
2023  return ([self numberOfRows] - index);
2024 }
2025 
2026 - (void)_setAlignmentGridWidth:(float)width
2027 {
2028  _alignmentGridWidth = width;
2029 }
2030 
2031 - (BOOL)_validateItem:(id)item value:(id)value inRow:(int)row
2032 {
2033  return [self _queryCanSelectItem:item displayValue:value inRow:row];
2034 }
2035 
2036 - (BOOL)_queryCanSelectItem:(id)item displayValue:(id)value inRow:(int)row
2037 {
2038  return YES;
2039 }
2040 
2041 - (void)_windowChangedKeyState
2042 {
2043  [self setNeedsDisplay:YES];
2044 }
2045 
2046 - (void)setNeedsDisplay:(BOOL)flag
2047 {
2048  [_slices makeObjectsPerformSelector:@selector(setNeedsDisplay:) withObject:flag];
2049  [super setNeedsDisplay:flag];
2050 }
2051 
2052 - (void)setFrameSize:(CPSize)size
2053 {
2054  [self setNeedsDisplay:YES];
2055 
2056  if (CGRectGetWidth([self frame]) !== size.width)
2057  [_slices makeObjectsPerformSelector:@selector(setNeedsLayout)];
2058 
2059  [super setFrameSize:size];
2060 }
2061 
2062 - (CPIndexSet)_selectedSliceIndices
2063 {
2064  var selectedIndices = [CPMutableIndexSet indexSet],
2065  count = [_slices count],
2066  i;
2067 
2068  for (i = 0; i < count; i++)
2069  {
2070  var slice = _slices[i];
2071  if ([slice _isSelected])
2072  [selectedIndices addIndex:[slice rowIndex]];
2073  }
2074 
2075  return selectedIndices;
2076 }
2077 
2078 - (void)mouseDragged:(CPEvent)event
2079 {
2080  if (!_editable)
2081  return;
2082 
2083  var point = [self convertPoint:[event locationInWindow] fromView:nil],
2084  view = [_slices objectAtIndex:FLOOR(point.y / _sliceHeight)];
2085 
2086  if ([self _dragShouldBeginFromMouseDown:view])
2087  [self _performDragForSlice:view withEvent:event];
2088 }
2089 
2090 - (BOOL)_dragShouldBeginFromMouseDown:(CPView)view
2091 {
2092  return (([self nestingMode] === CPRuleEditorNestingModeList || [view rowIndex] !== 0) && _editable && [view isKindOfClass:[_CPRuleEditorViewSliceRow class]] && _draggingRows === nil);
2093 }
2094 
2095 - (BOOL)_performDragForSlice:(id)slice withEvent:(CPEvent)event
2096 {
2097  var dragPoint,
2098  mainRowIndex = [slice rowIndex],
2099  draggingRows = [CPIndexSet indexSetWithIndex:mainRowIndex],
2100  selected_indices = [self _selectedSliceIndices],
2101  pasteboard = [CPPasteboard pasteboardWithName:CPDragPboard];
2102 
2103  [pasteboard declareTypes:[CPArray arrayWithObjects:CPRuleEditorItemPBoardType, nil] owner: self];
2104 
2105  if ([selected_indices containsIndex:mainRowIndex])
2106  [draggingRows addIndexes:selected_indices];
2107  _draggingRows = [self _includeSubslicesForSlicesAtIndexes:draggingRows];
2108 
2109  var firstIndex = [_draggingRows firstIndex],
2110  firstSlice = [_slices objectAtIndex:firstIndex],
2111  dragview = [[CPView alloc] initWithFrame:[firstSlice frame]];
2112 
2113 #if PLATFORM(DOM)
2114  var html = firstSlice._DOMElement.innerHTML;
2115  dragview._DOMElement.innerHTML = [html copy];
2116 #endif
2117  [dragview setBackgroundColor:[firstSlice backgroundColor]];
2118  [dragview setAlphaValue:0.7];
2119 
2120  dragPoint = CPMakePoint(0, firstIndex * _sliceHeight);
2121 
2122  [self dragView:dragview
2123  at:dragPoint
2124  offset:CGSizeMake(0, _sliceHeight)
2125  event:event
2126  pasteboard:pasteboard
2127  source:self
2128  slideBack:YES];
2129 
2130  return YES;
2131 }
2132 
2133 - (CPDragOperation)draggingEntered:(id < CPDraggingInfo >)sender
2134 {
2135  if ([sender draggingSource] === self)
2136  {
2137  [self _clearDropLine];
2138  return CPDragOperationMove;
2139  }
2140 
2141  return CPDragOperationNone;
2142 }
2143 
2144 - (void)draggingExited:(id)sender
2145 {
2146  [self _clearDropLine];
2147  [self setNeedsDisplay:YES];
2148 }
2149 
2150 - (void)_clearDropLine
2151 {
2152  [_dropLineView setAlphaValue:0];
2153 
2154  if (_subviewIndexOfDropLine !== CPNotFound && _subviewIndexOfDropLine < _lastRow)
2155  {
2156  var previousBelowSlice = [_slices objectAtIndex:_subviewIndexOfDropLine];
2157  [previousBelowSlice setFrameOrigin:CGPointMake(0, [previousBelowSlice rowIndex] * _sliceHeight)];
2158  }
2159 
2160  _subviewIndexOfDropLine = CPNotFound;
2161 }
2162 
2163 - (CPDragOperation)draggingUpdated:(id <CPDraggingInfo>)sender
2164 {
2165  var point = [self convertPoint:[sender draggingLocation] fromView:nil],
2166  y = point.y + _sliceHeight / 2,
2167  indexOfDropLine = FLOOR(y / _sliceHeight),
2168  numberOfRows = [self numberOfRows];
2169 
2170  if (indexOfDropLine < 0 || indexOfDropLine > numberOfRows || (indexOfDropLine >= [_draggingRows firstIndex] && indexOfDropLine <= [_draggingRows lastIndex] + 1))
2171  {
2172  if (_subviewIndexOfDropLine !== CPNotFound && indexOfDropLine !== _subviewIndexOfDropLine)
2173  [self _clearDropLine];
2174  return CPDragOperationNone;
2175  }
2176 
2177  if (_subviewIndexOfDropLine !== indexOfDropLine)
2178  {
2179  if (_subviewIndexOfDropLine !== CPNotFound && _subviewIndexOfDropLine < numberOfRows)
2180  {
2181  var previousBelowSlice = [_slices objectAtIndex:_subviewIndexOfDropLine];
2182  [previousBelowSlice setFrameOrigin:CPMakePoint(0, [previousBelowSlice rowIndex] * _sliceHeight)];
2183  }
2184 
2185  if (indexOfDropLine <= _lastRow && indexOfDropLine < numberOfRows)
2186  {
2187  var belowSlice = [_slices objectAtIndex:indexOfDropLine];
2188  [belowSlice setFrameOrigin:CGPointMake(0, [belowSlice rowIndex] * _sliceHeight + 2)];
2189  }
2190 
2191  [_dropLineView setAlphaValue:1];
2192  [_dropLineView setFrameOrigin:CGPointMake(CGRectGetMinX([_dropLineView frame]), indexOfDropLine * _sliceHeight)];
2193 
2194  _subviewIndexOfDropLine = indexOfDropLine;
2195  }
2196 
2197  return CPDragOperationMove;
2198 }
2199 
2200 - (BOOL)prepareForDragOperation:(id < CPDraggingInfo >)sender
2201 {
2202  return (_subviewIndexOfDropLine !== CPNotFound);
2203 }
2204 
2205 - (BOOL)performDragOperation:(id < CPDraggingInfo >)info
2206 {
2207  var aboveInsertIndexCount = 0,
2208  object,
2209  removeIndex;
2210 
2211  var rowObjects = [_rowCache valueForKey:@"rowObject"],
2212  index = [_draggingRows lastIndex];
2213 
2214  var parentRowIndex = [self parentRowForRow:index], // first index of draggingrows
2215  parentRowObject = (parentRowIndex === -1) ? _boundArrayOwner : [[self _rowCacheForIndex:parentRowIndex] rowObject],
2216  insertIndex = _subviewIndexOfDropLine;
2217 
2218  while (index !== CPNotFound)
2219  {
2220  if (index >= insertIndex)
2221  {
2222  removeIndex = index + aboveInsertIndexCount;
2223  aboveInsertIndexCount += 1;
2224  }
2225  else
2226  {
2227  removeIndex = index;
2228  insertIndex -= 1;
2229  }
2230 
2231  object = [rowObjects objectAtIndex:removeIndex];
2232  [self removeRowAtIndex:removeIndex];
2233  [[self _subrowObjectsOfObject:parentRowObject] insertObject:object atIndex:insertIndex - parentRowIndex - 1];
2234 
2235  index = [_draggingRows indexLessThanIndex:index];
2236  }
2237 
2238  [self _clearDropLine];
2239  _draggingRows = nil;
2240  return YES;
2241 }
2242 
2243 - (CPIndexSet)_draggingTypes
2244 {
2245  return [CPIndexSet indexSetWithIndex:CPDragOperationMove];
2246 }
2247 
2248 - (void)draggedView:(CPView)dragView endedAt:(CPPoint)aPoint operation:(CPDragOperation)operation
2249 {
2250  _draggingRows = nil;
2251 
2252  [self _updatePredicate];
2253  [self _sendRuleAction];
2254  [self _postRuleOptionChangedNotification];
2255  [self _postRowCountChangedNotificationOfType:CPRuleEditorRowsDidChangeNotification indexes:nil]; // FIXME
2256 }
2257 
2258 - (BOOL)wantsPeriodicDraggingUpdates
2259 {
2260  return NO;
2261 }
2262 
2263 - (void)pasteboard:(CPPasteboard)pasteboard provideDataForType:(int)type
2264 {
2265 }
2266 
2267 - (void)_setWindow:(id)window
2268 {
2269  [super _setWindow:window];
2270 }
2271 
2272 - (void)_windowUpdate:(id)sender
2273 {
2274  [super _windowUpdate:sender];
2275 }
2276 
2277 - (void)_postRuleOptionChangedNotification
2278 {
2279  [[CPNotificationCenter defaultCenter] postNotificationName:CPRuleEditorRulesDidChangeNotification object:self];
2280 }
2281 
2282 - (void)_postRowCountChangedNotificationOfType:(CPString)notificationName indexes:indexes
2283 {
2284  var userInfo = [CPDictionary dictionaryWithObject:indexes forKey:"indexes"];
2285  [[CPNotificationCenter defaultCenter] postNotificationName:notificationName object:self userInfo:userInfo];
2286 }
2287 
2288 - (CPIndexSet)_globalIndexesForSubrowIndexes:(CPIndexSet)indexes ofParentObject:(id)parentRowObject
2289 {
2290  var _subrows = [self _subrowObjectsOfObject:parentRowObject],
2291  parentRowIndex = [self _rowIndexForRowObject:parentRowObject],
2292 
2293  globalIndexes = [CPMutableIndexSet indexSet],
2294  current_index = [indexes firstIndex],
2295  numberOfChildrenOfPreviousBrother = 0;
2296 
2297  while (current_index !== CPNotFound)
2298  {
2299  var globalChildIndex = current_index + parentRowIndex + 1 + numberOfChildrenOfPreviousBrother;
2300  [globalIndexes addIndex:globalChildIndex];
2301 
2302  if ([self rowTypeForRow:globalChildIndex] === CPRuleEditorRowTypeCompound)
2303  {
2304  var rowObject = [[self _rowCacheForIndex:current_index] rowObject],
2305  subrows = [self _subrowObjectsOfObject:rowObject],
2306  subIndexes = [self _globalIndexesForSubrowIndexes:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [subrows count])] ofParentObject:rowObject];
2307 
2308  numberOfChildrenOfPreviousBrother = [subIndexes count];
2309  }
2310 
2311  current_index = [indexes indexGreaterThanIndex:current_index];
2312  }
2313 
2314  return globalIndexes;
2315 }
2316 
2317 - (void)_sendRuleAction
2318 {
2319  var action = [self action],
2320  target = [self target];
2321 
2322  [self sendAction:action to:target];
2323 }
2324 
2325 - (BOOL)_sendsActionOnIncompleteTextChange
2326 {
2327  return YES;
2328 }
2329 
2330 - (void)_getAllAvailableItems:(id)items values:(id)values asChildrenOfItem:(id)parentItem inRow:(int)aRow
2331 {
2332  var type,
2333  indexofCriterion,
2334  numOfChildren;
2335 
2336  var availItems = [CPMutableArray array],
2337  availValues = [CPMutableArray array];
2338 
2339  var criterion = nil,
2340  value = nil;
2341 
2342  _lastRow = aRow;
2343  type = [self rowTypeForRow:aRow];
2344  numOfChildren = [self _queryNumberOfChildrenOfItem:parentItem withRowType:type];
2345 
2346  var criteria = [self criteriaForRow:aRow];
2347  indexofCriterion = [criteria indexOfObject:criterion];
2348 
2349  if (parentItem !== nil
2350  && indexofCriterion !== CPNotFound
2351  && indexofCriterion < [criteria count] - 1)
2352  {
2353  var next = indexofCriterion + 1;
2354 
2355  criterion = [criteria objectAtIndex:next];
2356  var values = [self displayValuesForRow:aRow];
2357  value = [values objectAtIndex:next];
2358  }
2359 
2360  for (var i = 0; i < numOfChildren; ++i)
2361  {
2362  var aChild = [self _queryChild:i ofItem:parentItem withRowType:type],
2363  availChild = aChild,
2364  availValue = value;
2365 
2366  if (criterion !== aChild)
2367  availValue = [self _queryValueForItem:aChild inRow:aRow];
2368 
2369  if (!availValue)
2370  availValue = [self _queryValueForItem:availChild inRow:aRow];
2371 
2372  [availItems addObject:availChild];
2373  [availValues addObject:availValue];
2374  }
2375 
2376  [items addObjectsFromArray:availItems];
2377  [values addObjectsFromArray:availValues];
2378 }
2379 
2380 @end
2381 
2382 var CPRuleEditorAlignmentGridWidthKey = @"CPRuleEditorAlignmentGridWidth",
2383  CPRuleEditorSliceHeightKey = @"CPRuleEditorSliceHeight",
2384  CPRuleEditorStringsFilenameKey = @"CPRuleEditorStringsFilename",
2385  CPRuleEditorEditableKey = @"CPRuleEditorEditable",
2386  CPRuleEditorAllowsEmptyCompoundRowsKey = @"CPRuleEditorAllowsEmptyCompoundRows",
2387  CPRuleEditorDisallowEmptyKey = @"CPRuleEditorDisallowEmpty",
2388  CPRuleEditorNestingModeKey = @"CPRuleEditorNestingMode",
2389  CPRuleEditorRowTypeKeyPathKey = @"CPRuleEditorRowTypeKeyPath",
2390  CPRuleEditorItemsKeyPathKey = @"CPRuleEditorItemsKeyPath",
2391  CPRuleEditorValuesKeyPathKey = @"CPRuleEditorValuesKeyPath",
2392  CPRuleEditorSubrowsArrayKeyPathKey = @"CPRuleEditorSubrowsArrayKeyPath",
2393  CPRuleEditorBoundArrayKeyPathKey = @"CPRuleEditorBoundArrayKeyPath",
2394  CPRuleEditorRowClassKey = @"CPRuleEditorRowClass",
2395  CPRuleEditorSlicesHolderKey = @"CPRuleEditorSlicesHolder",
2396  CPRuleEditorSlicesKey = @"CPRuleEditorSlices",
2397  CPRuleEditorDelegateKey = @"CPRuleEditorDelegate",
2398  CPRuleEditorBoundArrayOwnerKey = @"CPRuleEditorBoundArrayOwner";
2399 
2400 @implementation CPRuleEditor (CPCoding)
2401 
2402 - (id)initWithCoder:(CPCoder)coder
2403 {
2404  self = [super initWithCoder:coder];
2405  if (self !== nil)
2406  {
2407  [self setFormattingStringsFilename:[coder decodeObjectForKey:CPRuleEditorStringsFilenameKey]];
2408  _alignmentGridWidth = [coder decodeFloatForKey:CPRuleEditorAlignmentGridWidthKey];
2409  _sliceHeight = [coder decodeDoubleForKey:CPRuleEditorSliceHeightKey];
2410  _editable = [coder decodeBoolForKey:CPRuleEditorEditableKey];
2411  _allowsEmptyCompoundRows = [coder decodeBoolForKey:CPRuleEditorAllowsEmptyCompoundRowsKey];
2412  _disallowEmpty = [coder decodeBoolForKey:CPRuleEditorDisallowEmptyKey];
2413  _nestingMode = [coder decodeIntForKey:CPRuleEditorNestingModeKey];
2414  _typeKeyPath = [coder decodeObjectForKey:CPRuleEditorRowTypeKeyPathKey];
2415  _itemsKeyPath = [coder decodeObjectForKey:CPRuleEditorItemsKeyPathKey];
2416  _valuesKeyPath = [coder decodeObjectForKey:CPRuleEditorValuesKeyPathKey];
2417  _subrowsArrayKeyPath = [coder decodeObjectForKey:CPRuleEditorSubrowsArrayKeyPathKey];
2418  _boundArrayKeyPath = [coder decodeObjectForKey:CPRuleEditorBoundArrayKeyPathKey];
2419 
2420  _slicesHolder = [[self subviews] objectAtIndex:0];
2421  _boundArrayOwner = [coder decodeObjectForKey:CPRuleEditorBoundArrayOwnerKey];
2422  _slices = [coder decodeObjectForKey:CPRuleEditorSlicesKey];
2423  _ruleDelegate = [coder decodeObjectForKey:CPRuleEditorDelegateKey];
2424 
2425  [self _initRuleEditorShared];
2426  }
2427 
2428  return self;
2429 }
2430 
2431 - (void)encodeWithCoder:(id)coder
2432 {
2433  [super encodeWithCoder:coder];
2434 
2435  [coder encodeBool:_editable forKey:CPRuleEditorEditableKey];
2436  [coder encodeBool:_allowsEmptyCompoundRows forKey:CPRuleEditorAllowsEmptyCompoundRowsKey];
2437  [coder encodeBool:_disallowEmpty forKey:CPRuleEditorDisallowEmptyKey];
2438 
2439  [coder encodeFloat:_alignmentGridWidth forKey:CPRuleEditorAlignmentGridWidthKey];
2440  [coder encodeDouble:_sliceHeight forKey:CPRuleEditorSliceHeightKey];
2441  [coder encodeInt:_nestingMode forKey:CPRuleEditorNestingModeKey];
2442 
2443  [coder encodeObject:_stringsFilename forKey:CPRuleEditorStringsFilenameKey];
2444  [coder encodeObject:_typeKeyPath forKey:CPRuleEditorRowTypeKeyPathKey];
2445  [coder encodeObject:_itemsKeyPath forKey:CPRuleEditorItemsKeyPathKey];
2446  [coder encodeObject:_valuesKeyPath forKey:CPRuleEditorValuesKeyPathKey];
2447  [coder encodeObject:_boundArrayKeyPath forKey:CPRuleEditorBoundArrayKeyPathKey];
2448  [coder encodeObject:_subrowsArrayKeyPath forKey:CPRuleEditorSubrowsArrayKeyPathKey];
2449 
2450  [coder encodeConditionalObject:_slicesHolder forKey:CPRuleEditorSlicesHolderKey];
2451  [coder encodeObject:_slices forKey:CPRuleEditorSlicesKey];
2452  [coder encodeObject:_boundArrayOwner forKey:CPRuleEditorBoundArrayOwnerKey];
2453 }
2454 
2455 @end
2456 
2457 var CriteriaKey = @"criteria",
2458  SubrowsKey = @"subrows",
2459  DisplayValuesKey = @"displayValues",
2460  RowTypeKey = @"rowType";
2461 
2462 @implementation _CPRuleEditorRowObject : CPObject
2463 {
2464  CPArray subrows;
2465  CPArray criteria;
2466  CPArray displayValues;
2467  CPInteger rowType;
2468 }
2469 
2470 - (id)copy
2471 {
2472  var copy = [[_CPRuleEditorRowObject alloc] init];
2473  [copy setSubrows:[[CPArray alloc] initWithArray:subrows copyItems:YES]];
2474  [copy setCriteria:[[CPArray alloc] initWithArray:criteria copyItems:YES]];
2475  [copy setDisplayValues:[[CPArray alloc] initWithArray:displayValues copyItems:YES]];
2476  [copy setRowType:rowType];
2477 
2478  return copy;
2479 }
2480 
2481 - (CPString)description
2482 {
2483  return "<" + [self className] + ">\nsubrows = " + [subrows description] + "\ncriteria = " + [criteria description] + "\ndisplayValues = " + [displayValues description];
2484 }
2485 
2486 - (id)initWithCoder:(id)coder
2487 {
2488  self = [super init];
2489  if (self !== nil)
2490  {
2491  subrows = [coder decodeObjectForKey:SubrowsKey];
2492  criteria = [coder decodeObjectForKey:CriteriaKey];
2493  displayValues = [coder decodeObjectForKey:DisplayValuesKey];
2494  rowType = [coder decodeIntForKey:RowTypeKey];
2495  }
2496 
2497  return self;
2498 }
2499 
2500 - (void)encodeWithCoder:(id)coder
2501 {
2502  [coder encodeObject:subrows forKey:SubrowsKey];
2503  [coder encodeObject:criteria forKey:CriteriaKey];
2504  [coder encodeObject:displayValues forKey:DisplayValuesKey];
2505  [coder encodeInt:rowType forKey:RowTypeKey];
2506 }
2507 
2508 @end
2509 
2510 @implementation _CPRuleEditorCache : CPObject
2511 {
2512  CPDictionary rowObject;
2513  CPInteger rowIndex;
2514  CPInteger indentation;
2515 }
2516 
2517 - (CPString)description
2518 {
2519  return [CPString stringWithFormat:@"<%d object:%d rowIndex:%d indentation:%d>", [self hash], [rowObject hash], rowIndex, indentation];
2520 }
2521 
2522 @end
2523 
2524 var CPBoundArrayKey = @"CPBoundArray";
2525 
2526 @implementation _CPRuleEditorViewUnboundRowHolder : CPObject
2527 {
2528  CPArray boundArray;
2529 }
2530 
2531 - (id)init
2532 {
2533  if (self = [super init])
2534  boundArray = [[CPArray alloc] init];
2535 
2536  return self;
2537 }
2538 
2539 - (id)initWithCoder:(id)coder
2540 {
2541  if (self = [super init])
2542  boundArray = [coder decodeObjectForKey:CPBoundArrayKey];
2543 
2544  return self;
2545 }
2546 
2547 - (void)encodeWithCoder:(id)coder
2548 {
2549  [coder encodeObject:boundArray forKey:CPBoundArrayKey];
2550 }
2551 
2552 @end
2553 @implementation _CPRuleEditorViewSliceHolder : CPView
2554 {
2555  id __doxygen__;
2556 }
2557 
2558 - (void)addSubview:(CPView)subview
2559 {
2560  [self setNeedsDisplay:YES];
2561  [super addSubview:subview];
2562 }
2563 
2564 @end
2565 
2566 var dropSeparatorColor = [CPColor colorWithHexString:@"4886ca"];
2567 @implementation _CPRuleEditorViewSliceDropSeparator : CPView
2568 {
2569  id __doxygen__;
2570 }
2571 
2572 - (void)drawRect:(CPRect)rect
2573 {
2574  var context = [[CPGraphicsContext currentContext] graphicsPort];
2575  CGContextSetFillColor(context, dropSeparatorColor);
2576  CGContextFillRect(context, [self bounds]);
2577 }
2578 
2579 @end
2580 
2581 @implementation CPObject (CPRuleEditorSliceRow)
2582 
2583 - (int)valueType
2584 {
2585  var result = 0,
2586  isString = [self isKindOfClass:CPString];
2587 
2588  if (!isString)
2589  {
2590  var isView = [self isKindOfClass:CPView];
2591  result = 1;
2592 
2593  if (!isView)
2594  {
2595  var ismenuItem = [self isKindOfClass:CPMenuItem];
2596  result = 2;
2597 
2598  if (!ismenuItem)
2599  {
2600  [CPException raise:CPGenericException reason:@"Unknown type for " + self];
2601  result = -1;
2602  }
2603  }
2604  }
2605 
2606  return result;
2607 }
2608 
2609 @end
2612 @implementation CPRuleEditor (CPSynthesizedAccessors)
2613 
2617 - (CPString)boundArrayKeyPath
2618 {
2619  return _boundArrayKeyPath;
2620 }
2621 
2625 - (void)setBoundArrayKeyPath:(CPString)aValue
2626 {
2627  _boundArrayKeyPath = aValue;
2628 }
2629 
2633 - (_CPRuleEditorLocalizer)standardLocalizer
2634 {
2635  return _standardLocalizer;
2636 }
2637 
2641 - (void)setStandardLocalizer:(_CPRuleEditorLocalizer)aValue
2642 {
2643  _standardLocalizer = aValue;
2644 }
2645 
2646 @end