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