bookmark_pasteboard_helper_mac.mm revision 21d179b334e59e9a3bfcaed4c4430bef1bc5759d
1// Copyright (c) 2010 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/bookmarks/bookmark_pasteboard_helper_mac.h"
6
7#import <Cocoa/Cocoa.h>
8
9#include "base/sys_string_conversions.h"
10#include "chrome/browser/bookmarks/bookmark_model.h"
11#include "chrome/browser/ui/cocoa/bookmarks/bookmark_drag_source.h"
12#include "chrome/browser/tab_contents/tab_contents_view_mac.h"
13
14NSString* const kBookmarkDictionaryListPboardType =
15    @"BookmarkDictionaryListPboardType";
16
17namespace {
18
19// An unofficial standard pasteboard title type to be provided alongside the
20// |NSURLPboardType|.
21NSString* const kNSURLTitlePboardType =
22    @"public.url-name";
23
24// Pasteboard type used to store profile path to determine which profile
25// a set of bookmarks came from.
26NSString* const kChromiumProfilePathPboardType =
27    @"ChromiumProfilePathPboardType";
28
29// Internal bookmark ID for a bookmark node.  Used only when moving inside
30// of one profile.
31NSString* const kChromiumBookmarkId =
32    @"ChromiumBookmarkId";
33
34// Mac WebKit uses this type, declared in
35// WebKit/mac/History/WebURLsWithTitles.h.
36NSString* const kWebURLsWithTitlesPboardType =
37    @"WebURLsWithTitlesPboardType";
38
39// Keys for the type of node in BookmarkDictionaryListPboardType.
40NSString* const kWebBookmarkType =
41    @"WebBookmarkType";
42
43NSString* const kWebBookmarkTypeList =
44    @"WebBookmarkTypeList";
45
46NSString* const kWebBookmarkTypeLeaf =
47    @"WebBookmarkTypeLeaf";
48
49void ConvertPlistToElements(NSArray* input,
50                            std::vector<BookmarkNodeData::Element>& elements) {
51  NSUInteger len = [input count];
52  for (NSUInteger i = 0; i < len; ++i) {
53    NSDictionary* pboardBookmark = [input objectAtIndex:i];
54    scoped_ptr<BookmarkNode> new_node(new BookmarkNode(0, GURL()));
55    int64 node_id =
56        [[pboardBookmark objectForKey:kChromiumBookmarkId] longLongValue];
57    new_node->set_id(node_id);
58    BOOL is_folder = [[pboardBookmark objectForKey:kWebBookmarkType]
59        isEqualToString:kWebBookmarkTypeList];
60    if (is_folder) {
61      new_node->set_type(BookmarkNode::FOLDER);
62      NSString* title = [pboardBookmark objectForKey:@"Title"];
63      new_node->SetTitle(base::SysNSStringToUTF16(title));
64    } else {
65      new_node->set_type(BookmarkNode::URL);
66      NSDictionary* uriDictionary =
67          [pboardBookmark objectForKey:@"URIDictionary"];
68      NSString* title = [uriDictionary objectForKey:@"title"];
69      NSString* urlString = [pboardBookmark objectForKey:@"URLString"];
70      new_node->SetTitle(base::SysNSStringToUTF16(title));
71      new_node->SetURL(GURL(base::SysNSStringToUTF8(urlString)));
72    }
73    BookmarkNodeData::Element e = BookmarkNodeData::Element(new_node.get());
74    if(is_folder)
75      ConvertPlistToElements([pboardBookmark objectForKey:@"Children"],
76                             e.children);
77    elements.push_back(e);
78  }
79}
80
81bool ReadBookmarkDictionaryListPboardType(NSPasteboard* pb,
82      std::vector<BookmarkNodeData::Element>& elements) {
83  NSArray* bookmarks =
84      [pb propertyListForType:kBookmarkDictionaryListPboardType];
85  if (!bookmarks) return false;
86  ConvertPlistToElements(bookmarks, elements);
87  return true;
88}
89
90bool ReadWebURLsWithTitlesPboardType(NSPasteboard* pb,
91      std::vector<BookmarkNodeData::Element>& elements) {
92  NSArray* bookmarkPairs =
93      [pb propertyListForType:kWebURLsWithTitlesPboardType];
94  if (![bookmarkPairs isKindOfClass:[NSArray class]]) {
95    return false;
96  }
97  NSArray* urlsArr = [bookmarkPairs objectAtIndex:0];
98  NSArray* titlesArr = [bookmarkPairs objectAtIndex:1];
99  if ([urlsArr count] < 1) {
100    return false;
101  }
102  if ([urlsArr count] != [titlesArr count]) {
103    return false;
104  }
105
106  NSUInteger len = [titlesArr count];
107  for (NSUInteger i = 0; i < len; ++i) {
108    string16 title = base::SysNSStringToUTF16([titlesArr objectAtIndex:i]);
109    std::string url = base::SysNSStringToUTF8([urlsArr objectAtIndex:i]);
110    if (!url.empty()) {
111      BookmarkNodeData::Element element;
112      element.is_url = true;
113      element.url = GURL(url);
114      element.title = title;
115      elements.push_back(element);
116    }
117  }
118  return true;
119}
120
121bool ReadNSURLPboardType(NSPasteboard* pb,
122                         std::vector<BookmarkNodeData::Element>& elements) {
123  NSURL* url = [NSURL URLFromPasteboard:pb];
124  if (url == nil) {
125    return false;
126  }
127  std::string urlString = base::SysNSStringToUTF8([url absoluteString]);
128  NSString* title = [pb stringForType:kNSURLTitlePboardType];
129  if (!title)
130    title = [pb stringForType:NSStringPboardType];
131
132  BookmarkNodeData::Element element;
133  element.is_url = true;
134  element.url = GURL(urlString);
135  element.title = base::SysNSStringToUTF16(title);
136  elements.push_back(element);
137  return true;
138}
139
140NSArray* GetPlistForBookmarkList(
141    const std::vector<BookmarkNodeData::Element>& elements) {
142  NSMutableArray* plist = [NSMutableArray array];
143  for (size_t i = 0; i < elements.size(); ++i) {
144    BookmarkNodeData::Element element = elements[i];
145    if (element.is_url) {
146      NSString* title = base::SysUTF16ToNSString(element.title);
147      NSString* url = base::SysUTF8ToNSString(element.url.spec());
148      int64 elementId = element.get_id();
149      NSNumber* idNum = [NSNumber numberWithLongLong:elementId];
150      NSDictionary* uriDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
151              title, @"title", nil];
152      NSDictionary* object = [NSDictionary dictionaryWithObjectsAndKeys:
153          uriDictionary, @"URIDictionary",
154          url, @"URLString",
155          kWebBookmarkTypeLeaf, kWebBookmarkType,
156          idNum, kChromiumBookmarkId,
157          nil];
158      [plist addObject:object];
159    } else {
160      NSString* title = base::SysUTF16ToNSString(element.title);
161      NSArray* children = GetPlistForBookmarkList(element.children);
162      int64 elementId = element.get_id();
163      NSNumber* idNum = [NSNumber numberWithLongLong:elementId];
164      NSDictionary* object = [NSDictionary dictionaryWithObjectsAndKeys:
165          title, @"Title",
166          children, @"Children",
167          kWebBookmarkTypeList, kWebBookmarkType,
168          idNum, kChromiumBookmarkId,
169          nil];
170      [plist addObject:object];
171    }
172  }
173  return plist;
174}
175
176void WriteBookmarkDictionaryListPboardType(NSPasteboard* pb,
177    const std::vector<BookmarkNodeData::Element>& elements) {
178  NSArray* plist = GetPlistForBookmarkList(elements);
179  [pb setPropertyList:plist forType:kBookmarkDictionaryListPboardType];
180}
181
182void FillFlattenedArraysForBookmarks(
183    const std::vector<BookmarkNodeData::Element>& elements,
184    NSMutableArray* titles, NSMutableArray* urls) {
185  for (size_t i = 0; i < elements.size(); ++i) {
186    BookmarkNodeData::Element element = elements[i];
187    if (element.is_url) {
188      NSString* title = base::SysUTF16ToNSString(element.title);
189      NSString* url = base::SysUTF8ToNSString(element.url.spec());
190      [titles addObject:title];
191      [urls addObject:url];
192    } else {
193      FillFlattenedArraysForBookmarks(element.children, titles, urls);
194    }
195  }
196}
197
198void WriteSimplifiedBookmarkTypes(NSPasteboard* pb,
199    const std::vector<BookmarkNodeData::Element>& elements) {
200  NSMutableArray* titles = [NSMutableArray array];
201  NSMutableArray* urls = [NSMutableArray array];
202  FillFlattenedArraysForBookmarks(elements, titles, urls);
203
204  // These bookmark types only act on urls, not folders.
205  if ([urls count] < 1)
206    return;
207
208  // Write WebURLsWithTitlesPboardType.
209  [pb setPropertyList:[NSArray arrayWithObjects:urls, titles, nil]
210              forType:kWebURLsWithTitlesPboardType];
211
212  // Write NSStringPboardType.
213  [pb setString:[urls componentsJoinedByString:@"\n"]
214      forType:NSStringPboardType];
215
216  // Write NSURLPboardType (with title).
217  NSURL* url = [NSURL URLWithString:[urls objectAtIndex:0]];
218  [url writeToPasteboard:pb];
219  NSString* titleString = [titles objectAtIndex:0];
220  [pb setString:titleString forType:kNSURLTitlePboardType];
221}
222
223void WriteToClipboardPrivate(
224    const std::vector<BookmarkNodeData::Element>& elements,
225    NSPasteboard* pb,
226    FilePath::StringType profile_path) {
227  if (elements.size() == 0) {
228    return;
229  }
230  NSArray* types = [NSArray arrayWithObjects:kBookmarkDictionaryListPboardType,
231                                             kWebURLsWithTitlesPboardType,
232                                             NSStringPboardType,
233                                             NSURLPboardType,
234                                             kNSURLTitlePboardType,
235                                             kChromiumProfilePathPboardType,
236                                             nil];
237  [pb declareTypes:types owner:nil];
238  [pb setString:base::SysUTF8ToNSString(profile_path)
239        forType:kChromiumProfilePathPboardType];
240  WriteBookmarkDictionaryListPboardType(pb, elements);
241  WriteSimplifiedBookmarkTypes(pb, elements);
242}
243
244bool ReadFromClipboardPrivate(
245    std::vector<BookmarkNodeData::Element>& elements,
246    NSPasteboard* pb,
247    FilePath::StringType* profile_path) {
248  elements.clear();
249  NSString* profile = [pb stringForType:kChromiumProfilePathPboardType];
250  profile_path->assign(base::SysNSStringToUTF8(profile));
251  return (ReadBookmarkDictionaryListPboardType(pb, elements) ||
252         ReadWebURLsWithTitlesPboardType(pb, elements) ||
253         ReadNSURLPboardType(pb, elements));
254}
255
256bool ClipboardContainsBookmarksPrivate(NSPasteboard* pb) {
257  NSArray* availableTypes =
258      [NSArray arrayWithObjects:kBookmarkDictionaryListPboardType,
259                                kWebURLsWithTitlesPboardType,
260                                NSURLPboardType,
261                                nil];
262  return [pb availableTypeFromArray:availableTypes] != nil;
263}
264
265}  // anonymous namespace
266
267namespace bookmark_pasteboard_helper_mac {
268
269void WriteToClipboard(const std::vector<BookmarkNodeData::Element>& elements,
270                      FilePath::StringType profile_path) {
271  NSPasteboard* pb = [NSPasteboard generalPasteboard];
272  WriteToClipboardPrivate(elements, pb, profile_path);
273}
274
275void WriteToDragClipboard(
276    const std::vector<BookmarkNodeData::Element>& elements,
277    FilePath::StringType profile_path) {
278  NSPasteboard* pb = [NSPasteboard pasteboardWithName:NSDragPboard];
279  WriteToClipboardPrivate(elements, pb, profile_path);
280}
281
282bool ReadFromClipboard(std::vector<BookmarkNodeData::Element>& elements,
283                       FilePath::StringType* profile_path) {
284  NSPasteboard* pb = [NSPasteboard generalPasteboard];
285  return ReadFromClipboardPrivate(elements, pb, profile_path);
286}
287
288bool ReadFromDragClipboard(std::vector<BookmarkNodeData::Element>& elements,
289                           FilePath::StringType* profile_path) {
290  NSPasteboard* pb = [NSPasteboard pasteboardWithName:NSDragPboard];
291  return ReadFromClipboardPrivate(elements, pb, profile_path);
292}
293
294
295bool ClipboardContainsBookmarks() {
296  NSPasteboard* pb = [NSPasteboard generalPasteboard];
297  return ClipboardContainsBookmarksPrivate(pb);
298}
299
300bool DragClipboardContainsBookmarks() {
301  NSPasteboard* pb = [NSPasteboard pasteboardWithName:NSDragPboard];
302  return ClipboardContainsBookmarksPrivate(pb);
303}
304
305void StartDrag(Profile* profile, const std::vector<const BookmarkNode*>& nodes,
306    gfx::NativeView view) {
307  DCHECK([view isKindOfClass:[TabContentsViewCocoa class]]);
308  TabContentsViewCocoa* tabView = static_cast<TabContentsViewCocoa*>(view);
309  std::vector<BookmarkNodeData::Element> elements;
310  for (std::vector<const BookmarkNode*>::const_iterator it = nodes.begin();
311       it != nodes.end(); ++it) {
312    elements.push_back(BookmarkNodeData::Element(*it));
313  }
314  NSPasteboard* pb = [NSPasteboard pasteboardWithName:NSDragPboard];
315  scoped_nsobject<BookmarkDragSource> source([[BookmarkDragSource alloc]
316      initWithContentsView:tabView
317                  dropData:elements
318                   profile:profile
319                pasteboard:pb
320         dragOperationMask:NSDragOperationEvery]);
321  [source startDrag];
322}
323
324}  // namespace bookmark_pasteboard_helper_mac
325