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

Side by Side 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, 11 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 unified diff | Download patch
OLDNEW
(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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698