Chromium Code Reviews| 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 |