/** NSArrayController Controller class for arrays Copyright (C) 2006, 2020 Free Software Foundation, Inc. Author: Fred Kiefer Date: June 2006 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 or write to the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import #import #import #import #import #import #import #import #import "AppKit/NSArrayController.h" #import "AppKit/NSKeyValueBinding.h" #import "GSBindingHelpers.h" #import "GSFastEnumeration.h" @implementation GSObservableArray - (id) initWithArray: (NSArray *)array { self = [super init]; if (self) { ASSIGN(_array, array); } return self; } - (void) dealloc { RELEASE(_array); [super dealloc]; } - (NSUInteger) count { return [_array count]; } - (NSUInteger) indexOfObject: (id)anObject { return [_array indexOfObject: anObject]; } - (id) objectAtIndex: (NSUInteger)index { return [_array objectAtIndex: index]; } - (NSArray *) objectsAtIndexes: (NSIndexSet *)indexes { NSArray *result = [_array objectsAtIndexes: indexes]; return AUTORELEASE([[GSObservableArray alloc] initWithArray: result]); } - (id) valueForKey: (NSString*)key { id result = [_array valueForKey: key]; if ([result isKindOfClass: [NSArray class]]) { // FIXME: Using the correct memory management here // Leads to an issue inside of KVO. For now we leak the // object until this gets fixed. //return AUTORELEASE([[GSObservableArray alloc] return ([[GSObservableArray alloc] initWithArray: result]); } return result; } - (NSArray*) arrayByAddingObject: (id)anObject { NSArray * result = [_array arrayByAddingObject: anObject]; return AUTORELEASE([[GSObservableArray alloc] initWithArray: result]); } - (NSArray*) arrayByAddingObjectsFromArray: (NSArray*)anotherArray { NSArray * result = [_array arrayByAddingObjectsFromArray: anotherArray]; return AUTORELEASE([[GSObservableArray alloc] initWithArray: result]); } - (void) addObserver: (NSObject*)anObserver forKeyPath: (NSString*)aPath options: (NSKeyValueObservingOptions)options context: (void*)aContext { NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [self count])]; [self addObserver: anObserver toObjectsAtIndexes: indexes forKeyPath: aPath options: options context: aContext]; } - (void) removeObserver: (NSObject*)anObserver forKeyPath: (NSString*)aPath { NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [self count])]; [self removeObserver: anObserver fromObjectsAtIndexes: indexes forKeyPath: aPath]; } @end @implementation NSArrayController + (void) initialize { if (self == [NSArrayController class]) { [self exposeBinding: NSContentArrayBinding]; [self exposeBinding: NSSelectionIndexesBinding]; [self setKeys: [NSArray arrayWithObjects: NSContentBinding, NSContentObjectBinding, nil] triggerChangeNotificationsForDependentKey: @"arrangedObjects"]; } } - (id) initWithContent: (id)content { if ((self = [super initWithContent: content]) != nil) { [self setSelectsInsertedObjects: YES]; } return self; } - (id) init { NSMutableArray *new = [[NSMutableArray alloc] init]; self = [self initWithContent: new]; RELEASE(new); return self; } - (void) dealloc { DESTROY(_arranged_objects); DESTROY(_selection_indexes); DESTROY(_sort_descriptors); DESTROY(_filter_predicate); [super dealloc]; } - (void) addObject: (id)obj { [self willChangeValueForKey: NSContentBinding]; [_content addObject: obj]; if ([self automaticallyRearrangesObjects]) { [self rearrangeObjects]; } else { DESTROY(_arranged_objects); } if ([self selectsInsertedObjects]) { [self addSelectedObjects: [NSArray arrayWithObject: obj]]; } [self didChangeValueForKey: NSContentBinding]; } - (void) addObjects: (NSArray*)obj { [self willChangeValueForKey: NSContentBinding]; [_content addObjectsFromArray: obj]; if ([self automaticallyRearrangesObjects]) { [self rearrangeObjects]; } else { DESTROY(_arranged_objects); } if ([self selectsInsertedObjects]) { [self addSelectedObjects: obj]; } [self didChangeValueForKey: NSContentBinding]; } - (void) removeObject: (id)obj { [self willChangeValueForKey: NSContentBinding]; [_content removeObject: obj]; [self removeSelectedObjects: [NSArray arrayWithObject: obj]]; if ([self automaticallyRearrangesObjects]) { [self rearrangeObjects]; } else { DESTROY(_arranged_objects); } [self didChangeValueForKey: NSContentBinding]; } - (void) removeObjects: (NSArray*)obj { [self willChangeValueForKey: NSContentBinding]; [_content removeObjectsInArray: obj]; [self removeSelectedObjects: obj]; if ([self automaticallyRearrangesObjects]) { [self rearrangeObjects]; } else { DESTROY(_arranged_objects); } [self didChangeValueForKey: NSContentBinding]; } - (BOOL) canInsert { return YES; } - (void) setContent: (id)content { [super setContent: content]; [self rearrangeObjects]; } - (void) insert: (id)sender { id new = [self newObject]; [self addObject: new]; RELEASE(new); } - (NSIndexSet*) _indexSetForObjects: (NSArray*)objects { NSMutableIndexSet *tmp = [NSMutableIndexSet new]; id enumerator = objects; FOR_IN (id, obj, enumerator) { NSUInteger index = [_arranged_objects indexOfObject: obj]; if (NSNotFound != index) { [tmp addIndex: index]; } } END_FOR_IN(enumerator) return AUTORELEASE(tmp); } - (BOOL) addSelectedObjects: (NSArray*)obj { return [self addSelectionIndexes: [self _indexSetForObjects: obj]]; } - (BOOL) addSelectionIndexes: (NSIndexSet*)idx { NSMutableIndexSet *tmp = AUTORELEASE([_selection_indexes mutableCopy]); [tmp addIndexes: idx]; return [self setSelectionIndexes: tmp]; } - (BOOL) setSelectedObjects: (NSArray*)obj { return [self setSelectionIndexes: [self _indexSetForObjects: obj]]; } - (BOOL) setSelectionIndex: (NSUInteger)idx { return [self setSelectionIndexes: [NSIndexSet indexSetWithIndex: idx]]; } - (BOOL) setSelectionIndexes: (NSIndexSet*)idx { if ([_selection_indexes isEqual: idx]) { return NO; } else { ASSIGNCOPY(_selection_indexes, idx); return YES; } } - (BOOL) removeSelectedObjects: (NSArray*)obj { return [self removeSelectionIndexes: [self _indexSetForObjects: obj]]; } - (BOOL) removeSelectionIndexes: (NSIndexSet*)idx { NSMutableIndexSet *tmp = AUTORELEASE([_selection_indexes mutableCopy]); [tmp removeIndexes: idx]; return [self setSelectionIndexes: tmp]; } - (BOOL) canSelectNext { NSUInteger cur = [self selectionIndex]; if ((cur == NSNotFound) || (cur + 1 == [_content count])) { return NO; } else { return YES; } } - (BOOL) canSelectPrevious { NSUInteger cur = [self selectionIndex]; if ((cur == NSNotFound) || (cur == 0)) { return NO; } else { return YES; } } - (void) selectNext: (id)sender { NSUInteger cur = [self selectionIndex]; [self setSelectionIndexes: [NSIndexSet indexSetWithIndex: cur + 1]]; } - (void) selectPrevious: (id)sender { NSUInteger cur = [self selectionIndex]; [self setSelectionIndexes: [NSIndexSet indexSetWithIndex: cur - 1]]; } - (NSArray*) selectedObjects { // We make the selection work on the arranged objects return [[self arrangedObjects] objectsAtIndexes: _selection_indexes]; } - (NSUInteger) selectionIndex { return [_selection_indexes firstIndex]; } - (NSIndexSet*) selectionIndexes { return AUTORELEASE([_selection_indexes copy]); } - (BOOL) avoidsEmptySelection { return _acflags.avoids_empty_selection; } - (void) setAvoidsEmptySelection: (BOOL)flag { _acflags.avoids_empty_selection = flag; } - (BOOL) preservesSelection { return _acflags.preserves_selection; } - (void) setPreservesSelection: (BOOL)flag { _acflags.preserves_selection = flag; } - (BOOL) alwaysUsesMultipleValuesMarker { return _acflags.always_uses_multiple_values_marker; } - (void) setAlwaysUsesMultipleValuesMarker: (BOOL)flag { _acflags.always_uses_multiple_values_marker = flag; } - (BOOL) clearsFilterPredicateOnInsertion { return _acflags.clears_filter_predicate_on_insertion; } - (void) setClearsFilterPredicateOnInsertion: (BOOL)flag { _acflags.clears_filter_predicate_on_insertion = flag; } - (BOOL) automaticallyRearrangesObjects { return _acflags.automatically_rearranges_objects; } - (void) setAutomaticallyRearrangesObjects: (BOOL)flag { _acflags.automatically_rearranges_objects = flag; } - (BOOL) selectsInsertedObjects { return _acflags.selects_inserted_objects; } - (void) setSelectsInsertedObjects: (BOOL)flag { _acflags.selects_inserted_objects = flag; } - (NSArray*) arrangeObjects: (NSArray*)obj { NSArray *temp = obj; if (_filter_predicate != nil) { temp = [obj filteredArrayUsingPredicate: _filter_predicate]; } return [temp sortedArrayUsingDescriptors: _sort_descriptors]; } - (id) arrangedObjects { if (_arranged_objects == nil) { [self rearrangeObjects]; } return _arranged_objects; } - (void) rearrangeObjects { [self willChangeValueForKey: @"arrangedObjects"]; DESTROY(_arranged_objects); _arranged_objects = [[GSObservableArray alloc] initWithArray: [self arrangeObjects: _content]]; [self didChangeValueForKey: @"arrangedObjects"]; } - (void) setSortDescriptors: (NSArray*)desc { ASSIGNCOPY(_sort_descriptors, desc); } - (NSArray*) sortDescriptors { return AUTORELEASE([_sort_descriptors copy]); } - (void) setFilterPredicate: (NSPredicate*)filterPredicate { ASSIGN(_filter_predicate, filterPredicate); } - (NSPredicate*) filterPredicate { return _filter_predicate; } - (void) insertObject: (id)obj atArrangedObjectIndex: (NSUInteger)idx { // FIXME [self addObject: obj]; } - (void) insertObjects: (NSArray*)obj atArrangedObjectIndexes: (NSIndexSet*)idx { // FIXME [self addObjects: obj]; } - (void) removeObjectAtArrangedObjectIndex: (NSUInteger)idx { [self removeObject: [_arranged_objects objectAtIndex: idx]]; } - (void) removeObjectsAtArrangedObjectIndexes: (NSIndexSet*)idx { [self removeObjects: [_arranged_objects objectsAtIndexes: idx]]; } - (void) bind: (NSString *)binding toObject: (id)anObject withKeyPath: (NSString *)keyPath options: (NSDictionary *)options { if ([binding isEqual: NSContentArrayBinding]) { GSKeyValueBinding *kvb; [self unbind: binding]; kvb = [[GSKeyValueBinding alloc] initWithBinding: @"content" withName: binding toObject: anObject withKeyPath: keyPath options: options fromObject: self]; // The binding will be retained in the binding table RELEASE(kvb); } else { [super bind: binding toObject: anObject withKeyPath: keyPath options: options]; } } - (Class) valueClassForBinding: (NSString *)binding { if ([binding isEqual: NSContentArrayBinding]) { return [NSArray class]; } else { return [super valueClassForBinding: binding]; } } - (void) encodeWithCoder: (NSCoder *)coder { [super encodeWithCoder: coder]; if ([coder allowsKeyedCoding]) { [coder encodeBool: [self avoidsEmptySelection] forKey: @"NSAvoidsEmptySelection"]; [coder encodeBool: [self preservesSelection] forKey: @"NSPreservesSelection"]; [coder encodeBool: [self selectsInsertedObjects] forKey: @"NSSelectsInsertedObjects"]; [coder encodeBool: [self clearsFilterPredicateOnInsertion] forKey: @"NSClearsFilterPredicateOnInsertion"]; [coder encodeBool: [self automaticallyRearrangesObjects] forKey: @"NSAutomaticallyRearrangesObjects"]; } else { BOOL f; f = _acflags.avoids_empty_selection; [coder encodeValueOfObjCType: @encode(BOOL) at: &f]; f = _acflags.preserves_selection; [coder encodeValueOfObjCType: @encode(BOOL) at: &f]; f = _acflags.selects_inserted_objects; [coder encodeValueOfObjCType: @encode(BOOL) at: &f]; f = _acflags.clears_filter_predicate_on_insertion; [coder encodeValueOfObjCType: @encode(BOOL) at: &f]; f = _acflags.automatically_rearranges_objects; [coder encodeValueOfObjCType: @encode(BOOL) at: &f]; } } - (id) initWithCoder: (NSCoder *)coder { if ((self = [super initWithCoder: coder]) == nil) { return nil; } if ([coder allowsKeyedCoding]) { if ([coder containsValueForKey: @"NSAvoidsEmptySelection"]) { [self setAvoidsEmptySelection: [coder decodeBoolForKey: @"NSAvoidsEmptySelection"]]; } if ([coder containsValueForKey: @"NSPreservesSelection"]) { [self setPreservesSelection: [coder decodeBoolForKey: @"NSPreservesSelection"]]; } if ([coder containsValueForKey: @"NSSelectsInsertedObjects"]) { [self setSelectsInsertedObjects: [coder decodeBoolForKey: @"NSSelectsInsertedObjects"]]; } if ([coder containsValueForKey: @"NSClearsFilterPredicateOnInsertion"]) { [self setClearsFilterPredicateOnInsertion: [coder decodeBoolForKey: @"NSClearsFilterPredicateOnInsertion"]]; } if ([coder containsValueForKey: @"NSAutomaticallyRearrangesObjects"]) { [self setAutomaticallyRearrangesObjects: [coder decodeBoolForKey: @"NSAutomaticallyRearrangesObjects"]]; } } else { BOOL f; [coder decodeValueOfObjCType: @encode(BOOL) at: &f]; _acflags.avoids_empty_selection = f; [coder decodeValueOfObjCType: @encode(BOOL) at: &f]; _acflags.preserves_selection = f; [coder decodeValueOfObjCType: @encode(BOOL) at: &f]; _acflags.selects_inserted_objects = f; [coder decodeValueOfObjCType: @encode(BOOL) at: &f]; _acflags.clears_filter_predicate_on_insertion = f; [coder decodeValueOfObjCType: @encode(BOOL) at: &f]; _acflags.automatically_rearranges_objects = f; } return self; } @end