/* |
File: BaseNode.m |
Abstract: Generic multi-use node object used with NSOutlineView and NSTreeController. |
|
Version: 1.5 |
|
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple |
Inc. ("Apple") in consideration of your agreement to the following |
terms, and your use, installation, modification or redistribution of |
this Apple software constitutes acceptance of these terms. If you do |
not agree with these terms, please do not use, install, modify or |
redistribute this Apple software. |
|
In consideration of your agreement to abide by the following terms, and |
subject to these terms, Apple grants you a personal, non-exclusive |
license, under Apple's copyrights in this original Apple software (the |
"Apple Software"), to use, reproduce, modify and redistribute the Apple |
Software, with or without modifications, in source and/or binary forms; |
provided that if you redistribute the Apple Software in its entirety and |
without modifications, you must retain this notice and the following |
text and disclaimers in all such redistributions of the Apple Software. |
Neither the name, trademarks, service marks or logos of Apple Inc. may |
be used to endorse or promote products derived from the Apple Software |
without specific prior written permission from Apple. Except as |
expressly stated in this notice, no other rights or licenses, express or |
implied, are granted by Apple herein, including but not limited to any |
patent rights that may be infringed by your derivative works or by other |
works in which the Apple Software may be incorporated. |
|
The Apple Software is provided by Apple on an "AS IS" basis. APPLE |
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION |
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS |
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND |
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. |
|
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL |
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, |
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED |
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), |
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE |
POSSIBILITY OF SUCH DAMAGE. |
|
Copyright (C) 2014 Apple Inc. All Rights Reserved. |
|
*/ |
|
#import "BaseNode.h" |
|
@implementation BaseNode |
|
@synthesize nodeTitle, children, isLeaf, urlString, nodeIcon; |
|
// ------------------------------------------------------------------------------- |
// init |
// ------------------------------------------------------------------------------- |
- (id)init |
{ |
self = [super init]; |
if (self) |
{ |
self.nodeTitle = @"BaseNode Untitled"; |
|
[self setChildren:[NSMutableArray array]]; |
[self setLeaf:NO]; // container by default |
} |
return self; |
} |
|
// ------------------------------------------------------------------------------- |
// initLeaf |
// ------------------------------------------------------------------------------- |
- (id)initLeaf |
{ |
self = [self init]; |
if (self) |
{ |
[self setLeaf:YES]; |
} |
return self; |
} |
|
// ------------------------------------------------------------------------------- |
// setLeaf:flag |
// ------------------------------------------------------------------------------- |
- (void)setLeaf:(BOOL)flag |
{ |
isLeaf = flag; |
if (isLeaf) |
[self setChildren:[NSMutableArray arrayWithObject:self]]; |
else |
[self setChildren:[NSMutableArray array]]; |
} |
|
// ------------------------------------------------------------------------------- |
// compare:aNode |
// ------------------------------------------------------------------------------- |
- (NSComparisonResult)compare:(BaseNode *)aNode |
{ |
return [[[self nodeTitle] lowercaseString] compare:[[aNode nodeTitle] lowercaseString]]; |
} |
|
|
#pragma mark - Drag and Drop |
|
// ------------------------------------------------------------------------------- |
// isDraggable |
// ------------------------------------------------------------------------------- |
- (BOOL)isDraggable |
{ |
BOOL result = YES; |
if ([[self urlString] isAbsolutePath] || [self nodeIcon] == nil) |
result = NO; // don't allow file system objects to be dragged or special group nodes |
return result; |
} |
|
// ------------------------------------------------------------------------------- |
// parentFromArray:array |
// |
// Finds the receiver's parent from the nodes contained in the array. |
// ------------------------------------------------------------------------------- |
- (id)parentFromArray:(NSArray *)array |
{ |
id result = nil; |
|
for (id node in array) |
{ |
if (node == self) // If we are in the root array, return nil |
break; |
|
if ([[node children] indexOfObjectIdenticalTo:self] != NSNotFound) |
{ |
result = node; |
break; |
} |
|
if (![node isLeaf]) |
{ |
id innerNode = [self parentFromArray:[node children]]; |
if (innerNode) |
{ |
result = innerNode; |
break; |
} |
} |
} |
|
return result; |
} |
|
// ------------------------------------------------------------------------------- |
// removeObjectFromChildren:obj |
// |
// Recursive method which searches children and children of all sub-nodes |
// to remove the given object. |
// ------------------------------------------------------------------------------- |
- (void)removeObjectFromChildren:(id)obj |
{ |
// remove object from children or the children of any sub-nodes |
for (id node in self.children) |
{ |
if (node == obj) |
{ |
[self.children removeObjectIdenticalTo:obj]; |
return; |
} |
|
if (![node isLeaf]) |
[node removeObjectFromChildren:obj]; |
} |
} |
|
// ------------------------------------------------------------------------------- |
// descendants |
// |
// Generates an array of all descendants. |
// ------------------------------------------------------------------------------- |
- (NSArray *)descendants |
{ |
NSMutableArray *descendants = [NSMutableArray array]; |
id node = nil; |
for (node in self.children) |
{ |
[descendants addObject:node]; |
|
if (![node isLeaf]) |
[descendants addObjectsFromArray:[node descendants]]; // Recursive - will go down the chain to get all |
} |
return descendants; |
} |
|
// ------------------------------------------------------------------------------- |
// allChildLeafs: |
// |
// Generates an array of all leafs in children and children of all sub-nodes. |
// Useful for generating a list of leaf-only nodes. |
// ------------------------------------------------------------------------------- |
- (NSArray *)allChildLeafs |
{ |
NSMutableArray *childLeafs = [NSMutableArray array]; |
id node = nil; |
|
for (node in self.children) |
{ |
if ([node isLeaf]) |
[childLeafs addObject:node]; |
else |
[childLeafs addObjectsFromArray:[node allChildLeafs]]; // Recursive - will go down the chain to get all |
} |
return childLeafs; |
} |
|
// ------------------------------------------------------------------------------- |
// groupChildren |
// |
// Returns only the children that are group nodes. |
// ------------------------------------------------------------------------------- |
- (NSArray *)groupChildren |
{ |
NSMutableArray *groupChildren = [NSMutableArray array]; |
BaseNode *child; |
|
for (child in self.children) |
{ |
if (![child isLeaf]) |
[groupChildren addObject:child]; |
} |
return groupChildren; |
} |
|
// ------------------------------------------------------------------------------- |
// isDescendantOfOrOneOfNodes:nodes |
// |
// Returns YES if self is contained anywhere inside the children or children of |
// sub-nodes of the nodes contained inside the given array. |
// ------------------------------------------------------------------------------- |
- (BOOL)isDescendantOfOrOneOfNodes:(NSArray *)nodes |
{ |
// returns YES if we are contained anywhere inside the array passed in, including inside sub-nodes |
id node = nil; |
for (node in nodes) |
{ |
if (node == self) |
return YES; // we found ourselv |
|
// check all the sub-nodes |
if (![node isLeaf]) |
{ |
if ([self isDescendantOfOrOneOfNodes:[node children]]) |
return YES; |
} |
} |
|
return NO; |
} |
|
// ------------------------------------------------------------------------------- |
// isDescendantOfNodes:nodes |
// |
// Returns YES if any node in the array passed in is an ancestor of ours. |
// ------------------------------------------------------------------------------- |
- (BOOL)isDescendantOfNodes:(NSArray *)nodes |
{ |
id node = nil; |
for (node in nodes) |
{ |
// check all the sub-nodes |
if (![node isLeaf]) |
{ |
if ([self isDescendantOfOrOneOfNodes:[node children]]) |
return YES; |
} |
} |
|
return NO; |
} |
|
// ------------------------------------------------------------------------------- |
// indexPathInArray:array |
// |
// Returns the index path of within the given array, useful for drag and drop. |
// ------------------------------------------------------------------------------- |
- (NSIndexPath *)indexPathInArray:(NSArray *)array |
{ |
NSIndexPath *indexPath = nil; |
NSMutableArray *reverseIndexes = [NSMutableArray array]; |
id parent, doc = self; |
NSInteger index; |
|
parent = [doc parentFromArray:array]; |
while (parent) |
{ |
index = [[parent children] indexOfObjectIdenticalTo:doc]; |
if (index == NSNotFound) |
return nil; |
|
[reverseIndexes addObject:[NSNumber numberWithInt:index]]; |
doc = parent; |
} |
|
// If parent is nil, we should just be in the parent array |
index = [array indexOfObjectIdenticalTo:doc]; |
if (index == NSNotFound) |
return nil; |
[reverseIndexes addObject:[NSNumber numberWithInt:index]]; |
|
// now build the index path |
NSEnumerator *re = [reverseIndexes reverseObjectEnumerator]; |
NSNumber *indexNumber; |
for (indexNumber in re) |
{ |
if (indexPath == nil) |
indexPath = [NSIndexPath indexPathWithIndex:[indexNumber intValue]]; |
else |
indexPath = [indexPath indexPathByAddingIndex:[indexNumber intValue]]; |
} |
|
return indexPath; |
} |
|
|
#pragma mark - Archiving And Copying Support |
|
// ------------------------------------------------------------------------------- |
// mutableKeys: |
// |
// Override this method to maintain support for archiving and copying. |
// ------------------------------------------------------------------------------- |
- (NSArray *)mutableKeys |
{ |
return [NSArray arrayWithObjects: |
@"nodeTitle", |
@"isLeaf", // isLeaf MUST come before children for initWithDictionary: to work |
@"children", |
@"nodeIcon", |
@"urlString", |
nil]; |
} |
|
// ------------------------------------------------------------------------------- |
// initWithDictionary:dictionary |
// ------------------------------------------------------------------------------- |
- (id)initWithDictionary:(NSDictionary *)dictionary |
{ |
self = [self init]; |
if (self) |
{ |
NSString *key; |
for (key in [self mutableKeys]) |
{ |
if ([key isEqualToString:@"children"]) |
{ |
if ([[dictionary objectForKey:@"isLeaf"] boolValue]) |
[self setChildren:[NSMutableArray arrayWithObject:self]]; |
else |
{ |
NSArray *dictChildren = [dictionary objectForKey:key]; |
NSMutableArray *newChildren = [NSMutableArray array]; |
|
for (id node in dictChildren) |
{ |
id newNode = [[[self class] alloc] initWithDictionary:node]; |
[newChildren addObject:newNode]; |
} |
[self setChildren:newChildren]; |
} |
} |
else |
{ |
[self setValue:[dictionary objectForKey:key] forKey:key]; |
} |
} |
} |
return self; |
} |
|
// ------------------------------------------------------------------------------- |
// dictionaryRepresentation |
// ------------------------------------------------------------------------------- |
- (NSDictionary *)dictionaryRepresentation |
{ |
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; |
|
for (NSString *key in [self mutableKeys]) |
{ |
// convert all children to dictionaries |
if ([key isEqualToString:@"children"]) |
{ |
if (!self.isLeaf) |
{ |
NSMutableArray *dictChildren = [NSMutableArray array]; |
for (id node in self.children) |
{ |
[dictChildren addObject:[node dictionaryRepresentation]]; |
} |
|
[dictionary setObject:dictChildren forKey:key]; |
} |
} |
else if ([self valueForKey:key]) |
{ |
[dictionary setObject:[self valueForKey:key] forKey:key]; |
} |
} |
return dictionary; |
} |
|
// ------------------------------------------------------------------------------- |
// initWithCoder:coder |
// ------------------------------------------------------------------------------- |
- (id)initWithCoder:(NSCoder *)coder |
{ |
self = [self init]; |
if (self) |
{ |
for (NSString *key in [self mutableKeys]) |
[self setValue:[coder decodeObjectForKey:key] forKey:key]; |
} |
return self; |
} |
|
// ------------------------------------------------------------------------------- |
// encodeWithCoder:coder |
// ------------------------------------------------------------------------------- |
- (void)encodeWithCoder:(NSCoder *)coder |
{ |
for (NSString *key in [self mutableKeys]) |
{ |
[coder encodeObject:[self valueForKey:key] forKey:key]; |
} |
} |
|
// ------------------------------------------------------------------------------- |
// copyWithZone:zone |
// ------------------------------------------------------------------------------- |
- (id)copyWithZone:(NSZone *)zone |
{ |
id newNode = [[[self class] allocWithZone:zone] init]; |
|
for (NSString *key in [self mutableKeys]) |
[newNode setValue:[self valueForKey:key] forKey:key]; |
|
return newNode; |
} |
|
// ------------------------------------------------------------------------------- |
// setNilValueForKey:key |
// |
// Override this for any non-object values |
// ------------------------------------------------------------------------------- |
- (void)setNilValueForKey:(NSString *)key |
{ |
if ([key isEqualToString:@"isLeaf"]) |
isLeaf = NO; |
else |
[super setNilValueForKey:key]; |
} |
|
@end |