SourceView/MyOutlineViewController.m
/* |
Copyright (C) 2018 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
Abstract: |
The master view controller containing the NSOutlineView and NSTreeController. |
*/ |
#import "MyOutlineViewController.h" |
#import "ChildNode.h" |
#import "IconViewController.h" |
#import "ChildEditViewController.h" |
#import "FileViewController.h" |
#import "WebViewController.h" |
#import "SeparatorView.h" |
#import "PrimaryViewController.h" |
#define INITIAL_INFODICT @"Outline" // name of the dictionary file to populate our outline view |
#define ICONVIEW_IDENTIFIER @"IconViewController" // storyboard identifier for the icon view |
#define FILEVIEW_IDENTIFIER @"FileViewController" // storyboard identifier for the file view |
#define WEBVIEW_IDENTIFIER @"WebViewController" // storyboard identifier for the web view |
#define CHILDEDIT_IDENTIFIER @"ChildEditWindowController" // storyboard identifier the child edit window controller |
#define SEPARATOR_VIEW @"Separator" |
// keys in our disk-based dictionary representing our outline view's data |
#define KEY_NAME @"name" |
#define KEY_URL @"url" |
#define KEY_SEPARATOR @"separator" |
#define KEY_GROUP @"group" |
#define KEY_FOLDER @"folder" |
#define KEY_ENTRIES @"entries" |
#define kNodesPBoardType @"myNodesPBoardType" // drag and drop pasteboard type |
#pragma mark - |
// ------------------------------------------------------------------------------- |
// TreeAdditionObj |
// |
// This object is used for passing data between the main and secondary thread |
// which populates the outline view. |
// ------------------------------------------------------------------------------- |
@interface TreeAdditionObj : NSObject |
@property (unsafe_unretained, readonly) NSIndexPath *indexPath; |
@property (unsafe_unretained, readonly) NSURL *nodeURL; |
@property (unsafe_unretained, readonly) NSString *nodeName; |
@property (readonly) BOOL selectItsParent; |
@end |
#pragma mark - |
@implementation TreeAdditionObj |
// ------------------------------------------------------------------------------- |
// initWithURL:url:name:select |
// ------------------------------------------------------------------------------- |
- (instancetype)initWithURL:(NSURL *)url withName:(NSString *)name selectItsParent:(BOOL)select |
{ |
self = [super init]; |
_nodeName = name; |
_nodeURL = url; |
_selectItsParent = select; |
return self; |
} |
@end |
#pragma mark - |
@interface MyOutlineViewController () |
@property (nonatomic, weak) IBOutlet NSOutlineView *myOutlineView; |
@property (nonatomic, weak) IBOutlet NSView *placeHolderView; |
@property (nonatomic, strong) NSArray *dragNodesArray; // used to keep track of dragged nodes |
@property (nonatomic, strong) NSMutableArray *contents; // used to keep track of dragged nodes |
@property (nonatomic, strong) IconViewController *iconViewController; |
@property (nonatomic, strong) FileViewController *fileViewController; |
@property (nonatomic, strong) WebViewController *webViewController; |
@property (nonatomic, strong) NSWindowController *childEditWindowController; |
@end |
#pragma mark - |
@implementation MyOutlineViewController |
- (void)viewDidLoad |
{ |
[super viewDidLoad]; |
_contents = [[NSMutableArray alloc] init]; |
// load the icon view controller for later use |
_iconViewController = [self.storyboard instantiateControllerWithIdentifier:ICONVIEW_IDENTIFIER]; |
self.iconViewController.view.translatesAutoresizingMaskIntoConstraints = NO; |
// load the file view controller for later use |
_fileViewController = [self.storyboard instantiateControllerWithIdentifier:FILEVIEW_IDENTIFIER]; |
self.fileViewController.view.translatesAutoresizingMaskIntoConstraints = NO; |
// load the web view controller for later use |
_webViewController = [self.storyboard instantiateControllerWithIdentifier:WEBVIEW_IDENTIFIER]; |
self.webViewController.view.translatesAutoresizingMaskIntoConstraints = NO; |
// load the child edit view controller for later use |
_childEditWindowController = [self.storyboard instantiateControllerWithIdentifier:CHILDEDIT_IDENTIFIER]; |
[self populateOutlineContents]; |
// scroll to the top in case the outline contents is very long |
self.myOutlineView.enclosingScrollView.verticalScroller.floatValue = 0.0; |
[self.myOutlineView.enclosingScrollView.contentView scrollToPoint:NSMakePoint(0,0)]; |
// make our outline view appear with gradient selection, and behave like the Finder, iTunes, etc. |
self.myOutlineView.selectionHighlightStyle = NSTableViewSelectionHighlightStyleSourceList; |
// drag and drop support |
[self.myOutlineView registerForDraggedTypes:@[kNodesPBoardType, // our internal drag type |
NSURLPboardType, // single url from pasteboard |
NSFilenamesPboardType, // from Safari or Finder |
NSFilesPromisePboardType]]; |
// notification to add a folder |
[[NSNotificationCenter defaultCenter] addObserver:self |
selector:@selector(addFolder:) |
name:kAddFolderNotification |
object:nil]; |
// notification to remove a folder |
[[NSNotificationCenter defaultCenter] addObserver:self |
selector:@selector(removeFolder:) |
name:kRemoveFolderNotification |
object:nil]; |
// notification to add a bookmark |
[[NSNotificationCenter defaultCenter] addObserver:self |
selector:@selector(addBookmark:) |
name:kAddBookmarkNotification |
object:nil]; |
// notification to edit a bookmark |
[[NSNotificationCenter defaultCenter] addObserver:self |
selector:@selector(editBookmark:) |
name:kEditBookmarkNotification |
object:nil]; |
} |
// ------------------------------------------------------------------------------- |
// dealloc |
// ------------------------------------------------------------------------------- |
- (void)dealloc |
{ |
[[NSNotificationCenter defaultCenter] removeObserver:self name:kAddFolderNotification object:nil]; |
[[NSNotificationCenter defaultCenter] removeObserver:self name:kRemoveFolderNotification object:nil]; |
[[NSNotificationCenter defaultCenter] removeObserver:self name:kAddBookmarkNotification object:nil]; |
[[NSNotificationCenter defaultCenter] removeObserver:self name:kEditBookmarkNotification object:nil]; |
} |
#pragma mark - Actions |
// ------------------------------------------------------------------------------- |
// selectParentFromSelection |
// |
// Take the currently selected node and select its parent. |
// ------------------------------------------------------------------------------- |
- (void)selectParentFromSelection |
{ |
if (self.treeController.selectedNodes.count > 0) |
{ |
NSTreeNode *firstSelectedNode = self.treeController.selectedNodes[0]; |
NSTreeNode *parentNode = firstSelectedNode.parentNode; |
if (parentNode) |
{ |
// select the parent |
NSIndexPath *parentIndex = parentNode.indexPath; |
[self.treeController setSelectionIndexPath:parentIndex]; |
} |
else |
{ |
// no parent exists (we are at the top of tree), so make no selection in our outline |
NSArray *selectionIndexPaths = self.treeController.selectionIndexPaths; |
[self.treeController removeSelectionIndexPaths:selectionIndexPaths]; |
} |
} |
} |
// ------------------------------------------------------------------------------- |
// performAddFolder:treeAddition |
// ------------------------------------------------------------------------------- |
- (void)performAddFolder:(TreeAdditionObj *)treeAddition |
{ |
// NSTreeController inserts objects using NSIndexPath, so we need to calculate this |
NSIndexPath *indexPath = nil; |
// if there is no selection, we will add a new group to the end of the contents array |
if (self.treeController.selectedObjects.count == 0) |
{ |
// there's no selection so add the folder to the top-level and at the end |
indexPath = [NSIndexPath indexPathWithIndex:self.contents.count]; |
} |
else |
{ |
// get the index of the currently selected node, then add the number its children to the path - |
// this will give us an index which will allow us to add a node to the end of the currently selected node's children array. |
// |
indexPath = self.treeController.selectionIndexPath; |
if ([self.treeController.selectedObjects[0] isLeaf]) |
{ |
// user is trying to add a folder on a selected child, |
// so deselect child and select its parent for addition |
[self selectParentFromSelection]; |
} |
else |
{ |
indexPath = [indexPath indexPathByAddingIndex:[self.treeController.selectedObjects[0] children].count]; |
} |
} |
ChildNode *node = [[ChildNode alloc] init]; |
node.nodeTitle = treeAddition.nodeName; |
// the user is adding a child node, tell the controller directly |
[self.treeController insertObject:node atArrangedObjectIndexPath:indexPath]; |
} |
// ------------------------------------------------------------------------------- |
// performAddChild:treeAddition |
// ------------------------------------------------------------------------------- |
- (void)performAddChild:(TreeAdditionObj *)treeAddition |
{ |
if (self.treeController.selectedObjects.count > 0) |
{ |
// we have a selection |
if ([self.treeController.selectedObjects[0] isLeaf]) |
{ |
// trying to add a child to a selected leaf node, so select its parent for add |
[self selectParentFromSelection]; |
} |
} |
// find the selection to insert our node |
NSIndexPath *indexPath; |
if (self.treeController.selectedObjects.count > 0) |
{ |
// we have a selection, insert at the end of the selection |
indexPath = self.treeController.selectionIndexPath; |
indexPath = [indexPath indexPathByAddingIndex:[self.treeController.selectedObjects[0] children].count]; |
} |
else |
{ |
// no selection, just add the child to the end of the tree |
indexPath = [NSIndexPath indexPathWithIndex:self.contents.count]; |
} |
// create a leaf node |
ChildNode *node = [[ChildNode alloc] initLeaf];; |
node.url = treeAddition.nodeURL; |
if (treeAddition.nodeURL != nil) |
{ |
// the child to insert has a valid URL, use its display name as the node title |
if (treeAddition.nodeName) |
{ |
node.nodeTitle = treeAddition.nodeName; |
} |
else |
{ |
node.nodeTitle = [[NSFileManager defaultManager] displayNameAtPath:node.url.absoluteString]; |
} |
} |
// the user is adding a child node, tell the controller directly |
[self.treeController insertObject:node atArrangedObjectIndexPath:indexPath]; |
// adding a child automatically becomes selected by NSOutlineView, so keep its parent selected |
if (treeAddition.selectItsParent) |
{ |
[self selectParentFromSelection]; |
} |
} |
// ------------------------------------------------------------------------------- |
// addChild:url:withName:selectParent |
// ------------------------------------------------------------------------------- |
- (void)addChild:(NSURL *)url withName:(NSString *)nameStr selectParent:(BOOL)select |
{ |
TreeAdditionObj *treeObjInfo = [[TreeAdditionObj alloc] initWithURL:url |
withName:nameStr |
selectItsParent:select]; |
[self performAddChild:treeObjInfo]; |
} |
// ------------------------------------------------------------------------------- |
// addEntries:discloseParent: |
// ------------------------------------------------------------------------------- |
- (void)addEntries:(NSDictionary *)entries discloseParent:(BOOL)discloseParent |
{ |
for (id entry in entries) |
{ |
if ([entry isKindOfClass:[NSDictionary class]]) |
{ |
NSString *urlStr = entry[KEY_URL]; |
NSURL *url = [NSURL URLWithString:urlStr]; |
if (entry[KEY_SEPARATOR]) |
{ |
// its a separator mark, we treat is as a leaf |
[self addChild:nil withName:nil selectParent:YES]; |
} |
else if (entry[KEY_FOLDER]) |
{ |
// we treat file system folders as a leaf and show its contents in the NSCollectionView |
NSString *folderName = entry[KEY_FOLDER]; |
[self addChild:url withName:folderName selectParent:YES]; |
} |
else if (entry[KEY_URL]) |
{ |
// its a leaf item with a URL |
NSString *nameStr = entry[KEY_NAME]; |
[self addChild:url withName:nameStr selectParent:YES]; |
} |
else |
{ |
// it's a generic container |
NSString *folderName = entry[KEY_GROUP]; |
[self addFolderWithName:folderName]; |
// add its children |
NSDictionary *newChildren = entry[KEY_ENTRIES]; |
[self addEntries:newChildren discloseParent:NO]; |
[self selectParentFromSelection]; |
} |
} |
} |
if (!discloseParent) |
{ |
// inserting children automatically expands its parent, we want to close it |
if (self.treeController.selectedNodes.count > 0) |
{ |
NSTreeNode *lastSelectedNode = self.treeController.selectedNodes[0]; |
[self.myOutlineView collapseItem:lastSelectedNode]; |
} |
} |
} |
// ------------------------------------------------------------------------------- |
// addBookmarksSection |
// |
// Populate the tree controller from disk-based dictionary (Outline.dict) |
// ------------------------------------------------------------------------------- |
- (void)addBookmarksSection |
{ |
// add the "Bookmarks" section |
[self addFolderWithName:[BaseNode bookmarksName]]; |
// add its content (contant determined our dictionary file) |
NSDictionary *initData = [NSDictionary dictionaryWithContentsOfFile: |
[[NSBundle mainBundle] pathForResource:INITIAL_INFODICT ofType:@"dict"]]; |
NSDictionary *entries = initData[KEY_ENTRIES]; |
[self addEntries:entries discloseParent:YES]; |
[self selectParentFromSelection]; |
} |
// ------------------------------------------------------------------------------- |
// addPlacesSection |
// ------------------------------------------------------------------------------- |
- (void)addPlacesSection |
{ |
// add the "Places" section |
[self addFolderWithName:[BaseNode placesName]]; |
// add its children (contents of the Home directory) |
[self addChild:[NSURL fileURLWithPath:NSHomeDirectory()] withName:@"Home" selectParent:YES]; |
NSArray *appsDirectory = NSSearchPathForDirectoriesInDomains(NSApplicationDirectory, NSLocalDomainMask, YES); |
[self addChild:[NSURL fileURLWithPath:appsDirectory[0]] withName:nil selectParent:YES]; |
[self selectParentFromSelection]; |
} |
// ------------------------------------------------------------------------------- |
// populateOutlineContents |
// ------------------------------------------------------------------------------- |
- (void)populateOutlineContents |
{ |
// hide the outline view - don't show it as we are building the content |
[self.myOutlineView setHidden:YES]; |
[self addPlacesSection]; // add the "Places" outline section |
[self addBookmarksSection]; // add the "Bookmark" outline content |
// remove the current selection |
NSArray *selection = self.treeController.selectionIndexPaths; |
[self.treeController removeSelectionIndexPaths:selection]; |
[self.myOutlineView setHidden:NO]; // we are done populating the outline view content, show it again |
} |
#pragma mark - Notifications |
// ------------------------------------------------------------------------------- |
// addFolder:folderName |
// ------------------------------------------------------------------------------- |
- (void)addFolderWithName:(NSString *)folderName |
{ |
TreeAdditionObj *treeObjInfo = [[TreeAdditionObj alloc] initWithURL:nil withName:folderName selectItsParent:NO]; |
[self performAddFolder:treeObjInfo]; |
} |
// ------------------------------------------------------------------------------- |
// addFolder:notif |
// |
// Notification sent from PrimaryViewController class, to add a folder. |
// ------------------------------------------------------------------------------- |
- (void)addFolder:(NSNotification *)notif |
{ |
[self addFolderWithName:[BaseNode untitledName]]; |
} |
// ------------------------------------------------------------------------------- |
// removeFolder:notif |
// |
// Notification sent from PrimaryViewController class, to remove a folder. |
// ------------------------------------------------------------------------------- |
- (void)removeFolder:(NSNotification *)notif |
{ |
[self.treeController remove:self]; |
} |
// ------------------------------------------------------------------------------- |
// addBookmark:notif |
// |
// Notification sent from PrimaryViewController class, to add a bookmark |
// ------------------------------------------------------------------------------- |
- (void)addBookmark:(NSNotification *)notif |
{ |
ChildEditViewController *childEditViewController = (ChildEditViewController *)self.childEditWindowController.contentViewController; |
childEditViewController.savedValues = @{kName_Key:[BaseNode untitledName], kURL_Key:HTTP_PREFIX}; |
[self.view.window beginSheet:self.childEditWindowController.window completionHandler:^(NSModalResponse returnCode) { |
if (returnCode == NSModalResponseOK) |
{ |
NSString *itemStr = childEditViewController.savedValues[kName_Key]; |
[self addChild:childEditViewController.savedValues[kURL_Key] |
withName:(itemStr.length > 0) ? childEditViewController.savedValues[kName_Key] : [BaseNode untitledName] |
selectParent:NO]; // add empty untitled child |
} |
}]; |
} |
// ------------------------------------------------------------------------------- |
// editBookmark:notif |
// |
// Notification sent from PrimaryViewController class, to edit a bookmark |
// ------------------------------------------------------------------------------- |
- (void)editBookmark:(NSNotification *)notif |
{ |
ChildEditViewController *childEditViewController = (ChildEditViewController *)self.childEditWindowController.contentViewController; |
// get the selected item's name and url |
NSArray *selection = self.treeController.selectedObjects; |
ChildNode *node = selection[0]; |
if (node.url == nil && !node.isBookmark) |
{ |
// it's a folder or a file-system based object, just allow editing the cell title |
NSInteger selectedRow = self.myOutlineView.selectedRow; |
[self.myOutlineView editColumn:0 row:selectedRow withEvent:NSApp.currentEvent select:YES]; |
} |
else |
{ |
childEditViewController.savedValues = @{kName_Key:node.nodeTitle, kURL_Key:node.url}; |
[self.view.window beginSheet:self.childEditWindowController.window completionHandler:^(NSModalResponse returnCode) { |
if (returnCode == NSModalResponseOK) |
{ |
// create a child node |
ChildNode *childNode = [[ChildNode alloc] initLeaf]; |
childNode.url = childEditViewController.savedValues[kURL_Key]; |
NSString *newNodeStr = childEditViewController.savedValues[kName_Key]; |
childNode.nodeTitle = (newNodeStr.length > 0) ? newNodeStr : [BaseNode untitledName]; |
// remove the current selection and replace it with the newly edited child |
NSIndexPath *indexPath = self.treeController.selectionIndexPath; |
[self.treeController remove:self]; |
[self.treeController insertObject:childNode atArrangedObjectIndexPath:indexPath]; |
} |
}]; |
} |
} |
#pragma mark - Managing Views |
// ------------------------------------------------------------------------------- |
// viewControllerForSelection:selection |
// ------------------------------------------------------------------------------- |
- (NSViewController *)viewControllerForSelection:(NSArray *)selection |
{ |
NSViewController *returnViewController = nil; |
if (selection != nil && selection.count == 1) |
{ |
BaseNode *node = [selection[0] representedObject]; |
if (node.url != nil) |
{ |
if (node.isBookmark) |
{ |
// it's a bookmark, |
// return a view controller with a web view, retarget with "urlStr" |
// |
WebView *webView = (WebView *)self.webViewController.view; |
webView.mainFrameURL = node.url.absoluteString; // re-target to the new url |
returnViewController = self.webViewController; |
} |
else |
{ |
// detect if the url is a directory |
if (node.isDirectory) |
{ |
// it's a folder |
self.iconViewController.url = node.url; |
returnViewController = self.iconViewController; |
} |
else |
{ |
// it's a file |
self.fileViewController.url = node.url; |
returnViewController = self.fileViewController; |
} |
} |
} |
else |
{ |
// it's a non-file system grouping of shortcuts |
self.iconViewController.baseNode = node; |
returnViewController = self.iconViewController; |
} |
} |
else |
{ |
// no view controller (no selection) |
} |
return returnViewController; |
} |
#pragma mark - NSOutlineViewDelegate |
// ------------------------------------------------------------------------------- |
// shouldSelectItem:item |
// ------------------------------------------------------------------------------- |
- (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item |
{ |
// don't allow special group nodes (Places and Bookmarks) to be selected |
BaseNode *node = [item representedObject]; |
return (!node.isSpecialGroup && !node.isSeparator); |
} |
// ------------------------------------------------------------------------------- |
// viewForTableColumn:tableColumn:item |
// ------------------------------------------------------------------------------- |
- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item |
{ |
NSTableCellView *result = [outlineView makeViewWithIdentifier:tableColumn.identifier owner:self]; |
BaseNode *node = [item representedObject]; |
if (node != nil) |
{ |
if ([self outlineView:outlineView isGroupItem:item]) // is it a special group (not a folder)? |
{ |
// Group items are sections of our outline that can be hidden/shown (i.e. PLACES/BOOKMARKS). |
NSString *identifier = outlineView.tableColumns[0].identifier; |
result = [outlineView makeViewWithIdentifier:identifier owner:self]; |
NSString *value = node.nodeTitle.uppercaseString; |
result.textField.stringValue = value; |
} |
else if (node.isSeparator) |
{ |
// Separators have no title or icon, just use the custom view to draw it. |
result = [outlineView makeViewWithIdentifier:@"Separator" owner:self]; |
} |
else |
{ |
result.textField.stringValue = node.nodeTitle; |
result.imageView.image = node.nodeIcon; |
if (node.isLeaf) |
{ |
[result.textField setEditable:YES]; // Just for fun, make leaf title's editable. |
} |
} |
} |
return result; |
} |
// ------------------------------------------------------------------------------- |
// textShouldEndEditing:fieldEditor |
// ------------------------------------------------------------------------------- |
- (BOOL)control:(NSControl *)control textShouldEndEditing:(NSText *)fieldEditor |
{ |
// don't allow empty node names |
return (fieldEditor.string.length == 0 ? NO : YES); |
} |
// ---------------------------------------------------------------------------------------- |
// outlineView:isGroupItem:item |
// |
// Determine if the item should be a special grouping (not a folder but a group with Hide/Show buttons) |
// ---------------------------------------------------------------------------------------- |
- (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item |
{ |
BaseNode *node = [item representedObject]; |
return (node.isSpecialGroup ? YES : NO); |
} |
#pragma mark - NSOutlineView drag and drop |
// ---------------------------------------------------------------------------------------- |
// outlineView:writeItems:toPasteboard |
// ---------------------------------------------------------------------------------------- |
- (BOOL)outlineView:(NSOutlineView *)ov writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard |
{ |
[pboard declareTypes:@[kNodesPBoardType] owner:self]; |
// keep track of this nodes for drag feedback in "validateDrop" |
self.dragNodesArray = items; |
return YES; |
} |
// ------------------------------------------------------------------------------- |
// outlineView:validateDrop:proposedItem:proposedChildrenIndex: |
// |
// This method is used by NSOutlineView to determine a valid drop target. |
// ------------------------------------------------------------------------------- |
- (NSDragOperation)outlineView:(NSOutlineView *)ov |
validateDrop:(id <NSDraggingInfo>)info |
proposedItem:(id)item |
proposedChildIndex:(NSInteger)index |
{ |
NSDragOperation result = NSDragOperationNone; |
if (item == nil) |
{ |
// no item to drop on |
result = NSDragOperationGeneric; |
} |
else |
{ |
BaseNode *node = [item representedObject]; |
if (node.isSpecialGroup) |
{ |
// don't allow dragging into special grouped sections (i.e. Places and Bookmarks) |
result = NSDragOperationNone; |
} |
else |
{ |
if (index == -1) |
{ |
// don't allow dropping on a child |
result = NSDragOperationNone; |
} |
else |
{ |
// drop location is a container |
result = NSDragOperationMove; |
BaseNode *dropLocation = [item representedObject]; // item we are dropping on |
BaseNode *draggedItem = [self.dragNodesArray[0] representedObject]; |
// don't allow an item to drop onto itself, or within it's content |
if (dropLocation == draggedItem || |
[dropLocation isDescendantOfNodes:@[draggedItem]]) |
{ |
result = NSDragOperationNone; |
} |
} |
} |
} |
return result; |
} |
// ------------------------------------------------------------------------------- |
// handleWebURLDrops:pboard:withIndexPath: |
// |
// The user is dragging URLs from Safari. |
// ------------------------------------------------------------------------------- |
- (void)handleWebURLDrops:(NSPasteboard *)pboard withIndexPath:(NSIndexPath *)indexPath |
{ |
NSArray *pbArray = [pboard propertyListForType:@"WebURLsWithTitlesPboardType"]; |
NSArray *urlArray = pbArray[0]; |
NSArray *nameArray = pbArray[1]; |
for (NSInteger i = (urlArray.count - 1); i >=0; i--) |
{ |
ChildNode *node = [[ChildNode alloc] init]; |
node.isLeaf = YES; |
node.nodeTitle = nameArray[i]; |
node.url = [NSURL URLWithString:urlArray[i]]; |
[self.treeController insertObject:node atArrangedObjectIndexPath:indexPath]; |
} |
} |
// ------------------------------------------------------------------------------- |
// handleInternalDrops:pboard:withIndexPath: |
// |
// The user is doing an intra-app drag within the outline view. |
// ------------------------------------------------------------------------------- |
- (void)handleInternalDrops:(NSPasteboard *)pboard withIndexPath:(NSIndexPath *)indexPath |
{ |
// user is doing an intra app drag within the outline view: |
// |
NSArray* newNodes = self.dragNodesArray; |
// move the items to their new place |
[self.treeController moveNodes:self.dragNodesArray toIndexPath:indexPath]; |
// keep the moved nodes selected |
NSMutableArray *indexPathList = [NSMutableArray array]; |
for (NSUInteger i = 0; i < newNodes.count; i++) |
{ |
[indexPathList addObject:[newNodes[i] indexPath]]; |
} |
[self.treeController setSelectionIndexPaths: indexPathList]; |
} |
// ------------------------------------------------------------------------------- |
// handleFileBasedDrops:pboard:withIndexPath: |
// |
// The user is dragging file-system based objects (probably from Finder) |
// ------------------------------------------------------------------------------- |
- (void)handleFileBasedDrops:(NSPasteboard *)pboard withIndexPath:(NSIndexPath *)indexPath |
{ |
NSArray *fileNames = [pboard propertyListForType:NSFilenamesPboardType]; |
if (fileNames.count > 0) |
{ |
NSInteger i; |
NSInteger count = fileNames.count; |
for (i = (count - 1); i >=0; i--) |
{ |
ChildNode *node = [[ChildNode alloc] init]; |
NSURL *url = [NSURL fileURLWithPath:fileNames[i]]; |
NSString *name = [[NSFileManager defaultManager] displayNameAtPath:url.path]; |
node.isLeaf = YES; |
node.nodeTitle = name; |
node.url = url; |
[self.treeController insertObject:node atArrangedObjectIndexPath:indexPath]; |
} |
} |
} |
// ------------------------------------------------------------------------------- |
// handleURLBasedDrops:pboard:withIndexPath: |
// |
// Handle dropping a raw URL. |
// ------------------------------------------------------------------------------- |
- (void)handleURLBasedDrops:(NSPasteboard *)pboard withIndexPath:(NSIndexPath *)indexPath |
{ |
NSURL *url = [NSURL URLFromPasteboard:pboard]; |
if (url != nil) |
{ |
ChildNode *node = [[ChildNode alloc] init]; |
if (url.isFileURL) |
{ |
// url is file-based, use it's display name |
NSString *name = [[NSFileManager defaultManager] displayNameAtPath:url.path]; |
node.nodeTitle = name; |
node.url = url; |
} |
else |
{ |
// url is non-file based (probably from Safari) |
// |
// the url might not end with a valid component name, use the best possible title from the URL |
if (url.path.pathComponents.count == 1) |
{ |
if (node.isBookmark) |
{ |
// use the url portion without the prefix |
NSRange prefixRange = [url.absoluteString rangeOfString:HTTP_PREFIX]; |
NSRange newRange = NSMakeRange(prefixRange.length, url.absoluteString.length- prefixRange.length - 1); |
node.nodeTitle = [url.absoluteString substringWithRange:newRange]; |
} |
else |
{ |
// prefix unknown, just use the url as its title |
node.nodeTitle = url.absoluteString; |
} |
} |
else |
{ |
// use the last portion of the URL as its title |
node.nodeTitle = url.path.lastPathComponent; |
} |
node.url = url; |
} |
node.isLeaf = YES; |
[self.treeController insertObject:node atArrangedObjectIndexPath:indexPath]; |
} |
} |
// ------------------------------------------------------------------------------- |
// outlineView:acceptDrop:item:childIndex |
// |
// This method is called when the mouse is released over an outline view that previously decided to allow a drop |
// via the validateDrop method. The data source should incorporate the data from the dragging pasteboard at this time. |
// 'index' is the location to insert the data as a child of 'item', and are the values previously set in the validateDrop: method. |
// |
// ------------------------------------------------------------------------------- |
- (BOOL)outlineView:(NSOutlineView*)ov acceptDrop:(id <NSDraggingInfo>)info item:(id)targetItem childIndex:(NSInteger)index |
{ |
// note that "targetItem" is a NSTreeNode proxy |
// |
BOOL result = NO; |
// find the index path to insert our dropped object(s) |
NSIndexPath *indexPath; |
if (targetItem != nil) |
{ |
// drop down inside the tree node: |
// feth the index path to insert our dropped node |
indexPath = [[targetItem indexPath] indexPathByAddingIndex:index]; |
} |
else |
{ |
// drop at the top root level |
if (index == -1) // drop area might be ambibguous (not at a particular location) |
indexPath = [NSIndexPath indexPathWithIndex:self.contents.count]; // drop at the end of the top level |
else |
indexPath = [NSIndexPath indexPathWithIndex:index]; // drop at a particular place at the top level |
} |
NSPasteboard *pboard = [info draggingPasteboard]; // get the pasteboard |
// check the dragging type - |
if ([pboard availableTypeFromArray:@[kNodesPBoardType]]) |
{ |
// user is doing an intra-app drag within the outline view |
[self handleInternalDrops:pboard withIndexPath:indexPath]; |
result = YES; |
} |
else if ([pboard availableTypeFromArray:@[@"WebURLsWithTitlesPboardType"]]) |
{ |
// the user is dragging URLs from Safari |
[self handleWebURLDrops:pboard withIndexPath:indexPath]; |
result = YES; |
} |
else if ([pboard availableTypeFromArray:@[NSFilenamesPboardType]]) |
{ |
// the user is dragging file-system based objects (probably from Finder) |
[self handleFileBasedDrops:pboard withIndexPath:indexPath]; |
result = YES; |
} |
else if ([pboard availableTypeFromArray:@[NSURLPboardType]]) |
{ |
// handle dropping a raw URL |
[self handleURLBasedDrops:pboard withIndexPath:indexPath]; |
result = YES; |
} |
return result; |
} |
@end |
Copyright © 2018 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2018-02-15