API  1.0.0
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 
26 
29 @global CPOrPredicateType
30 
31 @protocol CPRuleEditorDelegate <CPObject>
32 
33 @required
34 - (id)ruleEditor:(CPRuleEditor)editor child:(CPInteger)index forCriterion:(id)criterion withRowType:(CPRuleEditorRowType)rowType;
35 - (id)ruleEditor:(CPRuleEditor)editor displayValueForCriterion:(id)criterion inRow:(CPInteger)row;
36 - (CPInteger)ruleEditor:(CPRuleEditor)editor numberOfChildrenForCriterion:(id)criterion withRowType:(CPRuleEditorRowType)rowType;
37 
38 @optional
39 - (CPDictionary)ruleEditor:(CPRuleEditor)editor predicatePartsForCriterion:(id)criterion withDisplayValue:(id)value inRow:(CPInteger)row;
40 - (void)ruleEditorRowsDidChange:(CPNotification)notification;
41 
42 @end
43 
45 
46 var CPRuleEditorItemPBoardType = @"CPRuleEditorItemPBoardType",
47  itemsContext = "items",
48  valuesContext = "values",
49  subrowsContext = "subrows_array",
50  boundArrayContext = "bound_array";
51 
71 @implementation CPRuleEditor : CPControl
72 {
73  BOOL _suppressKeyDownHandling;
74  BOOL _allowsEmptyCompoundRows;
75  BOOL _disallowEmpty;
76  BOOL _delegateWantsValidation;
77  BOOL _editable;
78  BOOL _sendAction;
79 
80  Class _rowClass;
81 
82  CPIndexSet _draggingRows;
83  CPInteger _subviewIndexOfDropLine;
84  CPView _dropLineView;
85 
86  CPMutableArray _rowCache;
87  CPMutableArray _slices;
88 
89  CPPredicate _predicate;
90 
91  CPString _itemsKeyPath;
92  CPString _subrowsArrayKeyPath;
93  CPString _typeKeyPath;
94  CPString _valuesKeyPath;
95  CPString _boundArrayKeyPath;
96 
97  CPView _slicesHolder;
98  CPViewAnimation _currentAnimation;
99 
100  CPInteger _lastRow;
101  CPInteger _nestingMode;
102 
103  float _alignmentGridWidth;
104  float _sliceHeight;
105 
106  id _ruleDataSource;
107  id <CPRuleEditorDelegate> _ruleDelegate;
108  id _boundArrayOwner;
109  unsigned _implementedDelegateMethods;
110 
111  CPString _stringsFilename;
112 
113  BOOL _isKeyDown;
114  BOOL _nestingModeDidChange;
115 
116  _CPRuleEditorLocalizer _standardLocalizer;
117  CPDictionary _itemsAndValuesToAddForRowType;
118 }
119 
122 + (CPString)defaultThemeClass
123 {
124  return @"rule-editor";
125 }
126 
127 + (CPDictionary)themeAttributes
128 {
129  return @{
130  @"alternating-row-colors": [CPNull null],
131  @"selected-color": [CPNull null],
132  @"slice-top-border-color": [CPNull null],
133  @"slice-bottom-border-color": [CPNull null],
134  @"slice-last-bottom-border-color": [CPNull null],
135  @"font": [CPFont systemFontOfSize:12],
136  @"font-color": [CPNull null],
137  @"add-image": [CPNull null],
138  @"remove-image": [CPNull null],
139  @"vertical-alignment": [CPNull null]
140  };
141 }
142 
143 - (id)initWithFrame:(CGRect)frame
144 {
145  self = [super initWithFrame:frame];
146  if (self !== nil)
147  {
148  _slices = [[CPMutableArray alloc] init];
149 
150  _sliceHeight = 26.0;
151  _nestingMode = CPRuleEditorNestingModeSimple; // 10.5 default is CPRuleEditorNestingModeCompound
152  _editable = YES;
153  _allowsEmptyCompoundRows = NO;
154  _disallowEmpty = NO;
155 
156  [self setFormattingStringsFilename:nil];
157  [self setCriteriaKeyPath:@"criteria"];
158  [self setSubrowsKeyPath:@"subrows"];
159  [self setRowTypeKeyPath:@"rowType"];
160  [self setDisplayValuesKeyPath:@"displayValues"];
161  [self setBoundArrayKeyPath:@"boundArray"];
162 
163  _slicesHolder = [[_CPRuleEditorViewSliceHolder alloc] initWithFrame:[self bounds]];
164  [self addSubview:_slicesHolder];
165 
166  _boundArrayOwner = [[_CPRuleEditorViewUnboundRowHolder alloc] init];
167 
168  [self _initRuleEditorShared];
169  }
170 
171  return self;
172 }
173 
174 - (void)_initRuleEditorShared
175 {
176  _rowCache = [[CPMutableArray alloc] init];
177  _rowClass = [_CPRuleEditorRowObject class];
178  _isKeyDown = NO;
179  _subviewIndexOfDropLine = CPNotFound;
180  _lastRow = 0;
181  _delegateWantsValidation = YES;
182  _suppressKeyDownHandling = NO;
183  _nestingModeDidChange = NO;
184  _sendAction = YES;
185  _itemsAndValuesToAddForRowType = {};
186  var animation = [[CPViewAnimation alloc] initWithDuration:0.5 animationCurve:CPAnimationEaseInOut];
187  [self setAnimation:animation];
188 
189  [_slicesHolder setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
190 
191  _dropLineView = [self _createSliceDropSeparator];
192  [_slicesHolder addSubview:_dropLineView];
193 
194  [self registerForDraggedTypes:[CPArray arrayWithObjects:CPRuleEditorItemPBoardType,nil]];
195  [_boundArrayOwner addObserver:self forKeyPath:_boundArrayKeyPath options:CPKeyValueObservingOptionOld | CPKeyValueObservingOptionNew context:boundArrayContext];
196 }
197 
209 - (id)delegate
210 {
211  return _ruleDelegate;
212 }
213 
220 - (void)setDelegate:(id <CPRuleEditorDelegate>)aDelegate
221 {
222  if (_ruleDelegate === aDelegate)
223  return;
224 
226 
227  if (_ruleDelegate)
228  [nc removeObserver:_ruleDelegate name:nil object:self];
229 
230  _ruleDelegate = aDelegate;
231  _implementedDelegateMethods = 0;
232 
233  if ([_ruleDelegate respondsToSelector:@selector(ruleEditorRowsDidChange:)])
234  [nc addObserver:_ruleDelegate selector:@selector(ruleEditorRowsDidChange:) name:CPRuleEditorRowsDidChangeNotification object:nil];
235 
236  if ([_ruleDelegate respondsToSelector:@selector(ruleEditor:predicatePartsForCriterion:withDisplayValue:inRow:)])
238 }
245 - (BOOL)isEditable
246 {
247  return _editable;
248 }
249 
255 - (void)setEditable:(BOOL)editable
256 {
257  if (editable === _editable)
258  return;
259 
260  _editable = editable;
261 
262  if (!_editable)
263  [self _deselectAll];
264 
265  [_slices makeObjectsPerformSelector:@selector(setEditable:) withObject:_editable];
266 }
267 
273 - (CPRuleEditorNestingMode)nestingMode
274 {
275  return _nestingMode;
276 }
277 
285 - (void)setNestingMode:(CPRuleEditorNestingMode)mode
286 {
287  if (mode !== _nestingMode)
288  {
289  _nestingMode = mode;
290  if ([self numberOfRows] > 0)
291  _nestingModeDidChange = YES;
292  }
293 }
294 
300 - (BOOL)canRemoveAllRows
301 {
302  return !_disallowEmpty;
303 }
304 
310 - (void)setCanRemoveAllRows:(BOOL)canRemove
311 {
312  _disallowEmpty = !canRemove;
313  [self _updateButtonVisibilities];
314 }
315 
321 - (BOOL)allowsEmptyCompoundRows
322 {
323  return _allowsEmptyCompoundRows;
324 }
325 
331 - (void)setAllowsEmptyCompoundRows:(BOOL)allows
332 {
333  _allowsEmptyCompoundRows = allows;
334  [self _updateButtonVisibilities];
335 }
336 
342 - (CPInteger)rowHeight
343 {
344  return _sliceHeight;
345 }
346 
352 - (void)setRowHeight:(float)height
353 {
354  if (height === _sliceHeight)
355  return;
356 
357  _sliceHeight = MAX([self _minimumFrameHeight], height);
358  [self _reconfigureSubviewsAnimate:NO];
359 }
360 
371 - (CPDictionary)formattingDictionary
372 {
373  return [_standardLocalizer dictionary];
374 }
375 
383 - (void)setFormattingDictionary:(CPDictionary)dictionary
384 {
385  [_standardLocalizer setDictionary:dictionary];
386  _stringsFilename = nil;
387 }
388 
394 - (CPString)formattingStringsFilename
395 {
396  return _stringsFilename;
397 }
398 
406 - (void)setFormattingStringsFilename:(CPString)stringsFilename
407 {
408  if (_standardLocalizer === nil)
409  _standardLocalizer = [_CPRuleEditorLocalizer new];
410 
411  if (_stringsFilename !== stringsFilename)
412  {
413  // Convert an empty string to nil
414  _stringsFilename = stringsFilename || nil;
415 
416  if (stringsFilename !== nil)
417  {
418  if (![stringsFilename hasSuffix:@".strings"])
419  stringsFilename = stringsFilename + @".strings";
420 
421  var path = [[CPBundle mainBundle] pathForResource:stringsFilename];
422 
423  if (path !== nil)
424  [_standardLocalizer loadContentOfURL:[CPURL URLWithString:path]];
425  }
426  }
427 }
428 
437 - (void)reloadCriteria
438 {
439  var current_rows = [_boundArrayOwner valueForKey:_boundArrayKeyPath];
440  [self _stopObservingRowObjectsRecursively:current_rows];
441  [_boundArrayOwner setValue:[CPArray arrayWithArray:current_rows] forKey:_boundArrayKeyPath];
442 }
443 
452 - (void)setCriteria:(CPArray)criteria andDisplayValues:(CPArray)values forRowAtIndex:(int)rowIndex
453 {
454  if (criteria === nil || values === nil)
455  [CPException raise:CPInvalidArgumentException reason:_cmd + @". criteria and values parameters must not be nil."];
456 
457  if (rowIndex < 0 || rowIndex >= [self numberOfRows])
458  [CPException raise:CPRangeException reason:_cmd + @". rowIndex is out of bounds."];
459 
460  var rowObject = [[self _rowCacheForIndex:rowIndex] rowObject];
461 
462  [rowObject setValue:criteria forKey:_itemsKeyPath];
463  [rowObject setValue:values forKey:_valuesKeyPath];
464 
465  [self reloadCriteria];
466 }
467 
473 - (id)criteriaForRow:(CPInteger)row
474 {
475  var rowcache = [self _rowCacheForIndex:row];
476  if (rowcache)
477  return [[rowcache rowObject] valueForKey:_itemsKeyPath];
478 
479  return nil;
480 }
481 
492 - (CPMutableArray)displayValuesForRow:(CPInteger)row
493 {
494  var rowcache = [self _rowCacheForIndex:row];
495  if (rowcache)
496  return [[rowcache rowObject] valueForKey:_valuesKeyPath];
497 
498  return nil;
499 }
500 
505 - (int)numberOfRows
506 {
507  return [_slices count];
508 }
509 
515 - (int)parentRowForRow:(CPInteger)rowIndex
516 {
517  if (rowIndex < 0 || rowIndex >= [self numberOfRows])
518  [CPException raise:CPRangeException reason:_cmd + @" row " + rowIndex + " is out of range"];
519 
520  var targetObject = [[self _rowCacheForIndex:rowIndex] rowObject];
521 
522  for (var current_index = 0; current_index < rowIndex; current_index++)
523  {
524  if ([self rowTypeForRow:current_index] === CPRuleEditorRowTypeCompound)
525  {
526  var candidate = [[self _rowCacheForIndex:current_index] rowObject],
527  subObjects = [[self _subrowObjectsOfObject:candidate] _representedObject];
528 
529  if ([subObjects indexOfObjectIdenticalTo:targetObject] !== CPNotFound)
530  return current_index;
531  }
532  }
533 
534  return -1;
535 }
536 
537 /*
538 TODO: implement
539  Returns the index of the row containing a given value.
540 
541  displayValue The display value (string, view, or menu item) of an item in the receiver. This value must not be nil.
542 
543  The index of the row containing displayValue, or CPNotFound.
544 
545  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.
546 
547 - (CPInteger)rowForDisplayValue:(id)displayValue
548 */
549 
556 - (CPRuleEditorRowType)rowTypeForRow:(CPInteger)rowIndex
557 {
558  if (rowIndex < 0 || rowIndex > [self numberOfRows])
559  [CPException raise:CPRangeException reason:_cmd + @"row " + rowIndex + " is out of range"];
560 
561  var rowcache = [self _rowCacheForIndex:rowIndex];
562  if (rowcache)
563  {
564  var rowobject = [rowcache rowObject];
565  return [rowobject valueForKey:_typeKeyPath];
566  }
567 
568  return CPNotFound;
569 }
570 
577 - (CPIndexSet)subrowIndexesForRow:(CPInteger)rowIndex
578 {
579  var object;
580 
581  if (rowIndex === -1)
582  object = _boundArrayOwner;
583  else
584  object = [[self _rowCacheForIndex:rowIndex] rowObject];
585 
586  var subobjects = [self _subrowObjectsOfObject:object],
587  objectsCount = [subobjects count],
588  indexes = [CPMutableIndexSet indexSet],
589  count = [self numberOfRows];
590 
591  for (var i = rowIndex + 1; i < count; i++)
592  {
593  var candidate = [[self _rowCacheForIndex:i] rowObject],
594  indexInSubrows = [[subobjects _representedObject] indexOfObjectIdenticalTo:candidate];
595 
596  if (indexInSubrows !== CPNotFound)
597  {
598  [indexes addIndex:i];
599  objectsCount--;
600 
601  if ([self rowTypeForRow:i] === CPRuleEditorRowTypeCompound)
602  i += [[self subrowIndexesForRow:i] count];
603  }
604 
605  if (objectsCount === 0)
606  break;
607  }
608 
609  return indexes;
610 }
611 
616 - (CPIndexSet)selectedRowIndexes
617 {
618  return [self _selectedSliceIndices];
619 }
620 
626 - (void)selectRowIndexes:(CPIndexSet)indexes byExtendingSelection:(BOOL)extend
627 {
628  var count = [_slices count],
629  lastSelected = [indexes lastIndex];
630 
631  if (lastSelected >= [self numberOfRows])
632  [CPException raise:CPRangeException reason:@"row indexes " + indexes + " are out of range"];
633 
634  if (!extend)
635  [self _deselectAll];
636 
637  while (count--)
638  {
639  var slice = _slices[count],
640  rowIndex = [slice rowIndex],
641  contains = [indexes containsIndex:rowIndex],
642  shouldSelect = (contains && !(extend && [slice _isSelected]));
643 
644  if (contains)
645  [slice _setSelected:shouldSelect];
646  [slice _setLastSelected:(rowIndex === lastSelected)];
647  [slice setNeedsDisplay:YES];
648  }
649 }
650 
660 - (void)addRow:(id)sender
661 {
662  var parentRowIndex = -1,
663  rowtype,
664  numberOfRows = [self numberOfRows],
665  hasRows = (numberOfRows > 0),
666  nestingMode = [self _applicableNestingMode];
667 
668  switch (nestingMode)
669  {
672  if (hasRows)
673  parentRowIndex = 0;
674  break;
676  if (hasRows)
677  return;
679  rowtype = CPRuleEditorRowTypeSimple;
680  break;
682  rowtype = CPRuleEditorRowTypeCompound;
683  if (hasRows)
684  parentRowIndex = 0;
685  break;
686  default:
687  [CPException raise:CPInvalidArgumentException reason:@"Not supported CPRuleEditorNestingMode " + nestingMode];
688  // Compound mode: parentRowIndex=(lastRowType === CPRuleEditorRowTypeCompound)?lastRow :[self parentRowForRow:lastRow]; break;
689  }
690 
691  [self insertRowAtIndex:numberOfRows withType:rowtype asSubrowOfRow:parentRowIndex animate:YES];
692 }
693 
703 - (void)insertRowAtIndex:(int)rowIndex withType:(unsigned int)rowType asSubrowOfRow:(CPInteger)parentRow animate:(BOOL)shouldAnimate
704 {
705 /*
706  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.
707 */
708  var newObject = [self _insertNewRowAtIndex:rowIndex ofType:rowType withParentRow:parentRow];
709 
710  if (rowType === CPRuleEditorRowTypeCompound && !_allowsEmptyCompoundRows)
711  {
712  var subrow = [self _insertNewRowAtIndex:(rowIndex + 1) ofType:CPRuleEditorRowTypeSimple withParentRow:rowIndex];
713  }
714 }
715 
722 - (void)removeRowAtIndex:(int)rowIndex
723 {
724  // TO DO : Any subrows of the deleted row are adopted by the parent of the deleted row, or are made root rows.
725 
726  if (rowIndex < 0 || rowIndex >= [self numberOfRows])
727  [CPException raise:CPRangeException reason:@"row " + rowIndex + " is out of range"];
728 
730 }
731 
739 - (void)removeRowsAtIndexes:(CPIndexSet)rowIndexes includeSubrows:(BOOL)includeSubrows
740 {
741  if ([rowIndexes count] === 0)
742  return;
743 
744  if ([rowIndexes lastIndex] >= [self numberOfRows])
745  [CPException raise:CPRangeException reason:@"rows indexes " + rowIndexes + " are out of range"];
746 
747  var current_index = [rowIndexes firstIndex],
748  parentRowIndex = [self parentRowForRow:current_index],
749  childsIndexes = [CPMutableIndexSet indexSet],
750  subrows;
751 
752  if (parentRowIndex === -1)
753  subrows = [self _rootRowsArray];
754  else
755  {
756  var parentRowObject = [[self _rowCacheForIndex:parentRowIndex] rowObject];
757  subrows = [self _subrowObjectsOfObject:parentRowObject];
758  }
759 
760  while (current_index !== CPNotFound)
761  {
762  var rowObject = [[self _rowCacheForIndex:current_index] rowObject],
763  relativeChildIndex = [[subrows _representedObject] indexOfObjectIdenticalTo:rowObject];
764 
765  if (relativeChildIndex !== CPNotFound)
766  [childsIndexes addIndex:relativeChildIndex];
767 
768  if (includeSubrows && [self rowTypeForRow:current_index] === CPRuleEditorRowTypeCompound)
769  {
770  var more_childs = [self subrowIndexesForRow:current_index];
771  [self removeRowsAtIndexes:more_childs includeSubrows:includeSubrows];
772  }
773 
774  current_index = [rowIndexes indexGreaterThanIndex:current_index];
775  }
776 
777  [subrows removeObjectsAtIndexes:childsIndexes];
778 }
779 
789 - (CPPredicate)predicate
790 {
791  return _predicate;
792 }
793 
798 - (void)reloadPredicate
799 {
800  [self _updatePredicate];
801 }
802 
809 - (CPPredicate)predicateForRow:(CPInteger)aRow
810 {
811  var predicateParts = @{},
812  items = [self criteriaForRow:aRow],
813  count = [items count],
814  predicate,
815  i;
816 
817  for (i = 0; i < count; i++)
818  {
819  var item = [items objectAtIndex:i],
820  //var displayValue = [self _queryValueForItem:item inRow:aRow]; Ask the delegate or get cached value ?.
821  displayValue = [[self displayValuesForRow:aRow] objectAtIndex:i],
822  predpart = [self _sendDelegateRuleEditorPredicatePartsForCriterion:item withDisplayValue:displayValue inRow:aRow];
823 
824  if (predpart)
825  [predicateParts addEntriesFromDictionary:predpart];
826  }
827 
828  if ([self rowTypeForRow:aRow] === CPRuleEditorRowTypeCompound)
829  {
830  var compoundPredicate,
831  subpredicates = [CPMutableArray array],
832  subrowsIndexes = [self subrowIndexesForRow:aRow];
833 
834  if ([subrowsIndexes count] === 0)
835  return nil;
836 
837  var current_index = [subrowsIndexes firstIndex];
838  while (current_index !== CPNotFound)
839  {
840  var subpredicate = [self predicateForRow:current_index];
841  if (subpredicate !== nil)
842  [subpredicates addObject:subpredicate];
843 
844  current_index = [subrowsIndexes indexGreaterThanIndex:current_index];
845  }
846 
847  var compoundType = [predicateParts objectForKey:CPRuleEditorPredicateCompoundType];
848 
849  if ([subpredicates count] === 0)
850  return nil;
851  else
852  {
853  try
854  {
855  compoundPredicate = [[CPCompoundPredicate alloc ] initWithType:compoundType subpredicates:subpredicates];
856  }
857  catch(error)
858  {
859  CPLogConsole(@"Compound predicate error: [%@]\npredicateType:%i", [error description], compoundType);
860  compoundPredicate = nil;
861  }
862  finally
863  {
864  return compoundPredicate;
865  }
866 
867  }
868  }
869 
870  var lhs = [predicateParts objectForKey:CPRuleEditorPredicateLeftExpression],
871  rhs = [predicateParts objectForKey:CPRuleEditorPredicateRightExpression],
872  operator = [predicateParts objectForKey:CPRuleEditorPredicateOperatorType],
873  options = [predicateParts objectForKey:CPRuleEditorPredicateOptions],
874  modifier = [predicateParts objectForKey:CPRuleEditorPredicateComparisonModifier],
875  selector = CPSelectorFromString([predicateParts objectForKey:CPRuleEditorPredicateCustomSelector]);
876 
877  if (lhs === nil)
878  {
879  CPLogConsole(@"missing left expression in predicate parts dictionary");
880  return NULL;
881  }
882 
883  if (rhs === nil)
884  {
885  CPLogConsole(@"missing right expression in predicate parts dictionary");
886  return NULL;
887  }
888 
889  if (selector === nil && operator === nil)
890  {
891  CPLogConsole(@"missing operator and selector in predicate parts dictionary");
892  return NULL;
893  }
894 
895  if (modifier === nil)
896  CPLogConsole(@"missing modifier in predicate parts dictionary. Setting default: CPDirectPredicateModifier");
897 
898  if (options === nil)
899  CPLogConsole(@"missing options in predicate parts dictionary. Setting default: CPCaseInsensitivePredicateOption");
900 
901  try
902  {
903  if (selector !== nil)
905  rightExpression:rhs
906  customSelector:selector
907  ];
908  else
910  rightExpression:rhs
911  modifier:(modifier || CPDirectPredicateModifier)
912  type:operator
913  options:(options || CPCaseInsensitivePredicateOption)
914  ];
915  }
916  catch(error)
917  {
918  CPLogConsole(@"Row predicate error: [" + [error description] + "] for row " + aRow);
919  predicate = nil;
920  }
921  finally
922  {
923  return predicate;
924  }
925 }
926 
936 - (Class)rowClass
937 {
938  return _rowClass;
939 }
940 
946 - (void)setRowClass:(Class)rowClass
947 {
948  if (rowClass === [CPMutableDictionary class])
949  rowClass = [_CPRuleEditorRowObject class];
950 
951  _rowClass = rowClass;
952 }
953 
961 - (CPString)rowTypeKeyPath
962 {
963  return _typeKeyPath;
964 }
965 
971 - (void)setRowTypeKeyPath:(CPString)keyPath
972 {
973  if (_typeKeyPath !== keyPath)
974  _typeKeyPath = keyPath;
975 }
976 
984 - (CPString)subrowsKeyPath
985 {
986  return _subrowsArrayKeyPath;
987 }
988 
994 - (void)setSubrowsKeyPath:(CPString)keyPath
995 {
996  if (_subrowsArrayKeyPath !== keyPath)
997  _subrowsArrayKeyPath = keyPath;
998 }
999 
1007 - (CPString)criteriaKeyPath
1008 {
1009  return _itemsKeyPath;
1010 }
1011 
1017 - (void)setCriteriaKeyPath:(CPString)keyPath
1018 {
1019  if (_itemsKeyPath !== keyPath)
1020  _itemsKeyPath = keyPath;
1021 }
1022 
1030 - (CPString)displayValuesKeyPath
1031 {
1032  return _valuesKeyPath;
1033 }
1034 
1040 - (void)setDisplayValuesKeyPath:(CPString)keyPath
1041 {
1042  if (_valuesKeyPath !== keyPath)
1043  _valuesKeyPath = keyPath;
1044 }
1045 
1055 - (id)animation
1056 {
1057  return _currentAnimation;
1058 }
1059 
1066 - (void)setAnimation:(CPViewAnimation)animation
1067 {
1068  _currentAnimation = animation;
1069  [_currentAnimation setDelegate:self];
1070 }
1071 
1117 - (BOOL)acceptsFirstResponder
1118 {
1119  return YES;
1120 }
1121 
1122 - (void)keyDown:(CPEvent)event
1123 {
1124  if (!_suppressKeyDownHandling && [self _applicableNestingMode] === CPRuleEditorNestingModeCompound && !_isKeyDown && ([event modifierFlags] & CPAlternateKeyMask))
1125  {
1126  [_slices makeObjectsPerformSelector:@selector(_configurePlusButtonByRowType:) withObject:CPRuleEditorRowTypeCompound];
1127  }
1128 
1129  _isKeyDown = YES;
1130 }
1131 
1132 - (void)keyUp:(CPEvent)event
1133 {
1134  if (!_suppressKeyDownHandling)
1135  {
1136  [_slices makeObjectsPerformSelector:@selector(_configurePlusButtonByRowType:) withObject:CPRuleEditorRowTypeSimple];
1137  }
1138 
1139  _isKeyDown = NO;
1140 }
1141 
1142 - (_CPRuleEditorViewSliceDropSeparator)_createSliceDropSeparator
1143 {
1144  var view = [[_CPRuleEditorViewSliceDropSeparator alloc] initWithFrame:CGRectMake(0, -10, [self frame].size.width, 2)];
1145  [view setAutoresizingMask:CPViewWidthSizable];
1146  return view;
1147 }
1148 
1149 - (BOOL)_suppressKeyDownHandling
1150 {
1151  return _suppressKeyDownHandling;
1152 }
1153 
1154 - (BOOL)_wantsRowAnimations
1155 {
1156  return (_currentAnimation !== nil);
1157 }
1158 
1159 - (void)_updateButtonVisibilities
1160 {
1161  [_slices makeObjectsPerformSelector:@selector(_updateButtonVisibilities)];
1162 }
1163 
1164 - (float)_alignmentGridWidth
1165 {
1166  return _alignmentGridWidth;
1167 }
1168 
1169 - (float)_minimumFrameHeight
1170 {
1171  return 26.;
1172 }
1173 
1174 - (CPRuleEditorNestingMode)_applicableNestingMode
1175 {
1176  if (!_nestingModeDidChange)
1177  return _nestingMode;
1178 
1179  var a = (_nestingMode === CPRuleEditorNestingModeCompound || _nestingMode === CPRuleEditorNestingModeSimple),
1180  b = ([self rowTypeForRow:0] === CPRuleEditorRowTypeCompound);
1181 
1182  if (a === b)
1183  return _nestingMode;
1184 
1186 }
1187 
1188 - (BOOL)_shouldHideAddButtonForSlice:(id)slice
1189 {
1190  return (!_editable || [self _applicableNestingMode] === CPRuleEditorNestingModeSingle);
1191 }
1192 
1193 - (BOOL)_shouldHideSubtractButtonForSlice:(id)slice
1194 {
1195  if (!_editable)
1196  return YES;
1197 
1198  if (!_disallowEmpty)
1199  return NO;
1200 
1201  var shouldHide,
1202  rowIndex = [slice rowIndex],
1203  parentIndex = [self parentRowForRow:rowIndex],
1204  subrowsIndexes = [self subrowIndexesForRow:parentIndex],
1205  nestingMode = [self _applicableNestingMode];
1206 
1207  switch (nestingMode)
1208  {
1210  case CPRuleEditorNestingModeSimple: shouldHide = ([subrowsIndexes count] === 1 && !_allowsEmptyCompoundRows) || parentIndex === -1;
1211  break;
1212  case CPRuleEditorNestingModeList: shouldHide = ([self numberOfRows] === 1);
1213  break;
1214  case CPRuleEditorNestingModeSingle: shouldHide = YES;
1215  break;
1216  default: shouldHide = NO;
1217  }
1218 
1219  return shouldHide;
1220 }
1221 
1222 #pragma mark Rows management
1223 
1224 - (id)_rowCacheForIndex:(int)index
1225 {
1226  return [_rowCache objectAtIndex:index];
1227 }
1228 
1229 - (id)_searchCacheForRowObject:(id)rowObject
1230 {
1231  var count = [_rowCache count],
1232  i;
1233 
1234  for (i = 0; i < count; i++)
1235  {
1236  var cache = _rowCache[i];
1237  if ([cache rowObject] === rowObject)
1238  return cache;
1239  }
1240 
1241  return nil;
1242 }
1243 
1244 - (int)_rowIndexForRowObject:(id)rowobject
1245 {
1246  if (rowobject === _boundArrayOwner)
1247  return -1;
1248 
1249  return [[self _searchCacheForRowObject:rowobject] rowIndex]; // Pas bon car le rowIndex du row cache n'est pas synchro avec la position dans _rowCache.
1250 }
1251 
1252 - (CPMutableArray)_subrowObjectsOfObject:(id)object
1253 {
1254  if (object === _boundArrayOwner)
1255  return [self _rootRowsArray];
1256 
1257  return [object mutableArrayValueForKey:_subrowsArrayKeyPath];
1258 }
1259 
1260 - (CPIndexSet)_childlessParentsIfSlicesWereDeletedAtIndexes:(id)indexes
1261 {
1262  var childlessParents = [CPIndexSet indexSet],
1263  current_index = [indexes firstIndex];
1264 
1265  while (current_index !== CPNotFound)
1266  {
1267  var parentIndex = [self parentRowForRow:current_index],
1268  subrowsIndexes = [self subrowIndexesForRow:parentIndex];
1269 
1270  if ([subrowsIndexes count] === 1)
1271  {
1272  if (parentIndex !== -1)
1273  return [CPIndexSet indexSetWithIndex:0];
1274 
1275  var childlessGranPa = [self _childlessParentsIfSlicesWereDeletedAtIndexes:[CPIndexSet indexSetWithIndex:parentIndex]];
1276  [childlessParents addIndexes:childlessGranPa];
1277  }
1278 
1279  current_index = [indexes indexGreaterThanIndex:current_index];
1280  }
1281 
1282  return childlessParents;
1283  // (id)-[RuleEditor _includeSubslicesForSlicesAtIndexes:]
1284 }
1285 
1286 - (CPIndexSet)_includeSubslicesForSlicesAtIndexes:(CPIndexSet)indexes
1287 {
1288  var subindexes = [indexes copy],
1289  current_index = [indexes firstIndex];
1290 
1291  while (current_index !== CPNotFound)
1292  {
1293  var sub = [self subrowIndexesForRow:current_index];
1294  [subindexes addIndexes:[self _includeSubslicesForSlicesAtIndexes:sub]];
1295  current_index = [indexes indexGreaterThanIndex:current_index];
1296  }
1297 
1298  return subindexes;
1299 }
1300 
1301 - (void)_deleteSlice:(id)slice
1302 {
1303  var rowindexes = [CPIndexSet indexSetWithIndex:[slice rowIndex]];
1304 
1305  if (!_allowsEmptyCompoundRows)
1306  {
1307  var childlessIndexes = [self _childlessParentsIfSlicesWereDeletedAtIndexes:rowindexes];
1308  if ([childlessIndexes count] > 0)
1309  rowindexes = childlessIndexes;
1310  }
1311 
1312  [self removeRowsAtIndexes:rowindexes includeSubrows:YES];
1313 
1314  [self _updatePredicate];
1315  [self _sendRuleAction];
1316  [self _postRuleOptionChangedNotification];
1317  [self _postRowCountChangedNotificationOfType:CPRuleEditorRowsDidChangeNotification indexes:rowindexes];
1318 }
1319 
1320 - (CPArray)_rootRowsArray
1321 {
1322  return [_boundArrayOwner mutableArrayValueForKey:_boundArrayKeyPath];
1323 }
1324 
1325 - (BOOL)_nextUnusedItems:(CPArray)items andValues:(CPArray)values forRow:(CPInteger)rowIndex forRowType:(unsigned int)type
1326 {
1327  var parentItem = [items lastObject], // if empty items array, this is NULL aka the root item;
1328  childrenCount = [self _queryNumberOfChildrenOfItem:parentItem withRowType:type],
1329  foundIndex = CPNotFound;
1330 
1331  if (childrenCount === 0)
1332  return NO;
1333 
1334  var current_criterions = [CPMutableArray array],
1335  count = [self numberOfRows],
1336  row;
1337 
1338  for (row = 0; row < count; row++) // num of rows should be num of siblings of parentItem
1339  {
1340  var aCriteria = [self criteriaForRow:row],
1341  itemIndex = [items count];
1342 
1343  if ([self rowTypeForRow:row] === type && itemIndex < [aCriteria count])
1344  {
1345  var crit = [aCriteria objectAtIndex:itemIndex];
1346  [current_criterions addObject:crit];
1347  }
1348  }
1349 
1350  while (foundIndex === CPNotFound)
1351  {
1352  var buffer = [CPMutableArray arrayWithArray:current_criterions],
1353  i;
1354  for (i = 0; i < childrenCount; i++)
1355  {
1356  var child = [self _queryChild:i ofItem:parentItem withRowType:type];
1357  if ([current_criterions indexOfObject:child] === CPNotFound)
1358  {
1359  foundIndex = i;
1360  break;
1361  }
1362  }
1363 
1364  if (foundIndex === CPNotFound)
1365  {
1366  for (var k = 0; k < childrenCount; k++)
1367  {
1368  var anobject = [self _queryChild:k ofItem:parentItem withRowType:type],
1369  index = [buffer indexOfObject:anobject];
1370  if (index !== CPNotFound)
1371  [buffer removeObjectAtIndex:index];
1372  }
1373 
1374  current_criterions = buffer;
1375  }
1376  }
1377 
1378  var foundItem = [self _queryChild:foundIndex ofItem:parentItem withRowType:type],
1379  foundValue = [self _queryValueForItem:foundItem inRow:rowIndex];
1380 
1381  [items addObject:foundItem];
1382  [values addObject:foundValue];
1383 
1384  return YES;
1385 }
1386 
1387 - (CPMutableArray)_getItemsAndValuesToAddForRow:(CPInteger)rowIndex ofType:(CPRuleEditorRowType)type
1388 {
1389  //var cachedItemsAndValues = _itemsAndValuesToAddForRowType[type];
1390  //if (cachedItemsAndValues)
1391  // return cachedItemsAndValues;
1392 
1393  var itemsAndValues = [CPMutableArray array],
1394  items = [CPMutableArray array],
1395  values = [CPMutableArray array],
1396  unusedItems = YES;
1397 
1398  while (unusedItems)
1399  unusedItems = [self _nextUnusedItems:items andValues:values forRow:rowIndex forRowType:type];
1400 
1401  var count = [items count];
1402 
1403  for (var i = 0; i < count; i++)
1404  {
1405  var item = [items objectAtIndex:i],
1406  value = [values objectAtIndex:i],
1407  itemAndValue = @{
1408  "item": item,
1409  "value": value,
1410  };
1411 
1412  [itemsAndValues addObject:itemAndValue];
1413  }
1414 
1415  return itemsAndValues;
1416 }
1417 
1418 - (void)_addOptionFromSlice:(id)slice ofRowType:(unsigned int)type
1419 {
1420  // for CPRuleEditorNestingModeSimple only
1421 
1422  var rowIndexEvent = [slice rowIndex],
1423  rowTypeEvent = [self rowTypeForRow:rowIndexEvent],
1424  insertIndex = rowIndexEvent + 1,
1425  parentRowIndex = (rowTypeEvent === CPRuleEditorRowTypeCompound) ? rowIndexEvent:[self parentRowForRow:rowIndexEvent];
1426 
1427  [self insertRowAtIndex:insertIndex withType:type asSubrowOfRow:parentRowIndex animate:YES];
1428 }
1429 
1430 - (id)_insertNewRowAtIndex:(int)insertIndex ofType:(CPRuleEditorRowType)rowtype withParentRow:(CPInteger)parentRowIndex
1431 {
1432  var row = [[[self rowClass] alloc] init],
1433  itemsandvalues = [self _getItemsAndValuesToAddForRow:insertIndex ofType:rowtype],
1434  newitems = [itemsandvalues valueForKey:@"item"],
1435  newvalues = [itemsandvalues valueForKey:@"value"];
1436 
1437  [row setValue:newitems forKey:_itemsKeyPath];
1438  [row setValue:newvalues forKey:_valuesKeyPath];
1439  [row setValue:rowtype forKey:_typeKeyPath];
1440  [row setValue:[CPMutableArray array] forKey:_subrowsArrayKeyPath];
1441 
1442  var subrowsObjects;
1443  if (parentRowIndex === -1 || [self _applicableNestingMode] === CPRuleEditorNestingModeList)
1444  subrowsObjects = [self _rootRowsArray];
1445  else
1446  {
1447  var parentRowObject = [[self _rowCacheForIndex:parentRowIndex] rowObject];
1448  subrowsObjects = [self _subrowObjectsOfObject:parentRowObject];
1449  }
1450 
1451  var relInsertIndex = insertIndex - parentRowIndex - 1;
1452  [subrowsObjects insertObject:row atIndex:relInsertIndex];
1453 
1454  [self _updatePredicate];
1455  [self _sendRuleAction];
1456  [self _postRuleOptionChangedNotification];
1457  [self _postRowCountChangedNotificationOfType:CPRuleEditorRowsDidChangeNotification indexes:[CPIndexSet indexSetWithIndex:insertIndex]];
1458 
1459  return row;
1460 }
1461 
1462 #pragma mark Key value observing
1463 
1464 - (void)_startObservingRowObjectsRecursively:(CPArray)rowObjects
1465 {
1466  [_boundArrayOwner addObserver:self forKeyPath:_boundArrayKeyPath options:CPKeyValueObservingOptionOld | CPKeyValueObservingOptionNew context:boundArrayContext];
1467 
1468  var count = [rowObjects count];
1469 
1470  for (var i = 0; i < count; i++)
1471  {
1472  var rowObject = [rowObjects objectAtIndex:i];
1473 
1474  [rowObject addObserver:self forKeyPath:_itemsKeyPath options:CPKeyValueObservingOptionOld | CPKeyValueObservingOptionNew context:itemsContext];
1475  [rowObject addObserver:self forKeyPath:_valuesKeyPath options:CPKeyValueObservingOptionOld | CPKeyValueObservingOptionNew context:valuesContext];
1476  [rowObject addObserver:self forKeyPath:_subrowsArrayKeyPath options:CPKeyValueObservingOptionOld | CPKeyValueObservingOptionNew context:subrowsContext];
1477 
1478  var subrows = [self _subrowObjectsOfObject:rowObject];
1479  if ([subrows count] > 0)
1480  [self _startObservingRowObjectsRecursively:subrows];
1481  }
1482  // ORIG IMPL : calls +keyPathsForValuesAffectingValueForKey: for all keys
1483 }
1484 
1485 - (void)_stopObservingRowObjectsRecursively:(CPArray)rowObjects
1486 {
1487  [_boundArrayOwner removeObserver:self forKeyPath:_boundArrayKeyPath];
1488 
1489  var count = [rowObjects count];
1490 
1491  for (var i = 0; i < count; i++)
1492  {
1493  var rowObject = [rowObjects objectAtIndex:i];
1494  [rowObject removeObserver:self forKeyPath:_itemsKeyPath];
1495  [rowObject removeObserver:self forKeyPath:_valuesKeyPath];
1496  [rowObject removeObserver:self forKeyPath:_subrowsArrayKeyPath];
1497 
1498  var subrows = [rowObject valueForKey:_subrowsArrayKeyPath];
1499  if ([subrows count] > 0)
1500  [self _stopObservingRowObjectsRecursively:subrows];
1501  }
1502 }
1503 
1504 - (void)observeValueForKeyPath:(CPString)keypath ofObject:(id)object change:(CPDictionary)change context:(void)context
1505 {
1506  var changeKind = [change objectForKey:CPKeyValueChangeKindKey],
1507  changeNewValue = [change objectForKey:CPKeyValueChangeNewKey],
1508  changeOldValue = [change objectForKey:CPKeyValueChangeOldKey],
1509  newRows,
1510  oldRows;
1511 
1512  if (context === boundArrayContext || context === subrowsContext)
1513  {
1514  if (changeKind === CPKeyValueChangeSetting)
1515  {
1516  newRows = changeNewValue;
1517  oldRows = changeOldValue;
1518 
1519  }
1520  else if (changeKind === CPKeyValueChangeInsertion)
1521  {
1522  newRows = [self _subrowObjectsOfObject:object];
1523  oldRows = [CPArray arrayWithArray:newRows];
1524  [oldRows removeObjectsInArray:changeNewValue];
1525  }
1526 
1527  [self _changedRowArray:newRows withOldRowArray:oldRows forParent:object];
1528  [self _reconfigureSubviewsAnimate:[self _wantsRowAnimations]];
1529  }
1530 }
1531 
1532 - (void)_changedItem:(id)fromItem toItem:(id)toItem inRow:(CPInteger)aRow atCriteriaIndex:(int)fromItemIndex
1533 {
1534  var criteria = [self criteriaForRow:aRow],
1535  displayValues = [self displayValuesForRow:aRow],
1536  rowType = [self rowTypeForRow:aRow],
1537  anItem = toItem,
1538 
1539  items = [criteria subarrayWithRange:CPMakeRange(0, fromItemIndex)],
1540  values = [displayValues subarrayWithRange:CPMakeRange(0, fromItemIndex)];
1541 
1542  _lastRow = aRow;
1543 
1544  while (YES)
1545  {
1546  [items addObject:anItem];
1547  var value = [self _queryValueForItem:anItem inRow:aRow];
1548  [values addObject:value];
1549 
1550  if (![self _queryNumberOfChildrenOfItem:anItem withRowType:rowType])
1551  break;
1552 
1553  anItem = [self _queryChild:0 ofItem:anItem withRowType:rowType];
1554  }
1555 
1556  var object = [[self _rowCacheForIndex:aRow] rowObject];
1557  [object setValue:items forKey:_itemsKeyPath];
1558  [object setValue:values forKey:_valuesKeyPath];
1559 
1560  var slice = [_slices objectAtIndex:aRow];
1561  [slice _reconfigureSubviews];
1562 
1563  [self _updatePredicate];
1564  [self _sendRuleAction];
1565  [self _postRuleOptionChangedNotification];
1566 }
1567 
1568 - (void)_changedRowArray:(CPArray)newRows withOldRowArray:(CPArray)oldRows forParent:(id)parentRowObject
1569 {
1570  var newRowCount = [newRows count],
1571  oldRowCount = [oldRows count],
1572  deltaCount = newRowCount - oldRowCount,
1573  minusCount = MIN(newRowCount, oldRowCount),
1574  maxCount = MAX(newRowCount, oldRowCount),
1575 
1576  insertCacheIndexes = [CPIndexSet indexSet],
1577  newCaches = [CPArray array],
1578 
1579  parentCacheIndentation,
1580  parentCacheIndex = [self _rowIndexForRowObject:parentRowObject],
1581 
1582  newRowCacheIndex = 0,
1583  changeStartIndex = 0;
1584 
1585  [self _stopObservingRowObjectsRecursively:oldRows];
1586  [self _startObservingRowObjectsRecursively:newRows];
1587 
1588  //var gindexes = [self _globalIndexesForSubrowIndexes:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0,oldRowCount)] ofParentObject:parentRowObject];
1589 
1590  if (parentCacheIndex === -1)
1591  parentCacheIndentation = -1;
1592  else
1593  parentCacheIndentation = [[self _rowCacheForIndex:parentCacheIndex] indentation];
1594 
1595  for (; newRowCacheIndex < newRowCount; newRowCacheIndex++)
1596  {
1597  var newCacheGlobalIndex = (parentCacheIndex + 1) + newRowCacheIndex,
1598  obj = [newRows objectAtIndex:newRowCacheIndex],
1599  newRowType = [obj valueForKey:_typeKeyPath],
1600  cache = [[_CPRuleEditorCache alloc] init];
1601 
1602  [cache setRowObject:obj];
1603  [cache setRowIndex:newCacheGlobalIndex];
1604  [cache setIndentation:parentCacheIndentation + 1];
1605 
1606  [insertCacheIndexes addIndex:newCacheGlobalIndex];
1607  [newCaches addObject:cache];
1608  }
1609 
1610  //var lastCacheIndex = [self _rowIndexForRowObject:[oldRows lastObject]];
1611  [_rowCache removeObjectsInRange:CPMakeRange(parentCacheIndex + 1, [oldRows count])];
1612  [_rowCache insertObjects:newCaches atIndexes:insertCacheIndexes];
1613 
1614  for (; changeStartIndex < minusCount; changeStartIndex++)
1615  {
1616  var oldrow = [oldRows objectAtIndex:changeStartIndex],
1617  newrow = [newRows objectAtIndex:changeStartIndex];
1618 
1619  if (newrow !== oldrow)
1620  break;
1621  }
1622 
1623  var replaceCount = (deltaCount === 0) ? maxCount : maxCount - minusCount,
1624  startIndex = parentCacheIndex + changeStartIndex + 1;
1625 
1626  if (deltaCount <= 0)
1627  {
1628  var removeIndexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(startIndex, replaceCount)],
1629  removeSlices = [_slices objectsAtIndexes:removeIndexes];
1630 
1631  [removeSlices makeObjectsPerformSelector:@selector(removeFromSuperview)];
1632  [_slices removeObjectsAtIndexes:removeIndexes];
1633  }
1634 
1635  if (deltaCount >= 0)
1636  {
1637  var newIndentation = parentCacheIndentation + 1,
1638  newIndex = startIndex;
1639 
1640  for (; newIndex < startIndex + replaceCount; newIndex++)
1641  {
1642  var newslice = [self _newSlice],
1643  rowType = [self rowTypeForRow:newIndex];
1644 
1645  [newslice setRowIndex:newIndex];
1646  [newslice setIndentation:newIndentation];
1647  [newslice _setRowType:rowType];
1648  [newslice _configurePlusButtonByRowType:CPRuleEditorRowTypeSimple];
1649 
1650  [_slices insertObject:newslice atIndex:newIndex];
1651  }
1652  }
1653 
1654  var emptyArray = [CPArray array],
1655  count = [oldRows count],
1656  n;
1657  for (n = 0; n < count; n++)
1658  {
1659  var oldRow = [oldRows objectAtIndex:n],
1660  subOldRows = [self _subrowObjectsOfObject:oldRow];
1661 
1662  if ([subOldRows count] > 0)
1663  [self _changedRowArray:emptyArray withOldRowArray:subOldRows forParent:oldRow];
1664  }
1665 
1666  count = [newRows count];
1667  for (n = 0; n < count; n++)
1668  {
1669  var newRow = [newRows objectAtIndex:n],
1670  subnewRows = [self _subrowObjectsOfObject:newRow];
1671 
1672  if ([subnewRows count] > 0)
1673  [self _changedRowArray:subnewRows withOldRowArray:emptyArray forParent:newRow];
1674  }
1675 }
1676 
1677 - (void)bind:(CPString)aBinding toObject:(id)observableController withKeyPath:(CPString)aKeyPath options:(CPDictionary)options
1678 {
1679  if ([aBinding isEqualToString:@"rows"])
1680  {
1681  [self unbind:aBinding];
1682  [self _setBoundDataSource:observableController withKeyPath:aKeyPath options:options];
1683 
1684  [_rowCache removeAllObjects];
1685  [_slices removeAllObjects];
1686 
1687  var newRows = [CPArray array],
1688  oldRows = [self _rootRowsArray];
1689 
1690  [self _changedRowArray:newRows withOldRowArray:oldRows forParent:_boundArrayOwner];
1691  }
1692  else
1693  [super bind:aBinding toObject:observableController withKeyPath:aKeyPath options:options];
1694 }
1695 
1696 - (void)unbind:(CPString)object
1697 {
1698  _rowClass = [_CPRuleEditorRowObject class];
1699  [super unbind:object];
1700 }
1701 
1702 - (void)_setBoundDataSource:(id)datasource withKeyPath:(CPString)keyPath options:(CPDictionary)options
1703 {
1704  if ([datasource respondsToSelector:@selector(objectClass)])
1705  _rowClass = [datasource objectClass];
1706 
1707  _boundArrayKeyPath = keyPath;
1708  _boundArrayOwner = datasource;
1709 
1710  //var boundRows = [_boundArrayOwner valueForKey:_boundArrayKeyPath];
1711 
1712  [_boundArrayOwner addObserver:self forKeyPath:_boundArrayKeyPath options:CPKeyValueObservingOptionOld | CPKeyValueObservingOptionNew context:boundArrayContext];
1713 
1714  //if ([boundRows isKindOfClass:[CPArray class]] && [boundRows count] > 0)
1715  // [_boundArrayOwner setValue:boundRows forKey:_boundArrayKeyPath];
1716 }
1717 
1718 - (void)_setPredicate:(CPPredicate)predicate
1719 {
1720  if (_predicate !== predicate)
1721  _predicate = predicate;
1722 }
1723 
1724 - (void)_updatePredicate
1725 {
1726  if (_delegateWantsValidation)
1727  {
1728  if (![self _delegateRespondsToRuleEditorPredicatePartsForCriterionWithDisplayValueInRow])
1729  return;
1730 
1731  _delegateWantsValidation = NO;
1732  }
1733 
1734  var subpredicates = [CPMutableArray array],
1735  subindexes = [self subrowIndexesForRow:-1],
1736  current_index = [subindexes firstIndex];
1737 
1738  while (current_index !== CPNotFound)
1739  {
1740  var subpredicate = [self predicateForRow:current_index];
1741 
1742  if (subpredicate !== nil)
1743  [subpredicates addObject:subpredicate];
1744 
1745  current_index = [subindexes indexGreaterThanIndex:current_index];
1746  }
1747 
1748  var new_predicate = [[CPCompoundPredicate alloc] initWithType:CPOrPredicateType subpredicates:subpredicates];
1749 
1750  [self _setPredicate:new_predicate];
1751 }
1752 
1753 - (_CPRuleEditorViewSliceRow)_newSlice
1754 {
1755  var sliceRect = CGRectMake(0, 0, CGRectGetWidth([self frame]), 0),
1756  slice = [self _createNewSliceWithFrame:sliceRect ruleEditorView:self];
1757 
1758  return slice;
1759 }
1760 
1761 - (_CPRuleEditorViewSliceRow)_createNewSliceWithFrame:(CGRect)frame ruleEditorView:(CPRuleEditor)editor
1762 {
1763  return [[_CPRuleEditorViewSliceRow alloc] initWithFrame:frame ruleEditorView:editor];
1764 }
1765 
1766 - (void)_reconfigureSubviewsAnimate:(BOOL)animate
1767 {
1768  var viewAnimations = [CPMutableArray array],
1769  added_slices = [CPMutableArray array],
1770  count = [_slices count];
1771 
1772  [self _updateSliceRows];
1773 
1774  if ([[self superview] isKindOfClass:[CPClipView class]])
1775  [self setFrameSize:CGSizeMake(CGRectGetWidth([self frame]), count * _sliceHeight)];
1776 
1777  for (var i = 0; i < count; i++)
1778  {
1779  var aslice = [_slices objectAtIndex:i],
1780  targetRect = [aslice _animationTargetRect],
1781  startRect = [aslice frame],
1782  startIndex = [aslice rowIndex] - 1;
1783 
1784  if ([aslice superview] === nil)
1785  {
1786  startRect = CGRectMake(0, startIndex * _sliceHeight, CGRectGetWidth(startRect), _sliceHeight);
1787  [aslice _reconfigureSubviews];
1788  [added_slices addObject:aslice];
1789  }
1790 
1791  if (animate)
1792  {
1793  var animation = @{};
1794  [animation setObject:aslice forKey:CPViewAnimationTargetKey];
1795  [animation setObject:startRect forKey:CPViewAnimationStartFrameKey];
1796  [animation setObject:targetRect forKey:CPViewAnimationEndFrameKey];
1797 
1798  [viewAnimations insertObject:animation atIndex:0];
1799  }
1800  else
1801  [aslice setFrame:targetRect];
1802  }
1803 
1804  var addcount = [added_slices count];
1805  for (var i = 0; i < addcount; i++)
1806  [_slicesHolder addSubview:added_slices[i] positioned:CPWindowBelow relativeTo:nil];
1807 
1808  if (animate)
1809  {
1810  [_currentAnimation setViewAnimations:viewAnimations];
1811  [_currentAnimation startAnimation];
1812  }
1813 
1814  _lastRow = [self numberOfRows] - 1;
1815 
1816  if (_lastRow === -1)
1817  _nestingModeDidChange = NO;
1818 
1819  [self setNeedsDisplay:YES];
1820  [_slices makeObjectsPerformSelector:@selector(_updateButtonVisibilities)];
1821 }
1822 
1823 - (void)animationDidEnd:(CPViewAnimation)animation
1824 {
1825 // var nextSimple = [self _getItemsAndValuesToAddForRow:0 ofType:CPRuleEditorRowTypeSimple],
1826 // nextCompound = [self _getItemsAndValuesToAddForRow:0 ofType:CPRuleEditorRowTypeCompound];
1827 
1828 // _itemsAndValuesToAddForRowType = {CPRuleEditorRowTypeSimple:nextSimple, CPRuleEditorRowTypeCompound:nextCompound};
1829 }
1830 
1831 - (void)_updateSliceRows
1832 {
1833  var width = [self frame].size.width,
1834  count = [_slices count];
1835 
1836  for (var i = 0; i < count; i++)
1837  {
1838  var slice = [_slices objectAtIndex:i],
1839  targetRect = CGRectMake(0, i * _sliceHeight, width, _sliceHeight);
1840 
1841  [slice setRowIndex:i];
1842  [slice _setAnimationTargetRect:targetRect];
1843  }
1844 }
1845 
1846 - (CPArray)_backgroundColors
1847 {
1848  return [self valueForThemeAttribute:@"alternating-row-colors"];
1849 }
1850 
1851 - (CPColor)_selectedRowColor
1852 {
1853  return [self valueForThemeAttribute:@"selected-color"];
1854 }
1855 
1856 - (CPColor)_sliceTopBorderColor
1857 {
1858  return [self valueForThemeAttribute:@"slice-top-border-color"];
1859 }
1860 
1861 - (CPColor)_sliceBottomBorderColor
1862 {
1863  return [self valueForThemeAttribute:@"slice-bottom-border-color"];
1864 }
1865 
1866 - (CPColor)_sliceLastBottomBorderColor
1867 {
1868  return [self valueForThemeAttribute:@"slice-last-bottom-border-color"];
1869 }
1870 
1871 - (CPFont)font
1872 {
1873  return [self valueForThemeAttribute:@"font"];
1874 }
1875 
1876 - (CPColor)_fontColor
1877 {
1878  return [self valueForThemeAttribute:@"font-color"];
1879 }
1880 
1881 - (CPImage)_imageAdd
1882 {
1883  return [self valueForThemeAttribute:@"add-image" inState:CPThemeStateNormal];
1884 }
1885 
1886 - (CPImage)_imageAddHighlighted
1887 {
1888  return [self valueForThemeAttribute:@"add-image" inState:CPThemeStateHighlighted];
1889 }
1890 
1891 - (CPImage)_imageRemove
1892 {
1893  return [self valueForThemeAttribute:@"remove-image" inState:CPThemeStateNormal];
1894 }
1895 
1896 - (CPImage)_imageRemoveHighlighted
1897 {
1898  return [self valueForThemeAttribute:@"remove-image" inState:CPThemeStateHighlighted];
1899 }
1900 
1901 - (CPVerticalTextAlignment)_verticalAlignment
1902 {
1903  return [self valueForThemeAttribute:@"vertical-alignment"];
1904 }
1905 
1906 - (CPString)_toolTipForAddCompoundRowButton
1907 {
1908  return [_standardLocalizer localizedStringForString:@"Add compound row"];
1909 }
1910 
1911 - (CPString)_toolTipForAddSimpleRowButton
1912 {
1913  return [_standardLocalizer localizedStringForString:@"Add row"];
1914 }
1915 
1916 - (CPString)_toolTipForDeleteRowButton
1917 {
1918  return [_standardLocalizer localizedStringForString:@"Delete row"];
1919 }
1920 
1921 - (void)_updateSliceIndentations
1922 {
1923  [self _updateSliceIndentationAtIndex:0 toIndentation:0 withIndexSet:[self subrowIndexesForRow:0]];
1924 }
1925 
1926 - (void)_updateSliceIndentationAtIndex:(int)index toIndentation:(int)indentation withIndexSet:(id)indexes
1927 {
1928  var current_index = [indexes firstIndex];
1929 
1930  while (current_index !== CPNotFound)
1931  {
1932  var subindexes = [self subrowIndexesForRow:index];
1933  [self _updateSliceIndentationAtIndex:current_index toIndentation:indentation + 1 withIndexSet:subindexes];
1934  current_index = [indexes indexGreaterThanIndex:current_index];
1935  }
1936 
1937  [[_slices objectAtIndex:index] setIndentation:indentation];
1938 }
1939 
1940 - (CPArray)_selectedSlices
1941 {
1942  var _selectedSlices = [CPMutableArray array],
1943  count = [_slices count],
1944  i;
1945 
1946  for (i = 0; i < count; i++)
1947  {
1948  var slice = _slices[i];
1949  if ([slice _isSelected])
1950  [_selectedSlices addObject:slice];
1951  }
1952 
1953  return _selectedSlices;
1954 }
1955 
1956 - (int)_lastSelectedSliceIndex
1957 {
1958  var lastIndex = -1,
1959  count = [_slices count],
1960  i;
1961 
1962  for (i = 0; i < count; i++)
1963  {
1964  var slice = _slices[i];
1965  if ([slice _isLastSelected])
1966  return [slice rowIndex];
1967  }
1968 
1969  return CPNotFound;
1970 }
1971 
1972 - (void)_mouseUpOnSlice:(id)slice withEvent:(CPEvent)event
1973 {
1974  if ([slice _rowType] !== CPRuleEditorRowTypeSimple)
1975  return;
1976 
1977  var modifierFlags = [event modifierFlags],
1978  extend = (modifierFlags & CPCommandKeyMask) || (modifierFlags & CPShiftKeyMask),
1979  rowIndexes = [CPIndexSet indexSetWithIndex:[slice rowIndex]];
1980 
1981  [self selectRowIndexes:rowIndexes byExtendingSelection:extend];
1982 }
1983 
1984 - (void)_mouseDownOnSlice:(id)slice withEvent:(CPEvent)event
1985 {
1986 }
1987 
1988 - (void)_rightMouseDownOnSlice:(_CPRuleEditorViewSlice)slice withEvent:(CPEvent)event
1989 {
1990 }
1991 
1992 - (void)_performClickOnSlice:(id)slice withEvent:(CPEvent)event
1993 {
1994 }
1995 
1996 - (void)_setSuppressKeyDownHandling:(BOOL)flag
1997 {
1998  _suppressKeyDownHandling = flag;
1999 }
2000 
2001 - (void)selectAll:(id)sender
2002 {
2003  var count = [_slices count];
2004 
2005  while (count--)
2006  {
2007  var slice = _slices[count];
2008  [slice _setSelected:YES];
2009  [slice setNeedsDisplay:YES];
2010  }
2011 }
2012 
2013 - (void)_deselectAll
2014 {
2015  var count = [_slices count];
2016 
2017  while (count--)
2018  {
2019  var slice = _slices[count];
2020  [slice _setSelected:NO];
2021  [slice _setLastSelected:NO];
2022  [slice setNeedsDisplay:YES];
2023  }
2024 }
2025 
2026 - (int)_queryNumberOfChildrenOfItem:(id)item withRowType:(CPRuleEditorRowType)type
2027 {
2028  return [_ruleDelegate ruleEditor:self numberOfChildrenForCriterion:item withRowType:type];
2029 }
2030 
2031 - (id)_queryChild:(int)childIndex ofItem:(id)item withRowType:(CPRuleEditorRowType)type
2032 {
2033  return [_ruleDelegate ruleEditor:self child:childIndex forCriterion:item withRowType:type];
2034 }
2035 
2036 - (id)_queryValueForItem:(id)item inRow:(CPInteger)row
2037 {
2038  return [_ruleDelegate ruleEditor:self displayValueForCriterion:item inRow:row];
2039 }
2040 
2041 - (int)_lastRow
2042 {
2043  return _lastRow;
2044 }
2045 
2046 - (int)_countOfRowsStartingAtObject:(id)object
2047 {
2048  var index = [self _rowIndexForRowObject:object];
2049  return ([self numberOfRows] - index);
2050 }
2051 
2052 - (void)_setAlignmentGridWidth:(float)width
2053 {
2054  _alignmentGridWidth = width;
2055 }
2056 
2057 - (BOOL)_validateItem:(id)item value:(id)value inRow:(CPInteger)row
2058 {
2059  return [self _queryCanSelectItem:item displayValue:value inRow:row];
2060 }
2061 
2062 - (BOOL)_queryCanSelectItem:(id)item displayValue:(id)value inRow:(CPInteger)row
2063 {
2064  return YES;
2065 }
2066 
2067 - (void)_windowChangedKeyState
2068 {
2069  [self setNeedsDisplay:YES];
2070 }
2071 
2072 - (void)setNeedsDisplay:(BOOL)flag
2073 {
2074  [_slices makeObjectsPerformSelector:@selector(setNeedsDisplay:) withObject:flag];
2075  [super setNeedsDisplay:flag];
2076 }
2077 
2078 - (void)setFrameSize:(CGSize)size
2079 {
2080  [self setNeedsDisplay:YES];
2081 
2082  if (CGRectGetWidth([self frame]) !== size.width)
2083  [_slices makeObjectsPerformSelector:@selector(setNeedsLayout)];
2084 
2085  [super setFrameSize:size];
2086 }
2087 
2088 - (CPIndexSet)_selectedSliceIndices
2089 {
2090  var selectedIndices = [CPMutableIndexSet indexSet],
2091  count = [_slices count],
2092  i;
2093 
2094  for (i = 0; i < count; i++)
2095  {
2096  var slice = _slices[i];
2097  if ([slice _isSelected])
2098  [selectedIndices addIndex:[slice rowIndex]];
2099  }
2100 
2101  return selectedIndices;
2102 }
2103 
2104 - (void)mouseDragged:(CPEvent)event
2105 {
2106  if (!_editable)
2107  return;
2108 
2109  var point = [self convertPoint:[event locationInWindow] fromView:nil],
2110  view = [_slices objectAtIndex:FLOOR(MAX(0, point.y) / _sliceHeight)];
2111 
2112  if ([self _dragShouldBeginFromMouseDown:view])
2113  [self _performDragForSlice:view withEvent:event];
2114 }
2115 
2116 - (BOOL)_dragShouldBeginFromMouseDown:(CPView)view
2117 {
2118  return (([self nestingMode] === CPRuleEditorNestingModeList || [view rowIndex] !== 0) && _editable && [view isKindOfClass:[_CPRuleEditorViewSliceRow class]] && _draggingRows === nil);
2119 }
2120 
2121 - (BOOL)_performDragForSlice:(id)slice withEvent:(CPEvent)event
2122 {
2123  var dragPoint,
2124  mainRowIndex = [slice rowIndex],
2125  draggingRows = [CPIndexSet indexSetWithIndex:mainRowIndex],
2126  selected_indices = [self _selectedSliceIndices],
2127  pasteboard = [CPPasteboard pasteboardWithName:CPDragPboard];
2128 
2129  [pasteboard declareTypes:[CPArray arrayWithObjects:CPRuleEditorItemPBoardType, nil] owner: self];
2130 
2131  if ([selected_indices containsIndex:mainRowIndex])
2132  [draggingRows addIndexes:selected_indices];
2133  _draggingRows = [self _includeSubslicesForSlicesAtIndexes:draggingRows];
2134 
2135  var firstIndex = [_draggingRows firstIndex],
2136  firstSlice = [_slices objectAtIndex:firstIndex],
2137  dragview = [[CPView alloc] initWithFrame:[firstSlice frame]];
2138 
2139 #if PLATFORM(DOM)
2140  var html = firstSlice._DOMElement.innerHTML;
2141  dragview._DOMElement.innerHTML = [html copy];
2142 #endif
2143  [dragview setBackgroundColor:[firstSlice backgroundColor]];
2144  [dragview setAlphaValue:0.7];
2145 
2146  dragPoint = CGPointMake(0, firstIndex * _sliceHeight);
2147 
2148  [self dragView:dragview
2149  at:dragPoint
2150  offset:CGSizeMake(0, _sliceHeight)
2151  event:event
2152  pasteboard:pasteboard
2153  source:self
2154  slideBack:YES];
2155 
2156  return YES;
2157 }
2158 
2159 - (CPDragOperation)draggingEntered:(id /*< CPDraggingInfo >*/)sender
2160 {
2161  if ([sender draggingSource] === self)
2162  {
2163  [self _clearDropLine];
2164  return CPDragOperationMove;
2165  }
2166 
2167  return CPDragOperationNone;
2168 }
2169 
2170 - (void)draggingExited:(id)sender
2171 {
2172  [self _clearDropLine];
2173  [self setNeedsDisplay:YES];
2174 }
2175 
2176 - (void)_clearDropLine
2177 {
2178  [_dropLineView setAlphaValue:0];
2179 
2180  if (_subviewIndexOfDropLine !== CPNotFound && _subviewIndexOfDropLine < _lastRow)
2181  {
2182  var previousBelowSlice = [_slices objectAtIndex:_subviewIndexOfDropLine];
2183  [previousBelowSlice setFrameOrigin:CGPointMake(0, [previousBelowSlice rowIndex] * _sliceHeight)];
2184  }
2185 
2186  _subviewIndexOfDropLine = CPNotFound;
2187 }
2188 
2189 - (CPDragOperation)draggingUpdated:(id /*<CPDraggingInfo>*/)sender
2190 {
2191  var point = [self convertPoint:[sender draggingLocation] fromView:nil],
2192  y = point.y + _sliceHeight / 2,
2193  indexOfDropLine = FLOOR(y / _sliceHeight),
2194  numberOfRows = [self numberOfRows];
2195 
2196  if (indexOfDropLine <= 0 || indexOfDropLine > numberOfRows || (indexOfDropLine >= [_draggingRows firstIndex] && indexOfDropLine <= [_draggingRows lastIndex] + 1))
2197  {
2198  if (_subviewIndexOfDropLine !== CPNotFound && indexOfDropLine !== _subviewIndexOfDropLine)
2199  [self _clearDropLine];
2200  return CPDragOperationNone;
2201  }
2202 
2203  if (_subviewIndexOfDropLine !== indexOfDropLine)
2204  {
2205  if (_subviewIndexOfDropLine !== CPNotFound && _subviewIndexOfDropLine < numberOfRows)
2206  {
2207  var previousBelowSlice = [_slices objectAtIndex:_subviewIndexOfDropLine];
2208  [previousBelowSlice setFrameOrigin:CGPointMake(0, [previousBelowSlice rowIndex] * _sliceHeight)];
2209  }
2210 
2211  if (indexOfDropLine <= _lastRow && indexOfDropLine < numberOfRows)
2212  {
2213  var belowSlice = [_slices objectAtIndex:indexOfDropLine];
2214  [belowSlice setFrameOrigin:CGPointMake(0, [belowSlice rowIndex] * _sliceHeight + 2)];
2215  }
2216 
2217  [_dropLineView setAlphaValue:1];
2218  [_dropLineView setFrameOrigin:CGPointMake(CGRectGetMinX([_dropLineView frame]), indexOfDropLine * _sliceHeight)];
2219 
2220  _subviewIndexOfDropLine = indexOfDropLine;
2221  }
2222 
2223  return CPDragOperationMove;
2224 }
2225 
2226 - (BOOL)prepareForDragOperation:(id /*< CPDraggingInfo >*/)sender
2227 {
2228  return (_subviewIndexOfDropLine !== CPNotFound);
2229 }
2230 
2231 - (BOOL)performDragOperation:(id /*< CPDraggingInfo >*/)info
2232 {
2233  var aboveInsertIndexCount = 0,
2234  object,
2235  removeIndex;
2236 
2237  var rowObjects = [_rowCache valueForKey:@"rowObject"],
2238  index = [_draggingRows lastIndex];
2239 
2240  var parentRowIndex = [self parentRowForRow:index], // first index of draggingrows
2241  parentRowObject = (parentRowIndex === -1) ? _boundArrayOwner : [[self _rowCacheForIndex:parentRowIndex] rowObject],
2242  insertIndex = _subviewIndexOfDropLine;
2243 
2244  while (index !== CPNotFound)
2245  {
2246  if (index >= insertIndex)
2247  {
2248  removeIndex = index + aboveInsertIndexCount;
2249  aboveInsertIndexCount += 1;
2250  }
2251  else
2252  {
2253  removeIndex = index;
2254  insertIndex -= 1;
2255  }
2256 
2257  object = [rowObjects objectAtIndex:removeIndex];
2258  [self removeRowAtIndex:removeIndex];
2259  [[self _subrowObjectsOfObject:parentRowObject] insertObject:object atIndex:insertIndex - parentRowIndex - 1];
2260 
2261  index = [_draggingRows indexLessThanIndex:index];
2262  }
2263 
2264  [self _clearDropLine];
2265  _draggingRows = nil;
2266  return YES;
2267 }
2268 
2269 - (CPIndexSet)_draggingTypes
2270 {
2271  return [CPIndexSet indexSetWithIndex:CPDragOperationMove];
2272 }
2273 
2274 - (void)draggedView:(CPView)dragView endedAt:(CGPoint)aPoint operation:(CPDragOperation)operation
2275 {
2276  _draggingRows = nil;
2277 
2278  [self _updatePredicate];
2279  [self _sendRuleAction];
2280  [self _postRuleOptionChangedNotification];
2281  [self _postRowCountChangedNotificationOfType:CPRuleEditorRowsDidChangeNotification indexes:nil]; // FIXME
2282 }
2283 
2284 - (BOOL)wantsPeriodicDraggingUpdates
2285 {
2286  return NO;
2287 }
2288 
2289 - (void)pasteboard:(CPPasteboard)pasteboard provideDataForType:(int)type
2290 {
2291 }
2292 
2293 - (void)_setWindow:(CPWindow)window
2294 {
2295  [super _setWindow:window];
2296 }
2297 
2298 - (void)_windowUpdate:(id)sender
2299 {
2300  [super _windowUpdate:sender];
2301 }
2302 
2303 - (void)_postRuleOptionChangedNotification
2304 {
2305  [[CPNotificationCenter defaultCenter] postNotificationName:CPRuleEditorRulesDidChangeNotification object:self];
2306 }
2307 
2308 - (void)_postRowCountChangedNotificationOfType:(CPString)notificationName indexes:indexes
2309 {
2310  var userInfo = indexes === nil ? @{} : @{ "indexes": indexes };
2311  [[CPNotificationCenter defaultCenter] postNotificationName:notificationName object:self userInfo:userInfo];
2312 }
2313 
2314 - (CPIndexSet)_globalIndexesForSubrowIndexes:(CPIndexSet)indexes ofParentObject:(id)parentRowObject
2315 {
2316  var _subrows = [self _subrowObjectsOfObject:parentRowObject],
2317  parentRowIndex = [self _rowIndexForRowObject:parentRowObject],
2318 
2319  globalIndexes = [CPMutableIndexSet indexSet],
2320  current_index = [indexes firstIndex],
2321  numberOfChildrenOfPreviousBrother = 0;
2322 
2323  while (current_index !== CPNotFound)
2324  {
2325  var globalChildIndex = current_index + parentRowIndex + 1 + numberOfChildrenOfPreviousBrother;
2326  [globalIndexes addIndex:globalChildIndex];
2327 
2328  if ([self rowTypeForRow:globalChildIndex] === CPRuleEditorRowTypeCompound)
2329  {
2330  var rowObject = [[self _rowCacheForIndex:current_index] rowObject],
2331  subrows = [self _subrowObjectsOfObject:rowObject],
2332  subIndexes = [self _globalIndexesForSubrowIndexes:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [subrows count])] ofParentObject:rowObject];
2333 
2334  numberOfChildrenOfPreviousBrother = [subIndexes count];
2335  }
2336 
2337  current_index = [indexes indexGreaterThanIndex:current_index];
2338  }
2339 
2340  return globalIndexes;
2341 }
2342 
2343 - (void)_sendRuleAction
2344 {
2345  var action = [self action],
2346  target = [self target];
2347 
2348  [self sendAction:action to:target];
2349 }
2350 
2351 - (BOOL)_sendsActionOnIncompleteTextChange
2352 {
2353  return YES;
2354 }
2355 
2356 - (void)_getAllAvailableItems:(id)items values:(id)values asChildrenOfItem:(id)parentItem inRow:(CPInteger)aRow
2357 {
2358  var type,
2359  indexofCriterion,
2360  numOfChildren;
2361 
2362  var availItems = [CPMutableArray array],
2363  availValues = [CPMutableArray array];
2364 
2365  var criterion = nil,
2366  value = nil;
2367 
2368  _lastRow = aRow;
2369  type = [self rowTypeForRow:aRow];
2370  numOfChildren = [self _queryNumberOfChildrenOfItem:parentItem withRowType:type];
2371 
2372  var criteria = [self criteriaForRow:aRow];
2373  indexofCriterion = [criteria indexOfObject:criterion];
2374 
2375  if (parentItem !== nil
2376  && indexofCriterion !== CPNotFound
2377  && indexofCriterion < [criteria count] - 1)
2378  {
2379  var next = indexofCriterion + 1;
2380 
2381  criterion = [criteria objectAtIndex:next];
2382  var values = [self displayValuesForRow:aRow];
2383  value = [values objectAtIndex:next];
2384  }
2385 
2386  for (var i = 0; i < numOfChildren; ++i)
2387  {
2388  var aChild = [self _queryChild:i ofItem:parentItem withRowType:type],
2389  availChild = aChild,
2390  availValue = value;
2391 
2392  if (criterion !== aChild)
2393  availValue = [self _queryValueForItem:aChild inRow:aRow];
2394 
2395  if (!availValue)
2396  availValue = [self _queryValueForItem:availChild inRow:aRow];
2397 
2398  [availItems addObject:availChild];
2399  [availValues addObject:availValue];
2400  }
2401 
2402  [items addObjectsFromArray:availItems];
2403  [values addObjectsFromArray:availValues];
2404 }
2405 
2406 @end
2407 
2408 
2409 @implementation CPRuleEditor (CPRuleEditorDelegate)
2410 
2415 - (BOOL)_delegateRespondsToRuleEditorPredicatePartsForCriterionWithDisplayValueInRow
2416 {
2418 }
2419 
2424 - (CPDictionary)_sendDelegateRuleEditorPredicatePartsForCriterion:(id)criterion withDisplayValue:(id)value inRow:(CPInteger)row
2425 {
2427  return @{};
2428 
2429  return [_ruleDelegate ruleEditor:self predicatePartsForCriterion:criterion withDisplayValue:value inRow:row];
2430 }
2431 
2432 @end
2433 
2434 
2435 var CPRuleEditorAlignmentGridWidthKey = @"CPRuleEditorAlignmentGridWidth",
2436  CPRuleEditorSliceHeightKey = @"CPRuleEditorSliceHeight",
2437  CPRuleEditorStringsFilenameKey = @"CPRuleEditorStringsFilename",
2438  CPRuleEditorEditableKey = @"CPRuleEditorEditable",
2439  CPRuleEditorAllowsEmptyCompoundRowsKey = @"CPRuleEditorAllowsEmptyCompoundRows",
2440  CPRuleEditorDisallowEmptyKey = @"CPRuleEditorDisallowEmpty",
2441  CPRuleEditorNestingModeKey = @"CPRuleEditorNestingMode",
2442  CPRuleEditorRowTypeKeyPathKey = @"CPRuleEditorRowTypeKeyPath",
2443  CPRuleEditorItemsKeyPathKey = @"CPRuleEditorItemsKeyPath",
2444  CPRuleEditorValuesKeyPathKey = @"CPRuleEditorValuesKeyPath",
2445  CPRuleEditorSubrowsArrayKeyPathKey = @"CPRuleEditorSubrowsArrayKeyPath",
2446  CPRuleEditorBoundArrayKeyPathKey = @"CPRuleEditorBoundArrayKeyPath",
2447  CPRuleEditorRowClassKey = @"CPRuleEditorRowClass",
2448  CPRuleEditorSlicesHolderKey = @"CPRuleEditorSlicesHolder",
2449  CPRuleEditorSlicesKey = @"CPRuleEditorSlices",
2450  CPRuleEditorDelegateKey = @"CPRuleEditorDelegate",
2451  CPRuleEditorBoundArrayOwnerKey = @"CPRuleEditorBoundArrayOwner";
2452 
2453 @implementation CPRuleEditor (CPCoding)
2454 
2455 - (id)initWithCoder:(CPCoder)coder
2456 {
2457  self = [super initWithCoder:coder];
2458  if (self !== nil)
2459  {
2460  [self setFormattingStringsFilename:[coder decodeObjectForKey:CPRuleEditorStringsFilenameKey]];
2461  _alignmentGridWidth = [coder decodeFloatForKey:CPRuleEditorAlignmentGridWidthKey];
2462  _sliceHeight = [coder decodeDoubleForKey:CPRuleEditorSliceHeightKey];
2463  _editable = [coder decodeBoolForKey:CPRuleEditorEditableKey];
2464  _allowsEmptyCompoundRows = [coder decodeBoolForKey:CPRuleEditorAllowsEmptyCompoundRowsKey];
2465  _disallowEmpty = [coder decodeBoolForKey:CPRuleEditorDisallowEmptyKey];
2466  _nestingMode = [coder decodeIntForKey:CPRuleEditorNestingModeKey];
2467  _typeKeyPath = [coder decodeObjectForKey:CPRuleEditorRowTypeKeyPathKey];
2468  _itemsKeyPath = [coder decodeObjectForKey:CPRuleEditorItemsKeyPathKey];
2469  _valuesKeyPath = [coder decodeObjectForKey:CPRuleEditorValuesKeyPathKey];
2470  _subrowsArrayKeyPath = [coder decodeObjectForKey:CPRuleEditorSubrowsArrayKeyPathKey];
2471  _boundArrayKeyPath = [coder decodeObjectForKey:CPRuleEditorBoundArrayKeyPathKey];
2472 
2473  _slicesHolder = [[self subviews] objectAtIndex:0];
2474  _boundArrayOwner = [coder decodeObjectForKey:CPRuleEditorBoundArrayOwnerKey];
2475  _slices = [coder decodeObjectForKey:CPRuleEditorSlicesKey];
2476  _ruleDelegate = [coder decodeObjectForKey:CPRuleEditorDelegateKey];
2477 
2478  [self _initRuleEditorShared];
2479  }
2480 
2481  return self;
2482 }
2483 
2484 - (void)encodeWithCoder:(CPCoder)coder
2485 {
2486  [super encodeWithCoder:coder];
2487 
2488  [coder encodeBool:_editable forKey:CPRuleEditorEditableKey];
2489  [coder encodeBool:_allowsEmptyCompoundRows forKey:CPRuleEditorAllowsEmptyCompoundRowsKey];
2490  [coder encodeBool:_disallowEmpty forKey:CPRuleEditorDisallowEmptyKey];
2491 
2492  [coder encodeFloat:_alignmentGridWidth forKey:CPRuleEditorAlignmentGridWidthKey];
2493  [coder encodeDouble:_sliceHeight forKey:CPRuleEditorSliceHeightKey];
2494  [coder encodeInt:_nestingMode forKey:CPRuleEditorNestingModeKey];
2495 
2496  [coder encodeObject:_stringsFilename forKey:CPRuleEditorStringsFilenameKey];
2497  [coder encodeObject:_typeKeyPath forKey:CPRuleEditorRowTypeKeyPathKey];
2498  [coder encodeObject:_itemsKeyPath forKey:CPRuleEditorItemsKeyPathKey];
2499  [coder encodeObject:_valuesKeyPath forKey:CPRuleEditorValuesKeyPathKey];
2500  [coder encodeObject:_boundArrayKeyPath forKey:CPRuleEditorBoundArrayKeyPathKey];
2501  [coder encodeObject:_subrowsArrayKeyPath forKey:CPRuleEditorSubrowsArrayKeyPathKey];
2502 
2503  [coder encodeConditionalObject:_slicesHolder forKey:CPRuleEditorSlicesHolderKey];
2504  [coder encodeObject:_slices forKey:CPRuleEditorSlicesKey];
2505  [coder encodeObject:_boundArrayOwner forKey:CPRuleEditorBoundArrayOwnerKey];
2506 }
2507 
2508 @end
2509 
2510 var CriteriaKey = @"criteria",
2511  SubrowsKey = @"subrows",
2512  DisplayValuesKey = @"displayValues",
2513  RowTypeKey = @"rowType";
2514 
2515 @implementation _CPRuleEditorRowObject : CPObject
2516 {
2517  CPArray subrows;
2518  CPArray criteria;
2519  CPArray displayValues;
2520  CPInteger rowType;
2521 }
2522 
2523 - (id)copy
2524 {
2525  var copy = [[_CPRuleEditorRowObject alloc] init];
2526  [copy setSubrows:[[CPArray alloc] initWithArray:subrows copyItems:YES]];
2527  [copy setCriteria:[[CPArray alloc] initWithArray:criteria copyItems:YES]];
2528  [copy setDisplayValues:[[CPArray alloc] initWithArray:displayValues copyItems:YES]];
2529  [copy setRowType:rowType];
2530 
2531  return copy;
2532 }
2533 
2535 {
2536  return "<" + [self className] + ">\nsubrows = " + [subrows description] + "\ncriteria = " + [criteria description] + "\ndisplayValues = " + [displayValues description];
2537 }
2538 
2539 - (id)initWithCoder:(CPCoder)coder
2540 {
2541  self = [super init];
2542  if (self !== nil)
2543  {
2544  subrows = [coder decodeObjectForKey:SubrowsKey];
2545  criteria = [coder decodeObjectForKey:CriteriaKey];
2546  displayValues = [coder decodeObjectForKey:DisplayValuesKey];
2547  rowType = [coder decodeIntForKey:RowTypeKey];
2548  }
2549 
2550  return self;
2551 }
2552 
2553 - (void)encodeWithCoder:(CPCoder)coder
2554 {
2555  [coder encodeObject:subrows forKey:SubrowsKey];
2556  [coder encodeObject:criteria forKey:CriteriaKey];
2557  [coder encodeObject:displayValues forKey:DisplayValuesKey];
2558  [coder encodeInt:rowType forKey:RowTypeKey];
2559 }
2560 
2561 @end
2562 
2563 @implementation _CPRuleEditorCache : CPObject
2564 {
2565  CPDictionary rowObject;
2566  CPInteger rowIndex;
2567  CPInteger indentation;
2568 }
2569 
2571 {
2572  return [CPString stringWithFormat:@"<%d object:%d rowIndex:%d indentation:%d>", [self hash], [rowObject hash], rowIndex, indentation];
2573 }
2574 
2575 @end
2576 
2577 var CPBoundArrayKey = @"CPBoundArray";
2578 
2579 @implementation _CPRuleEditorViewUnboundRowHolder : CPObject
2580 {
2581  CPArray boundArray;
2582 }
2583 
2584 - (id)init
2585 {
2586  if (self = [super init])
2587  boundArray = [[CPArray alloc] init];
2588 
2589  return self;
2590 }
2591 
2592 - (id)initWithCoder:(CPCoder)coder
2593 {
2594  if (self = [super init])
2595  boundArray = [coder decodeObjectForKey:CPBoundArrayKey];
2596 
2597  return self;
2598 }
2599 
2600 - (void)encodeWithCoder:(CPCoder)coder
2601 {
2602  [coder encodeObject:boundArray forKey:CPBoundArrayKey];
2603 }
2604 
2605 @end
2606 @implementation _CPRuleEditorViewSliceHolder : CPView
2607 {
2608  id __doxygen__;
2609 }
2610 
2611 - (void)addSubview:(CPView)subview
2612 {
2613  [self setNeedsDisplay:YES];
2614  [super addSubview:subview];
2615 }
2616 
2617 @end
2618 
2619 var dropSeparatorColor = [CPColor colorWithHexString:@"4886ca"];
2620 @implementation _CPRuleEditorViewSliceDropSeparator : CPView
2621 {
2622  id __doxygen__;
2623 }
2624 
2625 - (void)drawRect:(CGRect)rect
2626 {
2627  var context = [[CPGraphicsContext currentContext] graphicsPort];
2628  CGContextSetFillColor(context, dropSeparatorColor);
2629  CGContextFillRect(context, [self bounds]);
2630 }
2631 
2632 @end
2633 
2634 @implementation CPObject (CPRuleEditorSliceRow)
2635 
2636 - (int)valueType
2637 {
2638  var result = 0,
2639  isString = [self isKindOfClass:CPString];
2640 
2641  if (!isString)
2642  {
2643  var isView = [self isKindOfClass:CPView];
2644  result = 1;
2645 
2646  if (!isView)
2647  {
2648  var ismenuItem = [self isKindOfClass:CPMenuItem];
2649  result = 2;
2650 
2651  if (!ismenuItem)
2652  {
2653  [CPException raise:CPGenericException reason:@"Unknown type for " + self];
2654  result = -1;
2655  }
2656  }
2657  }
2658 
2659  return result;
2660 }
2661 
2662 @end
2665 @implementation CPRuleEditor (CPSynthesizedAccessors)
2666 
2670 - (CPString)boundArrayKeyPath
2671 {
2672  return _boundArrayKeyPath;
2673 }
2674 
2678 - (void)setBoundArrayKeyPath:(CPString)aValue
2679 {
2680  _boundArrayKeyPath = aValue;
2681 }
2682 
2686 - (_CPRuleEditorLocalizer)standardLocalizer
2687 {
2688  return _standardLocalizer;
2689 }
2690 
2694 - (void)setStandardLocalizer:(_CPRuleEditorLocalizer)aValue
2695 {
2696  _standardLocalizer = aValue;
2697 }
2698 
2699 @end
void insertRowAtIndex:withType:asSubrowOfRow:animate:(int rowIndex, [withType] unsigned int rowType, [asSubrowOfRow] CPInteger parentRow, [animate] BOOL shouldAnimate)
Adds a new row of a given type at a given location.
Definition: CPRuleEditor.j:703
Used to implement exception handling (creating & raising).
Definition: CPException.h:2
Definition: CPFont.h:2
CPKeyValueChangeInsertion
CPMutableArray displayValuesForRow:(CPInteger row)
Returns the chosen values for a given row.
Definition: CPRuleEditor.j:492
CPRuleEditorPredicateCustomSelector
CPInteger lastIndex()
Definition: CPIndexSet.j:289
id init()
Definition: CALayer.j:126
CPColor colorWithHexString:(string hex)
Definition: CPColor.j:264
CPPredicate predicateForRow:(CPInteger aRow)
Returns the predicate for a given row.
Definition: CPRuleEditor.j:809
CGRect frame
int parentRowForRow:(CPInteger rowIndex)
Returns the index of the parent of a given row.
Definition: CPRuleEditor.j:515
CPRuleEditorNestingModeList
CPGraphicsContext currentContext()
void postNotificationName:object:userInfo:(CPString aNotificationName, [object] id anObject, [userInfo] CPDictionary aUserInfo)
An object representation of nil.
Definition: CPNull.h:2
CPFont systemFontOfSize:(CGSize aSize)
Definition: CPFont.j:282
CPDragOperation CPDragOperationNone
CPRuleEditorRowType CPRuleEditorRowTypeSimple
var CPRuleEditorDelegate_ruleEditor_predicatePartsForCriterion_withDisplayValue_inRow_
Definition: CPRuleEditor.j:44
CGPoint locationInWindow()
Definition: CPEvent.j:290
CPRuleEditorNestingModeCompound
var itemsContext
Definition: CPRuleEditor.j:47
id delegate()
Definition: CALayer.j:965
int width
void removeRowsAtIndexes:includeSubrows:(CPIndexSet rowIndexes, [includeSubrows] BOOL includeSubrows)
Removes the rows at a given index.
Definition: CPRuleEditor.j:739
void postNotificationName:object:(CPString aNotificationName, [object] id anObject)
void raise:reason:(CPString aName, [reason] CPString aReason)
Definition: CPException.j:66
A collection of unique integers.
Definition: CPIndexSet.h:2
CPDragOperationMove
function CPSelectorFromString(aSelectorName)
Definition: CPObjJRuntime.j:28
CPInteger firstIndex()
Definition: CPIndexSet.j:278
unsigned modifierFlags()
Definition: CPEvent.j:309
var boundArrayContext
Definition: CPRuleEditor.j:50
CPNotificationCenter defaultCenter()
A mutable key-value pair collection.
Definition: CPDictionary.h:2
CPRuleEditorNestingModeSingle
BOOL containsIndex:(CPInteger anIndex)
Definition: CPIndexSet.j:187
CPRuleEditorNestingModeSimple
void setNeedsDisplay()
Definition: CALayer.j:830
CPCompoundPredicate is a subclass of CPPredicate used to represent logical “gate” operations (AND/O...
CGRect bounds()
Definition: CALayer.j:203
CPKeyValueChangeSetting
CPRuleEditorRowTypeCompound
CPAlternateKeyMask
CPString pathForResource:(CPString aFilename)
Definition: CPBundle.j:158
An immutable string (collection of characters).
Definition: CPString.h:2
CPNull null()
Definition: CPNull.j:51
CPBundle mainBundle()
Definition: CPBundle.j:82
id objectForKey:(id aKey)
Definition: CPDictionary.j:515
CPInteger indexGreaterThanIndex:(CPInteger anIndex)
Definition: CPIndexSet.j:301
Definition: CPImage.h:2
CPCommandKeyMask
id initWithDuration:animationCurve:(float aDuration, [animationCurve] CPAnimationCurve anAnimationCurve)
Definition: CPAnimation.j:100
var valuesContext
Definition: CPRuleEditor.j:48
function CGContextSetFillColor(aContext, aColor)
Definition: CGContext.j:663
CPShiftKeyMask
void reloadCriteria()
Instructs the receiver to refetch criteria from its delegate. You can use this method to indicate th...
Definition: CPRuleEditor.j:437
var CPRuleEditorItemPBoardType
Definition: CPRuleEditor.j:46
A notification that can be posted to a CPNotificationCenter.
Definition: CPNotification.h:2
var subrowsContext
Definition: CPRuleEditor.j:49
A view for creating and configuring criteria.
Definition: CPRuleEditor.h:2
CPPredicate predicateWithLeftExpression:rightExpression:customSelector:(CPExpression left, [rightExpression] CPExpression right, [customSelector] SEL selector)
int numberOfRows()
Returns the number of rows in the receiver.
Definition: CPRuleEditor.j:505
Defines methods for use when archiving & restoring (enc/decoding).
Definition: CPCoder.h:2
CPNotFound
Definition: CPObjJRuntime.j:62
CPIndexSet subrowIndexesForRow:(CPInteger rowIndex)
Returns the immediate subrows of a given row.
Definition: CPRuleEditor.j:577
CPComparisonPredicate is a subclass of CPPredicate used to compare expressions.
Sends messages (CPNotification) between objects.
CPPredicate predicateWithLeftExpression:rightExpression:modifier:type:options:(CPExpression left, [rightExpression] CPExpression right, [modifier] CPComparisonPredicateModifier modifier, [type] int type, [options] unsigned options)
function CGContextFillRect(aContext, aRect)
Definition: CGContext.j:358
id indexSetWithIndexesInRange:(CPRange aRange)
Definition: CPIndexSet.j:60
Definition: CPEvent.h:2
id criteriaForRow:(CPInteger row)
Returns the currently chosen items for a given row.
Definition: CPRuleEditor.j:473
id URLWithString:(CPString URLString)
Definition: CPURL.j:78
Definition: CPURL.h:2
id indexSetWithIndex:(int anIndex)
Definition: CPIndexSet.j:51
id indexSet()
Definition: CPIndexSet.j:43
id pasteboardWithName:(CPString aName)
Definition: CPPasteboard.j:93
id alloc()
Definition: CPObject.j:130
Definition: CPView.j:137
id stringWithFormat:(CPString format, [,] ...)
Definition: CPString.j:166
FrameUpdater prototype description