Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(914)

Unified Diff: chrome/browser/cocoa/bookmark_tree_controller_pasteboard.mm

Issue 501073: Native Cocoa bookmark manager, part 1 (Closed)
Patch Set: Style fixes, and copy/paste unit tests Created 10 years, 12 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/browser/cocoa/bookmark_tree_controller_pasteboard.mm
diff --git a/chrome/browser/cocoa/bookmark_tree_controller_pasteboard.mm b/chrome/browser/cocoa/bookmark_tree_controller_pasteboard.mm
new file mode 100644
index 0000000000000000000000000000000000000000..4b94c5c2c368a9f16fa0e33e7786997ad0e688ef
--- /dev/null
+++ b/chrome/browser/cocoa/bookmark_tree_controller_pasteboard.mm
@@ -0,0 +1,439 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "chrome/browser/cocoa/bookmark_tree_controller.h"
+
+#include "base/sys_string_conversions.h"
+#include "chrome/browser/bookmarks/bookmark_model.h"
+#import "chrome/browser/cocoa/bookmark_manager_controller.h"
+#include "googleurl/src/gurl.h"
+
+
+// Safari uses this type, though it's not declared in any header.
+static NSString* const BookmarkDictionaryListPboardType =
+ @"BookmarkDictionaryListPboardType";
+
+// Mac WebKit uses this type, declared in WebKit/mac/History/WebURLsWithTitles.h
+static NSString* const WebURLsWithTitlesPboardType =
+ @"WebURLsWithTitlesPboardType";
+
+// Used internally to identify intra-outline drags.
+static NSString* const kCustomPboardType =
+ @"ChromeBookmarkTreeControllerPlaceholderType";
+
+
+@implementation BookmarkTreeController (Pasteboard)
+
+
+// One-time dnd setup; called from -awakeFromNib.
+- (void)registerDragTypes {
+ [outline_ registerForDraggedTypes:[NSArray arrayWithObjects:
+ BookmarkDictionaryListPboardType,
+ WebURLsWithTitlesPboardType,
+ NSURLPboardType, nil]];
+ [outline_ setDraggingSourceOperationMask:NSDragOperationEvery forLocal:YES];
+ [outline_ setDraggingSourceOperationMask:NSDragOperationEvery forLocal:NO];
+}
+
+// Selects a range of items in a parent node.
+- (void)selectNodesInFolder:(const BookmarkNode*)parent
+ atIndexes:(NSRange)childRange {
+ DCHECK(NSMaxRange(childRange) <= (NSUInteger)parent->GetChildCount());
+ id parentItem = [self itemFromNode:parent];
+ if (parentItem != nil) {
+ // If parent is not the root, need to offset range by parent's index:
+ int startRow = [outline_ rowForItem:parentItem];
+ if (startRow < 0) {
+ return;
+ }
+ if ([outline_ isItemExpanded:parentItem]) {
+ childRange.location += startRow + 1;
+ } else {
+ childRange.location = startRow;
+ childRange.length = 1;
+ }
+ }
+ NSIndexSet* indexes = [NSIndexSet indexSetWithIndexesInRange:childRange];
+ [outline_ selectRowIndexes:indexes byExtendingSelection:NO];
+}
+
+
+#pragma mark -
+#pragma mark DRAGGING OUT AND COPYING:
+
+
+// Generates parallel arrays of URLs and titles for contents of a node.
+static void flattenNode(const BookmarkNode* node,
+ NSMutableArray* urlStrings,
+ NSMutableArray* titles) {
+ if (node->is_folder()) {
+ for (int i = 0; i < node->GetChildCount(); i++) {
+ flattenNode(node->GetChild(i), urlStrings, titles);
+ }
+ } else if (node->is_url()) {
+ [urlStrings addObject:base::SysUTF8ToNSString(
+ node->GetURL().possibly_invalid_spec())];
+ [titles addObject:base::SysWideToNSString(node->GetTitle())];
+ }
+}
+
+// Writes data to the pasteboard given a list of row items.
+- (BOOL)writeItems:(NSArray*)items
+ toPasteboard:(NSPasteboard*)pb
+ includeCustom:(BOOL)includeCustom {
+ if ([items count] == 0) {
+ return NO;
+ }
+
+ [pb declareTypes:[NSMutableArray arrayWithObjects:
+ WebURLsWithTitlesPboardType,
+ NSStringPboardType, nil]
+ owner:self];
+
+ // Add URLs and titles:
+ NSMutableArray* urls = [NSMutableArray array];
+ NSMutableArray* titles = [NSMutableArray array];
+ for (id item in items) {
+ flattenNode([self nodeFromItem:item], urls, titles);
+ }
+ [pb setPropertyList:[NSArray arrayWithObjects:urls, titles, nil]
+ forType:WebURLsWithTitlesPboardType];
+
+ // Add plain text, as one URL per line:
+ [pb setString:[urls componentsJoinedByString:@"\n"]
+ forType:NSStringPboardType];
+
+ // Add custom type. The actual data doesn't matter since kCustomPboardType
+ // drags aren't recognized by anyone but us.
+ if (includeCustom) {
+ draggedNodes_.clear();
+ for (id item in items) {
+ draggedNodes_.push_back([self nodeFromItem:item]);
+ }
+ [pb addTypes:[NSArray arrayWithObject: kCustomPboardType] owner: self];
+ [pb setData:[NSData data] forType:kCustomPboardType];
+ }
+
+ // Add single URL:
+ if ([urls count] == 1) {
+ [pb addTypes:[NSArray arrayWithObject: NSURLPboardType] owner: self];
+ NSString* firstURLStr = [urls objectAtIndex:0];
+ [pb setString:firstURLStr forType:NSURLPboardType];
+ }
+ return YES;
+}
+
+// Invoked when dragging outline-view rows.
+- (BOOL)outlineView:(NSOutlineView*)outlineView
+ writeItems:(NSArray*)items
+ toPasteboard:(NSPasteboard*)pb {
+ [self writeItems:items toPasteboard:pb includeCustom:YES];
+ return YES;
+}
+
+
+// The Cut command.
+- (IBAction)cut:(id)sender {
+ if ([self writeItems:[self selectedItems]
+ toPasteboard:[NSPasteboard generalPasteboard]
+ includeCustom:NO]) {
+ [self delete:self];
+ } else {
+ NSBeep();
+ }
+}
+
+// The Copy command.
+- (IBAction)copy:(id)sender {
+ if (![self copyToPasteboard:[NSPasteboard generalPasteboard]])
+ NSBeep();
+}
+
+// Copy to any pasteboard.
+- (BOOL)copyToPasteboard:(NSPasteboard*)pb {
+ return [self writeItems:[self selectedItems]
viettrungluu 2010/01/04 21:19:01 Unindent 2 spaces.
+ toPasteboard:pb
+ includeCustom:NO];
+}
+
+
+#pragma mark -
+#pragma mark INCOMING DRAGS AND PASTING:
+
+
+// BookmarkDictionaryListPboardType represents bookmarks as dictionaries,
+// which have the following keys.
+// Strangely, folder nodes (whose WebBookmarkType is WebBookmarkTypeLeaf) have
+// their title under 'Title', while leaf nodes have it in 'URIDictionary.title'.
+static const NSString* kTitleKey = @"Title";
+static const NSString* kURIDictionaryKey = @"URIDictionary";
+static const NSString* kURIDictTitleKey = @"title";
+static const NSString* kURLStringKey = @"URLString";
+static const NSString* kTypeKey = @"WebBookmarkType";
+static const NSString* kLeafType = @"WebBookmarkTypeLeaf";
+//static const NSString* kListType = @"WebBookmarkTypeList"; // unused for now
+static const NSString* kChildrenKey = @"Children";
+
+// Helper that creates a dictionary in BookmarkDictionaryListPboardType format.
+// |name| may be nil, but |urlStr| is required.
+static NSDictionary* makeBookmarkPlistEntry(NSString* name, NSString* urlStr) {
+ if (!name) {
+ name = urlStr;
+ }
+ NSDictionary* nameDict = [NSDictionary dictionaryWithObject:name
+ forKey:kURIDictTitleKey];
+ return [NSDictionary dictionaryWithObjectsAndKeys:
+ kLeafType, kTypeKey,
+ nameDict, kURIDictionaryKey,
+ urlStr, kURLStringKey,
+ nil];
+}
+
+// Reads URL(s) off the pasteboard and returns them in BookmarkDictionaryList-
+// PboardType format, or nil on failure.
+- (NSArray*)readPropertyListFromPasteboard:(NSPasteboard*)pb {
+ NSString* type = [pb availableTypeFromArray:
+ [outline_ registeredDraggedTypes]];
+ if ([type isEqualToString:BookmarkDictionaryListPboardType]) {
+ // Safari's full bookmark plist type:
+ return [pb propertyListForType:type];
+
+ } else if ([type isEqualToString:WebURLsWithTitlesPboardType]) {
+ // Safari's parallel-URLs-and-titles type:
+ NSArray* contents = [pb propertyListForType:type];
+ NSArray* urlStrings = [contents objectAtIndex:0];
+ NSArray* titles = [contents objectAtIndex:1];
+ NSUInteger n = [urlStrings count];
+ if (n == 0 || [titles count] != n) {
+ return nil;
+ }
+ NSMutableArray* plist = [NSMutableArray array];
+ for (NSUInteger i = 0; i < n; i++) {
+ [plist addObject:makeBookmarkPlistEntry([titles objectAtIndex:i],
+ [urlStrings objectAtIndex:i])];
+ }
+ return plist;
+
+ } else if ([type isEqualToString:NSURLPboardType]) {
+ // Standard URL type:
+ NSString* urlStr = [[NSURL URLFromPasteboard:pb] absoluteString];
+ if (!urlStr) {
+ return nil;
+ }
+ NSString* title = [pb stringForType:@"public.url-name"];
+ if (!title)
+ title = [pb stringForType:NSStringPboardType];
+ return [NSArray arrayWithObject:makeBookmarkPlistEntry(title, urlStr)];
+
+ } else {
+ return nil;
+ }
+}
+
+
+// Moves BookmarkNodes into a parent folder, then selects them.
+- (void)moveNodes:(std::vector<const BookmarkNode*>)nodes
+ toFolder:(const BookmarkNode*)dstParent
+ atIndex:(int)dstIndex {
+ for (std::vector<const BookmarkNode*>::iterator it = nodes.begin();
+ it != nodes.end(); ++it) {
+ // Use an autorelease pool to clean up after the various observers that
+ // get called after each individual bookmark change.
+ NSAutoreleasePool* pool = [NSAutoreleasePool new];
+ const BookmarkNode* srcNode = *it;
+ const BookmarkNode* srcParent = srcNode->GetParent();
+ int srcIndex = srcParent->IndexOfChild(srcNode);
+ [manager_ bookmarkModel]->Move(srcNode, dstParent, dstIndex);
+ if (srcParent != dstParent || srcIndex >= dstIndex) {
+ dstIndex++;
+ }
+ [pool drain];
+ }
+
+ [self selectNodesInFolder:dstParent
+ atIndexes:NSMakeRange(dstIndex - nodes.size(), nodes.size())];
+}
+
+// Inserts bookmarks in BookmarkDictionaryListPboardType into a folder node.
+- (void)insertPropertyList:(NSArray*)plist
+ inFolder:(const BookmarkNode*)dstParent
+ atIndex:(NSInteger)dstIndex {
+ BookmarkModel* model = [manager_ bookmarkModel];
+ NSInteger i = 0;
+ for (NSDictionary* plistItem in plist) {
+ // Use an autorelease pool to clean up after the various observers that
+ // get called after each individual bookmark change.
+ NSAutoreleasePool* pool = [NSAutoreleasePool new];
+ if ([[plistItem objectForKey:kTypeKey] isEqual:kLeafType]) {
+ NSString* title = [[plistItem objectForKey:kURIDictionaryKey]
+ objectForKey:kURIDictTitleKey];
+ NSString* urlStr = [plistItem objectForKey:kURLStringKey];
+ if (title && urlStr) {
+ model->AddURL(dstParent,
+ dstIndex + i,
+ base::SysNSStringToWide(title),
+ GURL(base::SysNSStringToUTF8(urlStr)));
+ ++i;
+ }
+ } else {
+ NSString* title = [plistItem objectForKey:kTitleKey];
+ NSArray* children = [plistItem objectForKey:kChildrenKey];
+ if (title && children) {
+ const BookmarkNode* newFolder;
+ newFolder = model->AddGroup(dstParent,
+ dstIndex + i,
+ base::SysNSStringToWide(title));
+ ++i;
+ [self insertPropertyList:children
+ inFolder:newFolder
+ atIndex:0];
+ }
+ }
+ [pool drain];
+ }
+ [self selectNodesInFolder:dstParent
+ atIndexes:NSMakeRange(dstIndex, [plist count])];
+}
+
+
+// Validates whether or not the proposed drop is valid.
+- (NSDragOperation)outlineView:(NSOutlineView*)outlineView
+ validateDrop:(id <NSDraggingInfo>)info
+ proposedItem:(id)item
+ proposedChildIndex:(NSInteger)childIndex {
+ NSPasteboard* pb = [info draggingPasteboard];
+
+ // Check to see what we are proposed to be dropping on
+ const BookmarkNode*targetNode = [self nodeFromItem:item];
+ if (!targetNode->is_folder()) {
+ // The target node is not a container, but a leaf.
+ // Refuse the drop (we may get called again with a between)
+ if (childIndex == NSOutlineViewDropOnItemIndex) {
+ return NSDragOperationNone;
+ }
+ }
+
+ // Dragging within the outline?
+ if ([info draggingSource] == outlineView &&
+ [[pb types] containsObject:kCustomPboardType]) {
+ // If we are allowing the drop, we see if we are dragging from ourselves
+ // and dropping into a descendent, which wouldn't be allowed...
+ // See if the appropriate drag information is available on the pasteboard.
+ //TODO(snej): Re-implement this
+ /*
+ if (targetNode != group_ &&
+ [[[info draggingPasteboard] types] containsObject:kCustomPboardType]) {
+ for (NSDictionary* draggedNode in draggedNodes_) {
+ if ([self treeNode:targetNode isDescendantOfNode:draggedNode]) {
+ // Yup, it is, refuse it.
+ return NSDragOperationNone;
+ break;
+ }
+ }
+ */
+ return NSDragOperationMove;
+ }
+
+ // Drag from elsewhere is a copy.
+ return NSDragOperationCopy;
+}
+
+// Actually handles the drop.
+- (BOOL)outlineView:(NSOutlineView*)outlineView
+ acceptDrop:(id <NSDraggingInfo>)info
+ item:(id)item
+ childIndex:(NSInteger)childIndex
+{
+ NSPasteboard* pb = [info draggingPasteboard];
+
+ const BookmarkNode* targetNode = [self nodeFromItem:item];
+
+ // Determine the parent to insert into and the child index to insert at.
+ if (!targetNode->is_folder()) {
+ // If our target is a leaf, and we are dropping on it.
+ if (childIndex == NSOutlineViewDropOnItemIndex) {
+ return NO;
+ } else {
+ // We will be dropping on the item's parent at the target index
+ // of this child, plus one.
+ const BookmarkNode* oldTargetNode = targetNode;
+ targetNode = targetNode->GetParent();
+ childIndex = targetNode->IndexOfChild(oldTargetNode) + 1;
+ }
+ } else {
+ if (childIndex == NSOutlineViewDropOnItemIndex) {
+ // Insert it at the end, if we were dropping on it
+ childIndex = targetNode->GetChildCount();
+ }
+ }
+
+ if ([info draggingSource] == outlineView &&
+ [[pb types] containsObject:kCustomPboardType]) {
+ // If the source was ourselves, move the selected nodes.
+ [self moveNodes:draggedNodes_
+ toFolder:targetNode
+ atIndex:childIndex];
+ } else {
+ NSArray* plist = [self readPropertyListFromPasteboard:pb];
+ if (!plist) {
+ return NO;
+ }
+ [self insertPropertyList:plist
+ inFolder:targetNode
+ atIndex:childIndex];
+ }
+ return YES;
+}
+
+
+// The Paste command.
+- (IBAction)paste:(id)sender {
+ if (![self pasteFromPasteboard:[NSPasteboard generalPasteboard]])
+ NSBeep();
+}
+
+- (BOOL)pasteFromPasteboard:(NSPasteboard*)pb {
+ NSArray* plist = [self readPropertyListFromPasteboard: pb];
+ if (!plist)
+ return NO;
+
+ const BookmarkNode* targetNode;
+ NSInteger childIndex;
+ int selRow = [outline_ selectedRow];
+ if (selRow >= 0) {
+ // Insert after selected row.
+ const BookmarkNode* selNode = [self nodeFromItem:
+ [outline_ itemAtRow:selRow]];
+ targetNode = selNode->GetParent();
+ childIndex = targetNode->IndexOfChild(selNode) + 1;
+ } else {
+ // ...or at very end if there's no selection:
+ targetNode = [self nodeFromItem:group_];
+ childIndex = targetNode->GetChildCount();
+ }
+
+ [self insertPropertyList:plist
+ inFolder:targetNode
+ atIndex:childIndex];
+ return YES;
+}
+
+
+// Selectively enables/disables menu commands.
+- (BOOL)validateMenuItem:(NSMenuItem*)menuItem {
+ SEL action = [menuItem action];
+ if (action == @selector(cut:) || action == @selector(copy:) ||
+ action == @selector(delete:)) {
+ return [[outline_ selectedRowIndexes] count] > 0;
+ } else if (action == @selector(paste:)) {
+ return [[NSPasteboard generalPasteboard]
+ availableTypeFromArray:[outline_ registeredDraggedTypes]]
+ != nil;
+ } else {
+ return YES;
+ }
+}
+
+
+@end

Powered by Google App Engine
This is Rietveld 408576698