diff --git a/AppKit/CPKeyValueBinding.j b/AppKit/CPKeyValueBinding.j index 30c637549..3e0989998 100644 --- a/AppKit/CPKeyValueBinding.j +++ b/AppKit/CPKeyValueBinding.j @@ -173,6 +173,24 @@ var CPBindingOperationAnd = 0, return self; } ++ (BOOL)isBindingAllowed:(CPString)aBinding forObject:(id)anObject +{ + if ([[anObject class] isBindingExclusive:aBinding]) + { + var bindingsForObject = [bindingsMap objectForKey:[anObject UID]], + allBindings = [bindingsForObject allKeys], + count = [allBindings count]; + + while(count--) + { + if ([[anObject class] isBindingExclusive:allBindings[count]]) + return NO; + } + } + + return YES; +} + - (void)raiseIfNotApplicable:(id)aValue forKeyPath:(CPString)keyPath options:(CPDictionary)options { if (aValue === CPNotApplicableMarker && [options objectForKey:CPRaisesForNotApplicableKeysBindingOption]) @@ -401,6 +419,11 @@ var CPBindingOperationAnd = 0, return [CPBinder class]; } ++ (BOOL)isBindingExclusive:(CPString)aBinding +{ + return NO; +} + - (CPArray)exposedBindings { var exposedBindings = [], @@ -429,6 +452,12 @@ var CPBindingOperationAnd = 0, if (!anObject || !aKeyPath) return CPLog.error("Invalid object or path on " + self + " for " + aBinding); + if (![CPBinder isBindingAllowed:aBinding forObject:self]) + { + [CPException raise:CPGenericException + reason:@"Cannot bind " + aBinding + " because a binding with the same functionality is already in use."]; + return; + } //if (![[self exposedBindings] containsObject:aBinding]) // CPLog.warn("No binding exposed on " + self + " for " + aBinding); diff --git a/AppKit/CPPopUpButton.j b/AppKit/CPPopUpButton.j index 0484cfcb2..09aecb6fe 100644 --- a/AppKit/CPPopUpButton.j +++ b/AppKit/CPPopUpButton.j @@ -771,13 +771,6 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down"); [[self selectedItem] setState:CPOffState]; } -- (void)_reverseSetBinding -{ - [_CPPopUpButtonSelectionBinder _reverseSetValueFromExclusiveBinderForObject:self]; - - [super _reverseSetBinding]; -} - @end @implementation CPPopUpButton (BindingSupport) @@ -800,6 +793,20 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down"); return [super _binderClassForBinding:aBinding]; } ++ (BOOL)isBindingExclusive:(CPString)aBinding +{ + return (aBinding == CPSelectedIndexBinding || + aBinding == CPSelectedTagBinding || + aBinding == CPSelectedValueBinding); +} + +- (void)_reverseSetBinding +{ + [_CPPopUpButtonSelectionBinder _reverseSetValueFromExclusiveBinderForObject:self]; + + [super _reverseSetBinding]; +} + @end @implementation _CPPopUpButtonContentBinder : CPBinder diff --git a/AppKit/CPRadio.j b/AppKit/CPRadio.j index a887d73b1..8cf37cc3d 100644 --- a/AppKit/CPRadio.j +++ b/AppKit/CPRadio.j @@ -407,6 +407,13 @@ var CPRadioGroupRadiosKey = @"CPRadioGroupRadiosKey", return [super _binderClassForBinding:aBinding]; } ++ (BOOL)isBindingExclusive:(CPString)aBinding +{ + return (aBinding == CPSelectedIndexBinding || + aBinding == CPSelectedTagBinding || + aBinding == CPSelectedValueBinding); +} + @end @implementation _CPRadioGroupSelectionBinder : CPBinder diff --git a/AppKit/CPSegmentedControl.j b/AppKit/CPSegmentedControl.j index 5baf5646e..37b4ec316 100644 --- a/AppKit/CPSegmentedControl.j +++ b/AppKit/CPSegmentedControl.j @@ -49,7 +49,6 @@ CPSegmentSwitchTrackingMomentary = 2; unsigned _trackingSegment; BOOL _trackingHighlighted; - BOOL _hasBinding; } + (CPString)defaultThemeClass @@ -75,19 +74,6 @@ CPSegmentSwitchTrackingMomentary = 2; }; } -+ (Class)_binderClassForBinding:(CPString)aBinding -{ - if ([self _isSelectionBinding:aBinding]) - return [_CPSegmentedControlBinder class]; - - return [super _binderClassForBinding:aBinding]; -} - -+ (BOOL)_isSelectionBinding:(CPString)aBinding -{ - return (aBinding === CPSelectedIndexBinding || aBinding === CPSelectedLabelBinding || aBinding === CPSelectedTagBinding); -} - - (id)initWithFrame:(CGRect)aRect { _segments = []; @@ -105,27 +91,6 @@ CPSegmentSwitchTrackingMomentary = 2; return self; } -- (void)bind:(CPString)aBinding toObject:(id)anObject withKeyPath:(CPString)aKeyPath options:(CPDictionary)options -{ - if ([[self class] _isSelectionBinding:aBinding] && _trackingMode !== CPSegmentSwitchTrackingSelectOne) - { - CPLog.warn("Binding " + aBinding + " needs CPSegmentSwitchTrackingSelectOne tracking mode"); - return; - } - - if (_hasBinding) - [_CPSegmentedControlBinder unbindAllForObject:self]; - - [super bind:aBinding toObject:anObject withKeyPath:aKeyPath options:options]; - - _hasBinding = YES; -} - -- (void)_reverseSetBinding -{ - [_CPSegmentedControlBinder _reverseSetValueFromExclusiveBinderForObject:self]; -} - /*! Returns the tag of the selected segment. */ @@ -957,6 +922,44 @@ var CPSegmentedControlSegmentsKey = "CPSegmentedControlSegmentsKey", @end +@implementation CPSegmentedControl (BindingSupport) + ++ (Class)_binderClassForBinding:(CPString)aBinding +{ + if ([self _isSelectionBinding:aBinding]) + return [_CPSegmentedControlBinder class]; + + return [super _binderClassForBinding:aBinding]; +} + ++ (BOOL)_isSelectionBinding:(CPString)aBinding +{ + return (aBinding === CPSelectedIndexBinding || aBinding === CPSelectedLabelBinding || aBinding === CPSelectedTagBinding); +} + ++ (BOOL)isBindingExclusive:(CPString)aBinding +{ + return [self _isSelectionBinding:aBinding]; +} + +- (void)bind:(CPString)aBinding toObject:(id)anObject withKeyPath:(CPString)aKeyPath options:(CPDictionary)options +{ + if ([[self class] _isSelectionBinding:aBinding] && _trackingMode !== CPSegmentSwitchTrackingSelectOne) + { + CPLog.warn("Binding " + aBinding + " needs CPSegmentSwitchTrackingSelectOne tracking mode"); + return; + } + + [super bind:aBinding toObject:anObject withKeyPath:aKeyPath options:options]; +} + +- (void)_reverseSetBinding +{ + [_CPSegmentedControlBinder _reverseSetValueFromExclusiveBinderForObject:self]; +} + +@end + var CPSegmentedControlNoSelectionPlaceholder = "CPSegmentedControlNoSelectionPlaceholder"; @implementation _CPSegmentedControlBinder : CPBinder diff --git a/Tests/AppKit/CPKeyValueBindingTest.j b/Tests/AppKit/CPKeyValueBindingTest.j index 9812e81e6..701e1d858 100644 --- a/Tests/AppKit/CPKeyValueBindingTest.j +++ b/Tests/AppKit/CPKeyValueBindingTest.j @@ -342,6 +342,21 @@ [self assert:@"brie" equals:[control2 objectValue] message:@"control2 objectValue is wrong"]; } +- (void)testExclusiveBindingsThrowsException +{ + var segmented = [[CPSegmentedControl alloc] initWithFrame:CGRectMakeZero()]; + [segmented setSegmentCount:3]; + + var model = [BindingTester testerWithCheese:2]; + + [segmented bind:CPSelectedIndexBinding toObject:model withKeyPath:@"cheese" options:nil]; + + [self assertThrows:function() + { + [segmented bind:CPSelectedTagBinding toObject:model withKeyPath:@"cheese" options:nil]; + }]; +} + @end @implementation TextField : CPTextField