SourceView/BaseNode.m
/* |
Copyright (C) 2018 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
Abstract: |
Generic multi-use node object used with NSOutlineView and NSTreeController. |
*/ |
#import "BaseNode.h" |
#define PLACES_NAME @"PLACES" |
#define BOOKMARKS_NAME @"BOOKMARKS" |
@implementation BaseNode |
// ------------------------------------------------------------------------------- |
// init |
// ------------------------------------------------------------------------------- |
- (instancetype)init |
{ |
self = [super init]; |
if (self != nil) |
{ |
self.nodeTitle = @"BaseNode Untitled"; |
self.children = [NSMutableArray array]; |
[self setLeaf:NO]; // is container by default |
} |
return self; |
} |
// ------------------------------------------------------------------------------- |
// description |
// ------------------------------------------------------------------------------- |
+ (NSString *)description { return @"BaseNode"; } |
// ------------------------------------------------------------------------------- |
// String constants |
// ------------------------------------------------------------------------------- |
+ (NSString *)placesName { return @"PLACES"; } |
+ (NSString *)bookmarksName { return @"BOOKMARKS"; } |
+ (NSString *)untitledName { return @"Untitled"; } // default name for added folders and leafs |
// ------------------------------------------------------------------------------- |
// initLeaf |
// ------------------------------------------------------------------------------- |
- (instancetype)initLeaf |
{ |
self = [self init]; |
if (self != nil) |
{ |
[self setLeaf:YES]; |
} |
return self; |
} |
// ------------------------------------------------------------------------------- |
// setLeaf:flag |
// ------------------------------------------------------------------------------- |
- (void)setLeaf:(BOOL)flag |
{ |
_isLeaf = flag; |
if (_isLeaf) |
{ |
self.children = [NSMutableArray arrayWithObject:self]; |
} |
else |
{ |
self.children = [NSMutableArray array]; |
} |
} |
// ------------------------------------------------------------------------------- |
// isBookmark |
// ------------------------------------------------------------------------------- |
- (BOOL)isBookmark |
{ |
BOOL isBookmark = NO; |
if (self.url != nil) |
{ |
return !self.url.fileURL; |
} |
return isBookmark; |
} |
// ------------------------------------------------------------------------------- |
// setIsBookmark:isBookmark |
// ------------------------------------------------------------------------------- |
- (void)setIsBookmark:(BOOL)isBookmark |
{ |
self.isBookmark = isBookmark; |
} |
// ------------------------------------------------------------------------------- |
// isDirectory |
// ------------------------------------------------------------------------------- |
- (BOOL)isDirectory |
{ |
BOOL isDirectory = NO; |
if (self.url != nil) |
{ |
NSNumber *isURLDirectory = nil; |
[self.url getResourceValue:&isURLDirectory forKey:NSURLIsDirectoryKey error:nil]; |
isDirectory = isURLDirectory.boolValue; |
} |
return isDirectory; |
} |
// ------------------------------------------------------------------------------- |
// setIsBookmark:isBookmark |
// ------------------------------------------------------------------------------- |
- (void)setIsDirectory:(BOOL)isDirectory |
{ |
self.isDirectory = isDirectory; |
} |
// ------------------------------------------------------------------------------- |
// compare:aNode |
// ------------------------------------------------------------------------------- |
- (NSComparisonResult)compare:(BaseNode *)aNode |
{ |
return [self.nodeTitle.lowercaseString compare:aNode.nodeTitle.lowercaseString]; |
} |
// ------------------------------------------------------------------------------- |
// isSpecialGroup |
// ------------------------------------------------------------------------------- |
- (BOOL)isSpecialGroup |
{ |
return ([self.nodeTitle isEqualToString:BOOKMARKS_NAME] || [self.nodeTitle isEqualToString:PLACES_NAME]); |
} |
// ------------------------------------------------------------------------------- |
// isSeparator |
// ------------------------------------------------------------------------------- |
- (BOOL)isSeparator |
{ |
return (self.nodeIcon == nil && self.nodeTitle.length == 0); |
} |
// ------------------------------------------------------------------------------- |
// nodeIcon |
// ------------------------------------------------------------------------------- |
- (NSImage *)nodeIcon |
{ |
NSImage *icon = nil; |
if (self.isLeaf) |
{ |
// does it have a URL string? |
if (self.url != nil) |
{ |
if (self.isLeaf) |
{ |
if (self.isBookmark) |
{ |
icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGenericURLIcon)]; |
} |
else |
{ |
icon = [[NSWorkspace sharedWorkspace] iconForFile:self.url.path]; |
} |
} |
else |
{ |
icon = [[NSWorkspace sharedWorkspace] iconForFile:self.url.path]; |
} |
} |
else |
{ |
// it's a separator, don't bother with the icon |
} |
icon.size = NSMakeSize(kIconSmallImageSize, kIconSmallImageSize); |
} |
else if (!self.isSpecialGroup) |
{ |
// it's a folder, use the folderImage as its icon |
icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGenericFolderIcon)]; |
icon.size = NSMakeSize(kIconSmallImageSize, kIconSmallImageSize); |
} |
return icon; |
} |
#pragma mark - Drag and Drop |
// ------------------------------------------------------------------------------- |
// 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 ourselves |
// 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; |
} |
#pragma mark - Archiving And Copying Support |
// ------------------------------------------------------------------------------- |
// mutableKeys: |
// |
// Override this method to maintain support for archiving and copying. |
// ------------------------------------------------------------------------------- |
- (NSArray *)mutableKeys |
{ |
return @[ @"nodeTitle", |
@"isLeaf", // isLeaf MUST come before children for initWithDictionary: to work |
@"children", |
@"nodeIcon", |
@"urlString", |
@"isBookmark"]; |
} |
// ------------------------------------------------------------------------------- |
// initWithDictionary:dictionary |
// ------------------------------------------------------------------------------- |
- (instancetype)initWithDictionary:(NSDictionary *)dictionary |
{ |
self = [self init]; |
if (self != nil) |
{ |
NSString *key; |
for (key in self.mutableKeys) |
{ |
if ([key isEqualToString:@"children"]) |
{ |
if ([dictionary[@"isLeaf"] boolValue]) |
self.children = [NSMutableArray arrayWithObject:self]; |
else |
{ |
NSArray *dictChildren = dictionary[key]; |
NSMutableArray *newChildren = [NSMutableArray array]; |
for (id node in dictChildren) |
{ |
id newNode = [[[self class] alloc] initWithDictionary:node]; |
[newChildren addObject:newNode]; |
} |
self.children = newChildren; |
} |
} |
else |
{ |
[self setValue:dictionary[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[key] = dictChildren; |
} |
} |
else if ([self valueForKey:key]) |
{ |
dictionary[key] = [self valueForKey:key]; |
} |
} |
return dictionary; |
} |
// ------------------------------------------------------------------------------- |
// initWithCoder:coder |
// ------------------------------------------------------------------------------- |
- (instancetype)initWithCoder:(NSCoder *)coder |
{ |
self = [self init]; |
if (self != nil) |
{ |
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"]) |
{ |
self.isLeaf = NO; |
} |
else |
{ |
[super setNilValueForKey:key]; |
} |
} |
@end |
Copyright © 2018 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2018-02-15