libs-gui/Source/NSKeyValueBinding.m

668 lines
18 KiB
Mathematica
Raw Permalink Normal View History

/** <title>NSKeyValueBinding informal protocol reference</title>
Implementation of KeyValueBinding for GNUStep
Copyright (C) 2007 Free Software Foundation, Inc.
Written by: Chris Farber <chris@chrisfarber.net>
Date: 2007
Author: Fred Kiefer <fredkiefer@gmx.de>
Date: Decembre 2007
This file is part of the GNUstep GUI Library.
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 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; see the file COPYING.LIB.
If not, see <http://www.gnu.org/licenses/> or write to the
Free Software Foundation, 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#import <Foundation/NSArray.h>
#import <Foundation/NSDebug.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSEnumerator.h>
#import <Foundation/NSException.h>
#import <Foundation/NSInvocation.h>
#import <Foundation/NSKeyValueObserving.h>
#import <Foundation/NSKeyValueCoding.h>
#import <Foundation/NSLock.h>
#import <Foundation/NSMapTable.h>
#import <Foundation/NSValue.h>
#import <Foundation/NSValueTransformer.h>
2024-05-30 10:15:19 +00:00
#import <Foundation/NSLock.h>
#import "AppKit/NSKeyValueBinding.h"
#import "GSBindingHelpers.h"
#import "GSFastEnumeration.h"
@implementation NSObject (NSKeyValueBindingCreation)
+ (void) exposeBinding: (NSString *)binding
{
[GSKeyValueBinding exposeBinding: binding forClass: [self class]];
}
- (NSArray *) exposedBindings
{
NSMutableArray *exposedBindings = [NSMutableArray array];
NSArray *tmp;
Class class = [self class];
while (class && class != [NSObject class])
{
tmp = [GSKeyValueBinding exposedBindingsForClass: class];
if (tmp != nil)
{
[exposedBindings addObjectsFromArray: tmp];
}
class = [class superclass];
}
return exposedBindings;
}
- (Class) valueClassForBinding: (NSString *)binding
{
return [NSString class];
}
- (void) bind: (NSString *)binding
toObject: (id)anObject
withKeyPath: (NSString *)keyPath
options: (NSDictionary *)options
{
if ((anObject == nil)
|| (keyPath == nil))
{
NSLog(@"No object or path for binding on %@ for %@", self, binding);
return;
}
if ([[self exposedBindings] containsObject: binding])
{
GSKeyValueBinding *kvb;
[self unbind: binding];
kvb = [[GSKeyValueBinding alloc] initWithBinding: binding
withName: binding
toObject: anObject
withKeyPath: keyPath
options: options
fromObject: self];
// The binding will be retained in the binding table
RELEASE(kvb);
}
else
{
NSLog(@"No binding exposed on %@ for %@", self, binding);
}
}
- (NSDictionary *) infoForBinding: (NSString *)binding
{
return [GSKeyValueBinding infoForBinding: binding forObject: self];
}
- (void) unbind: (NSString *)binding
{
[GSKeyValueBinding unbind: binding forObject: self];
}
@end
static NSRecursiveLock *bindingLock = nil;
static NSMapTable *classTable = NULL; //available bindings
static NSMapTable *objectTable = NULL; //bound bindings
typedef enum {
GSBindingOperationAnd = 0,
GSBindingOperationOr
} GSBindingOperationKind;
//TODO: document
BOOL GSBindingResolveMultipleValueBool(NSString *key, NSDictionary *bindings,
GSBindingOperationKind operationKind);
//TODO: document
void GSBindingInvokeAction(NSString *targetKey, NSString *argumentKey,
NSDictionary *bindings);
@implementation GSKeyValueBinding
+ (void) initialize
{
if (self == [GSKeyValueBinding class])
{
2024-05-30 10:15:19 +00:00
bindingLock = [NSRecursiveLock new];
classTable = NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks,
NSObjectMapValueCallBacks, 128);
objectTable = NSCreateMapTable(NSNonRetainedObjectMapKeyCallBacks,
NSObjectMapValueCallBacks, 128);
}
}
+ (void) exposeBinding: (NSString *)binding forClass: (Class)clazz
{
NSMutableArray *bindings;
[bindingLock lock];
bindings = (NSMutableArray *)NSMapGet(classTable, (void*)clazz);
if (bindings == nil)
{
bindings = [[NSMutableArray alloc] initWithCapacity: 5];
NSMapInsert(classTable, (void*)clazz, (void*)bindings);
RELEASE(bindings);
}
[bindings addObject: binding];
[bindingLock unlock];
}
+ (NSArray *) exposedBindingsForClass: (Class)clazz
{
NSArray *tmp;
if (!classTable)
return nil;
[bindingLock lock];
tmp = NSMapGet(classTable, (void*)clazz);
[bindingLock unlock];
return tmp;
}
+ (GSKeyValueBinding *) getBinding: (NSString *)binding
forObject: (id)anObject
{
NSMutableDictionary *bindings;
GSKeyValueBinding *theBinding = nil;
if (!objectTable)
return nil;
2024-08-04 22:44:01 +00:00
NSDebugLLog(@"NSBinding", @"+++ called with %@, %@", binding, anObject);
[bindingLock lock];
bindings = (NSMutableDictionary *)NSMapGet(objectTable, (void *)anObject);
2024-08-04 22:44:01 +00:00
NSDebugLLog(@"NSBinding", @"+++ Bindings found for %@ => %@", anObject, bindings);
if (bindings != nil)
{
theBinding = (GSKeyValueBinding*)[bindings objectForKey: binding];
}
[bindingLock unlock];
return theBinding;
}
+ (NSDictionary *) infoForBinding: (NSString *)binding
forObject: (id)anObject
{
GSKeyValueBinding *theBinding;
theBinding = [self getBinding: binding forObject: anObject];
if (theBinding == nil)
return nil;
return theBinding->info;
}
+ (void) unbind: (NSString *)binding
forObject: (id)anObject
{
NSMutableDictionary *bindings;
id observedObject;
NSString *keyPath;
GSKeyValueBinding *theBinding;
if (!objectTable)
return;
[bindingLock lock];
bindings = (NSMutableDictionary *)NSMapGet(objectTable, (void *)anObject);
if (bindings != nil)
{
theBinding = (GSKeyValueBinding*)[bindings objectForKey: binding];
if (theBinding != nil)
{
observedObject = [theBinding->info objectForKey: NSObservedObjectKey];
keyPath = [theBinding->info objectForKey: NSObservedKeyPathKey];
[observedObject removeObserver: theBinding forKeyPath: keyPath];
[bindings setValue: nil forKey: binding];
}
}
[bindingLock unlock];
}
+ (void) unbindAllForObject: (id)anObject
{
NSEnumerator *enumerator;
NSString *binding;
NSDictionary *list;
if (!objectTable)
return;
[bindingLock lock];
list = (NSDictionary *)NSMapGet(objectTable, (void *)anObject);
if (list != nil)
{
NSArray *keys = [list allKeys];
enumerator = [keys objectEnumerator];
while ((binding = [enumerator nextObject]))
{
[anObject unbind: binding];
}
NSMapRemove(objectTable, (void *)anObject);
}
[bindingLock unlock];
}
- (id) initWithBinding: (NSString *)binding
withName: (NSString *)name
toObject: (id)dest
withKeyPath: (NSString *)keyPath
options: (NSDictionary *)options
fromObject: (id)source
{
NSMutableDictionary *bindings;
src = source;
if (options == nil)
{
info = [[NSDictionary alloc] initWithObjectsAndKeys:
dest, NSObservedObjectKey,
keyPath, NSObservedKeyPathKey,
nil];
}
else
{
info = [[NSDictionary alloc] initWithObjectsAndKeys:
dest, NSObservedObjectKey,
keyPath, NSObservedKeyPathKey,
options, NSOptionsKey,
nil];
}
[dest addObserver: self
forKeyPath: keyPath
options: NSKeyValueObservingOptionNew
context: binding];
[bindingLock lock];
bindings = (NSMutableDictionary *)NSMapGet(objectTable, (void *)source);
if (bindings == nil)
{
bindings = [NSMutableDictionary new];
NSMapInsert(objectTable, (void*)source, (void*)bindings);
RELEASE(bindings);
}
[bindings setObject: self forKey: name];
[bindingLock unlock];
[self setValueFor: binding];
return self;
}
- (void)dealloc
{
DESTROY(info);
src = nil;
[super dealloc];
}
- (id) observedObject
{
return [info objectForKey: NSObservedObjectKey];
}
- (id) destinationValue
{
id newValue;
id dest;
NSString *keyPath;
NSDictionary *options;
dest = [info objectForKey: NSObservedObjectKey];
keyPath = [info objectForKey: NSObservedKeyPathKey];
options = [info objectForKey: NSOptionsKey];
newValue = [dest valueForKeyPath: keyPath];
return [self transformValue: newValue withOptions: options];
}
- (id) sourceValueFor: (NSString *)binding
{
id newValue;
NSDictionary *options;
options = [info objectForKey: NSOptionsKey];
newValue = [src valueForKeyPath: binding];
return [self reverseTransformValue: newValue withOptions: options];
}
- (void) setValueFor: (NSString *)binding
{
id value = [self destinationValue];
NSDebugLLog(@"NSBinding", @"setValueFor: binding %@, source %@ value %@", binding, src, value);
[src setValue: value forKey: binding];
}
- (void) reverseSetValue: (id)value
{
NSString *keyPath;
id dest;
keyPath = [info objectForKey: NSObservedKeyPathKey];
dest = [info objectForKey: NSObservedObjectKey];
NSDebugLLog(@"NSBinding", @"reverseSetValue: keyPath %@, dest %@ value %@", keyPath, dest, value);
inReverseSet = YES;
[dest setValue: value forKeyPath: keyPath];
inReverseSet = NO;
}
- (void) reverseSetValueFor: (NSString *)binding
{
[self reverseSetValue: [self sourceValueFor: binding]];
}
- (void) observeValueForKeyPath: (NSString *)keyPath
ofObject: (id)object
change: (NSDictionary *)change
context: (void *)context
{
NSString *binding = (NSString *)context;
NSDictionary *options;
id newValue;
if (inReverseSet)
{
return;
}
if (change != nil)
{
options = [info objectForKey: NSOptionsKey];
newValue = [change objectForKey: NSKeyValueChangeNewKey];
newValue = [self transformValue: newValue withOptions: options];
NSDebugLLog(@"NSBinding", @"observeValueForKeyPath: binding %@, keyPath %@, source %@ value %@", binding, keyPath, src, newValue);
[src setValue: newValue forKey: binding];
}
}
- (id) transformValue: (id)value withOptions: (NSDictionary *)options
{
NSString *valueTransformerName;
NSValueTransformer *valueTransformer;
NSString *placeholder;
if (value == NSMultipleValuesMarker)
{
placeholder = [options objectForKey: NSMultipleValuesPlaceholderBindingOption];
if (placeholder == nil)
{
placeholder = @"Multiple Values";
}
return placeholder;
}
if (value == NSNoSelectionMarker)
{
placeholder = [options objectForKey: NSNoSelectionPlaceholderBindingOption];
if (placeholder == nil)
{
placeholder = @"No Selection";
}
return placeholder;
}
if (value == NSNotApplicableMarker)
{
if ([[options objectForKey: NSRaisesForNotApplicableKeysBindingOption]
boolValue])
{
[NSException raise: NSGenericException
format: @"This binding does not accept not applicable keys"];
}
placeholder = [options objectForKey:
NSNotApplicablePlaceholderBindingOption];
if (placeholder == nil)
{
placeholder = @"Not Applicable";
}
return placeholder;
}
if (value == nil)
{
placeholder = [options objectForKey:
NSNullPlaceholderBindingOption];
return placeholder;
}
valueTransformerName = [options objectForKey:
NSValueTransformerNameBindingOption];
if (valueTransformerName != nil)
{
valueTransformer = [NSValueTransformer valueTransformerForName:
valueTransformerName];
}
else
{
valueTransformer = [options objectForKey:
NSValueTransformerBindingOption];
}
if (valueTransformer != nil)
{
if ([value isKindOfClass: [NSArray class]])
{
NSArray *oldValue = (NSArray *)value;
NSMutableArray *newValue = [[NSMutableArray alloc] initWithCapacity: [oldValue count]];
id<NSFastEnumeration> enumerator = oldValue;
FOR_IN (id, obj, enumerator)
[newValue addObject: [valueTransformer transformedValue: obj]];
END_FOR_IN(enumerator)
value = AUTORELEASE(newValue);
}
else
{
value = [valueTransformer transformedValue: value];
}
}
return value;
}
- (id) reverseTransformValue: (id)value withOptions: (NSDictionary *)options
{
NSString *valueTransformerName;
NSValueTransformer *valueTransformer;
valueTransformerName = [options objectForKey:
NSValueTransformerNameBindingOption];
if (valueTransformerName != nil)
{
valueTransformer = [NSValueTransformer valueTransformerForName:
valueTransformerName];
}
else
{
valueTransformer = [options objectForKey:
NSValueTransformerBindingOption];
}
if ((valueTransformer != nil) && [[valueTransformer class]
allowsReverseTransformation])
{
value = [valueTransformer reverseTransformedValue: value];
}
return value;
}
- (NSString*) description
{
return [NSString stringWithFormat:
@"GSKeyValueBinding src (%@) info %@", src, info];
}
@end
@implementation GSKeyValueOrBinding : GSKeyValueBinding
- (void) setValueFor: (NSString *)binding
{
NSDictionary *bindings;
BOOL res;
if (!objectTable)
return;
[bindingLock lock];
bindings = (NSDictionary *)NSMapGet(objectTable, (void *)src);
if (!bindings)
return;
res = GSBindingResolveMultipleValueBool(binding, bindings,
GSBindingOperationOr);
[bindingLock unlock];
[src setValue: [NSNumber numberWithBool: res] forKey: binding];
}
- (void) observeValueForKeyPath: (NSString *)keyPath
ofObject: (id)object
change: (NSDictionary *)change
context: (void *)context
{
[self setValueFor: (NSString*)context];
}
@end
@implementation GSKeyValueAndBinding : GSKeyValueBinding
- (void) setValueFor: (NSString *)binding
{
NSDictionary *bindings;
BOOL res;
if (!objectTable)
return;
[bindingLock lock];
bindings = (NSDictionary *)NSMapGet(objectTable, (void *)src);
if (!bindings)
return;
res = GSBindingResolveMultipleValueBool(binding, bindings,
GSBindingOperationAnd);
[bindingLock unlock];
[src setValue: [NSNumber numberWithBool: res] forKey: binding];
}
- (void) observeValueForKeyPath: (NSString *)keyPath
ofObject: (id)object
change: (NSDictionary *)change
context: (void *)context
{
[self setValueFor: (NSString*)context];
}
@end
BOOL NSIsControllerMarker(id object)
{
return [NSMultipleValuesMarker isEqual: object]
|| [NSNoSelectionMarker isEqual: object]
|| [NSNotApplicableMarker isEqual: object];
}
//Helper functions
BOOL GSBindingResolveMultipleValueBool(NSString *key, NSDictionary *bindings,
GSBindingOperationKind operationKind)
{
NSString *bindingName;
NSDictionary *info;
int count = 1;
id object;
NSString *keyPath;
id value;
NSDictionary *options;
GSKeyValueBinding *theBinding;
bindingName = key;
while ((theBinding = [bindings objectForKey: bindingName]))
{
info = theBinding->info;
object = [info objectForKey: NSObservedObjectKey];
keyPath = [info objectForKey: NSObservedKeyPathKey];
options = [info objectForKey: NSOptionsKey];
value = [object valueForKeyPath: keyPath];
value = [theBinding transformValue: value withOptions: options];
if ([value boolValue] == operationKind)
{
return operationKind;
}
bindingName = [NSString stringWithFormat: @"%@%i", key, ++count];
}
return !operationKind;
}
void GSBindingInvokeAction(NSString *targetKey, NSString *argumentKey,
NSDictionary *bindings)
{
NSString *bindingName;
NSDictionary *info;
NSDictionary *options;
int count = 1;
id object;
id target;
SEL selector;
NSString *keyPath;
NSInvocation *invocation;
GSKeyValueBinding *theBinding;
theBinding = [bindings objectForKey: targetKey];
info = theBinding->info;
object = [info objectForKey: NSObservedObjectKey];
keyPath = [info objectForKey: NSObservedKeyPathKey];
options = [info objectForKey: NSOptionsKey];
target = [object valueForKeyPath: keyPath];
selector = NSSelectorFromString([options objectForKey:
NSSelectorNameBindingOption]);
if (target == nil || selector == NULL) return;
invocation = [NSInvocation invocationWithMethodSignature:
[target methodSignatureForSelector: selector]];
[invocation setSelector: selector];
bindingName = argumentKey;
while ((theBinding = [bindings objectForKey: bindingName]))
{
info = theBinding->info;
object = [info objectForKey: NSObservedObjectKey];
keyPath = [info objectForKey: NSObservedKeyPathKey];
if ((object = [object valueForKeyPath: keyPath]))
{
[invocation setArgument: object atIndex: ++count];
}
bindingName = [NSString stringWithFormat: @"%@%i", argumentKey, count];
}
[invocation invoke];
}