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