API 0.9.5
AppKit/CPKeyValueBinding.j
Go to the documentation of this file.
00001 /*
00002  * CPKeyValueBinding.j
00003  * AppKit
00004  *
00005  * Created by Ross Boucher 1/13/09
00006  * Copyright 280 North, Inc.
00007  *
00008  * Adapted from GNUStep
00009  * Released under the LGPL.
00010  *
00011  * This library is free software; you can redistribute it and/or
00012  * modify it under the terms of the GNU Lesser General Public
00013  * License as published by the Free Software Foundation; either
00014  * version 2.1 of the License, or (at your option) any later version.
00015  *
00016  * This library is distributed in the hope that it will be useful,
00017  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00018  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
00019  * Lesser General Public License for more details.
00020  *
00021  * You should have received a copy of the GNU Lesser General Public
00022  * License along with this library; if not, write to the Free Software
00023  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
00024  */
00025 
00026 
00027 
00028 var exposedBindingsMap = [CPDictionary new],
00029     bindingsMap = [CPDictionary new];
00030 
00031 var CPBindingOperationAnd = 0,
00032     CPBindingOperationOr  = 1;
00033 
00034 @implementation CPBinder : CPObject
00035 {
00036     CPDictionary    _info;
00037     id              _source;
00038 
00039     JSObject        _suppressedNotifications;
00040 }
00041 
00042 + (void)exposeBinding:(CPString)aBinding forClass:(Class)aClass
00043 {
00044     var bindings = [exposedBindingsMap objectForKey:[aClass UID]];
00045 
00046     if (!bindings)
00047     {
00048         bindings = [];
00049         [exposedBindingsMap setObject:bindings forKey:[aClass UID]];
00050     }
00051 
00052     bindings.push(aBinding);
00053 }
00054 
00055 + (CPArray)exposedBindingsForClass:(Class)aClass
00056 {
00057     return [[exposedBindingsMap objectForKey:[aClass UID]] copy];
00058 }
00059 
00060 + (CPBinder)getBinding:(CPString)aBinding forObject:(id)anObject
00061 {
00062     return [[bindingsMap objectForKey:[anObject UID]] objectForKey:aBinding];
00063 }
00064 
00065 + (CPDictionary)infoForBinding:(CPString)aBinding forObject:(id)anObject
00066 {
00067     var theBinding = [self getBinding:aBinding forObject:anObject];
00068 
00069     if (theBinding)
00070         return theBinding._info;
00071 
00072     return nil;
00073 }
00074 
00075 + (CPDictionary)allBindingsForObject:(id)anObject
00076 {
00077     return [bindingsMap objectForKey:[anObject UID]];
00078 }
00079 
00080 + (void)unbind:(CPString)aBinding forObject:(id)anObject
00081 {
00082     var bindings = [bindingsMap objectForKey:[anObject UID]];
00083 
00084     if (!bindings)
00085         return;
00086 
00087     var theBinding = [bindings objectForKey:aBinding];
00088 
00089     if (!theBinding)
00090         return;
00091 
00092     var infoDictionary = theBinding._info,
00093         observedObject = [infoDictionary objectForKey:CPObservedObjectKey],
00094         keyPath = [infoDictionary objectForKey:CPObservedKeyPathKey];
00095 
00096     [observedObject removeObserver:theBinding forKeyPath:keyPath];
00097     [bindings removeObjectForKey:aBinding];
00098 }
00099 
00100 + (void)unbindAllForObject:(id)anObject
00101 {
00102     var bindings = [bindingsMap objectForKey:[anObject UID]];
00103     if (!bindings)
00104         return;
00105 
00106     var allKeys = [bindings allKeys],
00107         count = allKeys.length;
00108 
00109     while (count--)
00110         [anObject unbind:[bindings objectForKey:allKeys[count]]];
00111 
00112     [bindingsMap removeObjectForKey:[anObject UID]];
00113 }
00114 
00115 - (id)initWithBinding:(CPString)aBinding name:(CPString)aName to:(id)aDestination keyPath:(CPString)aKeyPath options:(CPDictionary)options from:(id)aSource
00116 {
00117     self = [super init];
00118 
00119     if (self)
00120     {
00121         _source = aSource;
00122         _info   = [CPDictionary dictionaryWithObjects:[aDestination, aKeyPath] forKeys:[CPObservedObjectKey, CPObservedKeyPathKey]];
00123         _suppressedNotifications = {};
00124 
00125         if (options)
00126             [_info setObject:options forKey:CPOptionsKey];
00127 
00128         [aDestination addObserver:self forKeyPath:aKeyPath options:CPKeyValueObservingOptionNew context:aBinding];
00129 
00130         var bindings = [bindingsMap objectForKey:[_source UID]];
00131         if (!bindings)
00132         {
00133             bindings = [CPDictionary new];
00134             [bindingsMap setObject:bindings forKey:[_source UID]];
00135         }
00136 
00137         [bindings setObject:self forKey:aName];
00138         [self setValueFor:aBinding];
00139     }
00140 
00141     return self;
00142 }
00143 
00144 - (void)setValueFor:(CPString)aBinding
00145 {
00146     var destination = [_info objectForKey:CPObservedObjectKey],
00147         keyPath = [_info objectForKey:CPObservedKeyPathKey],
00148         options = [_info objectForKey:CPOptionsKey],
00149         newValue = [destination valueForKeyPath:keyPath];
00150 
00151     newValue = [self transformValue:newValue withOptions:options];
00152     [_source setValue:newValue forKey:aBinding];
00153 }
00154 
00155 - (void)reverseSetValueFor:(CPString)aBinding
00156 {
00157     var destination = [_info objectForKey:CPObservedObjectKey],
00158         keyPath = [_info objectForKey:CPObservedKeyPathKey],
00159         options = [_info objectForKey:CPOptionsKey],
00160         newValue = [_source valueForKeyPath:aBinding];
00161 
00162     newValue = [self reverseTransformValue:newValue withOptions:options];
00163     [destination setValue:newValue forKeyPath:keyPath];
00164 }
00165 
00166 - (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObject change:(CPDictionary)changes context:(id)context
00167 {
00168     if (!changes)
00169         return;
00170 
00171     var objectSuppressions = _suppressedNotifications[[anObject UID]];
00172     if (objectSuppressions && objectSuppressions[aKeyPath])
00173         return;
00174 
00175     [self setValueFor:context];
00176 }
00177 
00178 - (id)transformValue:(id)aValue withOptions:(CPDictionary)options
00179 {
00180     var valueTransformerName = [options objectForKey:CPValueTransformerNameBindingOption],
00181         valueTransformer;
00182 
00183     if (valueTransformerName)
00184     {
00185         valueTransformer = [CPValueTransformer valueTransformerForName:valueTransformerName];
00186 
00187         if (!valueTransformer)
00188         {
00189             var valueTransformerClass = CPClassFromString(valueTransformerName);
00190             if (valueTransformerClass)
00191             {
00192                 valueTransformer = [[valueTransformerClass alloc] init];
00193                 [valueTransformerClass setValueTransformer:valueTransformer forName:valueTransformerName];
00194             }
00195         }
00196     }
00197     else
00198         valueTransformer = [options objectForKey:CPValueTransformerBindingOption];
00199 
00200     if (valueTransformer)
00201         aValue = [valueTransformer transformedValue:aValue];
00202 
00203 
00204     if (aValue === undefined || aValue === nil || aValue === [CPNull null])
00205         aValue = [options objectForKey:CPNullPlaceholderBindingOption] || nil;
00206 
00207     return aValue;
00208 }
00209 
00210 - (id)reverseTransformValue:(id)aValue withOptions: (CPDictionary)options
00211 {
00212     var valueTransformerName = [options objectForKey:CPValueTransformerNameBindingOption],
00213         valueTransformer;
00214 
00215     if (valueTransformerName)
00216         valueTransformer = [CPValueTransformer valueTransformerForName:valueTransformerName];
00217     else
00218         valueTransformer = [options objectForKey:CPValueTransformerBindingOption];
00219 
00220     if (valueTransformer && [[valueTransformer class] allowsReverseTransformation])
00221         aValue = [valueTransformer reverseTransformedValue:aValue];
00222 
00223     return aValue;
00224 }
00225 
00226 - (BOOL)continuouslyUpdatesValue
00227 {
00228     var options = [_info objectForKey:CPOptionsKey];
00229     return [[options objectForKey:CPContinuouslyUpdatesValueBindingOption] boolValue];
00230 }
00231 
00232 - (BOOL)handlesContentAsCompoundValue
00233 {
00234     var options = [_info objectForKey:CPOptionsKey];
00235     return [[options objectForKey:CPHandlesContentAsCompoundValueBindingOption] boolValue];
00236 }
00237 
00241 - (void)suppressSpecificNotificationFromObject:(id)anObject keyPath:(CPString)aKeyPath
00242 {
00243     if (!anObject)
00244         return;
00245 
00246     var uid = [anObject UID],
00247         objectSuppressions = _suppressedNotifications[uid];
00248     if (!objectSuppressions)
00249         _suppressedNotifications[uid] = objectSuppressions = {};
00250 
00251     objectSuppressions[aKeyPath] = YES;
00252 }
00253 
00257 - (void)unsuppressSpecificNotificationFromObject:(id)anObject keyPath:(CPString)aKeyPath
00258 {
00259     if (!anObject)
00260         return;
00261 
00262     var uid = [anObject UID],
00263         objectSuppressions = _suppressedNotifications[uid];
00264     if (!objectSuppressions)
00265         return;
00266 
00267     delete objectSuppressions[aKeyPath];
00268 }
00269 
00270 @end
00271 
00272 @implementation CPObject (KeyValueBindingCreation)
00273 
00274 + (void)exposeBinding:(CPString)aBinding
00275 {
00276     [CPBinder exposeBinding:aBinding forClass:[self class]];
00277 }
00278 
00279 + (Class)_binderClassForBinding:(CPString)theBinding
00280 {
00281     return [CPBinder class];
00282 }
00283 
00284 - (CPArray)exposedBindings
00285 {
00286     var exposedBindings = [],
00287         theClass = [self class];
00288 
00289     while (theClass)
00290     {
00291         var temp = [CPBinder exposedBindingsForClass:theClass];
00292 
00293         if (temp)
00294             [exposedBindings addObjectsFromArray:temp];
00295 
00296         theClass = [theClass superclass];
00297     }
00298 
00299     return exposedBindings;
00300 }
00301 
00302 - (Class)valueClassForBinding:(CPString)binding
00303 {
00304     return [CPString class];
00305 }
00306 
00307 - (void)bind:(CPString)aBinding toObject:(id)anObject withKeyPath:(CPString)aKeyPath options:(CPDictionary)options
00308 {
00309     if (!anObject || !aKeyPath)
00310         return CPLog.error("Invalid object or path on " + self + " for " + aBinding);
00311 
00312     //if (![[self exposedBindings] containsObject:aBinding])
00313     //    CPLog.warn("No binding exposed on " + self + " for " + aBinding);
00314 
00315     var binderClass = [[self class] _binderClassForBinding:aBinding];
00316 
00317     [self unbind:aBinding];
00318     [[binderClass alloc] initWithBinding:[self _replacementKeyPathForBinding:aBinding] name:aBinding to:anObject keyPath:aKeyPath options:options from:self];
00319 }
00320 
00321 - (CPDictionary)infoForBinding:(CPString)aBinding
00322 {
00323     return [CPBinder infoForBinding:aBinding forObject:self];
00324 }
00325 
00326 - (void)unbind:(CPString)aBinding
00327 {
00328     var binderClass = [[self class] _binderClassForBinding:aBinding];
00329     [binderClass unbind:aBinding forObject:self];
00330 }
00331 
00332 - (id)_replacementKeyPathForBinding:(CPString)binding
00333 {
00334     return binding;
00335 }
00336 
00337 @end
00338 
00345 @implementation _CPValueBinder : CPBinder
00346 {
00347     id __doxygen__;
00348 }
00349 
00350 - (void)setValueFor:(CPString)theBinding
00351 {
00352     [super setValueFor:@"objectValue"];
00353 }
00354 
00355 - (void)reverseSetValueFor:(CPString)theBinding
00356 {
00357     [super reverseSetValueFor:@"objectValue"];
00358 }
00359 
00360 @end
00361 @implementation _CPKeyValueOrBinding : CPBinder
00362 {
00363     id __doxygen__;
00364 }
00365 
00366 - (void)setValueFor:(CPString)aBinding
00367 {
00368     var bindings = [bindingsMap valueForKey:[_source UID]];
00369 
00370     if (!bindings)
00371         return;
00372 
00373     [_source setValue:resolveMultipleValues(aBinding, bindings, CPBindingOperationOr) forKey:aBinding];
00374 }
00375 
00376 - (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObject change:(CPDictionary)changes context:(id)context
00377 {
00378     [self setValueFor:context];
00379 }
00380 
00381 @end
00382 @implementation _CPKeyValueAndBinding : CPBinder
00383 {
00384     id __doxygen__;
00385 }
00386 
00387 - (void)setValueFor:(CPString)aBinding
00388 {
00389     var bindings = [bindingsMap objectForKey:[_source UID]];
00390 
00391     if (!bindings)
00392         return;
00393 
00394     [_source setValue:resolveMultipleValues(aBinding, bindings, CPBindingOperationAnd) forKey:aBinding];
00395 }
00396 
00397 - (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObejct change:(CPDictionary)changes context:(id)context
00398 {
00399     [self setValueFor:context];
00400 }
00401 
00402 @end
00403 
00404 var resolveMultipleValues = function resolveMultipleValues(/*CPString*/key, /*CPDictionary*/bindings, /*GSBindingOperationKind*/operation)
00405 {
00406     var bindingName = key,
00407         theBinding,
00408         count = 1;
00409 
00410     while (theBinding = [bindings objectForKey:bindingName])
00411     {
00412         var infoDictionary = theBinding._info,
00413             object  = [infoDictionary objectForKey:CPObservedObjectKey],
00414             keyPath = [infoDictionary objectForKey:CPObservedKeyPathKey],
00415             options = [infoDictionary objectForKey:CPOptionsKey];
00416 
00417         var value = [theBinding transformValue:[object valueForKeyPath:keyPath] withOptions:options];
00418 
00419         if (value == operation)
00420             return operation;
00421 
00422         bindingName = [CPString stringWithFormat:@"%@%i", key, ++count];
00423     }
00424 
00425     return !operation;
00426 }
00427 
00428 var invokeAction = function invokeAction(/*CPString*/targetKey, /*CPString*/argumentKey, /*CPDictionary*/bindings)
00429 {
00430     var theBinding = [bindings objectForKey:targetKey],
00431         infoDictionary = theBinding._info,
00432 
00433         object   = [infoDictionary objectForKey:CPObservedObjectKey],
00434         keyPath  = [infoDictionary objectForKey:CPObservedKeyPathKey],
00435         options  = [infoDictionary objectForKey:CPOptionsKey],
00436 
00437         target   = [object valueForKeyPath:keyPath],
00438         selector = [options objectForKey:CPSelectorNameBindingOption];
00439 
00440     if (!target || !selector)
00441         return;
00442 
00443     var invocation = [CPInvocation invocationWithMethodSignature:[target methodSignatureForSelector:selector]];
00444     [invocation setSelector:selector];
00445 
00446     var bindingName = argumentKey,
00447         count = 1;
00448 
00449     while (theBinding = [bindings objectForKey:bindingName])
00450     {
00451         infoDictionary = theBinding._info;
00452 
00453         keyPath = [infoDictionary objectForKey:CPObserverKeyPathKey];
00454         object  = [[infoDictionary objectForKey:CPObservedObjectKey] valueForKeyPath:keyPath];
00455 
00456         if (object)
00457             [invocation setArgument:object atIndex:++count];
00458 
00459         bindingName = [CPString stringWithFormat:@"%@%i", argumentKey, count];
00460     }
00461 
00462     [invocation invoke];
00463 }
00464 
00465 // Keys in options dictionary
00466 
00467 // Keys in dictionary returned by infoForBinding
00468 CPObservedObjectKey     = @"CPObservedObjectKey";
00469 CPObservedKeyPathKey    = @"CPObservedKeyPathKey";
00470 CPOptionsKey            = @"CPOptionsKey";
00471 
00472 // special markers
00473 CPMultipleValuesMarker  = @"CPMultipleValuesMarker";
00474 CPNoSelectionMarker     = @"CPNoSelectionMarker";
00475 CPNotApplicableMarker   = @"CPNotApplicableMarker";
00476 CPNullMarker            = @"CPNullMarker";
00477 
00478 // Binding name constants
00479 CPAlignmentBinding      = @"alignment";
00480 CPEditableBinding       = @"editable";
00481 CPEnabledBinding        = @"enabled";
00482 CPFontBinding           = @"font";
00483 CPHiddenBinding         = @"hidden";
00484 CPSelectedIndexBinding  = @"selectedIndex";
00485 CPTextColorBinding      = @"textColor";
00486 CPToolTipBinding        = @"toolTip";
00487 CPValueBinding          = @"value";
00488 
00489 //Binding options constants
00490 CPAllowsEditingMultipleValuesSelectionBindingOption = @"CPAllowsEditingMultipleValuesSelection";
00491 CPAllowsNullArgumentBindingOption                   = @"CPAllowsNullArgument";
00492 CPConditionallySetsEditableBindingOption            = @"CPConditionallySetsEditable";
00493 CPConditionallySetsEnabledBindingOption             = @"CPConditionallySetsEnabled";
00494 CPConditionallySetsHiddenBindingOption              = @"CPConditionallySetsHidden";
00495 CPContinuouslyUpdatesValueBindingOption             = @"CPContinuouslyUpdatesValue";
00496 CPCreatesSortDescriptorBindingOption                = @"CPCreatesSortDescriptor";
00497 CPDeletesObjectsOnRemoveBindingsOption              = @"CPDeletesObjectsOnRemove";
00498 CPDisplayNameBindingOption                          = @"CPDisplayName";
00499 CPDisplayPatternBindingOption                       = @"CPDisplayPattern";
00500 CPHandlesContentAsCompoundValueBindingOption        = @"CPHandlesContentAsCompoundValue";
00501 CPInsertsNullPlaceholderBindingOption               = @"CPInsertsNullPlaceholder";
00502 CPInvokesSeparatelyWithArrayObjectsBindingOption    = @"CPInvokesSeparatelyWithArrayObjects";
00503 CPMultipleValuesPlaceholderBindingOption            = @"CPMultipleValuesPlaceholder";
00504 CPNoSelectionPlaceholderBindingOption               = @"CPNoSelectionPlaceholder";
00505 CPNotApplicablePlaceholderBindingOption             = @"CPNotApplicablePlaceholder";
00506 CPNullPlaceholderBindingOption                      = @"CPNullPlaceholder";
00507 CPPredicateFormatBindingOption                      = @"CPPredicateFormat";
00508 CPRaisesForNotApplicableKeysBindingOption           = @"CPRaisesForNotApplicableKeys";
00509 CPSelectorNameBindingOption                         = @"CPSelectorName";
00510 CPSelectsAllWhenSettingContentBindingOption         = @"CPSelectsAllWhenSettingContent";
00511 CPValidatesImmediatelyBindingOption                 = @"CPValidatesImmediately";
00512 CPValueTransformerNameBindingOption                 = @"CPValueTransformerName";
00513 CPValueTransformerBindingOption                     = @"CPValueTransformer";
00514 
00515 CPIsControllerMarker = function(/*id*/anObject)
00516 {
00517     return anObject === CPMultipleValuesMarker || anObject === CPNoSelectionMarker || anObject === CPNotApplicableMarker || anObject === CPNullMarker;
00518 }
 All Classes Files Functions Variables Defines