// Copyright (c) 2010 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. #include "chrome/browser/bookmarks/bookmark_pasteboard_helper_mac.h" #import <Cocoa/Cocoa.h> #include "base/sys_string_conversions.h" #include "chrome/browser/bookmarks/bookmark_model.h" #include "chrome/browser/ui/cocoa/bookmarks/bookmark_drag_source.h" #include "chrome/browser/tab_contents/tab_contents_view_mac.h" NSString* const kBookmarkDictionaryListPboardType = @"BookmarkDictionaryListPboardType"; namespace { // An unofficial standard pasteboard title type to be provided alongside the // |NSURLPboardType|. NSString* const kNSURLTitlePboardType = @"public.url-name"; // Pasteboard type used to store profile path to determine which profile // a set of bookmarks came from. NSString* const kChromiumProfilePathPboardType = @"ChromiumProfilePathPboardType"; // Internal bookmark ID for a bookmark node. Used only when moving inside // of one profile. NSString* const kChromiumBookmarkId = @"ChromiumBookmarkId"; // Mac WebKit uses this type, declared in // WebKit/mac/History/WebURLsWithTitles.h. NSString* const kWebURLsWithTitlesPboardType = @"WebURLsWithTitlesPboardType"; // Keys for the type of node in BookmarkDictionaryListPboardType. NSString* const kWebBookmarkType = @"WebBookmarkType"; NSString* const kWebBookmarkTypeList = @"WebBookmarkTypeList"; NSString* const kWebBookmarkTypeLeaf = @"WebBookmarkTypeLeaf"; void ConvertPlistToElements(NSArray* input, std::vector<BookmarkNodeData::Element>& elements) { NSUInteger len = [input count]; for (NSUInteger i = 0; i < len; ++i) { NSDictionary* pboardBookmark = [input objectAtIndex:i]; scoped_ptr<BookmarkNode> new_node(new BookmarkNode(0, GURL())); int64 node_id = [[pboardBookmark objectForKey:kChromiumBookmarkId] longLongValue]; new_node->set_id(node_id); BOOL is_folder = [[pboardBookmark objectForKey:kWebBookmarkType] isEqualToString:kWebBookmarkTypeList]; if (is_folder) { new_node->set_type(BookmarkNode::FOLDER); NSString* title = [pboardBookmark objectForKey:@"Title"]; new_node->set_title(base::SysNSStringToUTF16(title)); } else { new_node->set_type(BookmarkNode::URL); NSDictionary* uriDictionary = [pboardBookmark objectForKey:@"URIDictionary"]; NSString* title = [uriDictionary objectForKey:@"title"]; NSString* urlString = [pboardBookmark objectForKey:@"URLString"]; new_node->set_title(base::SysNSStringToUTF16(title)); new_node->SetURL(GURL(base::SysNSStringToUTF8(urlString))); } BookmarkNodeData::Element e = BookmarkNodeData::Element(new_node.get()); if(is_folder) ConvertPlistToElements([pboardBookmark objectForKey:@"Children"], e.children); elements.push_back(e); } } bool ReadBookmarkDictionaryListPboardType(NSPasteboard* pb, std::vector<BookmarkNodeData::Element>& elements) { NSArray* bookmarks = [pb propertyListForType:kBookmarkDictionaryListPboardType]; if (!bookmarks) return false; ConvertPlistToElements(bookmarks, elements); return true; } bool ReadWebURLsWithTitlesPboardType(NSPasteboard* pb, std::vector<BookmarkNodeData::Element>& elements) { NSArray* bookmarkPairs = [pb propertyListForType:kWebURLsWithTitlesPboardType]; if (![bookmarkPairs isKindOfClass:[NSArray class]]) { return false; } NSArray* urlsArr = [bookmarkPairs objectAtIndex:0]; NSArray* titlesArr = [bookmarkPairs objectAtIndex:1]; if ([urlsArr count] < 1) { return false; } if ([urlsArr count] != [titlesArr count]) { return false; } NSUInteger len = [titlesArr count]; for (NSUInteger i = 0; i < len; ++i) { string16 title = base::SysNSStringToUTF16([titlesArr objectAtIndex:i]); std::string url = base::SysNSStringToUTF8([urlsArr objectAtIndex:i]); if (!url.empty()) { BookmarkNodeData::Element element; element.is_url = true; element.url = GURL(url); element.title = title; elements.push_back(element); } } return true; } bool ReadNSURLPboardType(NSPasteboard* pb, std::vector<BookmarkNodeData::Element>& elements) { NSURL* url = [NSURL URLFromPasteboard:pb]; if (url == nil) { return false; } std::string urlString = base::SysNSStringToUTF8([url absoluteString]); NSString* title = [pb stringForType:kNSURLTitlePboardType]; if (!title) title = [pb stringForType:NSStringPboardType]; BookmarkNodeData::Element element; element.is_url = true; element.url = GURL(urlString); element.title = base::SysNSStringToUTF16(title); elements.push_back(element); return true; } NSArray* GetPlistForBookmarkList( const std::vector<BookmarkNodeData::Element>& elements) { NSMutableArray* plist = [NSMutableArray array]; for (size_t i = 0; i < elements.size(); ++i) { BookmarkNodeData::Element element = elements[i]; if (element.is_url) { NSString* title = base::SysUTF16ToNSString(element.title); NSString* url = base::SysUTF8ToNSString(element.url.spec()); int64 elementId = element.get_id(); NSNumber* idNum = [NSNumber numberWithLongLong:elementId]; NSDictionary* uriDictionary = [NSDictionary dictionaryWithObjectsAndKeys: title, @"title", nil]; NSDictionary* object = [NSDictionary dictionaryWithObjectsAndKeys: uriDictionary, @"URIDictionary", url, @"URLString", kWebBookmarkTypeLeaf, kWebBookmarkType, idNum, kChromiumBookmarkId, nil]; [plist addObject:object]; } else { NSString* title = base::SysUTF16ToNSString(element.title); NSArray* children = GetPlistForBookmarkList(element.children); int64 elementId = element.get_id(); NSNumber* idNum = [NSNumber numberWithLongLong:elementId]; NSDictionary* object = [NSDictionary dictionaryWithObjectsAndKeys: title, @"Title", children, @"Children", kWebBookmarkTypeList, kWebBookmarkType, idNum, kChromiumBookmarkId, nil]; [plist addObject:object]; } } return plist; } void WriteBookmarkDictionaryListPboardType(NSPasteboard* pb, const std::vector<BookmarkNodeData::Element>& elements) { NSArray* plist = GetPlistForBookmarkList(elements); [pb setPropertyList:plist forType:kBookmarkDictionaryListPboardType]; } void FillFlattenedArraysForBookmarks( const std::vector<BookmarkNodeData::Element>& elements, NSMutableArray* titles, NSMutableArray* urls) { for (size_t i = 0; i < elements.size(); ++i) { BookmarkNodeData::Element element = elements[i]; if (element.is_url) { NSString* title = base::SysUTF16ToNSString(element.title); NSString* url = base::SysUTF8ToNSString(element.url.spec()); [titles addObject:title]; [urls addObject:url]; } else { FillFlattenedArraysForBookmarks(element.children, titles, urls); } } } void WriteSimplifiedBookmarkTypes(NSPasteboard* pb, const std::vector<BookmarkNodeData::Element>& elements) { NSMutableArray* titles = [NSMutableArray array]; NSMutableArray* urls = [NSMutableArray array]; FillFlattenedArraysForBookmarks(elements, titles, urls); // These bookmark types only act on urls, not folders. if ([urls count] < 1) return; // Write WebURLsWithTitlesPboardType. [pb setPropertyList:[NSArray arrayWithObjects:urls, titles, nil] forType:kWebURLsWithTitlesPboardType]; // Write NSStringPboardType. [pb setString:[urls componentsJoinedByString:@"\n"] forType:NSStringPboardType]; // Write NSURLPboardType (with title). NSURL* url = [NSURL URLWithString:[urls objectAtIndex:0]]; [url writeToPasteboard:pb]; NSString* titleString = [titles objectAtIndex:0]; [pb setString:titleString forType:kNSURLTitlePboardType]; } void WriteToClipboardPrivate( const std::vector<BookmarkNodeData::Element>& elements, NSPasteboard* pb, FilePath::StringType profile_path) { if (elements.empty()) return; NSArray* types = [NSArray arrayWithObjects:kBookmarkDictionaryListPboardType, kWebURLsWithTitlesPboardType, NSStringPboardType, NSURLPboardType, kNSURLTitlePboardType, kChromiumProfilePathPboardType, nil]; [pb declareTypes:types owner:nil]; [pb setString:base::SysUTF8ToNSString(profile_path) forType:kChromiumProfilePathPboardType]; WriteBookmarkDictionaryListPboardType(pb, elements); WriteSimplifiedBookmarkTypes(pb, elements); } bool ReadFromClipboardPrivate( std::vector<BookmarkNodeData::Element>& elements, NSPasteboard* pb, FilePath::StringType* profile_path) { elements.clear(); NSString* profile = [pb stringForType:kChromiumProfilePathPboardType]; profile_path->assign(base::SysNSStringToUTF8(profile)); return (ReadBookmarkDictionaryListPboardType(pb, elements) || ReadWebURLsWithTitlesPboardType(pb, elements) || ReadNSURLPboardType(pb, elements)); } bool ClipboardContainsBookmarksPrivate(NSPasteboard* pb) { NSArray* availableTypes = [NSArray arrayWithObjects:kBookmarkDictionaryListPboardType, kWebURLsWithTitlesPboardType, NSURLPboardType, nil]; return [pb availableTypeFromArray:availableTypes] != nil; } } // anonymous namespace namespace bookmark_pasteboard_helper_mac { void WriteToClipboard(const std::vector<BookmarkNodeData::Element>& elements, FilePath::StringType profile_path) { NSPasteboard* pb = [NSPasteboard generalPasteboard]; WriteToClipboardPrivate(elements, pb, profile_path); } void WriteToDragClipboard( const std::vector<BookmarkNodeData::Element>& elements, FilePath::StringType profile_path) { NSPasteboard* pb = [NSPasteboard pasteboardWithName:NSDragPboard]; WriteToClipboardPrivate(elements, pb, profile_path); } bool ReadFromClipboard(std::vector<BookmarkNodeData::Element>& elements, FilePath::StringType* profile_path) { NSPasteboard* pb = [NSPasteboard generalPasteboard]; return ReadFromClipboardPrivate(elements, pb, profile_path); } bool ReadFromDragClipboard(std::vector<BookmarkNodeData::Element>& elements, FilePath::StringType* profile_path) { NSPasteboard* pb = [NSPasteboard pasteboardWithName:NSDragPboard]; return ReadFromClipboardPrivate(elements, pb, profile_path); } bool ClipboardContainsBookmarks() { NSPasteboard* pb = [NSPasteboard generalPasteboard]; return ClipboardContainsBookmarksPrivate(pb); } bool DragClipboardContainsBookmarks() { NSPasteboard* pb = [NSPasteboard pasteboardWithName:NSDragPboard]; return ClipboardContainsBookmarksPrivate(pb); } void StartDrag(Profile* profile, const std::vector<const BookmarkNode*>& nodes, gfx::NativeView view) { DCHECK([view isKindOfClass:[TabContentsViewCocoa class]]); TabContentsViewCocoa* tabView = static_cast<TabContentsViewCocoa*>(view); std::vector<BookmarkNodeData::Element> elements; for (std::vector<const BookmarkNode*>::const_iterator it = nodes.begin(); it != nodes.end(); ++it) { elements.push_back(BookmarkNodeData::Element(*it)); } NSPasteboard* pb = [NSPasteboard pasteboardWithName:NSDragPboard]; scoped_nsobject<BookmarkDragSource> source([[BookmarkDragSource alloc] initWithContentsView:tabView dropData:elements profile:profile pasteboard:pb dragOperationMask:NSDragOperationEvery]); [source startDrag]; } } // namespace bookmark_pasteboard_helper_mac