/AppKit/CPObjectController.j

http://github.com/cacaodev/cappuccino · Unknown · 827 lines · 665 code · 162 blank · 0 comment · 0 complexity · c0b4ae4898122115c8245b8890c8475a MD5 · raw file

  1. /*
  2. * CPObjectController.j
  3. * AppKit
  4. *
  5. * Created by Ross Boucher.
  6. * Copyright 2009, 280 North, Inc.
  7. *
  8. * This library is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU Lesser General Public
  10. * License as published by the Free Software Foundation; either
  11. * version 2.1 of the License, or (at your option) any later version.
  12. *
  13. * This library is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  16. * Lesser General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU Lesser General Public
  19. * License along with this library; if not, write to the Free Software
  20. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  21. */
  22. @import <Foundation/CPDictionary.j>
  23. @import <Foundation/CPCountedSet.j>
  24. @import <Foundation/_CPCollectionKVCOperators.j>
  25. @import "CPController.j"
  26. @import "CPKeyValueBinding.j"
  27. /*!
  28. @class
  29. CPObjectController is a bindings-compatible controller class.
  30. Properties of the content object of an object of this class can be bound to user interface elements to change and access their values.
  31. The content of an CPObjectController instance is an CPMutableDictionary object by default.
  32. This allows a single CPObjectController instance to be used to manage several properties accessed by key value paths.
  33. The default content object class can be changed by calling setObjectClass:, which a subclass must override.
  34. */
  35. @implementation CPObjectController : CPController
  36. {
  37. id _contentObject;
  38. id _selection;
  39. Class _objectClass;
  40. CPString _objectClassName;
  41. BOOL _isEditable;
  42. BOOL _automaticallyPreparesContent;
  43. CPCountedSet _observedKeys;
  44. }
  45. + (void)initialize
  46. {
  47. if (self !== [CPObjectController class])
  48. return;
  49. [self exposeBinding:@"editable"];
  50. [self exposeBinding:@"contentObject"];
  51. }
  52. + (CPSet)keyPathsForValuesAffectingContentObject
  53. {
  54. return [CPSet setWithObjects:"content"];
  55. }
  56. + (BOOL)automaticallyNotifiesObserversForKey:(CPString)aKey
  57. {
  58. if (aKey === @"contentObject")
  59. return NO;
  60. return YES;
  61. }
  62. + (CPSet)keyPathsForValuesAffectingCanAdd
  63. {
  64. return [CPSet setWithObject:"editable"];
  65. }
  66. + (CPSet)keyPathsForValuesAffectingCanInsert
  67. {
  68. return [CPSet setWithObject:"editable"];
  69. }
  70. + (CPSet)keyPathsForValuesAffectingCanRemove
  71. {
  72. return [CPSet setWithObjects:"editable", "selection"];
  73. }
  74. /*!
  75. @ignore
  76. */
  77. - (id)init
  78. {
  79. return [self initWithContent:nil];
  80. }
  81. /*!
  82. Inits and returns a CPObjectController object with the given content.
  83. @param id aContent - The object the controller will use.
  84. @return id the CPObjectConroller instance.
  85. */
  86. - (id)initWithContent:(id)aContent
  87. {
  88. if (self = [super init])
  89. {
  90. [self setEditable:YES];
  91. [self setObjectClass:[CPMutableDictionary class]];
  92. _observedKeys = [[CPCountedSet alloc] init];
  93. _selection = [[CPControllerSelectionProxy alloc] initWithController:self];
  94. [self setContent:aContent];
  95. }
  96. return self;
  97. }
  98. /*!
  99. Returns the controller's content object.
  100. @return id - The content object of the controller.
  101. */
  102. - (id)content
  103. {
  104. return _contentObject;
  105. }
  106. /*!
  107. Sets the content object for the controller.
  108. @param id aContent - The new content object for the controller.
  109. */
  110. - (void)setContent:(id)aContent
  111. {
  112. [self willChangeValueForKey:@"contentObject"];
  113. [self _selectionWillChange];
  114. _contentObject = aContent;
  115. [self _selectionDidChange];
  116. [self didChangeValueForKey:@"contentObject"];
  117. }
  118. /*!
  119. @ignore
  120. */
  121. - (void)_setContentObject:(id)aContent
  122. {
  123. [self setContent:aContent];
  124. }
  125. /*!
  126. @ignore
  127. */
  128. - (id)_contentObject
  129. {
  130. return [self content];
  131. }
  132. /*!
  133. Sets whether the controller automatically creates and inserts new content objects automatically when loading from a cib file.
  134. If you pass YES and the controller uses prepareContent to create the content object.
  135. The default is NO.
  136. @param BOOL shouldAutomaticallyPrepareContent - YES if the content should be prepared, otherwise NO.
  137. */
  138. - (void)setAutomaticallyPreparesContent:(BOOL)shouldAutomaticallyPrepareContent
  139. {
  140. _automaticallyPreparesContent = shouldAutomaticallyPrepareContent;
  141. }
  142. /*!
  143. Returns if the controller prepares the content automatically.
  144. @return BOOL - YES if the content is prepared, otherwise NO.
  145. */
  146. - (BOOL)automaticallyPreparesContent
  147. {
  148. return _automaticallyPreparesContent;
  149. }
  150. /*!
  151. Overridden by a subclass that require control over the creation of new objects.
  152. */
  153. - (void)prepareContent
  154. {
  155. [self setContent:[self newObject]];
  156. }
  157. /*!
  158. Sets the object class when creating new objects.
  159. @param Class - the class of new objects that will be created.
  160. */
  161. - (void)setObjectClass:(Class)aClass
  162. {
  163. _objectClass = aClass;
  164. }
  165. /*!
  166. Returns the class of what new objects will be when they are created.
  167. @return Class - The class of new objects.
  168. */
  169. - (Class)objectClass
  170. {
  171. return _objectClass;
  172. }
  173. /*!
  174. @ignore
  175. */
  176. - (id)_defaultNewObject
  177. {
  178. return [[[self objectClass] alloc] init];
  179. }
  180. /*!
  181. Creates and returns a new object of the appropriate class.
  182. @return id - The object created.
  183. */
  184. - (id)newObject
  185. {
  186. return [self _defaultNewObject];
  187. }
  188. /*!
  189. Sets the controller's content object.
  190. @param id anObject - The object to set for the controller.
  191. */
  192. - (void)addObject:(id)anObject
  193. {
  194. [self setContent:anObject];
  195. var binderClass = [[self class] _binderClassForBinding:@"contentObject"];
  196. [[binderClass getBinding:@"contentObject" forObject:self] reverseSetValueFor:@"contentObject"];
  197. }
  198. /*!
  199. Removes a given object from the controller.
  200. @param id anObject - The object to remove from the receiver.
  201. */
  202. - (void)removeObject:(id)anObject
  203. {
  204. if ([self content] === anObject)
  205. [self setContent:nil];
  206. var binderClass = [[self class] _binderClassForBinding:@"contentObject"];
  207. [[binderClass getBinding:@"contentObject" forObject:self] reverseSetValueFor:@"contentObject"];
  208. }
  209. /*!
  210. Creates and adds a sets the object as the controller's content.
  211. @param id aSender - The sender of the message.
  212. */
  213. - (void)add:(id)aSender
  214. {
  215. // FIXME: This should happen on the next run loop?
  216. [self addObject:[self newObject]];
  217. }
  218. /*!
  219. @return BOOL - YES if you can added to the controller using add:
  220. */
  221. - (BOOL)canAdd
  222. {
  223. return [self isEditable];
  224. }
  225. /*!
  226. Removes the content object from the controller.
  227. @param id aSender - The sender of the message.
  228. */
  229. - (void)remove:(id)aSender
  230. {
  231. // FIXME: This should happen on the next run loop?
  232. [self removeObject:[self content]];
  233. }
  234. /*!
  235. @return BOOL - Returns YES if you can remove the controller's content using remove:
  236. */
  237. - (BOOL)canRemove
  238. {
  239. return [self isEditable] && [[self selectedObjects] count];
  240. }
  241. /*!
  242. Sets whether the controller allows for the editing of the content.
  243. @param BOOL shouldBeEditable - YES if the content should be editable, otherwise NO.
  244. */
  245. - (void)setEditable:(BOOL)shouldBeEditable
  246. {
  247. _isEditable = shouldBeEditable;
  248. }
  249. /*!
  250. @return BOOL - Returns YES if the content of the controller is editable, otherwise NO.
  251. */
  252. - (BOOL)isEditable
  253. {
  254. return _isEditable;
  255. }
  256. /*!
  257. @return CPArray - Returns an array of all objects to be affected by editing.
  258. */
  259. - (CPArray)selectedObjects
  260. {
  261. return [[_CPObservableArray alloc] initWithArray:[_contentObject]];
  262. }
  263. /*!
  264. Returns a proxy object representing the controller's selection.
  265. */
  266. - (id)selection
  267. {
  268. return _selection;
  269. }
  270. /*!
  271. @ignore
  272. */
  273. - (void)_selectionWillChange
  274. {
  275. [_selection controllerWillChange];
  276. [self willChangeValueForKey:@"selection"];
  277. }
  278. /*!
  279. @ignore
  280. */
  281. - (void)_selectionDidChange
  282. {
  283. if (_selection === undefined || _selection === nil)
  284. _selection = [[CPControllerSelectionProxy alloc] initWithController:self];
  285. [_selection controllerDidChange];
  286. [self didChangeValueForKey:@"selection"];
  287. }
  288. /*!
  289. @return id - Returns the keys which are being observed.
  290. */
  291. - (id)observedKeys
  292. {
  293. return _observedKeys;
  294. }
  295. - (void)addObserver:(id)anObserver forKeyPath:(CPString)aKeyPath options:(CPKeyValueObservingOptions)options context:(id)context
  296. {
  297. [_observedKeys addObject:aKeyPath];
  298. [super addObserver:anObserver forKeyPath:aKeyPath options:options context:context];
  299. }
  300. - (void)removeObserver:(id)anObserver forKeyPath:(CPString)aKeyPath
  301. {
  302. [_observedKeys removeObject:aKeyPath];
  303. [super removeObserver:anObserver forKeyPath:aKeyPath];
  304. }
  305. @end
  306. var CPObjectControllerContentKey = @"CPObjectControllerContentKey",
  307. CPObjectControllerObjectClassNameKey = @"CPObjectControllerObjectClassNameKey",
  308. CPObjectControllerIsEditableKey = @"CPObjectControllerIsEditableKey",
  309. CPObjectControllerAutomaticallyPreparesContentKey = @"CPObjectControllerAutomaticallyPreparesContentKey";
  310. @implementation CPObjectController (CPCoding)
  311. - (id)initWithCoder:(CPCoder)aCoder
  312. {
  313. self = [super init];
  314. if (self)
  315. {
  316. var objectClassName = [aCoder decodeObjectForKey:CPObjectControllerObjectClassNameKey],
  317. objectClass = CPClassFromString(objectClassName);
  318. [self setObjectClass:objectClass || [CPMutableDictionary class]];
  319. [self setEditable:[aCoder decodeBoolForKey:CPObjectControllerIsEditableKey]];
  320. [self setAutomaticallyPreparesContent:[aCoder decodeBoolForKey:CPObjectControllerAutomaticallyPreparesContentKey]];
  321. [self setContent:[aCoder decodeObjectForKey:CPObjectControllerContentKey]];
  322. _observedKeys = [[CPCountedSet alloc] init];
  323. }
  324. return self;
  325. }
  326. - (void)encodeWithCoder:(CPCoder)aCoder
  327. {
  328. [aCoder encodeObject:[self content] forKey:CPObjectControllerContentKey];
  329. if (_objectClass)
  330. [aCoder encodeObject:CPStringFromClass(_objectClass) forKey:CPObjectControllerObjectClassNameKey];
  331. else if (_objectClassName)
  332. [aCoder encodeObject:_objectClassName forKey:CPObjectControllerObjectClassNameKey];
  333. [aCoder encodeBool:[self isEditable] forKey:CPObjectControllerIsEditableKey];
  334. [aCoder encodeBool:[self automaticallyPreparesContent] forKey:CPObjectControllerAutomaticallyPreparesContentKey];
  335. }
  336. - (void)awakeFromCib
  337. {
  338. if (![self content] && [self automaticallyPreparesContent])
  339. [self prepareContent];
  340. }
  341. @end
  342. @implementation _CPObservationProxy : CPObject
  343. {
  344. id _keyPath;
  345. id _observer;
  346. id _object;
  347. BOOL _notifyObject;
  348. id _context;
  349. int _options;
  350. }
  351. - (id)initWithKeyPath:(id)aKeyPath observer:(id)anObserver object:(id)anObject
  352. {
  353. if (self = [super init])
  354. {
  355. _keyPath = aKeyPath;
  356. _observer = anObserver;
  357. _object = anObject;
  358. }
  359. return self;
  360. }
  361. - (id)observer
  362. {
  363. return _observer;
  364. }
  365. - (id)keyPath
  366. {
  367. return _keyPath;
  368. }
  369. - (id)context
  370. {
  371. return _context;
  372. }
  373. - (int)options
  374. {
  375. return _options;
  376. }
  377. - (void)setNotifyObject:(BOOL)notify
  378. {
  379. _notifyObject = notify;
  380. }
  381. - (BOOL)isEqual:(id)anObject
  382. {
  383. if (self === anObject)
  384. return YES;
  385. if (!anObject || [anObject class] !== [self class] || anObject._observer !== _observer || anObject._keyPath !== _keyPath || anObject._object !== _object)
  386. return NO;
  387. return YES;
  388. }
  389. - (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObject change:(CPDictionary)change context:(id)context
  390. {
  391. if (_notifyObject)
  392. [_object observeValueForKeyPath:aKeyPath ofObject:_object change:change context:context];
  393. [_observer observeValueForKeyPath:aKeyPath ofObject:_object change:change context:context];
  394. }
  395. - (CPString)description
  396. {
  397. return [super description] + [CPString stringWithFormat:@"observation proxy for %@ on key path %@", _observer, _keyPath];
  398. }
  399. @end
  400. // FIXME: This should subclass CPMutableArray not _CPJavaScriptArray
  401. @implementation _CPObservableArray : _CPJavaScriptArray
  402. {
  403. CPArray _observationProxies;
  404. }
  405. + (id)alloc
  406. {
  407. var a = [];
  408. a.isa = self;
  409. var ivars = class_copyIvarList(self),
  410. count = ivars.length;
  411. while (count--)
  412. a[ivar_getName(ivars[count])] = nil;
  413. return a;
  414. }
  415. - (CPString)description
  416. {
  417. return "<_CPObservableArray: " + [super description] + " >";
  418. }
  419. - (id)initWithArray:(CPArray)anArray
  420. {
  421. self = [super initWithArray:anArray];
  422. self.isa = [_CPObservableArray class];
  423. self._observationProxies = [];
  424. return self;
  425. }
  426. - (void)addObserver:(id)anObserver forKeyPath:(CPString)aKeyPath options:(CPKeyValueObservingOptions)options context:(id)context
  427. {
  428. if (aKeyPath.charAt(0) === "@")
  429. {
  430. // Simple collection operators are scalar and can't be proxied
  431. if ([_CPCollectionKVCOperator isSimpleCollectionOperator:aKeyPath])
  432. return;
  433. var proxy = [[_CPObservationProxy alloc] initWithKeyPath:aKeyPath observer:anObserver object:self];
  434. proxy._options = options;
  435. proxy._context = context;
  436. [_observationProxies addObject:proxy];
  437. var dotIndex = aKeyPath.indexOf("."),
  438. remaining = aKeyPath.substring(dotIndex + 1),
  439. indexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [self count])];
  440. [self addObserver:proxy toObjectsAtIndexes:indexes forKeyPath:remaining options:options context:context];
  441. }
  442. else
  443. {
  444. var indexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [self count])];
  445. [self addObserver:anObserver toObjectsAtIndexes:indexes forKeyPath:aKeyPath options:options context:context];
  446. }
  447. }
  448. - (void)removeObserver:(id)anObserver forKeyPath:(CPString)aKeyPath
  449. {
  450. if (aKeyPath.charAt(0) === "@")
  451. {
  452. // Simple collection operators are scalar and can't be proxied
  453. if ([_CPCollectionKVCOperator isSimpleCollectionOperator:aKeyPath])
  454. return;
  455. var proxy = [[_CPObservationProxy alloc] initWithKeyPath:aKeyPath observer:anObserver object:self],
  456. index = [_observationProxies indexOfObject:proxy];
  457. proxy = [_observationProxies objectAtIndex:index];
  458. var dotIndex = aKeyPath.indexOf("."),
  459. remaining = aKeyPath.substring(dotIndex + 1),
  460. indexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [self count])];
  461. [self removeObserver:proxy fromObjectsAtIndexes:indexes forKeyPath:remaining];
  462. }
  463. else
  464. {
  465. var indexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [self count])];
  466. [self removeObserver:anObserver fromObjectsAtIndexes:indexes forKeyPath:aKeyPath];
  467. }
  468. }
  469. - (void)insertObject:(id)anObject atIndex:(CPUInteger)anIndex
  470. {
  471. for (var i = 0, count = [_observationProxies count]; i < count; i++)
  472. {
  473. var proxy = [_observationProxies objectAtIndex:i],
  474. keyPath = [proxy keyPath],
  475. operator = keyPath.charAt(0) === ".";
  476. if (operator)
  477. [self willChangeValueForKey:keyPath];
  478. [anObject addObserver:proxy forKeyPath:keyPath options:[proxy options] context:[proxy context]];
  479. if (operator)
  480. [self didChangeValueForKey:keyPath];
  481. }
  482. [super insertObject:anObject atIndex:anIndex];
  483. }
  484. - (void)removeObjectAtIndex:(CPUInteger)anIndex
  485. {
  486. var currentObject = [self objectAtIndex:anIndex];
  487. for (var i = 0, count = [_observationProxies count]; i < count; i++)
  488. {
  489. var proxy = [_observationProxies objectAtIndex:i],
  490. keyPath = [proxy keyPath],
  491. operator = keyPath.charAt(0) === ".";
  492. if (operator)
  493. [self willChangeValueForKey:keyPath];
  494. [currentObject removeObserver:proxy forKeyPath:keyPath];
  495. if (operator)
  496. [self didChangeValueForKey:keyPath];
  497. }
  498. [super removeObjectAtIndex:anIndex];
  499. }
  500. - (CPArray)objectsAtIndexes:(CPIndexSet)theIndexes
  501. {
  502. return [_CPObservableArray arrayWithArray:[super objectsAtIndexes:theIndexes]];
  503. }
  504. - (void)addObject:(id)anObject
  505. {
  506. [self insertObject:anObject atIndex:[self count]];
  507. }
  508. - (void)removeLastObject
  509. {
  510. [self removeObjectAtIndex:[self count]];
  511. }
  512. - (void)replaceObjectAtIndex:(CPUInteger)anIndex withObject:(id)anObject
  513. {
  514. var currentObject = [self objectAtIndex:anIndex];
  515. for (var i = 0, count = [_observationProxies count]; i < count; i++)
  516. {
  517. var proxy = [_observationProxies objectAtIndex:i],
  518. keyPath = [proxy keyPath],
  519. operator = keyPath.charAt(0) === ".";
  520. if (operator)
  521. [self willChangeValueForKey:keyPath];
  522. [currentObject removeObserver:proxy forKeyPath:keyPath];
  523. [anObject addObserver:proxy forKeyPath:keyPath options:[proxy options] context:[proxy context]];
  524. if (operator)
  525. [self didChangeValueForKey:keyPath];
  526. }
  527. [super replaceObjectAtIndex:anIndex withObject:anObject];
  528. }
  529. @end
  530. @implementation CPControllerSelectionProxy : CPObject
  531. {
  532. id _controller;
  533. id _keys;
  534. CPDictionary _cachedValues;
  535. CPArray _observationProxies;
  536. Object _observedObjectsByKeyPath;
  537. }
  538. - (id)initWithController:(id)aController
  539. {
  540. if (self = [super init])
  541. {
  542. _cachedValues = @{};
  543. _observationProxies = [CPArray array];
  544. _controller = aController;
  545. _observedObjectsByKeyPath = {};
  546. }
  547. return self;
  548. }
  549. - (id)_controllerMarkerForValues:(CPArray)theValues
  550. {
  551. var count = [theValues count],
  552. value;
  553. if (!count)
  554. value = CPNoSelectionMarker;
  555. else if (count === 1)
  556. value = [theValues objectAtIndex:0];
  557. else
  558. {
  559. if ([_controller alwaysUsesMultipleValuesMarker])
  560. value = CPMultipleValuesMarker;
  561. else
  562. {
  563. value = [theValues objectAtIndex:0];
  564. for (var i = 0, count = [theValues count]; i < count && value != CPMultipleValuesMarker; i++)
  565. {
  566. if (![value isEqual:[theValues objectAtIndex:i]])
  567. value = CPMultipleValuesMarker;
  568. }
  569. }
  570. }
  571. if (value === nil || value.isa && [value isEqual:[CPNull null]])
  572. value = CPNullMarker;
  573. return value;
  574. }
  575. - (id)valueForKeyPath:(CPString)theKeyPath
  576. {
  577. var values = [[_controller selectedObjects] valueForKeyPath:theKeyPath];
  578. // Simple collection operators like @count return a scalar value, not an array or set
  579. if ([values isKindOfClass:CPArray] || [values isKindOfClass:CPSet])
  580. {
  581. var value = [self _controllerMarkerForValues:values];
  582. [_cachedValues setObject:value forKey:theKeyPath];
  583. return value;
  584. }
  585. else
  586. return values;
  587. }
  588. - (id)valueForKey:(CPString)theKeyPath
  589. {
  590. return [self valueForKeyPath:theKeyPath];
  591. }
  592. - (void)setValue:(id)theValue forKeyPath:(CPString)theKeyPath
  593. {
  594. [[_controller selectedObjects] setValue:theValue forKeyPath:theKeyPath];
  595. [_cachedValues removeObjectForKey:theKeyPath];
  596. // Allow handlesContentAsCompoundValue to work, based on observation of Cocoa's
  597. // NSArrayController - when handlesContentAsCompoundValue and setValue:forKey:@"selection.X"
  598. // is called, the array controller causes the compound value to be rewritten if
  599. // handlesContentAsCompoundValue == YES. Note that
  600. // A) this doesn't use observation (observe: X is not visible in backtraces)
  601. // B) it only happens through the selection proxy and not on arrangedObject.X, content.X
  602. // or even selectedObjects.X.
  603. // FIXME The main code for this should somehow be in CPArrayController and also work
  604. // for table based row edits.
  605. [[CPBinder getBinding:@"contentArray" forObject:_controller] _contentArrayDidChange];
  606. }
  607. - (void)setValue:(id)theValue forKey:(CPString)theKeyPath
  608. {
  609. [self setValue:theValue forKeyPath:theKeyPath];
  610. }
  611. - (unsigned)count
  612. {
  613. return [_cachedValues count];
  614. }
  615. - (id)keyEnumerator
  616. {
  617. return [_cachedValues keyEnumerator];
  618. }
  619. - (void)controllerWillChange
  620. {
  621. _keys = [_cachedValues allKeys];
  622. if (!_keys)
  623. return;
  624. for (var i = 0, count = _keys.length; i < count; i++)
  625. [self willChangeValueForKey:_keys[i]];
  626. [_cachedValues removeAllObjects];
  627. }
  628. - (void)controllerDidChange
  629. {
  630. [_cachedValues removeAllObjects];
  631. if (!_keys)
  632. return;
  633. for (var i = 0, count = _keys.length; i < count; i++)
  634. [self didChangeValueForKey:_keys[i]];
  635. _keys = nil;
  636. }
  637. - (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObject change:(CPDictionary)change context:(id)context
  638. {
  639. [_cachedValues removeObjectForKey:aKeyPath];
  640. }
  641. - (void)addObserver:(id)anObject forKeyPath:(CPString)aKeyPath options:(CPKeyValueObservingOptions)options context:(id)context
  642. {
  643. var proxy = [[_CPObservationProxy alloc] initWithKeyPath:aKeyPath observer:anObject object:self];
  644. [proxy setNotifyObject:YES];
  645. [_observationProxies addObject:proxy];
  646. // We keep a reference to the observed objects because removeObserver: will be called after the selection changes.
  647. var observedObjects = [_controller selectedObjects];
  648. _observedObjectsByKeyPath[aKeyPath] = observedObjects;
  649. [observedObjects addObserver:proxy forKeyPath:aKeyPath options:options context:context];
  650. }
  651. - (void)removeObserver:(id)anObject forKeyPath:(CPString)aKeyPath
  652. {
  653. [_observationProxies enumerateObjectsUsingBlock:function(aProxy, idx, stop)
  654. {
  655. if (aProxy._object === self && aProxy._keyPath == aKeyPath && aProxy._observer === anObject)
  656. {
  657. var observedObjects = _observedObjectsByKeyPath[aKeyPath];
  658. [observedObjects removeObserver:aProxy forKeyPath:aKeyPath];
  659. [_observationProxies removeObjectAtIndex:idx];
  660. _observedObjectsByKeyPath[aKeyPath] = nil;
  661. stop(YES);
  662. }
  663. }];
  664. }
  665. @end