API  0.9.6
 All Classes Files Functions Variables Macros Groups Pages
CPKeyValueBinding.j
Go to the documentation of this 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 
26 
27 
30 
33 
34 @implementation CPBinder : CPObject
35 {
36  CPDictionary _info;
37  id _source;
38 
39  JSObject _suppressedNotifications;
40  JSObject _placeholderForMarker;
41 }
42 
43 + (void)exposeBinding:(CPString)aBinding forClass:(Class)aClass
44 {
45  var bindings = [exposedBindingsMap objectForKey:[aClass UID]];
46 
47  if (!bindings)
48  {
49  bindings = [];
50  [exposedBindingsMap setObject:bindings forKey:[aClass UID]];
51  }
52 
53  bindings.push(aBinding);
54 }
55 
56 + (CPArray)exposedBindingsForClass:(Class)aClass
57 {
58  return [[exposedBindingsMap objectForKey:[aClass UID]] copy];
59 }
60 
61 + (CPBinder)getBinding:(CPString)aBinding forObject:(id)anObject
62 {
63  return [[bindingsMap objectForKey:[anObject UID]] objectForKey:aBinding];
64 }
65 
66 + (CPDictionary)infoForBinding:(CPString)aBinding forObject:(id)anObject
67 {
68  var theBinding = [self getBinding:aBinding forObject:anObject];
69 
70  if (theBinding)
71  return theBinding._info;
72 
73  return nil;
74 }
75 
76 + (CPDictionary)allBindingsForObject:(id)anObject
77 {
78  return [bindingsMap objectForKey:[anObject UID]];
79 }
80 
81 + (void)unbind:(CPString)aBinding forObject:(id)anObject
82 {
83  var bindings = [bindingsMap objectForKey:[anObject UID]];
84 
85  if (!bindings)
86  return;
87 
88  var theBinding = [bindings objectForKey:aBinding];
89 
90  if (!theBinding)
91  return;
92 
93  var infoDictionary = theBinding._info,
94  observedObject = [infoDictionary objectForKey:CPObservedObjectKey],
95  keyPath = [infoDictionary objectForKey:CPObservedKeyPathKey];
96 
97  [observedObject removeObserver:theBinding forKeyPath:keyPath];
98  [bindings removeObjectForKey:aBinding];
99 }
100 
101 + (void)unbindAllForObject:(id)anObject
102 {
103  var bindings = [bindingsMap objectForKey:[anObject UID]];
104  if (!bindings)
105  return;
106 
107  var allKeys = [bindings allKeys],
108  count = allKeys.length;
109 
110  while (count--)
111  [anObject unbind:[bindings objectForKey:allKeys[count]]];
112 
113  [bindingsMap removeObjectForKey:[anObject UID]];
114 }
115 
116 - (id)initWithBinding:(CPString)aBinding name:(CPString)aName to:(id)aDestination keyPath:(CPString)aKeyPath options:(CPDictionary)options from:(id)aSource
117 {
118  self = [super init];
119 
120  if (self)
121  {
122  _source = aSource;
123  _info = [CPDictionary dictionaryWithObjects:[aDestination, aKeyPath] forKeys:[CPObservedObjectKey, CPObservedKeyPathKey]];
124  _suppressedNotifications = {};
125  _placeholderForMarker = {};
126 
127  if (options)
128  [_info setObject:options forKey:CPOptionsKey];
129 
130  [self _updatePlaceholdersWithOptions:options forBinding:aName];
131 
132  [aDestination addObserver:self forKeyPath:aKeyPath options:CPKeyValueObservingOptionNew context:aBinding];
133 
134  var bindings = [bindingsMap objectForKey:[_source UID]];
135  if (!bindings)
136  {
137  bindings = [CPDictionary new];
138  [bindingsMap setObject:bindings forKey:[_source UID]];
139  }
140 
141  [bindings setObject:self forKey:aName];
142  [self setValueFor:aBinding];
143  }
144 
145  return self;
146 }
147 
148 - (void)setValueFor:(CPString)theBinding
149 {
150  var destination = [_info objectForKey:CPObservedObjectKey],
151  keyPath = [_info objectForKey:CPObservedKeyPathKey],
152  options = [_info objectForKey:CPOptionsKey],
153  newValue = [destination valueForKeyPath:keyPath],
154  isPlaceholder = CPIsControllerMarker(newValue);
155 
156  if (isPlaceholder)
157  {
158  if (newValue === CPNotApplicableMarker && [options objectForKey:CPRaisesForNotApplicableKeysBindingOption])
159  {
160  [CPException raise:CPGenericException
161  reason:@"Cannot transform non-applicable key on: " + _source + " key path: " + keyPath + " value: " + newValue];
162  }
163 
164  var value = [self _placeholderForMarker:newValue];
165  [self setPlaceholderValue:value withMarker:newValue forBinding:theBinding];
166  }
167  else
168  {
169  var value = [self transformValue:newValue withOptions:options];
170  [self setValue:value forBinding:theBinding];
171  }
172 }
173 
174 - (void)setPlaceholderValue:(id)aValue withMarker:(CPString)aMarker forBinding:(CPString)aBinding
175 {
176  [_source setValue:aValue forKey:aBinding];
177 }
178 
179 - (void)setValue:(id)aValue forBinding:(CPString)aBinding
180 {
181  [_source setValue:aValue forKey:aBinding];
182 }
183 
184 - (void)reverseSetValueFor:(CPString)aBinding
185 {
186  var destination = [_info objectForKey:CPObservedObjectKey],
187  keyPath = [_info objectForKey:CPObservedKeyPathKey],
188  options = [_info objectForKey:CPOptionsKey],
189  newValue = [self valueForBinding:aBinding];
190 
191  newValue = [self reverseTransformValue:newValue withOptions:options];
192 
193  [self suppressSpecificNotificationFromObject:destination keyPath:keyPath];
194  [destination setValue:newValue forKeyPath:keyPath];
195  [self unsuppressSpecificNotificationFromObject:destination keyPath:keyPath];
196 }
197 
198 - (id)valueForBinding:(CPString)aBinding
199 {
200  return [_source valueForKeyPath:aBinding];
201 }
202 
203 - (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObject change:(CPDictionary)changes context:(id)context
204 {
205  if (!changes)
206  return;
207 
208  var objectSuppressions = _suppressedNotifications[[anObject UID]];
209  if (objectSuppressions && objectSuppressions[aKeyPath])
210  return;
211 
212  [self setValueFor:context];
213 }
214 
215 - (id)transformValue:(id)aValue withOptions:(CPDictionary)options
216 {
217  var valueTransformerName = [options objectForKey:CPValueTransformerNameBindingOption],
218  valueTransformer;
219 
220  if (valueTransformerName)
221  {
222  valueTransformer = [CPValueTransformer valueTransformerForName:valueTransformerName];
223 
224  if (!valueTransformer)
225  {
226  var valueTransformerClass = CPClassFromString(valueTransformerName);
227  if (valueTransformerClass)
228  {
229  valueTransformer = [[valueTransformerClass alloc] init];
230  [valueTransformerClass setValueTransformer:valueTransformer forName:valueTransformerName];
231  }
232  }
233  }
234  else
235  valueTransformer = [options objectForKey:CPValueTransformerBindingOption];
236 
237  if (valueTransformer)
238  aValue = [valueTransformer transformedValue:aValue];
239 
240  if (aValue === undefined || aValue === nil || aValue === [CPNull null])
241  aValue = [options objectForKey:CPNullPlaceholderBindingOption] || nil;
242 
243  return aValue;
244 }
245 
246 - (id)reverseTransformValue:(id)aValue withOptions:(CPDictionary)options
247 {
248  var valueTransformerName = [options objectForKey:CPValueTransformerNameBindingOption],
249  valueTransformer;
250 
251  if (valueTransformerName)
252  valueTransformer = [CPValueTransformer valueTransformerForName:valueTransformerName];
253  else
254  valueTransformer = [options objectForKey:CPValueTransformerBindingOption];
255 
256  if (valueTransformer && [[valueTransformer class] allowsReverseTransformation])
257  aValue = [valueTransformer reverseTransformedValue:aValue];
258 
259  return aValue;
260 }
261 
262 - (BOOL)continuouslyUpdatesValue
263 {
264  var options = [_info objectForKey:CPOptionsKey];
265  return [[options objectForKey:CPContinuouslyUpdatesValueBindingOption] boolValue];
266 }
267 
268 - (BOOL)handlesContentAsCompoundValue
269 {
270  var options = [_info objectForKey:CPOptionsKey];
271  return [[options objectForKey:CPHandlesContentAsCompoundValueBindingOption] boolValue];
272 }
273 
277 - (void)suppressSpecificNotificationFromObject:(id)anObject keyPath:(CPString)aKeyPath
278 {
279  if (!anObject)
280  return;
281 
282  var uid = [anObject UID],
283  objectSuppressions = _suppressedNotifications[uid];
284  if (!objectSuppressions)
285  _suppressedNotifications[uid] = objectSuppressions = {};
286 
287  objectSuppressions[aKeyPath] = YES;
288 }
289 
293 - (void)unsuppressSpecificNotificationFromObject:(id)anObject keyPath:(CPString)aKeyPath
294 {
295  if (!anObject)
296  return;
297 
298  var uid = [anObject UID],
299  objectSuppressions = _suppressedNotifications[uid];
300  if (!objectSuppressions)
301  return;
302 
303  delete objectSuppressions[aKeyPath];
304 }
305 
306 - (void)_updatePlaceholdersWithOptions:(CPDictionary)options
307 {
308  var count = [CPBinderPlaceholderMarkers count];
309 
310  while (count--)
311  {
312  var marker = CPBinderPlaceholderMarkers[count],
313  optionName = CPBinderPlaceholderOptions[count],
314  isExplicit = [options containsKey:optionName],
315  placeholder = isExplicit ? [options objectForKey:optionName] : nil;
316  [self _setPlaceholder:placeholder forMarker:marker isDefault:!isExplicit];
317  }
318 }
319 
320 - (void)_updatePlaceholdersWithOptions:(CPDictionary)options forBinding:(CPString)aBinding
321 {
322  [self _updatePlaceholdersWithOptions:options];
323 }
324 
325 - (void)_placeholderForMarker:aMarker
326 {
327  var placeholder = _placeholderForMarker[aMarker];
328  if (placeholder)
329  return placeholder['value'];
330  return nil;
331 }
332 
333 - (void)_setPlaceholder:(id)aPlaceholder forMarker:(id)aMarker isDefault:(BOOL)isDefault
334 {
335  if (isDefault)
336  {
337  var existingPlaceholder = _placeholderForMarker[aMarker];
338 
339  // Don't overwrite an explicitly set placeholder with a default.
340  if (existingPlaceholder && !existingPlaceholder['isDefault'])
341  return;
342  }
343 
344  _placeholderForMarker[aMarker] = { 'isDefault': isDefault, 'value': aPlaceholder };
345 }
346 
347 @end
348 
350 
351 + (void)exposeBinding:(CPString)aBinding
352 {
353  [CPBinder exposeBinding:aBinding forClass:[self class]];
354 }
355 
356 + (Class)_binderClassForBinding:(CPString)theBinding
357 {
358  return [CPBinder class];
359 }
360 
361 - (CPArray)exposedBindings
362 {
363  var exposedBindings = [],
364  theClass = [self class];
365 
366  while (theClass)
367  {
368  var temp = [CPBinder exposedBindingsForClass:theClass];
369 
370  if (temp)
371  [exposedBindings addObjectsFromArray:temp];
372 
373  theClass = [theClass superclass];
374  }
375 
376  return exposedBindings;
377 }
378 
379 - (Class)valueClassForBinding:(CPString)binding
380 {
381  return [CPString class];
382 }
383 
384 - (void)bind:(CPString)aBinding toObject:(id)anObject withKeyPath:(CPString)aKeyPath options:(CPDictionary)options
385 {
386  if (!anObject || !aKeyPath)
387  return CPLog.error("Invalid object or path on " + self + " for " + aBinding);
388 
389  //if (![[self exposedBindings] containsObject:aBinding])
390  // CPLog.warn("No binding exposed on " + self + " for " + aBinding);
391 
392  var binderClass = [[self class] _binderClassForBinding:aBinding];
393 
394  [self unbind:aBinding];
395  [[binderClass alloc] initWithBinding:[self _replacementKeyPathForBinding:aBinding] name:aBinding to:anObject keyPath:aKeyPath options:options from:self];
396 }
397 
398 - (CPDictionary)infoForBinding:(CPString)aBinding
399 {
400  return [CPBinder infoForBinding:aBinding forObject:self];
401 }
402 
403 - (void)unbind:(CPString)aBinding
404 {
405  var binderClass = [[self class] _binderClassForBinding:aBinding];
406  [binderClass unbind:aBinding forObject:self];
407 }
408 
409 - (id)_replacementKeyPathForBinding:(CPString)binding
410 {
411  return binding;
412 }
413 
414 @end
415 
422 @implementation _CPValueBinder : CPBinder
423 {
424  id __doxygen__;
425 }
426 
427 - (void)setValueFor:(CPString)theBinding
428 {
429  [super setValueFor:@"objectValue"];
430 }
431 
432 - (void)reverseSetValueFor:(CPString)theBinding
433 {
434  [super reverseSetValueFor:@"objectValue"];
435 }
436 
437 @end
438 @implementation _CPKeyValueOrBinding : CPBinder
439 {
440  id __doxygen__;
441 }
442 
443 - (void)setValueFor:(CPString)aBinding
444 {
445  var bindings = [bindingsMap valueForKey:[_source UID]];
446 
447  if (!bindings)
448  return;
449 
450  [_source setValue:resolveMultipleValues(aBinding, bindings, CPBindingOperationOr) forKey:aBinding];
451 }
452 
453 - (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObject change:(CPDictionary)changes context:(id)context
454 {
455  [self setValueFor:context];
456 }
457 
458 @end
459 @implementation _CPKeyValueAndBinding : CPBinder
460 {
461  id __doxygen__;
462 }
463 
464 - (void)setValueFor:(CPString)aBinding
465 {
466  var bindings = [bindingsMap objectForKey:[_source UID]];
467 
468  if (!bindings)
469  return;
470 
471  [_source setValue:resolveMultipleValues(aBinding, bindings, CPBindingOperationAnd) forKey:aBinding];
472 }
473 
474 - (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObejct change:(CPDictionary)changes context:(id)context
475 {
476  [self setValueFor:context];
477 }
478 
479 @end
480 
481 var resolveMultipleValues = function(/*CPString*/key, /*CPDictionary*/bindings, /*GSBindingOperationKind*/operation)
482 {
483  var bindingName = key,
484  theBinding,
485  count = 1;
486 
487  while (theBinding = [bindings objectForKey:bindingName])
488  {
489  var infoDictionary = theBinding._info,
490  object = [infoDictionary objectForKey:CPObservedObjectKey],
491  keyPath = [infoDictionary objectForKey:CPObservedKeyPathKey],
492  options = [infoDictionary objectForKey:CPOptionsKey];
493 
494  var value = [theBinding transformValue:[object valueForKeyPath:keyPath] withOptions:options];
495 
496  if (value == operation)
497  return operation;
498 
499  bindingName = [CPString stringWithFormat:@"%@%i", key, ++count];
500  }
501 
502  return !operation;
503 };
504 
505 var invokeAction = function(/*CPString*/targetKey, /*CPString*/argumentKey, /*CPDictionary*/bindings)
506 {
507  var theBinding = [bindings objectForKey:targetKey],
508  infoDictionary = theBinding._info,
509 
510  object = [infoDictionary objectForKey:CPObservedObjectKey],
511  keyPath = [infoDictionary objectForKey:CPObservedKeyPathKey],
512  options = [infoDictionary objectForKey:CPOptionsKey],
513 
514  target = [object valueForKeyPath:keyPath],
515  selector = [options objectForKey:CPSelectorNameBindingOption];
516 
517  if (!target || !selector)
518  return;
519 
520  var invocation = [CPInvocation invocationWithMethodSignature:[target methodSignatureForSelector:selector]];
521  [invocation setSelector:selector];
522 
523  var bindingName = argumentKey,
524  count = 1;
525 
526  while (theBinding = [bindings objectForKey:bindingName])
527  {
528  infoDictionary = theBinding._info;
529 
530  keyPath = [infoDictionary objectForKey:CPObserverKeyPathKey];
531  object = [[infoDictionary objectForKey:CPObservedObjectKey] valueForKeyPath:keyPath];
532 
533  if (object)
534  [invocation setArgument:object atIndex:++count];
535 
536  bindingName = [CPString stringWithFormat:@"%@%i", argumentKey, count];
537  }
538 
539  [invocation invoke];
540 };
541 
542 // Keys in options dictionary
543 
544 // Keys in dictionary returned by infoForBinding
545 CPObservedObjectKey = @"CPObservedObjectKey";
546 CPObservedKeyPathKey = @"CPObservedKeyPathKey";
547 CPOptionsKey = @"CPOptionsKey";
548 
549 // special markers
550 CPMultipleValuesMarker = @"CPMultipleValuesMarker";
551 CPNoSelectionMarker = @"CPNoSelectionMarker";
552 CPNotApplicableMarker = @"CPNotApplicableMarker";
553 CPNullMarker = @"CPNullMarker";
554 
555 // Binding name constants
556 CPAlignmentBinding = @"alignment";
557 CPContentArrayBinding = @"contentArray";
558 CPContentBinding = @"content";
559 CPContentObjectBinding = @"contentObject";
560 CPContentObjectsBinding = @"contentObjects";
561 CPContentValuesBinding = @"contentValues";
562 CPEditableBinding = @"editable";
563 CPEnabledBinding = @"enabled";
564 CPFontBinding = @"font";
565 CPFontNameBinding = @"fontName";
566 CPFontBoldBinding = @"fontBold";
567 CPHiddenBinding = @"hidden";
568 CPFilterPredicateBinding = @"filterPredicate";
569 CPMaxValueBinding = @"maxValue";
570 CPMinValueBinding = @"minValue";
571 CPPredicateBinding = @"predicate";
572 CPSelectedIndexBinding = @"selectedIndex";
573 CPSelectedLabelBinding = @"selectedLabel";
574 CPSelectedObjectBinding = @"selectedObject";
575 CPSelectedObjectsBinding = @"selectedObjects";
576 CPSelectedTagBinding = @"selectedTag";
577 CPSelectedValueBinding = @"selectedValue";
578 CPSelectedValuesBinding = @"selectedValues";
579 CPSelectionIndexesBinding = @"selectionIndexes";
580 CPTextColorBinding = @"textColor";
581 CPTitleBinding = @"title";
582 CPToolTipBinding = @"toolTip";
583 CPValueBinding = @"value";
584 CPValueURLBinding = @"valueURL";
585 CPValuePathBinding = @"valuePath";
586 CPDataBinding = @"data";
587 
588 //Binding options constants
589 CPAllowsEditingMultipleValuesSelectionBindingOption = @"CPAllowsEditingMultipleValuesSelection";
590 CPAllowsNullArgumentBindingOption = @"CPAllowsNullArgument";
591 CPConditionallySetsEditableBindingOption = @"CPConditionallySetsEditable";
592 CPConditionallySetsEnabledBindingOption = @"CPConditionallySetsEnabled";
593 CPConditionallySetsHiddenBindingOption = @"CPConditionallySetsHidden";
594 CPContinuouslyUpdatesValueBindingOption = @"CPContinuouslyUpdatesValue";
595 CPCreatesSortDescriptorBindingOption = @"CPCreatesSortDescriptor";
596 CPDeletesObjectsOnRemoveBindingsOption = @"CPDeletesObjectsOnRemove";
597 CPDisplayNameBindingOption = @"CPDisplayName";
598 CPDisplayPatternBindingOption = @"CPDisplayPattern";
599 CPHandlesContentAsCompoundValueBindingOption = @"CPHandlesContentAsCompoundValue";
600 CPInsertsNullPlaceholderBindingOption = @"CPInsertsNullPlaceholder";
601 CPInvokesSeparatelyWithArrayObjectsBindingOption = @"CPInvokesSeparatelyWithArrayObjects";
602 CPMultipleValuesPlaceholderBindingOption = @"CPMultipleValuesPlaceholder";
603 CPNoSelectionPlaceholderBindingOption = @"CPNoSelectionPlaceholder";
604 CPNotApplicablePlaceholderBindingOption = @"CPNotApplicablePlaceholder";
605 CPNullPlaceholderBindingOption = @"CPNullPlaceholder";
606 CPPredicateFormatBindingOption = @"CPPredicateFormat";
607 CPRaisesForNotApplicableKeysBindingOption = @"CPRaisesForNotApplicableKeys";
608 CPSelectorNameBindingOption = @"CPSelectorName";
609 CPSelectsAllWhenSettingContentBindingOption = @"CPSelectsAllWhenSettingContent";
610 CPValidatesImmediatelyBindingOption = @"CPValidatesImmediately";
611 CPValueTransformerNameBindingOption = @"CPValueTransformerName";
612 CPValueTransformerBindingOption = @"CPValueTransformer";
613 
614 CPIsControllerMarker = function(/*id*/anObject)
615 {
616  return anObject === CPMultipleValuesMarker || anObject === CPNoSelectionMarker || anObject === CPNotApplicableMarker || anObject === CPNullMarker;
617 };
618 
619 var CPBinderPlaceholderMarkers = [CPMultipleValuesMarker, CPNoSelectionMarker, CPNotApplicableMarker, CPNullMarker],
620  CPBinderPlaceholderOptions = [CPMultipleValuesPlaceholderBindingOption, CPNoSelectionPlaceholderBindingOption, CPNotApplicablePlaceholderBindingOption, CPNullPlaceholderBindingOption];
621