OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2009 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 #import "chrome/browser/cocoa/bookmark_tree_controller.h" | |
6 | |
7 #include "base/sys_string_conversions.h" | |
8 #include "chrome/browser/bookmarks/bookmark_model.h" | |
9 #import "chrome/browser/cocoa/bookmark_manager_controller.h" | |
10 #include "googleurl/src/gurl.h" | |
11 | |
12 | |
13 // Safari uses this type, though it's not declared in any header. | |
14 static NSString* const BookmarkDictionaryListPboardType = | |
15 @"BookmarkDictionaryListPboardType"; | |
16 | |
17 // Mac WebKit uses this type, declared in WebKit/mac/History/WebURLsWithTitles.h | |
18 static NSString* const WebURLsWithTitlesPboardType = | |
19 @"WebURLsWithTitlesPboardType"; | |
20 | |
21 // Used internally to identify intra-outline drags. | |
22 static NSString* const kCustomPboardType = | |
23 @"ChromeBookmarkTreeControllerPlaceholderType"; | |
24 | |
25 | |
26 @implementation BookmarkTreeController (Pasteboard) | |
27 | |
28 | |
29 // One-time dnd setup; called from -awakeFromNib. | |
30 - (void)registerDragTypes { | |
31 [outline_ registerForDraggedTypes:[NSArray arrayWithObjects: | |
32 BookmarkDictionaryListPboardType, | |
33 WebURLsWithTitlesPboardType, | |
34 NSURLPboardType, nil]]; | |
35 [outline_ setDraggingSourceOperationMask:NSDragOperationEvery forLocal:YES]; | |
36 [outline_ setDraggingSourceOperationMask:NSDragOperationEvery forLocal:NO]; | |
37 } | |
38 | |
39 // Selects a range of items in a parent node. | |
40 - (void)selectNodesInFolder:(const BookmarkNode*)parent | |
41 atIndexes:(NSRange)childRange { | |
42 DCHECK(NSMaxRange(childRange) <= (NSUInteger)parent->GetChildCount()); | |
43 id parentItem = [self itemFromNode:parent]; | |
44 if (parentItem != nil) { | |
45 // If parent is not the root, need to offset range by parent's index: | |
46 int startRow = [outline_ rowForItem:parentItem]; | |
47 if (startRow < 0) { | |
48 return; | |
49 } | |
50 if ([outline_ isItemExpanded:parentItem]) { | |
51 childRange.location += startRow + 1; | |
52 } else { | |
53 childRange.location = startRow; | |
54 childRange.length = 1; | |
55 } | |
56 } | |
57 NSIndexSet* indexes = [NSIndexSet indexSetWithIndexesInRange:childRange]; | |
58 [outline_ selectRowIndexes:indexes byExtendingSelection:NO]; | |
59 } | |
60 | |
61 | |
62 #pragma mark - | |
63 #pragma mark DRAGGING OUT AND COPYING: | |
64 | |
65 | |
66 // Generates parallel arrays of URLs and titles for contents of a node. | |
67 static void flattenNode(const BookmarkNode* node, | |
68 NSMutableArray* urlStrings, | |
69 NSMutableArray* titles) { | |
70 if (node->is_folder()) { | |
71 for (int i = 0; i < node->GetChildCount(); i++) { | |
72 flattenNode(node->GetChild(i), urlStrings, titles); | |
73 } | |
74 } else if (node->is_url()) { | |
75 [urlStrings addObject:base::SysUTF8ToNSString( | |
76 node->GetURL().possibly_invalid_spec())]; | |
77 [titles addObject:base::SysWideToNSString(node->GetTitle())]; | |
78 } | |
79 } | |
80 | |
81 // Writes data to the pasteboard given a list of row items. | |
82 - (BOOL)writeItems:(NSArray*)items | |
83 toPasteboard:(NSPasteboard*)pb | |
84 includeCustom:(BOOL)includeCustom { | |
85 if ([items count] == 0) { | |
86 return NO; | |
87 } | |
88 | |
89 [pb declareTypes:[NSMutableArray arrayWithObjects: | |
90 WebURLsWithTitlesPboardType, | |
91 NSStringPboardType, nil] | |
92 owner:self]; | |
93 | |
94 // Add URLs and titles: | |
95 NSMutableArray* urls = [NSMutableArray array]; | |
96 NSMutableArray* titles = [NSMutableArray array]; | |
97 for (id item in items) { | |
98 flattenNode([self nodeFromItem:item], urls, titles); | |
99 } | |
100 [pb setPropertyList:[NSArray arrayWithObjects:urls, titles, nil] | |
101 forType:WebURLsWithTitlesPboardType]; | |
102 | |
103 // Add plain text, as one URL per line: | |
104 [pb setString:[urls componentsJoinedByString:@"\n"] | |
105 forType:NSStringPboardType]; | |
106 | |
107 // Add custom type. The actual data doesn't matter since kCustomPboardType | |
108 // drags aren't recognized by anyone but us. | |
109 if (includeCustom) { | |
110 draggedNodes_.clear(); | |
111 for (id item in items) { | |
112 draggedNodes_.push_back([self nodeFromItem:item]); | |
113 } | |
114 [pb addTypes:[NSArray arrayWithObject: kCustomPboardType] owner: self]; | |
115 [pb setData:[NSData data] forType:kCustomPboardType]; | |
116 } | |
117 | |
118 // Add single URL: | |
119 if ([urls count] == 1) { | |
120 [pb addTypes:[NSArray arrayWithObject: NSURLPboardType] owner: self]; | |
121 NSString* firstURLStr = [urls objectAtIndex:0]; | |
122 [pb setString:firstURLStr forType:NSURLPboardType]; | |
123 } | |
124 return YES; | |
125 } | |
126 | |
127 // Invoked when dragging outline-view rows. | |
128 - (BOOL)outlineView:(NSOutlineView*)outlineView | |
129 writeItems:(NSArray*)items | |
130 toPasteboard:(NSPasteboard*)pb { | |
131 [self writeItems:items toPasteboard:pb includeCustom:YES]; | |
132 return YES; | |
133 } | |
134 | |
135 | |
136 // The Cut command. | |
137 - (IBAction)cut:(id)sender { | |
138 if ([self writeItems:[self selectedItems] | |
139 toPasteboard:[NSPasteboard generalPasteboard] | |
140 includeCustom:NO]) { | |
141 [self delete:self]; | |
142 } else { | |
143 NSBeep(); | |
144 } | |
145 } | |
146 | |
147 // The Copy command. | |
148 - (IBAction)copy:(id)sender { | |
149 if (![self copyToPasteboard:[NSPasteboard generalPasteboard]]) | |
150 NSBeep(); | |
151 } | |
152 | |
153 // Copy to any pasteboard. | |
154 - (BOOL)copyToPasteboard:(NSPasteboard*)pb { | |
155 return [self writeItems:[self selectedItems] | |
viettrungluu
2010/01/04 21:19:01
Unindent 2 spaces.
| |
156 toPasteboard:pb | |
157 includeCustom:NO]; | |
158 } | |
159 | |
160 | |
161 #pragma mark - | |
162 #pragma mark INCOMING DRAGS AND PASTING: | |
163 | |
164 | |
165 // BookmarkDictionaryListPboardType represents bookmarks as dictionaries, | |
166 // which have the following keys. | |
167 // Strangely, folder nodes (whose WebBookmarkType is WebBookmarkTypeLeaf) have | |
168 // their title under 'Title', while leaf nodes have it in 'URIDictionary.title'. | |
169 static const NSString* kTitleKey = @"Title"; | |
170 static const NSString* kURIDictionaryKey = @"URIDictionary"; | |
171 static const NSString* kURIDictTitleKey = @"title"; | |
172 static const NSString* kURLStringKey = @"URLString"; | |
173 static const NSString* kTypeKey = @"WebBookmarkType"; | |
174 static const NSString* kLeafType = @"WebBookmarkTypeLeaf"; | |
175 //static const NSString* kListType = @"WebBookmarkTypeList"; // unused for now | |
176 static const NSString* kChildrenKey = @"Children"; | |
177 | |
178 // Helper that creates a dictionary in BookmarkDictionaryListPboardType format. | |
179 // |name| may be nil, but |urlStr| is required. | |
180 static NSDictionary* makeBookmarkPlistEntry(NSString* name, NSString* urlStr) { | |
181 if (!name) { | |
182 name = urlStr; | |
183 } | |
184 NSDictionary* nameDict = [NSDictionary dictionaryWithObject:name | |
185 forKey:kURIDictTitleKey]; | |
186 return [NSDictionary dictionaryWithObjectsAndKeys: | |
187 kLeafType, kTypeKey, | |
188 nameDict, kURIDictionaryKey, | |
189 urlStr, kURLStringKey, | |
190 nil]; | |
191 } | |
192 | |
193 // Reads URL(s) off the pasteboard and returns them in BookmarkDictionaryList- | |
194 // PboardType format, or nil on failure. | |
195 - (NSArray*)readPropertyListFromPasteboard:(NSPasteboard*)pb { | |
196 NSString* type = [pb availableTypeFromArray: | |
197 [outline_ registeredDraggedTypes]]; | |
198 if ([type isEqualToString:BookmarkDictionaryListPboardType]) { | |
199 // Safari's full bookmark plist type: | |
200 return [pb propertyListForType:type]; | |
201 | |
202 } else if ([type isEqualToString:WebURLsWithTitlesPboardType]) { | |
203 // Safari's parallel-URLs-and-titles type: | |
204 NSArray* contents = [pb propertyListForType:type]; | |
205 NSArray* urlStrings = [contents objectAtIndex:0]; | |
206 NSArray* titles = [contents objectAtIndex:1]; | |
207 NSUInteger n = [urlStrings count]; | |
208 if (n == 0 || [titles count] != n) { | |
209 return nil; | |
210 } | |
211 NSMutableArray* plist = [NSMutableArray array]; | |
212 for (NSUInteger i = 0; i < n; i++) { | |
213 [plist addObject:makeBookmarkPlistEntry([titles objectAtIndex:i], | |
214 [urlStrings objectAtIndex:i])]; | |
215 } | |
216 return plist; | |
217 | |
218 } else if ([type isEqualToString:NSURLPboardType]) { | |
219 // Standard URL type: | |
220 NSString* urlStr = [[NSURL URLFromPasteboard:pb] absoluteString]; | |
221 if (!urlStr) { | |
222 return nil; | |
223 } | |
224 NSString* title = [pb stringForType:@"public.url-name"]; | |
225 if (!title) | |
226 title = [pb stringForType:NSStringPboardType]; | |
227 return [NSArray arrayWithObject:makeBookmarkPlistEntry(title, urlStr)]; | |
228 | |
229 } else { | |
230 return nil; | |
231 } | |
232 } | |
233 | |
234 | |
235 // Moves BookmarkNodes into a parent folder, then selects them. | |
236 - (void)moveNodes:(std::vector<const BookmarkNode*>)nodes | |
237 toFolder:(const BookmarkNode*)dstParent | |
238 atIndex:(int)dstIndex { | |
239 for (std::vector<const BookmarkNode*>::iterator it = nodes.begin(); | |
240 it != nodes.end(); ++it) { | |
241 // Use an autorelease pool to clean up after the various observers that | |
242 // get called after each individual bookmark change. | |
243 NSAutoreleasePool* pool = [NSAutoreleasePool new]; | |
244 const BookmarkNode* srcNode = *it; | |
245 const BookmarkNode* srcParent = srcNode->GetParent(); | |
246 int srcIndex = srcParent->IndexOfChild(srcNode); | |
247 [manager_ bookmarkModel]->Move(srcNode, dstParent, dstIndex); | |
248 if (srcParent != dstParent || srcIndex >= dstIndex) { | |
249 dstIndex++; | |
250 } | |
251 [pool drain]; | |
252 } | |
253 | |
254 [self selectNodesInFolder:dstParent | |
255 atIndexes:NSMakeRange(dstIndex - nodes.size(), nodes.size())]; | |
256 } | |
257 | |
258 // Inserts bookmarks in BookmarkDictionaryListPboardType into a folder node. | |
259 - (void)insertPropertyList:(NSArray*)plist | |
260 inFolder:(const BookmarkNode*)dstParent | |
261 atIndex:(NSInteger)dstIndex { | |
262 BookmarkModel* model = [manager_ bookmarkModel]; | |
263 NSInteger i = 0; | |
264 for (NSDictionary* plistItem in plist) { | |
265 // Use an autorelease pool to clean up after the various observers that | |
266 // get called after each individual bookmark change. | |
267 NSAutoreleasePool* pool = [NSAutoreleasePool new]; | |
268 if ([[plistItem objectForKey:kTypeKey] isEqual:kLeafType]) { | |
269 NSString* title = [[plistItem objectForKey:kURIDictionaryKey] | |
270 objectForKey:kURIDictTitleKey]; | |
271 NSString* urlStr = [plistItem objectForKey:kURLStringKey]; | |
272 if (title && urlStr) { | |
273 model->AddURL(dstParent, | |
274 dstIndex + i, | |
275 base::SysNSStringToWide(title), | |
276 GURL(base::SysNSStringToUTF8(urlStr))); | |
277 ++i; | |
278 } | |
279 } else { | |
280 NSString* title = [plistItem objectForKey:kTitleKey]; | |
281 NSArray* children = [plistItem objectForKey:kChildrenKey]; | |
282 if (title && children) { | |
283 const BookmarkNode* newFolder; | |
284 newFolder = model->AddGroup(dstParent, | |
285 dstIndex + i, | |
286 base::SysNSStringToWide(title)); | |
287 ++i; | |
288 [self insertPropertyList:children | |
289 inFolder:newFolder | |
290 atIndex:0]; | |
291 } | |
292 } | |
293 [pool drain]; | |
294 } | |
295 [self selectNodesInFolder:dstParent | |
296 atIndexes:NSMakeRange(dstIndex, [plist count])]; | |
297 } | |
298 | |
299 | |
300 // Validates whether or not the proposed drop is valid. | |
301 - (NSDragOperation)outlineView:(NSOutlineView*)outlineView | |
302 validateDrop:(id <NSDraggingInfo>)info | |
303 proposedItem:(id)item | |
304 proposedChildIndex:(NSInteger)childIndex { | |
305 NSPasteboard* pb = [info draggingPasteboard]; | |
306 | |
307 // Check to see what we are proposed to be dropping on | |
308 const BookmarkNode*targetNode = [self nodeFromItem:item]; | |
309 if (!targetNode->is_folder()) { | |
310 // The target node is not a container, but a leaf. | |
311 // Refuse the drop (we may get called again with a between) | |
312 if (childIndex == NSOutlineViewDropOnItemIndex) { | |
313 return NSDragOperationNone; | |
314 } | |
315 } | |
316 | |
317 // Dragging within the outline? | |
318 if ([info draggingSource] == outlineView && | |
319 [[pb types] containsObject:kCustomPboardType]) { | |
320 // If we are allowing the drop, we see if we are dragging from ourselves | |
321 // and dropping into a descendent, which wouldn't be allowed... | |
322 // See if the appropriate drag information is available on the pasteboard. | |
323 //TODO(snej): Re-implement this | |
324 /* | |
325 if (targetNode != group_ && | |
326 [[[info draggingPasteboard] types] containsObject:kCustomPboardType]) { | |
327 for (NSDictionary* draggedNode in draggedNodes_) { | |
328 if ([self treeNode:targetNode isDescendantOfNode:draggedNode]) { | |
329 // Yup, it is, refuse it. | |
330 return NSDragOperationNone; | |
331 break; | |
332 } | |
333 } | |
334 */ | |
335 return NSDragOperationMove; | |
336 } | |
337 | |
338 // Drag from elsewhere is a copy. | |
339 return NSDragOperationCopy; | |
340 } | |
341 | |
342 // Actually handles the drop. | |
343 - (BOOL)outlineView:(NSOutlineView*)outlineView | |
344 acceptDrop:(id <NSDraggingInfo>)info | |
345 item:(id)item | |
346 childIndex:(NSInteger)childIndex | |
347 { | |
348 NSPasteboard* pb = [info draggingPasteboard]; | |
349 | |
350 const BookmarkNode* targetNode = [self nodeFromItem:item]; | |
351 | |
352 // Determine the parent to insert into and the child index to insert at. | |
353 if (!targetNode->is_folder()) { | |
354 // If our target is a leaf, and we are dropping on it. | |
355 if (childIndex == NSOutlineViewDropOnItemIndex) { | |
356 return NO; | |
357 } else { | |
358 // We will be dropping on the item's parent at the target index | |
359 // of this child, plus one. | |
360 const BookmarkNode* oldTargetNode = targetNode; | |
361 targetNode = targetNode->GetParent(); | |
362 childIndex = targetNode->IndexOfChild(oldTargetNode) + 1; | |
363 } | |
364 } else { | |
365 if (childIndex == NSOutlineViewDropOnItemIndex) { | |
366 // Insert it at the end, if we were dropping on it | |
367 childIndex = targetNode->GetChildCount(); | |
368 } | |
369 } | |
370 | |
371 if ([info draggingSource] == outlineView && | |
372 [[pb types] containsObject:kCustomPboardType]) { | |
373 // If the source was ourselves, move the selected nodes. | |
374 [self moveNodes:draggedNodes_ | |
375 toFolder:targetNode | |
376 atIndex:childIndex]; | |
377 } else { | |
378 NSArray* plist = [self readPropertyListFromPasteboard:pb]; | |
379 if (!plist) { | |
380 return NO; | |
381 } | |
382 [self insertPropertyList:plist | |
383 inFolder:targetNode | |
384 atIndex:childIndex]; | |
385 } | |
386 return YES; | |
387 } | |
388 | |
389 | |
390 // The Paste command. | |
391 - (IBAction)paste:(id)sender { | |
392 if (![self pasteFromPasteboard:[NSPasteboard generalPasteboard]]) | |
393 NSBeep(); | |
394 } | |
395 | |
396 - (BOOL)pasteFromPasteboard:(NSPasteboard*)pb { | |
397 NSArray* plist = [self readPropertyListFromPasteboard: pb]; | |
398 if (!plist) | |
399 return NO; | |
400 | |
401 const BookmarkNode* targetNode; | |
402 NSInteger childIndex; | |
403 int selRow = [outline_ selectedRow]; | |
404 if (selRow >= 0) { | |
405 // Insert after selected row. | |
406 const BookmarkNode* selNode = [self nodeFromItem: | |
407 [outline_ itemAtRow:selRow]]; | |
408 targetNode = selNode->GetParent(); | |
409 childIndex = targetNode->IndexOfChild(selNode) + 1; | |
410 } else { | |
411 // ...or at very end if there's no selection: | |
412 targetNode = [self nodeFromItem:group_]; | |
413 childIndex = targetNode->GetChildCount(); | |
414 } | |
415 | |
416 [self insertPropertyList:plist | |
417 inFolder:targetNode | |
418 atIndex:childIndex]; | |
419 return YES; | |
420 } | |
421 | |
422 | |
423 // Selectively enables/disables menu commands. | |
424 - (BOOL)validateMenuItem:(NSMenuItem*)menuItem { | |
425 SEL action = [menuItem action]; | |
426 if (action == @selector(cut:) || action == @selector(copy:) || | |
427 action == @selector(delete:)) { | |
428 return [[outline_ selectedRowIndexes] count] > 0; | |
429 } else if (action == @selector(paste:)) { | |
430 return [[NSPasteboard generalPasteboard] | |
431 availableTypeFromArray:[outline_ registeredDraggedTypes]] | |
432 != nil; | |
433 } else { | |
434 return YES; | |
435 } | |
436 } | |
437 | |
438 | |
439 @end | |
OLD | NEW |