![]() |
API 0.9.5
|
00001 /* 00002 * CPArrayController.j 00003 * AppKit 00004 * 00005 * Adapted from Cocotron, by Johannes Fortmann 00006 * 00007 * Created by Ross Boucher. 00008 * Copyright 2009, 280 North, Inc. 00009 * 00010 * This library is free software; you can redistribute it and/or 00011 * modify it under the terms of the GNU Lesser General Public 00012 * License as published by the Free Software Foundation; either 00013 * version 2.1 of the License, or (at your option) any later version. 00014 * 00015 * This library is distributed in the hope that it will be useful, 00016 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00017 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00018 * Lesser General Public License for more details. 00019 * 00020 * You should have received a copy of the GNU Lesser General Public 00021 * License along with this library; if not, write to the Free Software 00022 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 00023 */ 00024 00025 00026 00035 @implementation CPArrayController : CPObjectController 00036 { 00037 BOOL _avoidsEmptySelection; 00038 BOOL _clearsFilterPredicateOnInsertion; 00039 BOOL _filterRestrictsInsertion; 00040 BOOL _preservesSelection; 00041 BOOL _selectsInsertedObjects; 00042 BOOL _alwaysUsesMultipleValuesMarker; 00043 00044 BOOL _automaticallyRearrangesObjects; // FIXME: Not in use 00045 00046 CPIndexSet _selectionIndexes; 00047 CPArray _sortDescriptors; 00048 CPPredicate _filterPredicate; 00049 CPArray _arrangedObjects; 00050 00051 BOOL _disableSetContent; 00052 } 00053 00054 + (void)initialize 00055 { 00056 if (self !== [CPArrayController class]) 00057 return; 00058 00059 [self exposeBinding:@"contentArray"]; 00060 [self exposeBinding:@"contentSet"]; 00061 } 00062 00063 + (CPSet)keyPathsForValuesAffectingContentArray 00064 { 00065 return [CPSet setWithObjects:"content"]; 00066 } 00067 00068 + (CPSet)keyPathsForValuesAffectingArrangedObjects 00069 { 00070 // Also depends on "filterPredicate" but we'll handle that manually. 00071 return [CPSet setWithObjects:"content", "sortDescriptors"]; 00072 } 00073 00074 + (CPSet)keyPathsForValuesAffectingSelection 00075 { 00076 return [CPSet setWithObjects:"selectionIndexes"]; 00077 } 00078 00079 + (CPSet)keyPathsForValuesAffectingSelectionIndex 00080 { 00081 return [CPSet setWithObjects:"selectionIndexes"]; 00082 } 00083 00084 + (CPSet)keyPathsForValuesAffectingSelectionIndexes 00085 { 00086 // When the arranged objects change, selection preservation may cause the indexes 00087 // to change. 00088 return [CPSet setWithObjects:"arrangedObjects"]; 00089 } 00090 00091 + (CPSet)keyPathsForValuesAffectingSelectedObjects 00092 { 00093 // Don't need to depend on arrangedObjects here because selectionIndexes already does. 00094 return [CPSet setWithObjects:"selectionIndexes"]; 00095 } 00096 00097 + (CPSet)keyPathsForValuesAffectingCanRemove 00098 { 00099 return [CPSet setWithObjects:"selectionIndexes"]; 00100 } 00101 00102 + (CPSet)keyPathsForValuesAffectingCanSelectNext 00103 { 00104 return [CPSet setWithObjects:"selectionIndexes"]; 00105 } 00106 00107 + (CPSet)keyPathsForValuesAffectingCanSelectPrevious 00108 { 00109 return [CPSet setWithObjects:"selectionIndexes"]; 00110 } 00111 00112 00113 - (id)init 00114 { 00115 self = [super init]; 00116 00117 if (self) 00118 { 00119 _preservesSelection = YES; 00120 _selectsInsertedObjects = YES; 00121 _avoidsEmptySelection = YES; 00122 _clearsFilterPredicateOnInsertion = YES; 00123 _alwaysUsesMultipleValuesMarker = NO; 00124 _automaticallyRearrangesObjects = NO; 00125 00126 _filterRestrictsInsertion = YES; // FIXME: Not in use 00127 00128 [self _init]; 00129 } 00130 00131 return self; 00132 } 00133 00134 - (void)_init 00135 { 00136 _sortDescriptors = [CPArray array]; 00137 _filterPredicate = nil; 00138 _selectionIndexes = [CPIndexSet indexSet]; 00139 [self __setArrangedObjects:[CPArray array]]; 00140 } 00141 00142 - (void)prepareContent 00143 { 00144 [self _setContentArray:[[self newObject]]]; 00145 } 00150 - (BOOL)preservesSelection 00151 { 00152 return _preservesSelection; 00153 } 00154 00160 - (void)setPreservesSelection:(BOOL)value 00161 { 00162 _preservesSelection = value; 00163 } 00164 00168 - (BOOL)selectsInsertedObjects 00169 { 00170 return _selectsInsertedObjects; 00171 } 00172 00177 - (void)setSelectsInsertedObjects:(BOOL)value 00178 { 00179 _selectsInsertedObjects = value; 00180 } 00181 00185 - (BOOL)avoidsEmptySelection 00186 { 00187 return _avoidsEmptySelection; 00188 } 00189 00194 - (void)setAvoidsEmptySelection:(BOOL)value 00195 { 00196 _avoidsEmptySelection = value; 00197 } 00198 00204 - (BOOL)clearsFilterPredicateOnInsertion 00205 { 00206 return _clearsFilterPredicateOnInsertion; 00207 } 00208 00214 - (void)setClearsFilterPredicateOnInsertion:(BOOL)aFlag 00215 { 00216 _clearsFilterPredicateOnInsertion = aFlag; 00217 } 00218 00225 - (BOOL)alwaysUsesMultipleValuesMarker 00226 { 00227 return _alwaysUsesMultipleValuesMarker; 00228 } 00229 00236 - (void)setAlwaysUsesMultipleValuesMarker:(BOOL)aFlag 00237 { 00238 _alwaysUsesMultipleValuesMarker = aFlag; 00239 } 00240 00250 - (BOOL)automaticallyRearrangesObjects 00251 { 00252 return _automaticallyRearrangesObjects; 00253 } 00254 00264 - (void)setAutomaticallyRearrangesObjects:(BOOL)aFlag 00265 { 00266 _automaticallyRearrangesObjects = aFlag; 00267 } 00268 00274 - (void)setContent:(id)value 00275 { 00276 // This is used to ignore expected setContent: calls caused by a binding to our content 00277 // object when we are the ones modifying the content object and can deal with the update 00278 // faster directly in the code in charge of the modification. 00279 if (_disableSetContent) 00280 return; 00281 00282 if (value === nil) 00283 value = []; 00284 00285 if (![value isKindOfClass:[CPArray class]]) 00286 value = [value]; 00287 00288 var oldSelectedObjects = nil, 00289 oldSelectionIndexes = nil; 00290 00291 if ([self preservesSelection]) 00292 oldSelectedObjects = [self selectedObjects]; 00293 else 00294 oldSelectionIndexes = [self selectionIndexes]; 00295 00296 /* 00297 When the contents are changed, the selected indexes may no longer refer to the 00298 same items. This would cause problems when setSelectedObjects is called below. 00299 Any KVO observation would try to retrieve the 'before' value which could be 00300 wrong or even throw an exception for no longer existing indexes. 00301 00302 To avoid that, use the internal __setSelectedObjects which fires no notifications. 00303 The selectionIndexes notifications will fire later since they depend on the 00304 content key. This pattern is also applied for many other methods throughout this 00305 class. 00306 */ 00307 00308 if (_clearsFilterPredicateOnInsertion) 00309 [self willChangeValueForKey:@"filterPredicate"]; 00310 00311 // Don't use [super setContent:] as that would fire the contentObject change. 00312 // We need to be in control of when notifications fire. 00313 // Note that if we have a contentArray binding, setting the content does /not/ 00314 // cause a reverse binding set. 00315 _contentObject = value; 00316 00317 if (_clearsFilterPredicateOnInsertion && _filterPredicate != nil) 00318 [self __setFilterPredicate:nil]; // Causes a _rearrangeObjects. 00319 else 00320 [self _rearrangeObjects]; 00321 00322 if ([self preservesSelection]) 00323 [self __setSelectedObjects:oldSelectedObjects]; 00324 else 00325 [self __setSelectionIndexes:oldSelectionIndexes]; 00326 00327 if (_clearsFilterPredicateOnInsertion) 00328 [self didChangeValueForKey:@"filterPredicate"]; 00329 } 00330 00334 - (void)_setContentArray:(id)anArray 00335 { 00336 [self setContent:anArray]; 00337 } 00338 00342 - (void)_setContentSet:(id)aSet 00343 { 00344 [self setContent:[aSet allObjects]]; 00345 } 00346 00351 - (id)contentArray 00352 { 00353 return [self content]; 00354 } 00355 00361 - (id)contentSet 00362 { 00363 return [CPSet setWithArray:[self content]]; 00364 } 00365 00372 - (CPArray)arrangeObjects:(CPArray)objects 00373 { 00374 var filterPredicate = [self filterPredicate], 00375 sortDescriptors = [self sortDescriptors]; 00376 00377 if (filterPredicate && [sortDescriptors count] > 0) 00378 { 00379 var sortedObjects = [objects filteredArrayUsingPredicate:filterPredicate]; 00380 [sortedObjects sortUsingDescriptors:sortDescriptors]; 00381 return sortedObjects; 00382 } 00383 else if (filterPredicate) 00384 return [objects filteredArrayUsingPredicate:filterPredicate]; 00385 else if ([sortDescriptors count] > 0) 00386 return [objects sortedArrayUsingDescriptors:sortDescriptors]; 00387 00388 return [objects copy]; 00389 } 00390 00394 - (void)rearrangeObjects 00395 { 00396 [self willChangeValueForKey:@"arrangedObjects"]; 00397 [self _rearrangeObjects]; 00398 [self didChangeValueForKey:@"arrangedObjects"]; 00399 } 00400 00401 /* 00402 Like rearrangeObjects but don't fire any change notifications. 00403 @ignore 00404 */ 00405 - (void)_rearrangeObjects 00406 { 00407 /* 00408 Rearranging reapplies the selection criteria and may cause objects to disappear, 00409 so take care of the selection. 00410 */ 00411 var oldSelectedObjects = nil, 00412 oldSelectionIndexes = nil; 00413 00414 if ([self preservesSelection]) 00415 oldSelectedObjects = [self selectedObjects]; 00416 else 00417 oldSelectionIndexes = [self selectionIndexes]; 00418 00419 [self __setArrangedObjects:[self arrangeObjects:[self contentArray]]]; 00420 00421 if ([self preservesSelection]) 00422 [self __setSelectedObjects:oldSelectedObjects]; 00423 else 00424 [self __setSelectionIndexes:oldSelectionIndexes]; 00425 } 00426 00430 - (void)__setArrangedObjects:(id)value 00431 { 00432 if (_arrangedObjects === value) 00433 return; 00434 00435 _arrangedObjects = [[_CPObservableArray alloc] initWithArray:value]; 00436 } 00437 00442 - (id)arrangedObjects 00443 { 00444 return _arrangedObjects; 00445 } 00446 00451 - (CPArray)sortDescriptors 00452 { 00453 return _sortDescriptors; 00454 } 00455 00461 - (void)setSortDescriptors:(CPArray)value 00462 { 00463 if (_sortDescriptors === value) 00464 return; 00465 00466 _sortDescriptors = [value copy]; 00467 // Use the non-notification version since arrangedObjects already depends 00468 // on sortDescriptors. 00469 [self _rearrangeObjects]; 00470 } 00471 00478 - (CPPredicate)filterPredicate 00479 { 00480 return _filterPredicate; 00481 } 00482 00489 - (void)setFilterPredicate:(CPPredicate)value 00490 { 00491 if (_filterPredicate === value) 00492 return; 00493 00494 // __setFilterPredicate will call _rearrangeObjects without 00495 // sending notifications, so we must send them instead. 00496 [self willChangeValueForKey:@"arrangedObjects"]; 00497 [self __setFilterPredicate:value]; 00498 [self didChangeValueForKey:@"arrangedObjects"]; 00499 } 00500 00501 /* 00502 Like setFilterPredicate but don't fire any change notifications. 00503 @ignore 00504 */ 00505 - (void)__setFilterPredicate:(CPPredicate)value 00506 { 00507 if (_filterPredicate === value) 00508 return; 00509 00510 _filterPredicate = value; 00511 // Use the non-notification version. 00512 [self _rearrangeObjects]; 00513 } 00514 00519 - (BOOL)alwaysUsesMultipleValuesMarker 00520 { 00521 return _alwaysUsesMultipleValuesMarker; 00522 } 00523 00524 //Selection 00529 - (unsigned)selectionIndex 00530 { 00531 return [_selectionIndexes firstIndex]; 00532 } 00533 00540 - (BOOL)setSelectionIndex:(unsigned)index 00541 { 00542 return [self setSelectionIndexes:[CPIndexSet indexSetWithIndex:index]]; 00543 } 00544 00550 - (CPIndexSet)selectionIndexes 00551 { 00552 return _selectionIndexes; 00553 } 00554 00561 - (BOOL)setSelectionIndexes:(CPIndexSet)indexes 00562 { 00563 [self _selectionWillChange] 00564 var r = [self __setSelectionIndexes:indexes]; 00565 [self _selectionDidChange]; 00566 return r; 00567 } 00568 00569 /* 00570 Like setSelectionIndex but don't fire any change notifications. 00571 @ignore 00572 */ 00573 - (BOOL)__setSelectionIndex:(int)theIndex 00574 { 00575 return [self __setSelectionIndexes:[CPIndexSet indexSetWithIndex:theIndex]]; 00576 } 00577 00578 /* 00579 Like setSelectionIndexes but don't fire any change notifications. 00580 @ignore 00581 */ 00582 - (BOOL)__setSelectionIndexes:(CPIndexSet)indexes 00583 { 00584 var newIndexes = indexes; 00585 00586 if (!newIndexes) 00587 newIndexes = [CPIndexSet indexSet]; 00588 00589 if (![newIndexes count]) 00590 { 00591 if (_avoidsEmptySelection && [[self arrangedObjects] count]) 00592 newIndexes = [CPIndexSet indexSetWithIndex:0]; 00593 } 00594 else 00595 { 00596 var objectsCount = [[self arrangedObjects] count]; 00597 00598 // Don't trash the input - the caller might depend on it or we might have been 00599 // given _selectionIndexes as the input in which case the equality test below 00600 // would always succeed despite our change below. 00601 newIndexes = [newIndexes copy]; 00602 00603 // Remove out of bounds indexes. 00604 [newIndexes removeIndexesInRange:CPMakeRange(objectsCount, [newIndexes lastIndex] + 1)]; 00605 // When avoiding empty selection and the deleted selection was at the bottom, select the last item. 00606 if (![newIndexes count] && _avoidsEmptySelection && objectsCount) 00607 newIndexes = [CPIndexSet indexSetWithIndex:objectsCount - 1]; 00608 } 00609 00610 if ([_selectionIndexes isEqualToIndexSet:newIndexes]) 00611 return NO; 00612 00613 // If we haven't already created our own index instance, make sure to copy it here so that 00614 // the copy the user sent in is decoupled from our internal copy. 00615 _selectionIndexes = indexes === newIndexes ? [indexes copy] : newIndexes; 00616 00617 // Push back the new selection to the model for selectionIndexes if we have one. 00618 // There won't be an infinite loop because of the equality check above. 00619 var binderClass = [[self class] _binderClassForBinding:@"selectionIndexes"]; 00620 [[binderClass getBinding:@"selectionIndexes" forObject:self] reverseSetValueFor:@"selectionIndexes"]; 00621 00622 return YES; 00623 } 00624 00629 - (CPArray)selectedObjects 00630 { 00631 var objects = [[self arrangedObjects] objectsAtIndexes:[self selectionIndexes]]; 00632 00633 return [_CPObservableArray arrayWithArray:(objects || [])]; 00634 } 00635 00642 - (BOOL)setSelectedObjects:(CPArray)objects 00643 { 00644 [self willChangeValueForKey:@"selectionIndexes"]; 00645 [self _selectionWillChange]; 00646 00647 var r = [self __setSelectedObjects:objects]; 00648 00649 [self didChangeValueForKey:@"selectionIndexes"]; 00650 [self _selectionDidChange]; 00651 return r; 00652 } 00653 00654 /* 00655 Like setSelectedObjects but don't fire any change notifications. 00656 @ignore 00657 */ 00658 - (BOOL)__setSelectedObjects:(CPArray)objects 00659 { 00660 var set = [CPIndexSet indexSet], 00661 count = [objects count], 00662 arrangedObjects = [self arrangedObjects]; 00663 00664 for (var i = 0; i < count; i++) 00665 { 00666 var index = [arrangedObjects indexOfObject:[objects objectAtIndex:i]]; 00667 00668 if (index !== CPNotFound) 00669 [set addIndex:index]; 00670 } 00671 00672 [self __setSelectionIndexes:set]; 00673 return YES; 00674 } 00675 00676 //Moving selection 00682 - (BOOL)canSelectPrevious 00683 { 00684 return [[self selectionIndexes] firstIndex] > 0 00685 } 00686 00691 - (void)selectPrevious:(id)sender 00692 { 00693 var index = [[self selectionIndexes] firstIndex] - 1; 00694 00695 if (index >= 0) 00696 [self setSelectionIndexes:[CPIndexSet indexSetWithIndex:index]]; 00697 } 00698 00704 - (BOOL)canSelectNext 00705 { 00706 return [[self selectionIndexes] firstIndex] < [[self arrangedObjects] count] - 1; 00707 } 00708 00713 - (void)selectNext:(id)sender 00714 { 00715 var index = [[self selectionIndexes] firstIndex] + 1; 00716 00717 if (index < [[self arrangedObjects] count]) 00718 [self setSelectionIndexes:[CPIndexSet indexSetWithIndex:index]]; 00719 } 00720 00721 //Add/Remove 00722 00728 - (void)addObject:(id)object 00729 { 00730 if (![self canAdd]) 00731 return; 00732 00733 var willClearPredicate = NO; 00734 if (_clearsFilterPredicateOnInsertion && _filterPredicate) 00735 { 00736 [self willChangeValueForKey:@"filterPredicate"]; 00737 willClearPredicate = YES; 00738 } 00739 00740 [self willChangeValueForKey:@"content"]; 00741 00742 /* 00743 If the content array is bound then our addObject: message below will cause the observed 00744 array to change. The binding will call setContent:_contentObject on this array 00745 controller to let it know about the change. We want to ignore that message since we 00746 A) already have the right _contentObject and B) properly update _arrangedObjects 00747 by hand below. 00748 */ 00749 _disableSetContent = YES; 00750 [_contentObject addObject:object]; 00751 00752 // Allow handlesContentAsCompoundValue reverse sets to trigger. 00753 [[CPBinder getBinding:@"contentArray" forObject:self] _contentArrayDidChange]; 00754 00755 _disableSetContent = NO; 00756 00757 if (willClearPredicate) 00758 { 00759 // Full rearrange needed due to changed filter. 00760 _filterPredicate = nil; 00761 [self _rearrangeObjects]; 00762 } 00763 else if (_filterPredicate === nil || [_filterPredicate evaluateWithObject:object]) 00764 { 00765 // Insert directly into the array. 00766 var pos = [_arrangedObjects insertObject:object inArraySortedByDescriptors:_sortDescriptors]; 00767 00768 // selectionIndexes change notification will be fired as a result of the 00769 // content change. Don't fire manually. 00770 if (_selectsInsertedObjects) 00771 [self __setSelectionIndex:pos]; 00772 else 00773 [_selectionIndexes shiftIndexesStartingAtIndex:pos by:1]; 00774 } 00775 /* 00776 else if (_filterPredicate !== nil) 00777 ... 00778 // Implies _filterPredicate && ![_filterPredicate evaluateWithObject:object], so the new object does 00779 // not appear in arrangedObjects and we do not have to update at all. 00780 */ 00781 00782 // This will also send notificaitons for arrangedObjects. 00783 [self didChangeValueForKey:@"content"]; 00784 if (willClearPredicate) 00785 [self didChangeValueForKey:@"filterPredicate"]; 00786 } 00787 00795 - (void)insertObject:(id)anObject atArrangedObjectIndex:(int)anIndex 00796 { 00797 if (![self canAdd]) 00798 return; 00799 00800 var willClearPredicate = NO; 00801 if (_clearsFilterPredicateOnInsertion && _filterPredicate) 00802 { 00803 [self willChangeValueForKey:@"filterPredicate"]; 00804 willClearPredicate = YES; 00805 } 00806 00807 [self willChangeValueForKey:@"content"]; 00808 00809 /* 00810 See _disableSetContent explanation in addObject:. 00811 */ 00812 _disableSetContent = YES; 00813 00814 // The atArrangedObjectIndex: part of this method's name only refers to where the 00815 // object goes in arrangedObjects, not in the content array. So use addObject:, 00816 // not insertObject:atIndex: here for speed. 00817 [_contentObject addObject:anObject]; 00818 // Allow handlesContentAsCompoundValue reverse sets to trigger. 00819 [[CPBinder getBinding:@"contentArray" forObject:self] _contentArrayDidChange]; 00820 00821 _disableSetContent = NO; 00822 00823 if (willClearPredicate) 00824 [self __setFilterPredicate:nil]; 00825 00826 [[self arrangedObjects] insertObject:anObject atIndex:anIndex]; 00827 00828 // selectionIndexes change notification will be fired as a result of the 00829 // content change. Don't fire manually. 00830 if ([self selectsInsertedObjects]) 00831 [self __setSelectionIndex:anIndex]; 00832 else 00833 [[self selectionIndexes] shiftIndexesStartingAtIndex:anIndex by:1]; 00834 00835 if ([self avoidsEmptySelection] && [[self selectionIndexes] count] <= 0 && [_contentObject count] > 0) 00836 [self __setSelectionIndexes:[CPIndexSet indexSetWithIndex:0]]; 00837 00838 [self didChangeValueForKey:@"content"]; 00839 if (willClearPredicate) 00840 [self didChangeValueForKey:@"filterPredicate"]; 00841 } 00842 00848 - (void)removeObject:(id)object 00849 { 00850 [self willChangeValueForKey:@"content"]; 00851 00852 // See _disableSetContent explanation in addObject:. 00853 _disableSetContent = YES; 00854 00855 [_contentObject removeObject:object]; 00856 // Allow handlesContentAsCompoundValue reverse sets to trigger. 00857 [[CPBinder getBinding:@"contentArray" forObject:self] _contentArrayDidChange]; 00858 00859 _disableSetContent = NO; 00860 00861 if (_filterPredicate === nil || [_filterPredicate evaluateWithObject:object]) 00862 { 00863 // selectionIndexes change notification will be fired as a result of the 00864 // content change. Don't fire manually. 00865 var pos = [_arrangedObjects indexOfObject:object]; 00866 00867 [_arrangedObjects removeObjectAtIndex:pos]; 00868 [_selectionIndexes shiftIndexesStartingAtIndex:pos by:-1]; 00869 } 00870 00871 [self didChangeValueForKey:@"content"]; 00872 } 00873 00879 - (void)add:(id)sender 00880 { 00881 if (![self canAdd]) 00882 return; 00883 00884 [self insert:sender]; 00885 } 00886 00891 - (void)insert:(id)sender 00892 { 00893 if (![self canInsert]) 00894 return; 00895 00896 var newObject = [self automaticallyPreparesContent] ? [self newObject] : [self _defaultNewObject]; 00897 00898 [self addObject:newObject]; 00899 } 00900 00905 - (void)remove:(id)sender 00906 { 00907 [self removeObjectsAtArrangedObjectIndexes:_selectionIndexes]; 00908 } 00909 00914 - (void)removeObjectsAtArrangedObjectIndexes:(CPIndexSet)anIndexSet 00915 { 00916 [self willChangeValueForKey:@"content"]; 00917 00918 /* 00919 See _disableSetContent explanation in addObject:. 00920 */ 00921 _disableSetContent = YES; 00922 00923 var arrangedObjects = [self arrangedObjects], 00924 index = [anIndexSet lastIndex], 00925 position = CPNotFound, 00926 newSelectionIndexes = [_selectionIndexes copy]; 00927 00928 while (index !== CPNotFound) 00929 { 00930 var object = [arrangedObjects objectAtIndex:index]; 00931 00932 // First try the simple case which should work if there are no sort descriptors. 00933 if ([_contentObject objectAtIndex:index] === object) 00934 [_contentObject removeObjectAtIndex:index]; 00935 else 00936 { 00937 // Since we don't have a reverse mapping between the sorted order and the 00938 // unsorted one, we'll just simply have to remove an arbitrary pointer. It might 00939 // be the 'wrong' one - as in not the one the user selected - but the wrong 00940 // one is still just another pointer to the same object, so the user will not 00941 // be able to see any difference. 00942 contentIndex = [_contentObject indexOfObjectIdenticalTo:object]; 00943 [_contentObject removeObjectAtIndex:contentIndex]; 00944 } 00945 [arrangedObjects removeObjectAtIndex:index]; 00946 00947 // Deselect this row if it was selected, and either way shift all selection indexes 00948 // following it up by 1. 00949 [newSelectionIndexes removeIndex:index]; 00950 [newSelectionIndexes shiftIndexesStartingAtIndex:index by:-1]; 00951 00952 index = [anIndexSet indexLessThanIndex:index]; 00953 } 00954 // Allow handlesContentAsCompoundValue reverse sets to trigger. 00955 [[CPBinder getBinding:@"contentArray" forObject:self] _contentArrayDidChange]; 00956 _disableSetContent = NO; 00957 00958 // This will automatically handle the avoidsEmptySelection case. 00959 [self __setSelectionIndexes:newSelectionIndexes]; 00960 00961 [self didChangeValueForKey:@"content"]; 00962 } 00963 00969 - (void)addObjects:(CPArray)objects 00970 { 00971 if (![self canAdd]) 00972 return; 00973 00974 var contentArray = [self contentArray], 00975 count = [objects count]; 00976 00977 for (var i = 0; i < count; i++) 00978 [contentArray addObject:[objects objectAtIndex:i]]; 00979 00980 [self setContent:contentArray]; 00981 // Allow handlesContentAsCompoundValue reverse sets to trigger. 00982 [[CPBinder getBinding:@"contentArray" forObject:self] _contentArrayDidChange]; 00983 } 00984 00989 - (void)removeObjects:(CPArray)objects 00990 { 00991 [self _removeObjects:objects]; 00992 } 00993 00997 - (void)_removeObjects:(CPArray)objects 00998 { 00999 [self willChangeValueForKey:@"content"]; 01000 01001 // See _disableSetContent explanation in addObject:. 01002 _disableSetContent = YES; 01003 01004 [_contentObject removeObjectsInArray:objects]; 01005 // Allow handlesContentAsCompoundValue reverse sets to trigger. 01006 [[CPBinder getBinding:@"contentArray" forObject:self] _contentArrayDidChange]; 01007 01008 _disableSetContent = NO; 01009 01010 var arrangedObjects = [self arrangedObjects], 01011 position = [arrangedObjects indexOfObject:[objects objectAtIndex:0]]; 01012 01013 [arrangedObjects removeObjectsInArray:objects]; 01014 01015 var objectsCount = [arrangedObjects count], 01016 selectionIndexes = [CPIndexSet indexSet]; 01017 01018 if ([self preservesSelection] || [self avoidsEmptySelection]) 01019 { 01020 selectionIndexes = [CPIndexSet indexSetWithIndex:position]; 01021 01022 // Remove the selection if there are no objects 01023 if (objectsCount <= 0) 01024 selectionIndexes = [CPIndexSet indexSet]; 01025 01026 // Shift selection to last object if position is out of bounds 01027 else if (position >= objectsCount) 01028 selectionIndexes = [CPIndexSet indexSetWithIndex:objectsCount - 1]; 01029 } 01030 01031 _selectionIndexes = selectionIndexes; 01032 01033 [self didChangeValueForKey:@"content"]; 01034 } 01035 01040 - (BOOL)canInsert 01041 { 01042 return [self isEditable]; 01043 } 01044 01045 @end 01046 01047 @implementation CPArrayController (CPBinder) 01048 01049 + (Class)_binderClassForBinding:(CPString)theBinding 01050 { 01051 if (theBinding == @"contentArray") 01052 return [_CPArrayControllerContentBinder class]; 01053 01054 return [super _binderClassForBinding:theBinding]; 01055 } 01056 01057 @end 01058 @implementation _CPArrayControllerContentBinder : CPBinder 01059 { 01060 id __doxygen__; 01061 } 01062 01063 - (void)setValueFor:(CPString)aBinding 01064 { 01065 var destination = [_info objectForKey:CPObservedObjectKey], 01066 keyPath = [_info objectForKey:CPObservedKeyPathKey], 01067 options = [_info objectForKey:CPOptionsKey], 01068 isCompound = [self handlesContentAsCompoundValue]; 01069 01070 if (!isCompound) 01071 { 01072 newValue = [destination mutableArrayValueForKeyPath:keyPath]; 01073 } 01074 else 01075 { 01076 // handlesContentAsCompoundValue == YES so we cannot just set up a proxy. 01077 // Every read and every write must go through transformValue and 01078 // reverseTransformValue, and the resulting object cannot be described by 01079 // a key path. 01080 newValue = [destination valueForKeyPath:keyPath]; 01081 } 01082 01083 newValue = [self transformValue:newValue withOptions:options]; 01084 01085 if (isCompound) 01086 { 01087 // Make sure we can edit our copy of the content. TODO In Cocoa, this copy 01088 // appears to be deferred until the array actually needs to be edited. 01089 newValue = [newValue mutableCopy]; 01090 } 01091 01092 [_source setValue:newValue forKey:aBinding]; 01093 } 01094 01095 - (void)_contentArrayDidChange 01096 { 01097 // When handlesContentAsCompoundValue == YES, it is not sufficient to modify the content object 01098 // in place because what we are holding is an array 'unwrapped' from a compound value by 01099 // a value transformer. So when we modify it we need a reverse set and transform to create 01100 // a new compound value. 01101 // 01102 // (The Cocoa documentation on the subject is not very clear but after substantial 01103 // experimentation this seems both reasonable and compliant.) 01104 if ([self handlesContentAsCompoundValue]) 01105 { 01106 var destination = [_info objectForKey:CPObservedObjectKey], 01107 keyPath = [_info objectForKey:CPObservedKeyPathKey]; 01108 01109 [self suppressSpecificNotificationFromObject:destination keyPath:keyPath]; 01110 [self reverseSetValueFor:@"contentArray"]; 01111 [self unsuppressSpecificNotificationFromObject:destination keyPath:keyPath]; 01112 } 01113 } 01114 01115 @end 01116 01117 var CPArrayControllerAvoidsEmptySelection = @"CPArrayControllerAvoidsEmptySelection", 01118 CPArrayControllerClearsFilterPredicateOnInsertion = @"CPArrayControllerClearsFilterPredicateOnInsertion", 01119 CPArrayControllerFilterRestrictsInsertion = @"CPArrayControllerFilterRestrictsInsertion", 01120 CPArrayControllerPreservesSelection = @"CPArrayControllerPreservesSelection", 01121 CPArrayControllerSelectsInsertedObjects = @"CPArrayControllerSelectsInsertedObjects", 01122 CPArrayControllerAlwaysUsesMultipleValuesMarker = @"CPArrayControllerAlwaysUsesMultipleValuesMarker", 01123 CPArrayControllerAutomaticallyRearrangesObjects = @"CPArrayControllerAutomaticallyRearrangesObjects"; 01124 01125 @implementation CPArrayController (CPCoding) 01126 01127 - (id)initWithCoder:(CPCoder)aCoder 01128 { 01129 self = [super initWithCoder:aCoder]; 01130 01131 if (self) 01132 { 01133 _avoidsEmptySelection = [aCoder decodeBoolForKey:CPArrayControllerAvoidsEmptySelection]; 01134 _clearsFilterPredicateOnInsertion = [aCoder decodeBoolForKey:CPArrayControllerClearsFilterPredicateOnInsertion]; 01135 _filterRestrictsInsertion = [aCoder decodeBoolForKey:CPArrayControllerFilterRestrictsInsertion]; 01136 _preservesSelection = [aCoder decodeBoolForKey:CPArrayControllerPreservesSelection]; 01137 _selectsInsertedObjects = [aCoder decodeBoolForKey:CPArrayControllerSelectsInsertedObjects]; 01138 _alwaysUsesMultipleValuesMarker = [aCoder decodeBoolForKey:CPArrayControllerAlwaysUsesMultipleValuesMarker]; 01139 _automaticallyRearrangesObjects = [aCoder decodeBoolForKey:CPArrayControllerAutomaticallyRearrangesObjects]; 01140 _sortDescriptors = [CPArray array]; 01141 01142 if (![self content] && [self automaticallyPreparesContent]) 01143 [self prepareContent]; 01144 else if (![self content]) 01145 [self _setContentArray:[]]; 01146 } 01147 01148 return self; 01149 } 01150 01151 - (void)encodeWithCoder:(CPCoder)aCoder 01152 { 01153 [super encodeWithCoder:aCoder]; 01154 01155 [aCoder encodeBool:_avoidsEmptySelection forKey:CPArrayControllerAvoidsEmptySelection]; 01156 [aCoder encodeBool:_clearsFilterPredicateOnInsertion forKey:CPArrayControllerClearsFilterPredicateOnInsertion]; 01157 [aCoder encodeBool:_filterRestrictsInsertion forKey:CPArrayControllerFilterRestrictsInsertion]; 01158 [aCoder encodeBool:_preservesSelection forKey:CPArrayControllerPreservesSelection]; 01159 [aCoder encodeBool:_selectsInsertedObjects forKey:CPArrayControllerSelectsInsertedObjects]; 01160 [aCoder encodeBool:_alwaysUsesMultipleValuesMarker forKey:CPArrayControllerAlwaysUsesMultipleValuesMarker]; 01161 [aCoder encodeBool:_automaticallyRearrangesObjects forKey:CPArrayControllerAutomaticallyRearrangesObjects]; 01162 } 01163 01164 - (void)awakeFromCib 01165 { 01166 [self _selectionWillChange]; 01167 [self _selectionDidChange]; 01168 } 01169 01170 @end