![]() |
API 0.9.5
|
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