diff --git a/AppKit/CPArrayController.j b/AppKit/CPArrayController.j index 5d6f2efa7..56bf62af2 100644 --- a/AppKit/CPArrayController.j +++ b/AppKit/CPArrayController.j @@ -27,6 +27,7 @@ @import "CPObjectController.j" @import "CPKeyValueBinding.j" + /*! @class CPArrayController @@ -284,8 +285,7 @@ if (value === nil) value = []; - - if (![value isKindOfClass:[CPArray class]]) + else if (![value isKindOfClass:[CPArray class]]) value = [value]; var oldSelectedObjects = nil, @@ -339,14 +339,6 @@ [self setContent:anArray]; } -/*! - @ignore -*/ -- (void)_setContentSet:(id)aSet -{ - [self setContent:[aSet allObjects]]; -} - /*! Returns the content array of the controller. @return id the content array of the receiver @@ -356,6 +348,14 @@ return [self content]; } +/*! + @ignore +*/ +- (void)_setContentSet:(id)aSet +{ + [self setContent:[aSet allObjects]]; +} + /*! Returns the content of the receiver as a CPSet. @@ -564,8 +564,13 @@ - (BOOL)setSelectionIndexes:(CPIndexSet)indexes { [self _selectionWillChange] - [self __setSelectionIndexes:indexes]; - [self _selectionDidChange]; + + // When explicitly setting the selection, ignore avoidsEmptySelection + var changed = [self __setSelectionIndexes:indexes obeyAvoidsEmptySelection:NO]; + + [self _selectionDidChangeNotify:NO]; + + return changed; } /* @@ -574,7 +579,7 @@ */ - (BOOL)__setSelectionIndex:(int)theIndex { - [self __setSelectionIndexes:[CPIndexSet indexSetWithIndex:theIndex]]; + return [self __setSelectionIndexes:[CPIndexSet indexSetWithIndex:theIndex] obeyAvoidsEmptySelection:YES]; } /* @@ -582,13 +587,22 @@ @ignore */ - (BOOL)__setSelectionIndexes:(CPIndexSet)indexes +{ + [self __setSelectionIndexes:indexes obeyAvoidsEmptySelection:YES]; +} + +/* + Like setSelectionIndexes but don't fire any change notifications. + @ignore +*/ +- (BOOL)__setSelectionIndexes:(CPIndexSet)indexes obeyAvoidsEmptySelection:(BOOL)obeyAvoidsEmptySelection { if (!indexes) indexes = [CPIndexSet indexSet]; if (![indexes count]) { - if (_avoidsEmptySelection && [[self arrangedObjects] count]) + if (obeyAvoidsEmptySelection && _avoidsEmptySelection && [[self arrangedObjects] count]) indexes = [CPIndexSet indexSetWithIndex:0]; } else @@ -636,10 +650,10 @@ [self willChangeValueForKey:@"selectionIndexes"]; [self _selectionWillChange]; - [self __setSelectedObjects:objects]; + [self __setSelectedObjects:objects obeyAvoidsEmptySelection:NO]; [self didChangeValueForKey:@"selectionIndexes"]; - [self _selectionDidChange]; + [self _selectionDidChangeNotify:NO]; } /* @@ -647,6 +661,15 @@ @ignore */ - (BOOL)__setSelectedObjects:(CPArray)objects +{ + [self __setSelectedObjects:objects obeyAvoidsEmptySelection:YES]; +} + +/* + Like setSelectedObjects but don't fire any change notifications. + @ignore +*/ +- (BOOL)__setSelectedObjects:(CPArray)objects obeyAvoidsEmptySelection:(BOOL)obeyAvoidsEmptySelection { var set = [CPIndexSet indexSet], count = [objects count], @@ -660,7 +683,7 @@ [set addIndex:index]; } - [self __setSelectionIndexes:set]; + [self __setSelectionIndexes:set obeyAvoidsEmptySelection:obeyAvoidsEmptySelection]; return YES; } @@ -756,14 +779,15 @@ [_selectionIndexes shiftIndexesStartingAtIndex:pos by:1]; } /* - else if (_filterPredicate !== nil) - ... - // Implies _filterPredicate && ![_filterPredicate evaluateWithObject:object], so the new object does - // not appear in arrangedObjects and we do not have to update at all. + else if (_filterPredicate !== nil) + ... + Implies _filterPredicate && ![_filterPredicate evaluateWithObject:object], so the new object does + not appear in arrangedObjects and we do not have to update at all. */ // This will also send notificaitons for arrangedObjects. [self didChangeValueForKey:@"content"]; + if (willClearPredicate) [self didChangeValueForKey:@"filterPredicate"]; } @@ -816,6 +840,7 @@ [self __setSelectionIndexes:[CPIndexSet indexSetWithIndex:0]]; [self didChangeValueForKey:@"content"]; + if (willClearPredicate) [self didChangeValueForKey:@"filterPredicate"]; } @@ -942,6 +967,7 @@ index = [anIndexSet indexLessThanIndex:index]; } + // Allow handlesContentAsCompoundValue reverse sets to trigger. [[CPBinder getBinding:@"contentArray" forObject:self] _contentArrayDidChange]; _disableSetContent = NO; @@ -1063,7 +1089,7 @@ + (Class)_binderClassForBinding:(CPString)theBinding { - if (theBinding == @"contentArray") + if (theBinding === @"content" || theBinding === @"contentArray") return [_CPArrayControllerContentBinder class]; return [super _binderClassForBinding:theBinding]; @@ -1078,7 +1104,7 @@ var destination = [_info objectForKey:CPObservedObjectKey], keyPath = [_info objectForKey:CPObservedKeyPathKey], options = [_info objectForKey:CPOptionsKey], - isCompound = [self handlesContentAsCompoundValue]; + isCompound = [self handlesContentAsCompoundValue] || keyPath.indexOf("@") !== -1; if (!isCompound) { diff --git a/AppKit/CPKeyValueBinding.j b/AppKit/CPKeyValueBinding.j index 0809110ab..4876d6eaa 100644 --- a/AppKit/CPKeyValueBinding.j +++ b/AppKit/CPKeyValueBinding.j @@ -204,7 +204,6 @@ var CPBindingOperationAnd = 0, if (valueTransformer) aValue = [valueTransformer transformedValue:aValue]; - if (aValue === undefined || aValue === nil || aValue === [CPNull null]) aValue = [options objectForKey:CPNullPlaceholderBindingOption] || nil; @@ -311,7 +310,7 @@ var CPBindingOperationAnd = 0, - (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); + 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); @@ -488,6 +487,8 @@ CPSelectedIndexBinding = @"selectedIndex"; CPTextColorBinding = @"textColor"; CPToolTipBinding = @"toolTip"; CPValueBinding = @"value"; +CPContentBinding = @"content"; +CPContentValuesBinding = @"contentValues"; //Binding options constants CPAllowsEditingMultipleValuesSelectionBindingOption = @"CPAllowsEditingMultipleValuesSelection"; @@ -518,4 +519,4 @@ CPValueTransformerBindingOption = @"CPValueTransformer"; CPIsControllerMarker = function(/*id*/anObject) { return anObject === CPMultipleValuesMarker || anObject === CPNoSelectionMarker || anObject === CPNotApplicableMarker || anObject === CPNullMarker; -} \ No newline at end of file +} diff --git a/AppKit/CPObjectController.j b/AppKit/CPObjectController.j index 096104a3b..a302a5662 100644 --- a/AppKit/CPObjectController.j +++ b/AppKit/CPObjectController.j @@ -323,12 +323,22 @@ @ignore */ - (void)_selectionDidChange +{ + [self _selectionDidChangeNotify:YES]; +} + +/*! + @ignore +*/ +- (void)_selectionDidChangeNotify:(BOOL)notify { if (_selection === undefined || _selection === nil) _selection = [[CPControllerSelectionProxy alloc] initWithController:self]; [_selection controllerDidChange]; - [self didChangeValueForKey:@"selection"]; + + if (notify) + [self didChangeValueForKey:@"selection"]; } /*! @@ -666,21 +676,21 @@ var CPObjectControllerContentKey = @"CPObjectControllerCo var count = [theValues count]; if (!count) - value = CPNoSelectionMarker; + return CPNoSelectionMarker; else if (count === 1) value = [theValues objectAtIndex:0]; else { if ([_controller alwaysUsesMultipleValuesMarker]) - value = CPMultipleValuesMarker; + return CPMultipleValuesMarker; else { value = [theValues objectAtIndex:0]; - for (var i = 0, count= [theValues count]; i < count && value != CPMultipleValuesMarker; i++) + for (var i = 0, count = [theValues count]; i < count; i++) { if (![value isEqual:[theValues objectAtIndex:i]]) - value = CPMultipleValuesMarker; + return CPMultipleValuesMarker; } } } @@ -693,8 +703,21 @@ var CPObjectControllerContentKey = @"CPObjectControllerCo - (id)valueForKeyPath:(CPString)theKeyPath { - var values = [[_controller selectedObjects] valueForKeyPath:theKeyPath]; - value = [self _controllerMarkerForValues:values]; + // If valueForKeyPath fails because of an undefined key, return CPNotApplicableMarker + var value; + + try + { + var values = [[_controller selectedObjects] valueForKeyPath:theKeyPath]; + value = [self _controllerMarkerForValues:values]; + } + catch (ex) + { + if ([ex name] === CPUndefinedKeyException) + value = CPNotApplicableMarker; + else + throw ex; + } [_cachedValues setObject:value forKey:theKeyPath]; diff --git a/Foundation/CPKeyValueObserving.j b/Foundation/CPKeyValueObserving.j index c7e47ecd0..b73563066 100644 --- a/Foundation/CPKeyValueObserving.j +++ b/Foundation/CPKeyValueObserving.j @@ -631,12 +631,12 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOpti { // Fire change events for the dependent keys var dependentKeysForClass = _nativeClass[DependentKeysKey], - dependantKeys = [dependentKeysForClass[theKeyPath] allObjects]; + dependentKeys = [dependentKeysForClass[theKeyPath] allObjects], + isBeforeFlag = !![theChanges objectForKey:CPKeyValueChangeNotificationIsPriorKey]; - var isBeforeFlag = !![theChanges objectForKey:CPKeyValueChangeNotificationIsPriorKey]; - for (var i = 0; i < [dependantKeys count]; i++) + for (var i = 0; i < [dependentKeys count]; i++) { - var dependantKey = [dependantKeys objectAtIndex:i]; + var dependantKey = [dependentKeys objectAtIndex:i]; [self _sendNotificationsForKey:dependantKey changeOptions:theChanges isBefore:isBeforeFlag]; } }