/AppKit/CPKeyValueBinding.j

http://github.com/cacaodev/cappuccino · Unknown · 958 lines · 756 code · 202 blank · 0 comment · 0 complexity · 5c9d2d4bfa694b937b7ab070096891b7 MD5 · raw file

  1. /*
  2. * CPKeyValueBinding.j
  3. * AppKit
  4. *
  5. * Created by Ross Boucher 1/13/09
  6. * Copyright 280 North, Inc.
  7. *
  8. * Adapted from GNUStep
  9. * Released under the LGPL.
  10. *
  11. * This library is free software; you can redistribute it and/or
  12. * modify it under the terms of the GNU Lesser General Public
  13. * License as published by the Free Software Foundation; either
  14. * version 2.1 of the License, or (at your option) any later version.
  15. *
  16. * This library is distributed in the hope that it will be useful,
  17. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  19. * Lesser General Public License for more details.
  20. *
  21. * You should have received a copy of the GNU Lesser General Public
  22. * License along with this library; if not, write to the Free Software
  23. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  24. */
  25. @import <Foundation/CPObject.j>
  26. @import <Foundation/CPArray.j>
  27. @import <Foundation/CPDictionary.j>
  28. @import <Foundation/CPInvocation.j>
  29. @import <Foundation/CPValueTransformer.j>
  30. @import <Foundation/CPKeyValueObserving.j>
  31. @class CPButton
  32. var exposedBindingsMap = @{},
  33. bindingsMap = @{};
  34. @typedef CPBindingOperationKind
  35. var CPBindingOperationAnd = 0,
  36. CPBindingOperationOr = 1;
  37. @implementation CPBinder : CPObject
  38. {
  39. CPDictionary _info;
  40. id _source @accessors(getter=source);
  41. JSObject _suppressedNotifications;
  42. JSObject _placeholderForMarker;
  43. }
  44. + (void)exposeBinding:(CPString)aBinding forClass:(Class)aClass
  45. {
  46. var bindings = [exposedBindingsMap objectForKey:[aClass UID]];
  47. if (!bindings)
  48. {
  49. bindings = [];
  50. [exposedBindingsMap setObject:bindings forKey:[aClass UID]];
  51. }
  52. bindings.push(aBinding);
  53. }
  54. + (CPArray)exposedBindingsForClass:(Class)aClass
  55. {
  56. return [[exposedBindingsMap objectForKey:[aClass UID]] copy];
  57. }
  58. + (CPBinder)getBinding:(CPString)aBinding forObject:(id)anObject
  59. {
  60. return [[bindingsMap objectForKey:[anObject UID]] objectForKey:aBinding];
  61. }
  62. + (void)_reverseSetValueFromExclusiveBinderForObject:(id)anObject
  63. {
  64. var bindersByBindingName = [bindingsMap objectForKey:[anObject UID]];
  65. [bindersByBindingName enumerateKeysAndObjectsUsingBlock:function(binding, binder, stop)
  66. {
  67. if ([binder isKindOfClass:[self class]])
  68. {
  69. [binder reverseSetValueFor:binding];
  70. stop(YES);
  71. }
  72. }];
  73. }
  74. + (CPDictionary)infoForBinding:(CPString)aBinding forObject:(id)anObject
  75. {
  76. var theBinding = [self getBinding:aBinding forObject:anObject];
  77. if (theBinding)
  78. return theBinding._info;
  79. return nil;
  80. }
  81. + (CPDictionary)allBindingsForObject:(id)anObject
  82. {
  83. return [bindingsMap objectForKey:[anObject UID]];
  84. }
  85. + (void)unbind:(CPString)aBinding forObject:(id)anObject
  86. {
  87. var bindings = [bindingsMap objectForKey:[anObject UID]];
  88. if (!bindings)
  89. return;
  90. var theBinding = [bindings objectForKey:aBinding];
  91. if (!theBinding)
  92. return;
  93. var info = theBinding._info,
  94. observedObject = [info objectForKey:CPObservedObjectKey],
  95. keyPath = [info objectForKey:CPObservedKeyPathKey];
  96. [observedObject removeObserver:theBinding forKeyPath:keyPath];
  97. [bindings removeObjectForKey:aBinding];
  98. }
  99. + (void)unbindAllForObject:(id)anObject
  100. {
  101. var bindings = [bindingsMap objectForKey:[anObject UID]];
  102. if (!bindings)
  103. return;
  104. var allKeys = [bindings allKeys],
  105. count = allKeys.length;
  106. while (count--)
  107. [anObject unbind:allKeys[count]];
  108. [bindingsMap removeObjectForKey:[anObject UID]];
  109. }
  110. - (id)initWithBinding:(CPString)aBinding name:(CPString)aName to:(id)aDestination keyPath:(CPString)aKeyPath options:(CPDictionary)options from:(id)aSource
  111. {
  112. // We use [self init] here because subclasses override init. We can't override this method
  113. // because their initialization has to occur before this method in this class is executed.
  114. self = [self init];
  115. if (self)
  116. {
  117. _source = aSource;
  118. _info = @{
  119. CPObservedObjectKey: aDestination,
  120. CPObservedKeyPathKey: aKeyPath,
  121. };
  122. _suppressedNotifications = {};
  123. _placeholderForMarker = {};
  124. if (options)
  125. [_info setObject:options forKey:CPOptionsKey];
  126. [self _updatePlaceholdersWithOptions:options forBinding:aName];
  127. [aDestination addObserver:self forKeyPath:aKeyPath options:0 context:aBinding];
  128. var bindings = [bindingsMap objectForKey:[_source UID]];
  129. if (!bindings)
  130. {
  131. bindings = @{};
  132. [bindingsMap setObject:bindings forKey:[_source UID]];
  133. }
  134. [bindings setObject:self forKey:aName];
  135. [self setValueFor:aBinding];
  136. }
  137. return self;
  138. }
  139. + (BOOL)isBindingAllowed:(CPString)aBinding forObject:(id)anObject
  140. {
  141. if ([[anObject class] isBindingExclusive:aBinding])
  142. {
  143. var bindingsForObject = [bindingsMap objectForKey:[anObject UID]],
  144. allBindings = [bindingsForObject allKeys],
  145. count = [allBindings count];
  146. while(count--)
  147. {
  148. if ([[anObject class] isBindingExclusive:allBindings[count]])
  149. return NO;
  150. }
  151. }
  152. return YES;
  153. }
  154. - (void)raiseIfNotApplicable:(id)aValue forKeyPath:(CPString)keyPath options:(CPDictionary)options
  155. {
  156. if (aValue === CPNotApplicableMarker && [options objectForKey:CPRaisesForNotApplicableKeysBindingOption])
  157. {
  158. [CPException raise:CPGenericException
  159. reason:@"Cannot transform non-applicable key on: " + _source + " key path: " + keyPath + " value: " + aValue];
  160. }
  161. }
  162. - (void)setValueFor:(CPString)theBinding
  163. {
  164. var destination = [_info objectForKey:CPObservedObjectKey],
  165. keyPath = [_info objectForKey:CPObservedKeyPathKey],
  166. options = [_info objectForKey:CPOptionsKey],
  167. newValue = [destination valueForKeyPath:keyPath];
  168. if (CPIsControllerMarker(newValue))
  169. {
  170. [self raiseIfNotApplicable:newValue forKeyPath:keyPath options:options];
  171. var value = [self _placeholderForMarker:newValue];
  172. [self setPlaceholderValue:value withMarker:newValue forBinding:theBinding];
  173. }
  174. else
  175. {
  176. var value = [self transformValue:newValue withOptions:options];
  177. [self setValue:value forBinding:theBinding];
  178. }
  179. }
  180. - (void)setPlaceholderValue:(id)aValue withMarker:(CPString)aMarker forBinding:(CPString)aBinding
  181. {
  182. [_source setValue:aValue forKey:aBinding];
  183. }
  184. - (void)setValue:(id)aValue forBinding:(CPString)aBinding
  185. {
  186. [_source setValue:aValue forKey:aBinding];
  187. }
  188. - (void)reverseSetValueFor:(CPString)aBinding
  189. {
  190. var destination = [_info objectForKey:CPObservedObjectKey],
  191. keyPath = [_info objectForKey:CPObservedKeyPathKey],
  192. options = [_info objectForKey:CPOptionsKey],
  193. newValue = [self valueForBinding:aBinding];
  194. newValue = [self reverseTransformValue:newValue withOptions:options];
  195. [self suppressSpecificNotificationFromObject:destination keyPath:keyPath];
  196. [destination setValue:newValue forKeyPath:keyPath];
  197. [self unsuppressSpecificNotificationFromObject:destination keyPath:keyPath];
  198. }
  199. - (id)valueForBinding:(CPString)aBinding
  200. {
  201. return [_source valueForKeyPath:aBinding];
  202. }
  203. - (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObject change:(CPDictionary)changes context:(id)context
  204. {
  205. if (!changes)
  206. return;
  207. var objectSuppressions = _suppressedNotifications[[anObject UID]];
  208. if (objectSuppressions && objectSuppressions[aKeyPath])
  209. return;
  210. [self setValueFor:context];
  211. }
  212. - (id)transformValue:(id)aValue withOptions:(CPDictionary)options
  213. {
  214. var valueTransformerName = [options objectForKey:CPValueTransformerNameBindingOption],
  215. valueTransformer;
  216. if (valueTransformerName)
  217. {
  218. valueTransformer = [CPValueTransformer valueTransformerForName:valueTransformerName];
  219. if (!valueTransformer)
  220. {
  221. var valueTransformerClass = CPClassFromString(valueTransformerName);
  222. if (valueTransformerClass)
  223. {
  224. valueTransformer = [[valueTransformerClass alloc] init];
  225. [valueTransformerClass setValueTransformer:valueTransformer forName:valueTransformerName];
  226. }
  227. }
  228. }
  229. else
  230. valueTransformer = [options objectForKey:CPValueTransformerBindingOption];
  231. if (valueTransformer)
  232. aValue = [valueTransformer transformedValue:aValue];
  233. // If the value is nil AND the source doesn't respond to setPlaceholderString: then
  234. // we set the value to the placeholder. Otherwise, we do not want to short cut the process
  235. // of setting the placeholder that is based on the fact that the value is nil.
  236. if ((aValue === undefined || aValue === nil || aValue === [CPNull null])
  237. && ![_source respondsToSelector:@selector(setPlaceholderString:)])
  238. aValue = [options objectForKey:CPNullPlaceholderBindingOption] || nil;
  239. return aValue;
  240. }
  241. - (id)reverseTransformValue:(id)aValue withOptions:(CPDictionary)options
  242. {
  243. var valueTransformerName = [options objectForKey:CPValueTransformerNameBindingOption],
  244. valueTransformer;
  245. if (valueTransformerName)
  246. valueTransformer = [CPValueTransformer valueTransformerForName:valueTransformerName];
  247. else
  248. valueTransformer = [options objectForKey:CPValueTransformerBindingOption];
  249. if (valueTransformer && [[valueTransformer class] allowsReverseTransformation])
  250. aValue = [valueTransformer reverseTransformedValue:aValue];
  251. return aValue;
  252. }
  253. - (BOOL)continuouslyUpdatesValue
  254. {
  255. var options = [_info objectForKey:CPOptionsKey];
  256. return [[options objectForKey:CPContinuouslyUpdatesValueBindingOption] boolValue];
  257. }
  258. - (BOOL)handlesContentAsCompoundValue
  259. {
  260. var options = [_info objectForKey:CPOptionsKey];
  261. return [[options objectForKey:CPHandlesContentAsCompoundValueBindingOption] boolValue];
  262. }
  263. /*!
  264. Use this to avoid reacting to the notifications coming out of a reverseTransformedValue:.
  265. */
  266. - (void)suppressSpecificNotificationFromObject:(id)anObject keyPath:(CPString)aKeyPath
  267. {
  268. if (!anObject)
  269. return;
  270. var uid = [anObject UID],
  271. objectSuppressions = _suppressedNotifications[uid];
  272. if (!objectSuppressions)
  273. _suppressedNotifications[uid] = objectSuppressions = {};
  274. objectSuppressions[aKeyPath] = YES;
  275. }
  276. /*!
  277. Use this to cancel suppressSpecificNotificationFromObject:keyPath:.
  278. */
  279. - (void)unsuppressSpecificNotificationFromObject:(id)anObject keyPath:(CPString)aKeyPath
  280. {
  281. if (!anObject)
  282. return;
  283. var uid = [anObject UID],
  284. objectSuppressions = _suppressedNotifications[uid];
  285. if (!objectSuppressions)
  286. return;
  287. delete objectSuppressions[aKeyPath];
  288. }
  289. - (void)_updatePlaceholdersWithOptions:(CPDictionary)options
  290. {
  291. var count = [CPBinderPlaceholderMarkers count];
  292. while (count--)
  293. {
  294. var marker = CPBinderPlaceholderMarkers[count],
  295. optionName = CPBinderPlaceholderOptions[count],
  296. isExplicit = [options containsKey:optionName],
  297. placeholder = isExplicit ? [options objectForKey:optionName] : nil;
  298. [self _setPlaceholder:placeholder forMarker:marker isDefault:!isExplicit];
  299. }
  300. }
  301. - (void)_updatePlaceholdersWithOptions:(CPDictionary)options forBinding:(CPString)aBinding
  302. {
  303. [self _updatePlaceholdersWithOptions:options];
  304. }
  305. - (JSObject)_placeholderForMarker:(id)aMarker
  306. {
  307. var placeholder = _placeholderForMarker[[aMarker UID]];
  308. if (placeholder)
  309. return placeholder.value;
  310. return nil;
  311. }
  312. - (void)_setPlaceholder:(id)aPlaceholder forMarker:(id)aMarker isDefault:(BOOL)isDefault
  313. {
  314. if (isDefault)
  315. {
  316. var existingPlaceholder = _placeholderForMarker[[aMarker UID]];
  317. // Don't overwrite an explicitly set placeholder with a default.
  318. if (existingPlaceholder && !existingPlaceholder.isDefault)
  319. return;
  320. }
  321. _placeholderForMarker[[aMarker UID]] = { 'isDefault': isDefault, 'value': aPlaceholder };
  322. }
  323. @end
  324. @implementation CPObject (KeyValueBindingCreation)
  325. + (void)exposeBinding:(CPString)aBinding
  326. {
  327. [CPBinder exposeBinding:aBinding forClass:[self class]];
  328. }
  329. + (Class)_binderClassForBinding:(CPString)aBinding
  330. {
  331. return [CPBinder class];
  332. }
  333. + (BOOL)isBindingExclusive:(CPString)aBinding
  334. {
  335. return NO;
  336. }
  337. - (CPArray)exposedBindings
  338. {
  339. var exposedBindings = [],
  340. theClass = [self class];
  341. while (theClass)
  342. {
  343. var temp = [CPBinder exposedBindingsForClass:theClass];
  344. if (temp)
  345. [exposedBindings addObjectsFromArray:temp];
  346. theClass = [theClass superclass];
  347. }
  348. return exposedBindings;
  349. }
  350. - (Class)valueClassForBinding:(CPString)binding
  351. {
  352. return [CPString class];
  353. }
  354. - (void)bind:(CPString)aBinding toObject:(id)anObject withKeyPath:(CPString)aKeyPath options:(CPDictionary)options
  355. {
  356. if (!anObject || !aKeyPath)
  357. return CPLog.error("Invalid object or path on " + self + " for " + aBinding);
  358. if (![CPBinder isBindingAllowed:aBinding forObject:self])
  359. {
  360. CPLog.warn([self description] + " : cannot bind " + aBinding + " because another binding with the same functionality is already in use.");
  361. return;
  362. }
  363. //if (![[self exposedBindings] containsObject:aBinding])
  364. // CPLog.warn("No binding exposed on " + self + " for " + aBinding);
  365. var binderClass = [[self class] _binderClassForBinding:aBinding];
  366. [self unbind:aBinding];
  367. [[binderClass alloc] initWithBinding:[self _replacementKeyPathForBinding:aBinding] name:aBinding to:anObject keyPath:aKeyPath options:options from:self];
  368. }
  369. - (CPDictionary)infoForBinding:(CPString)aBinding
  370. {
  371. return [CPBinder infoForBinding:aBinding forObject:self];
  372. }
  373. - (void)unbind:(CPString)aBinding
  374. {
  375. var binderClass = [[self class] _binderClassForBinding:aBinding];
  376. [binderClass unbind:aBinding forObject:self];
  377. }
  378. - (CPString)_replacementKeyPathForBinding:(CPString)binding
  379. {
  380. return binding;
  381. }
  382. @end
  383. /*!
  384. @ignore
  385. Provides stub implementations that simply calls super for the "objectValue" binding.
  386. This class should not be necessary but assures backwards compliance with our old way of doing bindings.
  387. IMPORTANT!
  388. Every class with a value binding should implement a subclass to handle its specific value binding logic.
  389. */
  390. @implementation _CPValueBinder : CPBinder
  391. - (void)setValueFor:(CPString)theBinding
  392. {
  393. [super setValueFor:@"objectValue"];
  394. }
  395. - (void)reverseSetValueFor:(CPString)theBinding
  396. {
  397. [super reverseSetValueFor:@"objectValue"];
  398. }
  399. @end
  400. @implementation _CPMultipleValueBooleanBinding : CPBinder
  401. {
  402. CPBindingOperationKind _operation;
  403. }
  404. - (void)setValueFor:(CPString)aBinding
  405. {
  406. var bindings = [bindingsMap valueForKey:[_source UID]];
  407. if (!bindings)
  408. return;
  409. var baseBinding = aBinding.replace(/\d$/, "");
  410. [_source setValue:[self resolveMultipleValuesForBinding:baseBinding bindings:bindings booleanOperation:_operation] forKey:baseBinding];
  411. }
  412. - (void)reverseSetValueFor:(CPString)theBinding
  413. {
  414. // read-only
  415. }
  416. - (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObject change:(CPDictionary)changes context:(id)context
  417. {
  418. [self setValueFor:context];
  419. }
  420. - (BOOL)resolveMultipleValuesForBinding:(CPString)aBinding bindings:(CPDictionary)bindings booleanOperation:(CPBindingOperationKind)operation
  421. {
  422. var bindingName = aBinding,
  423. theBinding,
  424. count = 2;
  425. while (theBinding = [bindings objectForKey:bindingName])
  426. {
  427. var info = theBinding._info,
  428. object = [info objectForKey:CPObservedObjectKey],
  429. keyPath = [info objectForKey:CPObservedKeyPathKey],
  430. options = [info objectForKey:CPOptionsKey],
  431. value = [object valueForKeyPath:keyPath];
  432. if (CPIsControllerMarker(value))
  433. {
  434. [self raiseIfNotApplicable:value forKeyPath:keyPath options:options];
  435. value = [theBinding _placeholderForMarker:value];
  436. }
  437. else
  438. value = [theBinding transformValue:value withOptions:options];
  439. if (operation === CPBindingOperationOr)
  440. {
  441. // Any true condition means true for OR
  442. if (value)
  443. return YES;
  444. }
  445. // Any false condition means false for AND
  446. else if (!value)
  447. return NO;
  448. bindingName = aBinding + (count++);
  449. }
  450. // If we get here, all OR conditions were false or all AND conditions were true
  451. return operation === CPBindingOperationOr ? NO : YES;
  452. }
  453. @end
  454. @implementation CPMultipleValueAndBinding : _CPMultipleValueBooleanBinding
  455. - (id)init
  456. {
  457. if (self = [super init])
  458. _operation = CPBindingOperationAnd;
  459. return self;
  460. }
  461. @end
  462. @implementation CPMultipleValueOrBinding : _CPMultipleValueBooleanBinding
  463. - (id)init
  464. {
  465. if (self = [super init])
  466. _operation = CPBindingOperationOr;
  467. return self;
  468. }
  469. @end
  470. @implementation _CPMultipleValueActionBinding : CPBinder
  471. {
  472. CPString _argumentBinding;
  473. CPString _targetBinding;
  474. }
  475. - (void)setValueFor:(CPString)theBinding
  476. {
  477. // Called when the binding is first created
  478. [self checkForNullBinding:theBinding initializing:YES];
  479. }
  480. - (void)reverseSetValueFor:(CPString)theBinding
  481. {
  482. // no-op
  483. }
  484. - (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObject change:(CPDictionary)changes context:(id)context
  485. {
  486. // context is the binding name
  487. [self checkForNullBinding:context initializing:NO];
  488. }
  489. /*!
  490. @ignore
  491. When the value of a multiple-value argument binding changes,
  492. if the binding is marked not to allow null arguments, we have to check
  493. if the binding's value is nil and disable the button if it is.
  494. Otherwise the button is enabled.
  495. */
  496. - (void)checkForNullBinding:(CPString)theBinding initializing:(BOOL)isInitializing
  497. {
  498. // Only done for buttons
  499. if (![_source isKindOfClass:CPButton])
  500. return;
  501. // We start with the button enabled for the first argument during init,
  502. // and subsequent checks can disable it.
  503. if (isInitializing && theBinding === CPArgumentBinding)
  504. [_source setEnabled:YES];
  505. var bindings = [bindingsMap valueForKey:[_source UID]],
  506. binding = [bindings objectForKey:theBinding],
  507. info = binding._info,
  508. options = [info objectForKey:CPOptionsKey];
  509. if (![options valueForKey:CPAllowsNullArgumentBindingOption])
  510. {
  511. var object = [info objectForKey:CPObservedObjectKey],
  512. keyPath = [info objectForKey:CPObservedKeyPathKey],
  513. value = [object valueForKeyPath:keyPath];
  514. if (value === nil || value === undefined)
  515. {
  516. [_source setEnabled:NO];
  517. return;
  518. }
  519. }
  520. // If a binding value changed and did not fail the null test, enable the button
  521. if (!isInitializing)
  522. [_source setEnabled:YES];
  523. }
  524. - (void)invokeAction
  525. {
  526. var bindings = [bindingsMap valueForKey:[_source UID]],
  527. theBinding = [bindings objectForKey:CPTargetBinding],
  528. info = theBinding._info,
  529. object = [info objectForKey:CPObservedObjectKey],
  530. keyPath = [info objectForKey:CPObservedKeyPathKey],
  531. options = [info objectForKey:CPOptionsKey],
  532. target = [object valueForKeyPath:keyPath],
  533. selector = [options objectForKey:CPSelectorNameBindingOption];
  534. if (!target || !selector)
  535. return;
  536. var invocation = [CPInvocation invocationWithMethodSignature:[target methodSignatureForSelector:selector]],
  537. bindingName = CPArgumentBinding,
  538. count = 1;
  539. while (theBinding = [bindings objectForKey:bindingName])
  540. {
  541. info = theBinding._info;
  542. object = [info objectForKey:CPObservedObjectKey];
  543. keyPath = [info objectForKey:CPObservedKeyPathKey];
  544. [invocation setArgument:[object valueForKeyPath:keyPath] atIndex:++count];
  545. bindingName = CPArgumentBinding + count;
  546. }
  547. [invocation setSelector:selector];
  548. [invocation invokeWithTarget:target];
  549. }
  550. @end
  551. @implementation CPActionBinding : _CPMultipleValueActionBinding
  552. - (id)init
  553. {
  554. if (self = [super init])
  555. {
  556. _argumentBinding = CPArgumentBinding;
  557. _targetBinding = CPTargetBinding;
  558. }
  559. return self;
  560. }
  561. @end
  562. @implementation CPDoubleClickActionBinding : _CPMultipleValueActionBinding
  563. - (id)init
  564. {
  565. if (self = [super init])
  566. {
  567. _argumentBinding = CPArgumentBinding;
  568. _targetBinding = CPTargetBinding;
  569. }
  570. return self;
  571. }
  572. @end
  573. /*!
  574. Abstract superclass for CPValueWithPatternBinding and CPTitleWithPatternBinding.
  575. */
  576. @implementation _CPPatternBinding : CPBinder
  577. {
  578. CPString _bindingKey;
  579. CPString _patternPlaceholder;
  580. }
  581. - (void)setValueFor:(CPString)aBinding
  582. {
  583. var bindings = [bindingsMap valueForKey:[_source UID]];
  584. if (!bindings)
  585. return;
  586. // Strip off any trailing number from the binding name
  587. var baseBinding = aBinding.replace(/\d$/, ""),
  588. result = [self resolveMultipleValuesForBindings:bindings];
  589. if (result.isPlaceholder)
  590. [self setPlaceholderValue:result.value withMarker:result.marker forBinding:baseBinding];
  591. else
  592. [self setValue:result.value forBinding:baseBinding];
  593. }
  594. - (void)reverseSetValueFor:(CPString)theBinding
  595. {
  596. // read-only
  597. }
  598. - (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObject change:(CPDictionary)changes context:(id)context
  599. {
  600. [self setValueFor:context];
  601. }
  602. - (JSObject)resolveMultipleValuesForBindings:(CPDictionary)bindings
  603. {
  604. var theBinding,
  605. result = { value:@"", isPlaceholder:NO, marker:nil };
  606. for (var count = 1; theBinding = [bindings objectForKey:_bindingKey + count]; ++count)
  607. {
  608. var info = theBinding._info,
  609. object = [info objectForKey:CPObservedObjectKey],
  610. keyPath = [info objectForKey:CPObservedKeyPathKey],
  611. options = [info objectForKey:CPOptionsKey],
  612. value = [object valueForKeyPath:keyPath];
  613. if (count === 1)
  614. result.value = [options objectForKey:CPDisplayPatternBindingOption];
  615. if (CPIsControllerMarker(value))
  616. {
  617. [self raiseIfNotApplicable:value forKeyPath:keyPath options:options];
  618. result.isPlaceholder = YES;
  619. result.marker = value;
  620. value = [theBinding _placeholderForMarker:value];
  621. }
  622. else
  623. value = [theBinding transformValue:value withOptions:options];
  624. if (value === nil || value === undefined)
  625. value = @"";
  626. result.value = result.value.replace("%{" + _patternPlaceholder + count + "}@", [value description]);
  627. }
  628. return result;
  629. }
  630. @end
  631. /*!
  632. Users of this class must override setValue:forKey: and if the
  633. key is CPDisplayPatternValueBinding, set the appropriate value
  634. for the control class. For example, CPTextField uses setObjectValue.
  635. */
  636. @implementation CPValueWithPatternBinding : _CPPatternBinding
  637. - (id)init
  638. {
  639. if (self = [super init])
  640. {
  641. _bindingKey = CPDisplayPatternValueBinding;
  642. _patternPlaceholder = @"value";
  643. }
  644. return self;
  645. }
  646. @end
  647. /*!
  648. Users of this class must override setValue:forKey: and if the
  649. key is CPDisplayPatternTitleBinding, set the appropriate value
  650. for the control class. For example, CPBox uses setTitle.
  651. */
  652. @implementation CPTitleWithPatternBinding : _CPPatternBinding
  653. - (id)init
  654. {
  655. if (self = [super init])
  656. {
  657. _bindingKey = CPDisplayPatternTitleBinding;
  658. _patternPlaceholder = @"title";
  659. }
  660. return self;
  661. }
  662. @end
  663. @implementation _CPStateMarker : CPObject
  664. {
  665. CPString _name;
  666. }
  667. - (id)initWithName:(CPString)aName
  668. {
  669. if (self = [super init])
  670. _name = aName
  671. return self;
  672. }
  673. - (CPString)description
  674. {
  675. return "<" + _name + ">";
  676. }
  677. @end
  678. // Keys in options dictionary
  679. // Keys in dictionary returned by infoForBinding
  680. CPObservedObjectKey = @"CPObservedObjectKey";
  681. CPObservedKeyPathKey = @"CPObservedKeyPathKey";
  682. CPOptionsKey = @"CPOptionsKey";
  683. // special markers
  684. CPNoSelectionMarker = [[_CPStateMarker alloc] initWithName:@"NO SELECTION MARKER"];
  685. CPMultipleValuesMarker = [[_CPStateMarker alloc] initWithName:@"MULTIPLE VALUES MARKER"];
  686. CPNotApplicableMarker = [[_CPStateMarker alloc] initWithName:@"NOT APPLICABLE MARKER"];
  687. CPNullMarker = [[_CPStateMarker alloc] initWithName:@"NULL MARKER"];
  688. // Binding name constants
  689. CPAlignmentBinding = @"alignment";
  690. CPArgumentBinding = @"argument";
  691. CPContentArrayBinding = @"contentArray";
  692. CPContentBinding = @"content";
  693. CPContentObjectBinding = @"contentObject";
  694. CPContentObjectsBinding = @"contentObjects";
  695. CPContentValuesBinding = @"contentValues";
  696. CPDisplayPatternTitleBinding = @"displayPatternTitle";
  697. CPDisplayPatternValueBinding = @"displayPatternValue";
  698. CPDoubleClickArgumentBinding = @"doubleClickArgument";
  699. CPDoubleClickTargetBinding = @"doubleClickTarget";
  700. CPEditableBinding = @"editable";
  701. CPEnabledBinding = @"enabled";
  702. CPFontBinding = @"font";
  703. CPFontNameBinding = @"fontName";
  704. CPFontBoldBinding = @"fontBold";
  705. CPHiddenBinding = @"hidden";
  706. CPFilterPredicateBinding = @"filterPredicate";
  707. CPMaxValueBinding = @"maxValue";
  708. CPMinValueBinding = @"minValue";
  709. CPPredicateBinding = @"predicate";
  710. CPSelectedIndexBinding = @"selectedIndex";
  711. CPSelectedLabelBinding = @"selectedLabel";
  712. CPSelectedObjectBinding = @"selectedObject";
  713. CPSelectedObjectsBinding = @"selectedObjects";
  714. CPSelectedTagBinding = @"selectedTag";
  715. CPSelectedValueBinding = @"selectedValue";
  716. CPSelectedValuesBinding = @"selectedValues";
  717. CPSelectionIndexesBinding = @"selectionIndexes";
  718. CPTargetBinding = @"target";
  719. CPTextColorBinding = @"textColor";
  720. CPTitleBinding = @"title";
  721. CPToolTipBinding = @"toolTip";
  722. CPValueBinding = @"value";
  723. CPValueURLBinding = @"valueURL";
  724. CPValuePathBinding = @"valuePath";
  725. CPDataBinding = @"data";
  726. // Binding options constants
  727. CPAllowsEditingMultipleValuesSelectionBindingOption = @"CPAllowsEditingMultipleValuesSelection";
  728. CPAllowsNullArgumentBindingOption = @"CPAllowsNullArgument";
  729. CPConditionallySetsEditableBindingOption = @"CPConditionallySetsEditable";
  730. CPConditionallySetsEnabledBindingOption = @"CPConditionallySetsEnabled";
  731. CPConditionallySetsHiddenBindingOption = @"CPConditionallySetsHidden";
  732. CPContinuouslyUpdatesValueBindingOption = @"CPContinuouslyUpdatesValue";
  733. CPCreatesSortDescriptorBindingOption = @"CPCreatesSortDescriptor";
  734. CPDeletesObjectsOnRemoveBindingsOption = @"CPDeletesObjectsOnRemove";
  735. CPDisplayNameBindingOption = @"CPDisplayName";
  736. CPDisplayPatternBindingOption = @"CPDisplayPattern";
  737. CPHandlesContentAsCompoundValueBindingOption = @"CPHandlesContentAsCompoundValue";
  738. CPInsertsNullPlaceholderBindingOption = @"CPInsertsNullPlaceholder";
  739. CPInvokesSeparatelyWithArrayObjectsBindingOption = @"CPInvokesSeparatelyWithArrayObjects";
  740. CPMultipleValuesPlaceholderBindingOption = @"CPMultipleValuesPlaceholder";
  741. CPNoSelectionPlaceholderBindingOption = @"CPNoSelectionPlaceholder";
  742. CPNotApplicablePlaceholderBindingOption = @"CPNotApplicablePlaceholder";
  743. CPNullPlaceholderBindingOption = @"CPNullPlaceholder";
  744. CPPredicateFormatBindingOption = @"CPPredicateFormat";
  745. CPRaisesForNotApplicableKeysBindingOption = @"CPRaisesForNotApplicableKeys";
  746. CPSelectorNameBindingOption = @"CPSelectorName";
  747. CPSelectsAllWhenSettingContentBindingOption = @"CPSelectsAllWhenSettingContent";
  748. CPValidatesImmediatelyBindingOption = @"CPValidatesImmediately";
  749. CPValueTransformerNameBindingOption = @"CPValueTransformerName";
  750. CPValueTransformerBindingOption = @"CPValueTransformer";
  751. CPIsControllerMarker = function(/*id*/anObject)
  752. {
  753. return anObject === CPMultipleValuesMarker || anObject === CPNoSelectionMarker || anObject === CPNotApplicableMarker || anObject === CPNullMarker;
  754. };
  755. var CPBinderPlaceholderMarkers = [CPMultipleValuesMarker, CPNoSelectionMarker, CPNotApplicableMarker, CPNullMarker],
  756. CPBinderPlaceholderOptions = [CPMultipleValuesPlaceholderBindingOption, CPNoSelectionPlaceholderBindingOption, CPNotApplicablePlaceholderBindingOption, CPNullPlaceholderBindingOption];