mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-08-25 13:07:02 +00:00
892 lines
27 KiB
Plaintext
892 lines
27 KiB
Plaintext
/*
|
|
* CPKeyValueBinding.j
|
|
* AppKit
|
|
*
|
|
* Created by Ross Boucher 1/13/09
|
|
* Copyright 280 North, Inc.
|
|
*
|
|
* Adapted from GNUStep
|
|
* Released under the LGPL.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
@import <Foundation/CPObject.j>
|
|
@import <Foundation/CPArray.j>
|
|
@import <Foundation/CPDictionary.j>
|
|
@import <Foundation/CPInvocation.j>
|
|
@import <Foundation/CPValueTransformer.j>
|
|
@import <Foundation/CPKeyValueObserving.j>
|
|
|
|
@class CPButton
|
|
|
|
var exposedBindingsMap = @{},
|
|
bindingsMap = @{};
|
|
|
|
var CPBindingOperationAnd = 0,
|
|
CPBindingOperationOr = 1;
|
|
|
|
@implementation CPBinder : CPObject
|
|
{
|
|
CPDictionary _info;
|
|
id _source @accessors(getter=source);
|
|
|
|
JSObject _suppressedNotifications;
|
|
JSObject _placeholderForMarker;
|
|
}
|
|
|
|
+ (void)exposeBinding:(CPString)aBinding forClass:(Class)aClass
|
|
{
|
|
var bindings = [exposedBindingsMap objectForKey:[aClass UID]];
|
|
|
|
if (!bindings)
|
|
{
|
|
bindings = [];
|
|
[exposedBindingsMap setObject:bindings forKey:[aClass UID]];
|
|
}
|
|
|
|
bindings.push(aBinding);
|
|
}
|
|
|
|
+ (CPArray)exposedBindingsForClass:(Class)aClass
|
|
{
|
|
return [[exposedBindingsMap objectForKey:[aClass UID]] copy];
|
|
}
|
|
|
|
+ (CPBinder)getBinding:(CPString)aBinding forObject:(id)anObject
|
|
{
|
|
return [[bindingsMap objectForKey:[anObject UID]] objectForKey:aBinding];
|
|
}
|
|
|
|
+ (CPDictionary)infoForBinding:(CPString)aBinding forObject:(id)anObject
|
|
{
|
|
var theBinding = [self getBinding:aBinding forObject:anObject];
|
|
|
|
if (theBinding)
|
|
return theBinding._info;
|
|
|
|
return nil;
|
|
}
|
|
|
|
+ (CPDictionary)allBindingsForObject:(id)anObject
|
|
{
|
|
return [bindingsMap objectForKey:[anObject UID]];
|
|
}
|
|
|
|
+ (void)unbind:(CPString)aBinding forObject:(id)anObject
|
|
{
|
|
var bindings = [bindingsMap objectForKey:[anObject UID]];
|
|
|
|
if (!bindings)
|
|
return;
|
|
|
|
var theBinding = [bindings objectForKey:aBinding];
|
|
|
|
if (!theBinding)
|
|
return;
|
|
|
|
var info = theBinding._info,
|
|
observedObject = [info objectForKey:CPObservedObjectKey],
|
|
keyPath = [info objectForKey:CPObservedKeyPathKey];
|
|
|
|
[observedObject removeObserver:theBinding forKeyPath:keyPath];
|
|
[bindings removeObjectForKey:aBinding];
|
|
}
|
|
|
|
+ (void)unbindAllForObject:(id)anObject
|
|
{
|
|
var bindings = [bindingsMap objectForKey:[anObject UID]];
|
|
|
|
if (!bindings)
|
|
return;
|
|
|
|
var allKeys = [bindings allKeys],
|
|
count = allKeys.length;
|
|
|
|
while (count--)
|
|
[anObject unbind:[bindings objectForKey:allKeys[count]]];
|
|
|
|
[bindingsMap removeObjectForKey:[anObject UID]];
|
|
}
|
|
|
|
- (id)initWithBinding:(CPString)aBinding name:(CPString)aName to:(id)aDestination keyPath:(CPString)aKeyPath options:(CPDictionary)options from:(id)aSource
|
|
{
|
|
// We use [self init] here because subclasses override init. We can't override this method
|
|
// because their initialization has to occur before this method in this class is executed.
|
|
self = [self init];
|
|
|
|
if (self)
|
|
{
|
|
_source = aSource;
|
|
_info = @{
|
|
CPObservedObjectKey: aDestination,
|
|
CPObservedKeyPathKey: aKeyPath,
|
|
};
|
|
_suppressedNotifications = {};
|
|
_placeholderForMarker = {};
|
|
|
|
if (options)
|
|
[_info setObject:options forKey:CPOptionsKey];
|
|
|
|
[self _updatePlaceholdersWithOptions:options forBinding:aName];
|
|
|
|
[aDestination addObserver:self forKeyPath:aKeyPath options:CPKeyValueObservingOptionNew context:aBinding];
|
|
|
|
var bindings = [bindingsMap objectForKey:[_source UID]];
|
|
|
|
if (!bindings)
|
|
{
|
|
bindings = @{};
|
|
[bindingsMap setObject:bindings forKey:[_source UID]];
|
|
}
|
|
|
|
[bindings setObject:self forKey:aName];
|
|
[self setValueFor:aBinding];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)raiseIfNotApplicable:(id)aValue forKeyPath:(CPString)keyPath options:(CPDictionary)options
|
|
{
|
|
if (aValue === CPNotApplicableMarker && [options objectForKey:CPRaisesForNotApplicableKeysBindingOption])
|
|
{
|
|
[CPException raise:CPGenericException
|
|
reason:@"Cannot transform non-applicable key on: " + _source + " key path: " + keyPath + " value: " + aValue];
|
|
}
|
|
}
|
|
|
|
- (void)setValueFor:(CPString)theBinding
|
|
{
|
|
var destination = [_info objectForKey:CPObservedObjectKey],
|
|
keyPath = [_info objectForKey:CPObservedKeyPathKey],
|
|
options = [_info objectForKey:CPOptionsKey],
|
|
newValue = [destination valueForKeyPath:keyPath];
|
|
|
|
if (CPIsControllerMarker(newValue))
|
|
{
|
|
[self raiseIfNotApplicable:newValue forKeyPath:keyPath options:options];
|
|
|
|
var value = [self _placeholderForMarker:newValue];
|
|
[self setPlaceholderValue:value withMarker:newValue forBinding:theBinding];
|
|
}
|
|
else
|
|
{
|
|
var value = [self transformValue:newValue withOptions:options];
|
|
[self setValue:value forBinding:theBinding];
|
|
}
|
|
}
|
|
|
|
- (void)setPlaceholderValue:(id)aValue withMarker:(CPString)aMarker forBinding:(CPString)aBinding
|
|
{
|
|
[_source setValue:aValue forKey:aBinding];
|
|
}
|
|
|
|
- (void)setValue:(id)aValue forBinding:(CPString)aBinding
|
|
{
|
|
[_source setValue:aValue forKey:aBinding];
|
|
}
|
|
|
|
- (void)reverseSetValueFor:(CPString)aBinding
|
|
{
|
|
var destination = [_info objectForKey:CPObservedObjectKey],
|
|
keyPath = [_info objectForKey:CPObservedKeyPathKey],
|
|
options = [_info objectForKey:CPOptionsKey],
|
|
newValue = [self valueForBinding:aBinding];
|
|
|
|
newValue = [self reverseTransformValue:newValue withOptions:options];
|
|
|
|
[self suppressSpecificNotificationFromObject:destination keyPath:keyPath];
|
|
[destination setValue:newValue forKeyPath:keyPath];
|
|
[self unsuppressSpecificNotificationFromObject:destination keyPath:keyPath];
|
|
}
|
|
|
|
- (id)valueForBinding:(CPString)aBinding
|
|
{
|
|
return [_source valueForKeyPath:aBinding];
|
|
}
|
|
|
|
- (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObject change:(CPDictionary)changes context:(id)context
|
|
{
|
|
if (!changes)
|
|
return;
|
|
|
|
var objectSuppressions = _suppressedNotifications[[anObject UID]];
|
|
|
|
if (objectSuppressions && objectSuppressions[aKeyPath])
|
|
return;
|
|
|
|
[self setValueFor:context];
|
|
}
|
|
|
|
- (id)transformValue:(id)aValue withOptions:(CPDictionary)options
|
|
{
|
|
var valueTransformerName = [options objectForKey:CPValueTransformerNameBindingOption],
|
|
valueTransformer;
|
|
|
|
if (valueTransformerName)
|
|
{
|
|
valueTransformer = [CPValueTransformer valueTransformerForName:valueTransformerName];
|
|
|
|
if (!valueTransformer)
|
|
{
|
|
var valueTransformerClass = CPClassFromString(valueTransformerName);
|
|
|
|
if (valueTransformerClass)
|
|
{
|
|
valueTransformer = [[valueTransformerClass alloc] init];
|
|
[valueTransformerClass setValueTransformer:valueTransformer forName:valueTransformerName];
|
|
}
|
|
}
|
|
}
|
|
else
|
|
valueTransformer = [options objectForKey:CPValueTransformerBindingOption];
|
|
|
|
if (valueTransformer)
|
|
aValue = [valueTransformer transformedValue:aValue];
|
|
|
|
if (aValue === undefined || aValue === nil || aValue === [CPNull null])
|
|
aValue = [options objectForKey:CPNullPlaceholderBindingOption] || nil;
|
|
|
|
return aValue;
|
|
}
|
|
|
|
- (id)reverseTransformValue:(id)aValue withOptions:(CPDictionary)options
|
|
{
|
|
var valueTransformerName = [options objectForKey:CPValueTransformerNameBindingOption],
|
|
valueTransformer;
|
|
|
|
if (valueTransformerName)
|
|
valueTransformer = [CPValueTransformer valueTransformerForName:valueTransformerName];
|
|
else
|
|
valueTransformer = [options objectForKey:CPValueTransformerBindingOption];
|
|
|
|
if (valueTransformer && [[valueTransformer class] allowsReverseTransformation])
|
|
aValue = [valueTransformer reverseTransformedValue:aValue];
|
|
|
|
return aValue;
|
|
}
|
|
|
|
- (BOOL)continuouslyUpdatesValue
|
|
{
|
|
var options = [_info objectForKey:CPOptionsKey];
|
|
return [[options objectForKey:CPContinuouslyUpdatesValueBindingOption] boolValue];
|
|
}
|
|
|
|
- (BOOL)handlesContentAsCompoundValue
|
|
{
|
|
var options = [_info objectForKey:CPOptionsKey];
|
|
return [[options objectForKey:CPHandlesContentAsCompoundValueBindingOption] boolValue];
|
|
}
|
|
|
|
/*!
|
|
Use this to avoid reacting to the notifications coming out of a reverseTransformedValue:.
|
|
*/
|
|
- (void)suppressSpecificNotificationFromObject:(id)anObject keyPath:(CPString)aKeyPath
|
|
{
|
|
if (!anObject)
|
|
return;
|
|
|
|
var uid = [anObject UID],
|
|
objectSuppressions = _suppressedNotifications[uid];
|
|
|
|
if (!objectSuppressions)
|
|
_suppressedNotifications[uid] = objectSuppressions = {};
|
|
|
|
objectSuppressions[aKeyPath] = YES;
|
|
}
|
|
|
|
/*!
|
|
Use this to cancel suppressSpecificNotificationFromObject:keyPath:.
|
|
*/
|
|
- (void)unsuppressSpecificNotificationFromObject:(id)anObject keyPath:(CPString)aKeyPath
|
|
{
|
|
if (!anObject)
|
|
return;
|
|
|
|
var uid = [anObject UID],
|
|
objectSuppressions = _suppressedNotifications[uid];
|
|
|
|
if (!objectSuppressions)
|
|
return;
|
|
|
|
delete objectSuppressions[aKeyPath];
|
|
}
|
|
|
|
- (void)_updatePlaceholdersWithOptions:(CPDictionary)options
|
|
{
|
|
var count = [CPBinderPlaceholderMarkers count];
|
|
|
|
while (count--)
|
|
{
|
|
var marker = CPBinderPlaceholderMarkers[count],
|
|
optionName = CPBinderPlaceholderOptions[count],
|
|
isExplicit = [options containsKey:optionName],
|
|
placeholder = isExplicit ? [options objectForKey:optionName] : nil;
|
|
|
|
[self _setPlaceholder:placeholder forMarker:marker isDefault:!isExplicit];
|
|
}
|
|
}
|
|
|
|
- (void)_updatePlaceholdersWithOptions:(CPDictionary)options forBinding:(CPString)aBinding
|
|
{
|
|
[self _updatePlaceholdersWithOptions:options];
|
|
}
|
|
|
|
- (void)_placeholderForMarker:aMarker
|
|
{
|
|
var placeholder = _placeholderForMarker[aMarker];
|
|
|
|
if (placeholder)
|
|
return placeholder['value'];
|
|
|
|
return nil;
|
|
}
|
|
|
|
- (void)_setPlaceholder:(id)aPlaceholder forMarker:(id)aMarker isDefault:(BOOL)isDefault
|
|
{
|
|
if (isDefault)
|
|
{
|
|
var existingPlaceholder = _placeholderForMarker[aMarker];
|
|
|
|
// Don't overwrite an explicitly set placeholder with a default.
|
|
if (existingPlaceholder && !existingPlaceholder['isDefault'])
|
|
return;
|
|
}
|
|
|
|
_placeholderForMarker[aMarker] = { 'isDefault': isDefault, 'value': aPlaceholder };
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation CPObject (KeyValueBindingCreation)
|
|
|
|
+ (void)exposeBinding:(CPString)aBinding
|
|
{
|
|
[CPBinder exposeBinding:aBinding forClass:[self class]];
|
|
}
|
|
|
|
+ (Class)_binderClassForBinding:(CPString)aBinding
|
|
{
|
|
return [CPBinder class];
|
|
}
|
|
|
|
- (CPArray)exposedBindings
|
|
{
|
|
var exposedBindings = [],
|
|
theClass = [self class];
|
|
|
|
while (theClass)
|
|
{
|
|
var temp = [CPBinder exposedBindingsForClass:theClass];
|
|
|
|
if (temp)
|
|
[exposedBindings addObjectsFromArray:temp];
|
|
|
|
theClass = [theClass superclass];
|
|
}
|
|
|
|
return exposedBindings;
|
|
}
|
|
|
|
- (Class)valueClassForBinding:(CPString)binding
|
|
{
|
|
return [CPString class];
|
|
}
|
|
|
|
- (void)bind:(CPString)aBinding toObject:(id)anObject withKeyPath:(CPString)aKeyPath options:(CPDictionary)options
|
|
{
|
|
if (!anObject || !aKeyPath)
|
|
return CPLog.error("Invalid object or path on " + self + " for " + aBinding);
|
|
|
|
//if (![[self exposedBindings] containsObject:aBinding])
|
|
// CPLog.warn("No binding exposed on " + self + " for " + aBinding);
|
|
|
|
var binderClass = [[self class] _binderClassForBinding:aBinding];
|
|
|
|
[self unbind:aBinding];
|
|
[[binderClass alloc] initWithBinding:[self _replacementKeyPathForBinding:aBinding] name:aBinding to:anObject keyPath:aKeyPath options:options from:self];
|
|
}
|
|
|
|
- (CPDictionary)infoForBinding:(CPString)aBinding
|
|
{
|
|
return [CPBinder infoForBinding:aBinding forObject:self];
|
|
}
|
|
|
|
- (void)unbind:(CPString)aBinding
|
|
{
|
|
var binderClass = [[self class] _binderClassForBinding:aBinding];
|
|
[binderClass unbind:aBinding forObject:self];
|
|
}
|
|
|
|
- (CPString)_replacementKeyPathForBinding:(CPString)binding
|
|
{
|
|
return binding;
|
|
}
|
|
|
|
@end
|
|
|
|
/*!
|
|
@ignore
|
|
Provides stub implementations that simply calls super for the "objectValue" binding.
|
|
This class should not be necessary but assures backwards compliance with our old way of doing bindings.
|
|
|
|
IMPORTANT!
|
|
Every class with a value binding should implement a subclass to handle its specific value binding logic.
|
|
*/
|
|
@implementation _CPValueBinder : CPBinder
|
|
|
|
- (void)setValueFor:(CPString)theBinding
|
|
{
|
|
[super setValueFor:@"objectValue"];
|
|
}
|
|
|
|
- (void)reverseSetValueFor:(CPString)theBinding
|
|
{
|
|
[super reverseSetValueFor:@"objectValue"];
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation _CPMultipleValueBooleanBinding : CPBinder
|
|
{
|
|
CPBindingOperationKind _operation;
|
|
}
|
|
|
|
- (void)setValueFor:(CPString)aBinding
|
|
{
|
|
var bindings = [bindingsMap valueForKey:[_source UID]];
|
|
|
|
if (!bindings)
|
|
return;
|
|
|
|
var baseBinding = aBinding.replace(/\d$/, "");
|
|
|
|
[_source setValue:[self resolveMultipleValuesForBinding:baseBinding bindings:bindings booleanOperation:_operation] forKey:baseBinding];
|
|
}
|
|
|
|
- (void)reverseSetValueFor:(CPString)theBinding
|
|
{
|
|
// read-only
|
|
}
|
|
|
|
- (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObject change:(CPDictionary)changes context:(id)context
|
|
{
|
|
[self setValueFor:context];
|
|
}
|
|
|
|
- (BOOL)resolveMultipleValuesForBinding:(CPString)aBinding bindings:(CPDictionary)bindings booleanOperation:(CPBindingOperationKind)operation
|
|
{
|
|
var bindingName = aBinding,
|
|
theBinding,
|
|
count = 2;
|
|
|
|
while (theBinding = [bindings objectForKey:bindingName])
|
|
{
|
|
var info = theBinding._info,
|
|
object = [info objectForKey:CPObservedObjectKey],
|
|
keyPath = [info objectForKey:CPObservedKeyPathKey],
|
|
options = [info objectForKey:CPOptionsKey],
|
|
value = [object valueForKeyPath:keyPath];
|
|
|
|
if (CPIsControllerMarker(value))
|
|
{
|
|
[self raiseIfNotApplicable:value forKeyPath:keyPath options:options];
|
|
value = [theBinding _placeholderForMarker:value];
|
|
}
|
|
else
|
|
value = [theBinding transformValue:value withOptions:options];
|
|
|
|
if (operation === CPBindingOperationOr)
|
|
{
|
|
// Any true condition means true for OR
|
|
if (value)
|
|
return YES;
|
|
}
|
|
|
|
// Any false condition means false for AND
|
|
else if (!value)
|
|
return NO;
|
|
|
|
bindingName = aBinding + (count++);
|
|
}
|
|
|
|
// If we get here, all OR conditions were false or all AND conditions were true
|
|
return operation === CPBindingOperationOr ? NO : YES;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation CPMultipleValueAndBinding : _CPMultipleValueBooleanBinding
|
|
|
|
- (id)init
|
|
{
|
|
if (self = [super init])
|
|
_operation = CPBindingOperationAnd;
|
|
|
|
return self;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation CPMultipleValueOrBinding : _CPMultipleValueBooleanBinding
|
|
|
|
- (id)init
|
|
{
|
|
if (self = [super init])
|
|
_operation = CPBindingOperationOr;
|
|
|
|
return self;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation _CPMultipleValueActionBinding : CPBinder
|
|
{
|
|
CPString _argumentBinding;
|
|
CPString _targetBinding;
|
|
}
|
|
|
|
- (void)setValueFor:(CPString)theBinding
|
|
{
|
|
// Called when the binding is first created
|
|
[self checkForNullBinding:theBinding initializing:YES];
|
|
}
|
|
|
|
- (void)reverseSetValueFor:(CPString)theBinding
|
|
{
|
|
// no-op
|
|
}
|
|
|
|
- (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObject change:(CPDictionary)changes context:(id)context
|
|
{
|
|
// context is the binding name
|
|
[self checkForNullBinding:context initializing:NO];
|
|
}
|
|
|
|
/*!
|
|
@ignore
|
|
|
|
When the value of a multiple-value argument binding changes,
|
|
if the binding is marked not to allow null arguments, we have to check
|
|
if the binding's value is nil and disable the button if it is.
|
|
Otherwise the button is enabled.
|
|
*/
|
|
- (void)checkForNullBinding:(CPString)theBinding initializing:(BOOL)isInitializing
|
|
{
|
|
// Only done for buttons
|
|
if (![_source isKindOfClass:CPButton])
|
|
return;
|
|
|
|
// We start with the button enabled for the first argument during init,
|
|
// and subsequent checks can disable it.
|
|
if (isInitializing && theBinding === CPArgumentBinding)
|
|
[_source setEnabled:YES];
|
|
|
|
var bindings = [bindingsMap valueForKey:[_source UID]],
|
|
binding = [bindings objectForKey:theBinding],
|
|
info = binding._info,
|
|
options = [info objectForKey:CPOptionsKey];
|
|
|
|
if (![options valueForKey:CPAllowsNullArgumentBindingOption])
|
|
{
|
|
var object = [info objectForKey:CPObservedObjectKey],
|
|
keyPath = [info objectForKey:CPObservedKeyPathKey],
|
|
value = [object valueForKeyPath:keyPath];
|
|
|
|
if (value === nil || value === undefined)
|
|
{
|
|
[_source setEnabled:NO];
|
|
return;
|
|
}
|
|
}
|
|
|
|
// If a binding value changed and did not fail the null test, enable the button
|
|
if (!isInitializing)
|
|
[_source setEnabled:YES];
|
|
}
|
|
|
|
- (void)invokeAction
|
|
{
|
|
var bindings = [bindingsMap valueForKey:[_source UID]],
|
|
theBinding = [bindings objectForKey:CPTargetBinding],
|
|
|
|
info = theBinding._info,
|
|
object = [info objectForKey:CPObservedObjectKey],
|
|
keyPath = [info objectForKey:CPObservedKeyPathKey],
|
|
options = [info objectForKey:CPOptionsKey],
|
|
|
|
target = [object valueForKeyPath:keyPath],
|
|
selector = [options objectForKey:CPSelectorNameBindingOption];
|
|
|
|
if (!target || !selector)
|
|
return;
|
|
|
|
var invocation = [CPInvocation invocationWithMethodSignature:[target methodSignatureForSelector:selector]],
|
|
bindingName = CPArgumentBinding,
|
|
count = 1;
|
|
|
|
while (theBinding = [bindings objectForKey:bindingName])
|
|
{
|
|
info = theBinding._info;
|
|
object = [info objectForKey:CPObservedObjectKey];
|
|
keyPath = [info objectForKey:CPObservedKeyPathKey];
|
|
|
|
[invocation setArgument:[object valueForKeyPath:keyPath] atIndex:++count];
|
|
|
|
bindingName = CPArgumentBinding + count;
|
|
}
|
|
|
|
[invocation setSelector:selector];
|
|
[invocation invokeWithTarget:target];
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation CPActionBinding : _CPMultipleValueActionBinding
|
|
|
|
- (id)init
|
|
{
|
|
if (self = [super init])
|
|
{
|
|
_argumentBinding = CPArgumentBinding;
|
|
_targetBinding = CPTargetBinding;
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation CPDoubleClickActionBinding : _CPMultipleValueActionBinding
|
|
|
|
- (id)init
|
|
{
|
|
if (self = [super init])
|
|
{
|
|
_argumentBinding = CPArgumentBinding;
|
|
_targetBinding = CPTargetBinding;
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
@end
|
|
|
|
/*!
|
|
Abstract superclass for CPValueWithPatternBinding and CPTitleWithPatternBinding.
|
|
*/
|
|
@implementation _CPPatternBinding : CPBinder
|
|
{
|
|
CPString _bindingKey;
|
|
CPString _patternPlaceholder;
|
|
}
|
|
|
|
- (void)setValueFor:(CPString)aBinding
|
|
{
|
|
var bindings = [bindingsMap valueForKey:[_source UID]];
|
|
|
|
if (!bindings)
|
|
return;
|
|
|
|
// Strip off any trailing number from the binding name
|
|
var baseBinding = aBinding.replace(/\d$/, ""),
|
|
result = [self resolveMultipleValuesForBindings:bindings];
|
|
|
|
if (result.isPlaceholder)
|
|
[self setPlaceholderValue:result.value withMarker:result.marker forBinding:baseBinding];
|
|
else
|
|
[self setValue:result.value forBinding:baseBinding];
|
|
}
|
|
|
|
- (void)reverseSetValueFor:(CPString)theBinding
|
|
{
|
|
// read-only
|
|
}
|
|
|
|
- (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObject change:(CPDictionary)changes context:(id)context
|
|
{
|
|
[self setValueFor:context];
|
|
}
|
|
|
|
- (JSObject)resolveMultipleValuesForBindings:(CPDictionary)bindings
|
|
{
|
|
var theBinding,
|
|
result = { value:@"", isPlaceholder:NO, marker:nil };
|
|
|
|
for (var count = 1; theBinding = [bindings objectForKey:_bindingKey + count]; ++count)
|
|
{
|
|
var info = theBinding._info,
|
|
object = [info objectForKey:CPObservedObjectKey],
|
|
keyPath = [info objectForKey:CPObservedKeyPathKey],
|
|
options = [info objectForKey:CPOptionsKey],
|
|
value = [object valueForKeyPath:keyPath];
|
|
|
|
if (count === 1)
|
|
result.value = [options objectForKey:CPDisplayPatternBindingOption];
|
|
|
|
if (CPIsControllerMarker(value))
|
|
{
|
|
[self raiseIfNotApplicable:value forKeyPath:keyPath options:options];
|
|
|
|
result.isPlaceholder = YES;
|
|
result.marker = value;
|
|
|
|
value = [theBinding _placeholderForMarker:value];
|
|
}
|
|
else
|
|
value = [theBinding transformValue:value withOptions:options];
|
|
|
|
if (value === nil || value === undefined)
|
|
value = @"";
|
|
|
|
result.value = result.value.replace("%{" + _patternPlaceholder + count + "}@", [value description]);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
/*!
|
|
Users of this class must override setValue:forKey: and if the
|
|
key is CPDisplayPatternValueBinding, set the appropriate value
|
|
for the control class. For example, CPTextField uses setObjectValue.
|
|
*/
|
|
@implementation CPValueWithPatternBinding : _CPPatternBinding
|
|
|
|
- (id)init
|
|
{
|
|
if (self = [super init])
|
|
{
|
|
_bindingKey = CPDisplayPatternValueBinding;
|
|
_patternPlaceholder = @"value";
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
/*!
|
|
Users of this class must override setValue:forKey: and if the
|
|
key is CPDisplayPatternTitleBinding, set the appropriate value
|
|
for the control class. For example, CPBox uses setTitle.
|
|
*/
|
|
@implementation CPTitleWithPatternBinding : _CPPatternBinding
|
|
|
|
- (id)init
|
|
{
|
|
if (self = [super init])
|
|
{
|
|
_bindingKey = CPDisplayPatternTitleBinding;
|
|
_patternPlaceholder = @"title";
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
// Keys in options dictionary
|
|
|
|
// Keys in dictionary returned by infoForBinding
|
|
CPObservedObjectKey = @"CPObservedObjectKey";
|
|
CPObservedKeyPathKey = @"CPObservedKeyPathKey";
|
|
CPOptionsKey = @"CPOptionsKey";
|
|
|
|
// special markers
|
|
CPMultipleValuesMarker = @"CPMultipleValuesMarker";
|
|
CPNoSelectionMarker = @"CPNoSelectionMarker";
|
|
CPNotApplicableMarker = @"CPNotApplicableMarker";
|
|
CPNullMarker = @"CPNullMarker";
|
|
|
|
// Binding name constants
|
|
CPAlignmentBinding = @"alignment";
|
|
CPArgumentBinding = @"argument";
|
|
CPContentArrayBinding = @"contentArray";
|
|
CPContentBinding = @"content";
|
|
CPContentObjectBinding = @"contentObject";
|
|
CPContentObjectsBinding = @"contentObjects";
|
|
CPContentValuesBinding = @"contentValues";
|
|
CPDisplayPatternTitleBinding = @"displayPatternTitle";
|
|
CPDisplayPatternValueBinding = @"displayPatternValue";
|
|
CPDoubleClickArgumentBinding = @"doubleClickArgument";
|
|
CPDoubleClickTargetBinding = @"doubleClickTarget";
|
|
CPEditableBinding = @"editable";
|
|
CPEnabledBinding = @"enabled";
|
|
CPFontBinding = @"font";
|
|
CPFontNameBinding = @"fontName";
|
|
CPFontBoldBinding = @"fontBold";
|
|
CPHiddenBinding = @"hidden";
|
|
CPFilterPredicateBinding = @"filterPredicate";
|
|
CPMaxValueBinding = @"maxValue";
|
|
CPMinValueBinding = @"minValue";
|
|
CPPredicateBinding = @"predicate";
|
|
CPSelectedIndexBinding = @"selectedIndex";
|
|
CPSelectedLabelBinding = @"selectedLabel";
|
|
CPSelectedObjectBinding = @"selectedObject";
|
|
CPSelectedObjectsBinding = @"selectedObjects";
|
|
CPSelectedTagBinding = @"selectedTag";
|
|
CPSelectedValueBinding = @"selectedValue";
|
|
CPSelectedValuesBinding = @"selectedValues";
|
|
CPSelectionIndexesBinding = @"selectionIndexes";
|
|
CPTargetBinding = @"target";
|
|
CPTextColorBinding = @"textColor";
|
|
CPTitleBinding = @"title";
|
|
CPToolTipBinding = @"toolTip";
|
|
CPValueBinding = @"value";
|
|
CPValueURLBinding = @"valueURL";
|
|
CPValuePathBinding = @"valuePath";
|
|
CPDataBinding = @"data";
|
|
|
|
// Binding options constants
|
|
CPAllowsEditingMultipleValuesSelectionBindingOption = @"CPAllowsEditingMultipleValuesSelection";
|
|
CPAllowsNullArgumentBindingOption = @"CPAllowsNullArgument";
|
|
CPConditionallySetsEditableBindingOption = @"CPConditionallySetsEditable";
|
|
CPConditionallySetsEnabledBindingOption = @"CPConditionallySetsEnabled";
|
|
CPConditionallySetsHiddenBindingOption = @"CPConditionallySetsHidden";
|
|
CPContinuouslyUpdatesValueBindingOption = @"CPContinuouslyUpdatesValue";
|
|
CPCreatesSortDescriptorBindingOption = @"CPCreatesSortDescriptor";
|
|
CPDeletesObjectsOnRemoveBindingsOption = @"CPDeletesObjectsOnRemove";
|
|
CPDisplayNameBindingOption = @"CPDisplayName";
|
|
CPDisplayPatternBindingOption = @"CPDisplayPattern";
|
|
CPHandlesContentAsCompoundValueBindingOption = @"CPHandlesContentAsCompoundValue";
|
|
CPInsertsNullPlaceholderBindingOption = @"CPInsertsNullPlaceholder";
|
|
CPInvokesSeparatelyWithArrayObjectsBindingOption = @"CPInvokesSeparatelyWithArrayObjects";
|
|
CPMultipleValuesPlaceholderBindingOption = @"CPMultipleValuesPlaceholder";
|
|
CPNoSelectionPlaceholderBindingOption = @"CPNoSelectionPlaceholder";
|
|
CPNotApplicablePlaceholderBindingOption = @"CPNotApplicablePlaceholder";
|
|
CPNullPlaceholderBindingOption = @"CPNullPlaceholder";
|
|
CPPredicateFormatBindingOption = @"CPPredicateFormat";
|
|
CPRaisesForNotApplicableKeysBindingOption = @"CPRaisesForNotApplicableKeys";
|
|
CPSelectorNameBindingOption = @"CPSelectorName";
|
|
CPSelectsAllWhenSettingContentBindingOption = @"CPSelectsAllWhenSettingContent";
|
|
CPValidatesImmediatelyBindingOption = @"CPValidatesImmediately";
|
|
CPValueTransformerNameBindingOption = @"CPValueTransformerName";
|
|
CPValueTransformerBindingOption = @"CPValueTransformer";
|
|
|
|
CPIsControllerMarker = function(/*id*/anObject)
|
|
{
|
|
return anObject === CPMultipleValuesMarker || anObject === CPNoSelectionMarker || anObject === CPNotApplicableMarker || anObject === CPNullMarker;
|
|
};
|
|
|
|
var CPBinderPlaceholderMarkers = [CPMultipleValuesMarker, CPNoSelectionMarker, CPNotApplicableMarker, CPNullMarker],
|
|
CPBinderPlaceholderOptions = [CPMultipleValuesPlaceholderBindingOption, CPNoSelectionPlaceholderBindingOption, CPNotApplicablePlaceholderBindingOption, CPNullPlaceholderBindingOption];
|
|
|