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