API 0.9.5
AppKit/CPObjectController.j
Go to the documentation of this file.
00001 /*
00002  * CPObjectController.j
00003  * AppKit
00004  *
00005  * Created by Ross Boucher.
00006  * Copyright 2009, 280 North, Inc.
00007  *
00008  * This library is free software; you can redistribute it and/or
00009  * modify it under the terms of the GNU Lesser General Public
00010  * License as published by the Free Software Foundation; either
00011  * version 2.1 of the License, or (at your option) any later version.
00012  *
00013  * This library is distributed in the hope that it will be useful,
00014  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00015  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
00016  * Lesser General Public License for more details.
00017  *
00018  * You should have received a copy of the GNU Lesser General Public
00019  * License along with this library; if not, write to the Free Software
00020  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
00021  */
00022 
00023 
00024 
00035 @implementation CPObjectController : CPController
00036 {
00037     id              _contentObject;
00038     id              _selection;
00039 
00040     Class           _objectClass;
00041     CPString        _objectClassName;
00042 
00043     BOOL            _isEditable;
00044     BOOL            _automaticallyPreparesContent;
00045 
00046     CPCountedSet    _observedKeys;
00047 }
00048 
00049 + (id)initialize
00050 {
00051     [self exposeBinding:@"editable"];
00052     [self exposeBinding:@"contentObject"];
00053 }
00054 
00055 + (CPSet)keyPathsForValuesAffectingContentObject
00056 {
00057     return [CPSet setWithObjects:"content"];
00058 }
00059 
00060 + (BOOL)automaticallyNotifiesObserversForKey:(CPString)aKey
00061 {
00062     if (aKey === @"contentObject")
00063         return NO;
00064 
00065     return YES;
00066 }
00067 
00068 + (CPSet)keyPathsForValuesAffectingCanAdd
00069 {
00070     return [CPSet setWithObject:"editable"];
00071 }
00072 
00073 + (CPSet)keyPathsForValuesAffectingCanInsert
00074 {
00075     return [CPSet setWithObject:"editable"];
00076 }
00077 
00078 + (CPSet)keyPathsForValuesAffectingCanRemove
00079 {
00080     return [CPSet setWithObjects:"editable", "selection"];
00081 }
00082 
00086 - (id)init
00087 {
00088     return [self initWithContent:nil];
00089 }
00090 
00097 - (id)initWithContent:(id)aContent
00098 {
00099     if (self = [super init])
00100     {
00101         [self setContent:aContent];
00102         [self setEditable:YES];
00103         [self setObjectClass:[CPMutableDictionary class]];
00104 
00105         _observedKeys = [[CPCountedSet alloc] init];
00106     }
00107 
00108     return self;
00109 }
00110 
00115 - (id)content
00116 {
00117     return _contentObject;
00118 }
00119 
00124 - (void)setContent:(id)aContent
00125 {
00126     [self willChangeValueForKey:@"contentObject"];
00127     [self _selectionWillChange];
00128 
00129     _contentObject = aContent;
00130 
00131     [self _selectionDidChange];
00132     [self didChangeValueForKey:@"contentObject"];
00133 }
00134 
00138 - (void)_setContentObject:(id)aContent
00139 {
00140     [self setContent:aContent];
00141 }
00142 
00146 - (id)_contentObject
00147 {
00148     return [self content];
00149 }
00150 
00158 - (void)setAutomaticallyPreparesContent:(BOOL)shouldAutomaticallyPrepareContent
00159 {
00160     _automaticallyPreparesContent = shouldAutomaticallyPrepareContent;
00161 }
00162 
00167 - (BOOL)automaticallyPreparesContent
00168 {
00169     return _automaticallyPreparesContent;
00170 }
00171 
00175 - (void)prepareContent
00176 {
00177     [self setContent:[self newObject]];
00178 }
00179 
00184 - (void)setObjectClass:(Class)aClass
00185 {
00186     _objectClass = aClass;
00187 }
00188 
00194 - (Class)objectClass
00195 {
00196     return _objectClass;
00197 }
00198 
00202 - (id)_defaultNewObject
00203 {
00204     return [[[self objectClass] alloc] init];
00205 }
00206 
00211 - (id)newObject
00212 {
00213     return [self _defaultNewObject];
00214 }
00215 
00220 - (void)addObject:(id)anObject
00221 {
00222     [self setContent:anObject];
00223 
00224     var binderClass = [[self class] _binderClassForBinding:@"contentObject"];
00225     [[binderClass getBinding:@"contentObject" forObject:self] reverseSetValueFor:@"contentObject"];
00226 }
00227 
00232 - (void)removeObject:(id)anObject
00233 {
00234     if ([self content] === anObject)
00235         [self setContent:nil];
00236 
00237     var binderClass = [[self class] _binderClassForBinding:@"contentObject"];
00238     [[binderClass getBinding:@"contentObject" forObject:self] reverseSetValueFor:@"contentObject"];
00239 }
00240 
00245 - (void)add:(id)aSender
00246 {
00247     // FIXME: This should happen on the next run loop?
00248     [self addObject:[self newObject]];
00249 }
00250 
00254 - (BOOL)canAdd
00255 {
00256     return [self isEditable];
00257 }
00258 
00263 - (void)remove:(id)aSender
00264 {
00265     // FIXME: This should happen on the next run loop?
00266     [self removeObject:[self content]];
00267 }
00268 
00272 - (BOOL)canRemove
00273 {
00274     return [self isEditable] && [[self selectedObjects] count];
00275 }
00276 
00281 - (void)setEditable:(BOOL)shouldBeEditable
00282 {
00283     _isEditable = shouldBeEditable;
00284 }
00285 
00289 - (BOOL)isEditable
00290 {
00291     return _isEditable;
00292 }
00293 
00297 - (CPArray)selectedObjects
00298 {
00299     return [[_CPObservableArray alloc] initWithArray:[_contentObject]];
00300 }
00301 
00305 - (id)selection
00306 {
00307     return _selection;
00308 }
00309 
00313 - (void)_selectionWillChange
00314 {
00315     [_selection controllerWillChange];
00316     [self willChangeValueForKey:@"selection"];
00317 }
00318 
00322 - (void)_selectionDidChange
00323 {
00324     if (_selection === undefined || _selection === nil)
00325         _selection = [[CPControllerSelectionProxy alloc] initWithController:self];
00326 
00327     [_selection controllerDidChange];
00328     [self didChangeValueForKey:@"selection"];
00329 }
00330 
00334 - (id)observedKeys
00335 {
00336     return _observedKeys;
00337 }
00338 
00339 - (void)addObserver:(id)anObserver forKeyPath:(CPString)aKeyPath options:(CPKeyValueObservingOptions)options context:(id)context
00340 {
00341    [_observedKeys addObject:aKeyPath];
00342    [super addObserver:anObserver forKeyPath:aKeyPath options:options context:context];
00343 }
00344 
00345 - (void)removeObserver:(id)anObserver forKeyPath:(CPString)aKeyPath
00346 {
00347    [_observedKeys removeObject:aKeyPath];
00348    [super removeObserver:anObserver forKeyPath:aKeyPath];
00349 }
00350 
00351 @end
00352 
00353 var CPObjectControllerContentKey                        = @"CPObjectControllerContentKey",
00354     CPObjectControllerObjectClassNameKey                = @"CPObjectControllerObjectClassNameKey",
00355     CPObjectControllerIsEditableKey                     = @"CPObjectControllerIsEditableKey",
00356     CPObjectControllerAutomaticallyPreparesContentKey   = @"CPObjectControllerAutomaticallyPreparesContentKey";
00357 
00358 @implementation CPObjectController (CPCoding)
00359 
00360 - (id)initWithCoder:(CPCoder)aCoder
00361 {
00362     self = [super init];
00363 
00364     if (self)
00365     {
00366         var objectClassName = [aCoder decodeObjectForKey:CPObjectControllerObjectClassNameKey],
00367             objectClass = CPClassFromString(objectClassName);
00368 
00369         [self setObjectClass:objectClass || [CPMutableDictionary class]];
00370         [self setEditable:[aCoder decodeBoolForKey:CPObjectControllerIsEditableKey]];
00371         [self setAutomaticallyPreparesContent:[aCoder decodeBoolForKey:CPObjectControllerAutomaticallyPreparesContentKey] || NO];
00372         [self setContent:[aCoder decodeObjectForKey:CPObjectControllerContentKey]];
00373 
00374         _observedKeys = [[CPCountedSet alloc] init];
00375     }
00376 
00377     return self;
00378 }
00379 
00380 - (void)encodeWithCoder:(CPCoder)aCoder
00381 {
00382     [aCoder encodeObject:[self content] forKey:CPObjectControllerContentKey];
00383 
00384     if (_objectClass)
00385         [aCoder encodeObject:CPStringFromClass(_objectClass) forKey:CPObjectControllerObjectClassNameKey];
00386     else if (_objectClassName)
00387         [aCoder encodeObject:_objectClassName forKey:CPObjectControllerObjectClassNameKey];
00388 
00389     [aCoder encodeBool:[self isEditable] forKey:CPObjectControllerIsEditableKey];
00390     [aCoder encodeBool:[self automaticallyPreparesContent] forKey:CPObjectControllerAutomaticallyPreparesContentKey];
00391 }
00392 
00393 - (void)awakeFromCib
00394 {
00395     if (![self content] && [self automaticallyPreparesContent])
00396         [self prepareContent];
00397 }
00398 
00399 @end
00400 
00401 @implementation _CPObservationProxy : CPObject
00402 {
00403     id      _keyPath;
00404     id      _observer;
00405     id      _object;
00406 
00407     BOOL    _notifyObject;
00408 
00409     id      _context;
00410     int     _options;
00411 }
00412 
00413 - (id)initWithKeyPath:(id)aKeyPath observer:(id)anObserver object:(id)anObject
00414 {
00415     if (self = [super init])
00416     {
00417         _keyPath  = aKeyPath;
00418         _observer = anObserver;
00419         _object   = anObject;
00420     }
00421 
00422     return self;
00423 }
00424 
00425 - (id)observer
00426 {
00427     return _observer;
00428 }
00429 
00430 - (id)keyPath
00431 {
00432     return _keyPath;
00433 }
00434 
00435 - (id)context
00436 {
00437    return _context;
00438 }
00439 
00440 - (int)options
00441 {
00442    return _options;
00443 }
00444 
00445 - (void)setNotifyObject:(BOOL)notify
00446 {
00447    _notifyObject = notify;
00448 }
00449 
00450 - (BOOL)isEqual:(id)anObject
00451 {
00452     if ([anObject class] === [self class])
00453     {
00454         if (anObject._observer === _observer && [anObject._keyPath isEqual:_keyPath] && [anObject._object isEqual:_object])
00455             return YES;
00456     }
00457 
00458     return NO;
00459 }
00460 
00461 - (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObject change:(CPDictionary)change context:(id)context
00462 {
00463     if (_notifyObject)
00464         [_object observeValueForKeyPath:aKeyPath ofObject:_object change:change context:context];
00465 
00466     [_observer observeValueForKeyPath:aKeyPath ofObject:_object change:change context:context];
00467 }
00468 
00469 - (CPString)description
00470 {
00471     return [super description] + [CPString stringWithFormat:@"observation proxy for %@ on key path %@", _observer, _keyPath];
00472 }
00473 
00474 @end
00475 
00476 // FIXME: This should subclass CPMutableArray not _CPJavaScriptArray
00477 @implementation _CPObservableArray : _CPJavaScriptArray
00478 {
00479     CPArray     _observationProxies;
00480 }
00481 
00482 + (id)alloc
00483 {
00484     var a = [];
00485     a.isa = self;
00486 
00487     var ivars = class_copyIvarList(self),
00488         count = ivars.length;
00489 
00490     while (count--)
00491         a[ivar_getName(ivars[count])] = nil;
00492 
00493     return a;
00494 }
00495 
00496 - (CPString)description
00497 {
00498     return "<_CPObservableArray: " + [super description] + " >";
00499 }
00500 
00501 - (id)initWithArray:(CPArray)anArray
00502 {
00503     self = [super initWithArray:anArray];
00504 
00505     self.isa = [_CPObservableArray class];
00506     self._observationProxies = [];
00507 
00508     return self;
00509 }
00510 
00511 - (void)addObserver:(id)anObserver forKeyPath:(CPString)aKeyPath options:(CPKeyValueObservingOptions)options context:(id)context
00512 {
00513     if (aKeyPath.indexOf("@") === 0)
00514     {
00515         var proxy = [[_CPObservationProxy alloc] initWithKeyPath:aKeyPath observer:anObserver object:self];
00516 
00517         proxy._options = options;
00518         proxy._context = context;
00519 
00520         [_observationProxies addObject:proxy];
00521 
00522         var dotIndex = aKeyPath.indexOf("."),
00523             remaining = aKeyPath.substring(dotIndex + 1),
00524             indexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [self count])];
00525 
00526         [self addObserver:proxy toObjectsAtIndexes:indexes forKeyPath:remaining options:options context:context];
00527     }
00528     else
00529     {
00530         var indexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [self count])];
00531         [self addObserver:anObserver toObjectsAtIndexes:indexes forKeyPath:aKeyPath options:options context:context];
00532     }
00533 }
00534 
00535 - (void)removeObserver:(id)anObserver forKeyPath:(CPString)aKeyPath
00536 {
00537     if (aKeyPath.indexOf("@") === 0)
00538     {
00539         var proxy = [[_CPObservationProxy alloc] initWithKeyPath:aKeyPath observer:anObserver object:self],
00540             index = [_observationProxies indexOfObject:proxy];
00541 
00542         proxy = [_observationProxies objectAtIndex:index];
00543 
00544         var dotIndex = aKeyPath.indexOf("."),
00545             remaining = aKeyPath.substring(dotIndex + 1),
00546             indexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [self count])];
00547 
00548         [self removeObserver:proxy fromObjectsAtIndexes:indexes forKeyPath:remaining];
00549     }
00550     else
00551     {
00552         var indexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [self count])];
00553         [self removeObserver:anObserver fromObjectsAtIndexes:indexes forKeyPath:aKeyPath];
00554     }
00555 }
00556 
00557 - (void)insertObject:(id)anObject atIndex:(unsigned)anIndex
00558 {
00559     for (var i = 0, count = [_observationProxies count]; i < count; i++)
00560     {
00561         var proxy = [_observationProxies objectAtIndex:i],
00562             keyPath = [proxy keyPath],
00563             operator = keyPath.indexOf(".") === 0;
00564 
00565         if (operator)
00566             [self willChangeValueForKey:keyPath];
00567 
00568         [anObject addObserver:proxy forKeyPath:keyPath options:[proxy options] context:[proxy context]];
00569 
00570         if (operator)
00571             [self didChangeValueForKey:keyPath];
00572     }
00573 
00574     [super insertObject:anObject atIndex:anIndex];
00575 }
00576 
00577 - (void)removeObjectAtIndex:(unsigned)anIndex
00578 {
00579     for (var i = 0, count = [_observationProxies count]; i < count; i++)
00580     {
00581         var proxy = [_observationProxies objectAtIndex:i],
00582             keyPath = [proxy keyPath],
00583             operator = keyPath.indexOf(".") === 0;
00584 
00585         if (operator)
00586             [self willChangeValueForKey:keyPath];
00587 
00588         [anObject removeObserver:proxy forKeyPath:keyPath];
00589 
00590         if (operator)
00591             [self didChangeValueForKey:keyPath];
00592     }
00593 
00594     [super removeObjectAtIndex:anIndex];
00595 }
00596 
00597 - (_CPObservableArray)objectsAtIndexes:(CPIndexSet)theIndexes
00598 {
00599     return [_CPObservableArray arrayWithArray:[super objectsAtIndexes:theIndexes]];
00600 }
00601 
00602 - (void)addObject:(id)anObject
00603 {
00604    [self insertObject:anObject atIndex:[self count]];
00605 }
00606 
00607 - (void)removeLastObject
00608 {
00609    [self removeObjectAtIndex:[self count]];
00610 }
00611 
00612 - (void)replaceObjectAtIndex:(unsigned)anIndex withObject:(id)anObject
00613 {
00614     var currentObject = [self objectAtIndex:anIndex];
00615 
00616     for (var i = 0, count = [_observationProxies count]; i < count; i++)
00617     {
00618         var proxy = [_observationProxies objectAtIndex:i],
00619             keyPath = [proxy keyPath],
00620             operator = keyPath.indexOf(".") === 0;
00621 
00622         if (operator)
00623             [self willChangeValueForKey:keyPath];
00624 
00625         [currentObject removeObserver:proxy forKeyPath:keyPath];
00626         [anObject addObserver:proxy forKeyPath:keyPath options:[proxy options] context:[proxy context]];
00627 
00628         if (operator)
00629             [self didChangeValueForKey:keyPath];
00630     }
00631 
00632     [super replaceObjectAtIndex:anIndex withObject:anObject];
00633 }
00634 
00635 @end
00636 
00637 @implementation CPControllerSelectionProxy : CPObject
00638 {
00639     id                  _controller;
00640     id                  _keys;
00641 
00642     CPDictionary        _cachedValues;
00643     CPArray             _observationProxies;
00644 
00645     Object              _observedObjectsByKeyPath;
00646 }
00647 
00648 - (id)initWithController:(id)aController
00649 {
00650     if (self = [super init])
00651     {
00652         _cachedValues = [CPDictionary dictionary];
00653         _observationProxies = [CPArray array];
00654         _controller = aController;
00655         _observedObjectsByKeyPath = {};
00656     }
00657 
00658     return self;
00659 }
00660 
00661 - (id)_controllerMarkerForValues:(CPArray)theValues
00662 {
00663     var count = [theValues count];
00664 
00665     if (!count)
00666         value = CPNoSelectionMarker;
00667     else if (count === 1)
00668         value = [theValues objectAtIndex:0];
00669     else
00670     {
00671         if ([_controller alwaysUsesMultipleValuesMarker])
00672             value = CPMultipleValuesMarker;
00673         else
00674         {
00675             value = [theValues objectAtIndex:0];
00676 
00677             for (var i = 0, count= [theValues count]; i < count && value != CPMultipleValuesMarker; i++)
00678             {
00679                 if (![value isEqual:[theValues objectAtIndex:i]])
00680                     value = CPMultipleValuesMarker;
00681             }
00682         }
00683     }
00684 
00685     if (value === nil || value.isa && [value isEqual:[CPNull null]])
00686         value = CPNullMarker;
00687 
00688     return value;
00689 }
00690 
00691 - (id)valueForKeyPath:(CPString)theKeyPath
00692 {
00693     var values = [[_controller selectedObjects] valueForKeyPath:theKeyPath];
00694     value = [self _controllerMarkerForValues:values];
00695 
00696     [_cachedValues setObject:value forKey:theKeyPath];
00697 
00698     return value;
00699 }
00700 
00701 - (id)valueForKey:(CPString)theKeyPath
00702 {
00703     return [self valueForKeyPath:theKeyPath];
00704 }
00705 
00706 - (void)setValue:(id)theValue forKeyPath:(CPString)theKeyPath
00707 {
00708     [[_controller selectedObjects] setValue:theValue forKeyPath:theKeyPath];
00709     [_cachedValues removeObjectForKey:theKeyPath];
00710 
00711     // Allow handlesContentAsCompoundValue to work, based on observation of Cocoa's
00712     // NSArrayController - when handlesContentAsCompoundValue and setValue:forKey:@"selection.X"
00713     // is called, the array controller causes the compound value to be rewritten if
00714     // handlesContentAsCompoundValue == YES. Note that
00715     // A) this doesn't use observation (observe: X is not visible in backtraces)
00716     // B) it only happens through the selection proxy and not on arrangedObject.X, content.X
00717     // or even selectedObjects.X.
00718     // FIXME The main code for this should somehow be in CPArrayController and also work
00719     // for table based row edits.
00720     [[CPBinder getBinding:@"contentArray" forObject:_controller] _contentArrayDidChange];
00721 }
00722 
00723 - (void)setValue:(id)theValue forKey:(CPString)theKeyPath
00724 {
00725     [self setValue:theKeyPath forKeyPath:theKeyPath];
00726 }
00727 
00728 - (unsigned)count
00729 {
00730     return [_cachedValues count];
00731 }
00732 
00733 - (id)keyEnumerator
00734 {
00735     return [_cachedValues keyEnumerator];
00736 }
00737 
00738 - (void)controllerWillChange
00739 {
00740     _keys = [_cachedValues allKeys];
00741 
00742     if (!_keys)
00743         return;
00744 
00745     for (var i = 0, count = _keys.length; i < count; i++)
00746         [self willChangeValueForKey:_keys[i]];
00747 
00748     [_cachedValues removeAllObjects];
00749 }
00750 
00751 - (void)controllerDidChange
00752 {
00753     [_cachedValues removeAllObjects];
00754 
00755     if (!_keys)
00756         return;
00757 
00758     for (var i = 0, count = _keys.length; i < count; i++)
00759         [self didChangeValueForKey:_keys[i]];
00760 
00761    _keys = nil;
00762 }
00763 
00764 - (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObject change:(CPDictionary)change context:(id)context
00765 {
00766     [_cachedValues removeObjectForKey:aKeyPath];
00767 }
00768 
00769 - (void)addObserver:(id)anObject forKeyPath:(CPString)aKeyPath options:(CPKeyValueObservingOptions)options context:(id)context
00770 {
00771     var proxy = [[_CPObservationProxy alloc] initWithKeyPath:aKeyPath observer:anObject object:self];
00772 
00773     [proxy setNotifyObject:YES];
00774     [_observationProxies addObject:proxy];
00775 
00776     // We keep are reference to the observed objects
00777     // because the removeObserver: will be called after the selection changes
00778     var observedObjects = [_controller selectedObjects];
00779     _observedObjectsByKeyPath[aKeyPath] = observedObjects;
00780     [observedObjects addObserver:proxy forKeyPath:aKeyPath options:options context:context];
00781 }
00782 
00783 - (void)removeObserver:(id)anObject forKeyPath:(CPString)aKeyPath
00784 {
00785     var proxy = [[_CPObservationProxy alloc] initWithKeyPath:aKeyPath observer:anObject object:self],
00786         index = [_observationProxies indexOfObject:proxy];
00787 
00788     var observedObjects = _observedObjectsByKeyPath[aKeyPath];
00789     [observedObjects removeObserver:[_observationProxies objectAtIndex:index] forKeyPath:aKeyPath];
00790 
00791     [_observationProxies removeObjectAtIndex:index];
00792 
00793     _observedObjects = nil;
00794 }
00795 
00796 @end
 All Classes Files Functions Variables Defines