assorted controller behavior fixes

This commit is contained in:
Christopher Lloyd 2010-03-10 16:33:22 +00:00
parent 3c0f029d38
commit 418c3a5bd9
7 changed files with 146 additions and 205 deletions

View File

@ -9,7 +9,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
#import <AppKit/NSObjectController.h>
@class NSPredicate,NSIndexSet;
@class NSPredicate,NSIndexSet,NSMutableIndexSet;
@interface NSArrayController : NSObjectController {
struct
@ -21,7 +21,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
long selectsInsertedObjects:1;
long alwaysUsesMultipleValuesMarker:1;
} _flags;
id _selectionIndexes;
NSMutableIndexSet *_selectionIndexes;
id _sortDescriptors;
id _filterPredicate;
id _arrangedObjects;

View File

@ -112,9 +112,9 @@ triggerChangeNotificationsForDependentKey:@"selectionIndex"];
-(void)setContent:(id)value
{
if(![value isKindOfClass:[NSArray class]])
value=[NSArray arrayWithObject:value];
if(value!=nil && ![value isKindOfClass:[NSArray class]])
value=[NSArray arrayWithObject:value];
id oldSelection=nil;
id oldSelectionIndexes=[[[self selectionIndexes] copy] autorelease];
if([self preservesSelection])
@ -147,16 +147,19 @@ triggerChangeNotificationsForDependentKey:@"selectionIndex"];
}
- (id)contentArray {
return [self content];
id result=[self content];
return result;
}
-(NSArray*)arrangeObjects:(NSArray*)objects
{
-(NSArray*)arrangeObjects:(NSArray*)objects {
id sortedObjects=objects;
if([self filterPredicate])
sortedObjects=[sortedObjects filteredArrayUsingPredicate:[self filterPredicate]];
if([self sortDescriptors])
sortedObjects=[sortedObjects sortedArrayUsingDescriptors:[self sortDescriptors]];
return sortedObjects;
}
@ -166,15 +169,11 @@ triggerChangeNotificationsForDependentKey:@"selectionIndex"];
}
- (void)_setArrangedObjects:(id)value {
if (_arrangedObjects != value)
{
[_arrangedObjects release];
_arrangedObjects = [[_NSObservableArray alloc] initWithArray:value];
}
[_arrangedObjects autorelease];
_arrangedObjects = [[_NSObservableArray alloc] initWithArray:value];
}
-(id)arrangedObjects
{
-arrangedObjects {
return _arrangedObjects;
}
@ -221,23 +220,24 @@ triggerChangeNotificationsForDependentKey:@"selectionIndex"];
}
- (NSIndexSet *)selectionIndexes {
return [[_selectionIndexes retain] autorelease];
return _selectionIndexes;
}
- (BOOL)setSelectionIndexes:(NSIndexSet *)value {
if(![value count] && _flags.avoidsEmptySelection && [[self arrangedObjects] count])
value=[NSIndexSet indexSetWithIndex:0];
if(_flags.avoidsEmptySelection && [value count]==0 && [[self arrangedObjects] count])
value=[NSIndexSet indexSetWithIndex:0];
value=[[value mutableCopy] autorelease];
[(NSMutableIndexSet *)value removeIndexesInRange:NSMakeRange([[self arrangedObjects] count]+1, NSNotFound)];
NSMutableIndexSet *mutableValue=[[value mutableCopy] autorelease];
[mutableValue removeIndexesInRange:NSMakeRange([[self arrangedObjects] count]+1, NSNotFound)];
// use isEqualToIndexSet: ?
if (_selectionIndexes != value) {
if (![_selectionIndexes isEqualToIndexSet: mutableValue]) {
[self willChangeValueForKey:@"selectionIndexes"];
[self _selectionWillChange];
[_selectionIndexes release];
_selectionIndexes = [value copy];
_selectionIndexes = [mutableValue retain];
[self _selectionDidChange];
[self didChangeValueForKey:@"selectionIndexes"];
@ -319,9 +319,6 @@ triggerChangeNotificationsForDependentKey:@"selectionIndex"];
}
}
#pragma mark -
#pragma mark NSSet support
-(id)_contentSet
{
return [NSSet setWithArray:_content];
@ -332,13 +329,8 @@ triggerChangeNotificationsForDependentKey:@"selectionIndex"];
[self setContent:[set allObjects]];
}
#pragma mark -
#pragma mark Add/Remove
- (void)addObject:(id)object
{
if(![self canAdd])
return;
- (void)addObject:(id)object {
// Don't check canAdd here as this can be used programmatically to add objects
[self willChangeValueForKey:@"content"];
[_content addObject:object];
@ -347,39 +339,44 @@ triggerChangeNotificationsForDependentKey:@"selectionIndex"];
if(_flags.clearsFilterPredicateOnInsertion)
[self setFilterPredicate:nil];
if([_filterPredicate evaluateWithObject:object])
{
[self willChangeValueForKey:@"selectionIndexes"];
if([self filterPredicate]==nil || [_filterPredicate evaluateWithObject:object]){
// FIXME: this should probably use arrangeObjects: to get subclass behavior
[self willChangeValueForKey:@"arrangedObjects"];
NSUInteger pos=[_arrangedObjects _insertObject:object inArraySortedByDescriptors:_sortDescriptors];
[self didChangeValueForKey:@"arrangedObjects"];
[self willChangeValueForKey:@"selectionIndexes"];
[_selectionIndexes shiftIndexesStartingAtIndex:pos by:1];
[self didChangeValueForKey:@"selectionIndexes"];
}
}
- (void)removeObject:(id)object
{
if(![self canRemove])
return;
-(void)removeObject:(id)object {
// Don't check canremove/editable here as this can be used programmatically to remove objects
[self willChangeValueForKey:@"content"];
[_content removeObject:object];
[self didChangeValueForKey:@"content"];
if([_filterPredicate evaluateWithObject:object])
{
if([self filterPredicate]==nil || [_filterPredicate evaluateWithObject:object]){
// FIXME: this should probably use arrangeObjects: to get subclass behavior
[self willChangeValueForKey:@"arrangedObjects"];
NSUInteger pos=[_arrangedObjects indexOfObject:object];
[self willChangeValueForKey:@"selectionIndexes"];
[_arrangedObjects removeObject:object];
[self didChangeValueForKey:@"arrangedObjects"];
[self willChangeValueForKey:@"selectionIndexes"];
[_selectionIndexes shiftIndexesStartingAtIndex:pos by:-1];
[self didChangeValueForKey:@"selectionIndexes"];
}
}
-(void)add:(id)sender
{
-(void)add:(id)sender {
if(![self canAdd])
return;
[self insert:sender];
}
@ -387,6 +384,7 @@ triggerChangeNotificationsForDependentKey:@"selectionIndex"];
{
if(![self canInsert])
return;
id toAdd=nil;
if([self automaticallyPreparesContent])
toAdd=[[self newObject] autorelease];
@ -395,22 +393,21 @@ triggerChangeNotificationsForDependentKey:@"selectionIndex"];
[self addObject:toAdd];
}
-(void)remove:(id)sender
{
-(void)remove:(id)sender {
if(![self canRemove])
return;
[self removeObjects:[[self contentArray] objectsAtIndexes:[self selectionIndexes]]];
}
-(void)removeObjectsAtArrangedObjectIndexes:(NSIndexSet*)indexes
{
// FIXME: this should remove no matter what canRemove returns
[self removeObjects:[[self contentArray] objectsAtIndexes:indexes]];
-(void)removeObjectsAtArrangedObjectIndexes:(NSIndexSet*)indexes {
[self removeObjects:[[self contentArray] objectsAtIndexes:indexes]];
}
- (void)addObjects:(NSArray *)objects
{
if(![self canAdd])
return;
- (void)addObjects:(NSArray *)objects {
// Don't check canAdd/editable here as this can be used programmatically to add objects
id contentArray=[[[self contentArray] mutableCopy] autorelease];
int count=[objects count];
int i;
@ -420,10 +417,8 @@ triggerChangeNotificationsForDependentKey:@"selectionIndex"];
}
- (void)removeObjects:(NSArray *)objects
{
if(![self canRemove])
return;
- (void)removeObjects:(NSArray *)objects {
// Don't check canRemove here as this can be used programmatically to remove objects
id contentArray=[[[self contentArray] mutableCopy] autorelease];
int count=[objects count];

View File

@ -164,7 +164,6 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
_NSObservationProxy *proxy=[[_NSObservationProxy alloc] initWithKeyPath:keyPath observer:observer object:self];
int idx=[_observationProxies indexOfObject:proxy];
if(idx==NSNotFound) {
NSLog(@"%@ not found in %@", proxy, _observationProxies);
}
[proxy release];

View File

@ -20,30 +20,27 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
@end
@implementation NSObjectController
+(void)initialize
{
[self setKeys:[NSArray arrayWithObjects:@"editable", nil]
triggerChangeNotificationsForDependentKey:@"canAdd"];
[self setKeys:[NSArray arrayWithObjects:@"editable", nil]
triggerChangeNotificationsForDependentKey:@"canInsert"];
[self setKeys:[NSArray arrayWithObjects:@"editable", @"selection", nil]
triggerChangeNotificationsForDependentKey:@"canRemove"];
[self setKeys:[NSArray arrayWithObjects:@"content", nil]
triggerChangeNotificationsForDependentKey:@"contentObject"];
+(void)initialize {
[self setKeys:[NSArray arrayWithObjects:@"editable", nil] triggerChangeNotificationsForDependentKey:@"canAdd"];
[self setKeys:[NSArray arrayWithObjects:@"editable", nil] triggerChangeNotificationsForDependentKey:@"canInsert"];
[self setKeys:[NSArray arrayWithObjects:@"editable", @"selection", nil] triggerChangeNotificationsForDependentKey:@"canRemove"];
[self setKeys:[NSArray arrayWithObjects:@"content", nil] triggerChangeNotificationsForDependentKey:@"contentObject"];
}
-(id)initWithCoder:(NSCoder*)coder
{
if((self=[super init]))
{
_objectClassName=[[coder decodeObjectForKey:@"NSObjectClassName"] retain];
_editable = [coder decodeBoolForKey:@"NSEditable"];
_automaticallyPreparesContent = [coder decodeBoolForKey:@"NSAutomaticallyPreparesContent"];
_observedKeys=[[NSCountedSet alloc] init];
_selection=[[NSControllerSelectionProxy alloc] initWithController:self];
}
return self;
-initWithCoder:(NSCoder*)coder {
if((self=[super init])) {
_objectClassName=[[coder decodeObjectForKey:@"NSObjectClassName"] copy];
if(_objectClassName==nil)
_objectClassName=@"NSMutableDictionary";
_editable = [coder decodeBoolForKey:@"NSEditable"];
_automaticallyPreparesContent = [coder decodeBoolForKey:@"NSAutomaticallyPreparesContent"];
_observedKeys=[[NSCountedSet alloc] init];
_selection=[[NSControllerSelectionProxy alloc] initWithController:self];
}
return self;
}
- (id)content {
@ -73,7 +70,8 @@ triggerChangeNotificationsForDependentKey:@"contentObject"];
-(NSArray *)selectedObjects
{
return [NSArray arrayWithObject:_content];
NSArray *result=[NSArray arrayWithObject:_content];
return result;
}
-(id)selection

View File

@ -15,7 +15,7 @@
void* _context;
NSKeyValueObservingOptions _options;
}
-(id)initWithKeyPath:(id)keyPath observer:(id)observer object:(id)object;
-initWithKeyPath:(NSString *)keyPath observer:(id)observer object:(id)object;
-(id)observer;
-(id)keyPath;
-(void)setNotifyObject:(BOOL)val;

View File

@ -1,46 +1,58 @@
/* Copyright (c) 2007 Johannes Fortmann
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
#import "NSObservationProxy.h"
#import <Foundation/NSDictionary.h>
#import <Foundation/NSString.h>
#import <Foundation/NSException.h>
#import "NSStringKVCFunctions.h"
#import <Foundation/NSKeyValueObserving.h>
#import <Foundation/NSIndexSet.h>
@implementation _NSObservationProxy
-(id)initWithKeyPath:(id)keyPath observer:(id)observer object:(id)object
{
if((self=[super init]))
void NSStringKVCSplitOnDot(NSString *self,NSString **before,NSString **after){
NSRange range=[self rangeOfString:@"."];
if(range.location!=NSNotFound)
{
_keyPath=[keyPath retain];
_observer=observer;
_object=object;
*before=[self substringToIndex:range.location];
*after=[self substringFromIndex:range.location+1];
}
else
{
*before=self;
*after=nil;
}
}
@implementation _NSObservationProxy
-initWithKeyPath:(NSString *)keyPath observer:(id)observer object:(id)object {
_keyPath=[keyPath retain];
_observer=observer;
_object=object;
return self;
}
-(void)dealloc
{
[_keyPath release];
[super dealloc];
-(void)dealloc {
[_keyPath release];
[super dealloc];
}
-(id)observer
{
-observer {
return _observer;
}
-(id)keyPath
{
return _keyPath;
-keyPath {
return _keyPath;
}
-(void*)context
{
-(void *)context {
return _context;
}
-(NSKeyValueObservingOptions)options
{
-(NSKeyValueObservingOptions)options {
return _options;
}
@ -60,76 +72,54 @@
return NO;
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if(_notifyObject)
{
[_object observeValueForKeyPath:_keyPath
ofObject:_object
change:change
context:_context];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if(_notifyObject) {
[_object observeValueForKeyPath:_keyPath ofObject:_object change:change context:_context];
}
[_observer observeValueForKeyPath:_keyPath
ofObject:_object
change:change
context:_context];
[_observer observeValueForKeyPath:_keyPath ofObject:_object change:change context:_context];
}
-(NSString*)description
{
-(NSString *)description {
return [NSString stringWithFormat:@"observation proxy for %@ on key path %@", _observer, _keyPath];
}
@end
@implementation _NSObservableArray
-(id)objectAtIndex:(NSUInteger)idx
{
return [_array objectAtIndex:idx];
-objectAtIndex:(NSUInteger)idx {
return [_array objectAtIndex:idx];
}
-(NSUInteger)count
{
-(NSUInteger)count {
return [_array count];
}
-(id)init {
-init {
return [self initWithObjects:NULL count:0];
}
-initWithObjects:(id *)objects count:(NSUInteger)count;
{
if((self=[super init]))
{
_array=[[NSMutableArray alloc] initWithObjects:objects count:count];
_observationProxies=[NSMutableArray new];
}
-initWithObjects:(id *)objects count:(NSUInteger)count {
_array=[[NSMutableArray alloc] initWithObjects:objects count:count];
_observationProxies=[NSMutableArray new];
return self;
}
-(void)dealloc
{
if([_observationProxies count]>0)
[NSException raise:NSInvalidArgumentException
format:@"_NSObservableArray still being observed by %@ on %@",
[[_observationProxies objectAtIndex:0] observer],
[[_observationProxies objectAtIndex:0] keyPath]];
[_observationProxies release];
[_array release];
-(void)dealloc {
if([_observationProxies count]>0)
[NSException raise:NSInvalidArgumentException format:@"_NSObservableArray still being observed by %@ on %@",
[[_observationProxies objectAtIndex:0] observer],[[_observationProxies objectAtIndex:0] keyPath]];
[_observationProxies release];
[_array release];
[_roi release];
[super dealloc];
[super dealloc];
}
-(void)addObserver:(id)observer forKeyPath:(NSString*)keyPath options:(NSKeyValueObservingOptions)options context:(void*)context;
{
-(void)addObserver:(id)observer forKeyPath:(NSString*)keyPath options:(NSKeyValueObservingOptions)options context:(void*)context {
// init the proxy
_NSObservationProxy *proxy=[[_NSObservationProxy alloc] initWithKeyPath:keyPath
observer:observer
object:self];
_NSObservationProxy *proxy=[[_NSObservationProxy alloc] initWithKeyPath:keyPath observer:observer object:self];
proxy->_options=options;
proxy->_context=context;
[_observationProxies addObject:proxy];
@ -149,10 +139,7 @@
NSStringKVCSplitOnDot(keyPath,&firstPart,&rest);
// observe ourselves
[super addObserver:observer
forKeyPath:keyPath
options:options
context:context];
[super addObserver:observer forKeyPath:keyPath options:options context:context];
// if there's anything the operator depends on, observe _all_ objects for that path
keyPath=rest;
@ -161,20 +148,13 @@
// add observer proxy for all relevant indexes
if([_array count] && keyPath) {
[_array addObserver:proxy
toObjectsAtIndexes:idxs
forKeyPath:keyPath
options:options
context:context];
[_array addObserver:proxy toObjectsAtIndexes:idxs forKeyPath:keyPath options:options context:context];
}
}
-(void)removeObserver:(id)observer forKeyPath:(NSString*)keyPath;
{
-(void)removeObserver:(id)observer forKeyPath:(NSString*)keyPath {
// find the proxy again
_NSObservationProxy *proxy=[[_NSObservationProxy alloc] initWithKeyPath:keyPath
observer:observer
object:self];
_NSObservationProxy *proxy=[[_NSObservationProxy alloc] initWithKeyPath:keyPath observer:observer object:self];
int idx=[_observationProxies indexOfObject:proxy];
[proxy release];
proxy=[[[_observationProxies objectAtIndex:idx] retain] autorelease];
@ -194,17 +174,14 @@
NSString* firstPart, *rest;
NSStringKVCSplitOnDot(keyPath,&firstPart,&rest);
[super removeObserver:observer
forKeyPath:keyPath];
[super removeObserver:observer forKeyPath:keyPath];
// remove dependent key path from all children
keyPath=rest;
idxs=[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [_array count])];
}
if([_array count] && keyPath) {
[_array removeObserver:proxy
fromObjectsAtIndexes:idxs
forKeyPath:keyPath];
[_array removeObserver:proxy fromObjectsAtIndexes:idxs forKeyPath:keyPath];
}
}
@ -263,14 +240,13 @@
NSString* firstPart, *rest;
NSStringKVCSplitOnDot(keyPath,&firstPart,&rest);
if(rest)
[obj removeObserver:proxy
forKeyPath:rest];
if(rest) {
[obj removeObserver:proxy forKeyPath:rest];
}
}
else {
if(!_roi || [_roi containsIndex:idx]) {
[obj removeObserver:proxy
forKeyPath:keyPath];
[obj removeObserver:proxy forKeyPath:keyPath];
}
}
}
@ -288,13 +264,11 @@
}
}
-(void)addObject:(id)obj
{
-(void)addObject:(id)obj {
[self insertObject:obj atIndex:[self count]];
}
-(void)removeLastObject
{
-(void)removeLastObject {
[self removeObjectAtIndex:[self count]-1];
}
@ -311,8 +285,7 @@
NSStringKVCSplitOnDot(keyPath,&firstPart,&rest);
if(rest) {
[old removeObserver:proxy
forKeyPath:[proxy keyPath]];
[old removeObserver:proxy forKeyPath:[proxy keyPath]];
[obj addObserver:proxy
forKeyPath:rest
@ -322,8 +295,7 @@
}
else {
if(!_roi || [_roi containsIndex:idx]) {
[old removeObserver:proxy
forKeyPath:[proxy keyPath]];
[old removeObserver:proxy forKeyPath:[proxy keyPath]];
[obj addObserver:proxy
forKeyPath:[proxy keyPath]

View File

@ -1,23 +0,0 @@
/* Copyright (c) 2007 Johannes Fortmann
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
#import <Foundation/NSString.h>
void NSStringKVCSplitOnDot(NSString *self,NSString **before,NSString **after){
NSRange range=[self rangeOfString:@"."];
if(range.location!=NSNotFound)
{
*before=[self substringToIndex:range.location];
*after=[self substringFromIndex:range.location+1];
}
else
{
*before=self;
*after=nil;
}
}