1/****************************************************************************** 2 * $Id: FileOutlineController.m 13340 2012-06-10 02:35:58Z livings124 $ 3 * 4 * Copyright (c) 2008-2012 Transmission authors and contributors 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining a 7 * copy of this software and associated documentation files (the "Software"), 8 * to deal in the Software without restriction, including without limitation 9 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 * and/or sell copies of the Software, and to permit persons to whom the 11 * Software is furnished to do so, subject to the following conditions: 12 * 13 * The above copyright notice and this permission notice shall be included in 14 * all copies or substantial portions of the Software. 15 * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 22 * DEALINGS IN THE SOFTWARE. 23 *****************************************************************************/ 24 25#import "FileOutlineController.h" 26#import "Torrent.h" 27#import "FileOutlineView.h" 28#import "FilePriorityCell.h" 29#import "FileListNode.h" 30#import "NSApplicationAdditions.h" 31#import "NSMutableArrayAdditions.h" 32#import "NSStringAdditions.h" 33#import <Quartz/Quartz.h> 34 35#define ROW_SMALL_HEIGHT 18.0 36 37typedef enum 38{ 39 FILE_CHECK_TAG, 40 FILE_UNCHECK_TAG 41} fileCheckMenuTag; 42 43typedef enum 44{ 45 FILE_PRIORITY_HIGH_TAG, 46 FILE_PRIORITY_NORMAL_TAG, 47 FILE_PRIORITY_LOW_TAG 48} filePriorityMenuTag; 49 50@interface FileOutlineController (Private) 51 52- (NSMenu *) menu; 53 54- (NSUInteger) findFileNode: (FileListNode *) node inList: (NSArray *) list atIndexes: (NSIndexSet *) range currentParent: (FileListNode *) currentParent finalParent: (FileListNode **) parent; 55 56@end 57 58@implementation FileOutlineController 59 60- (void) awakeFromNib 61{ 62 fFileList = [[NSMutableArray alloc] init]; 63 64 [fOutline setDoubleAction: @selector(revealFile:)]; 65 [fOutline setTarget: self]; 66 67 //set table header tool tips 68 [[fOutline tableColumnWithIdentifier: @"Check"] setHeaderToolTip: NSLocalizedString(@"Download", "file table -> header tool tip")]; 69 [[fOutline tableColumnWithIdentifier: @"Priority"] setHeaderToolTip: NSLocalizedString(@"Priority", "file table -> header tool tip")]; 70 71 [fOutline setMenu: [self menu]]; 72 73 [self setTorrent: nil]; 74} 75 76- (void) dealloc 77{ 78 [fFileList release]; 79 [fFilterText release]; 80 81 [super dealloc]; 82} 83 84- (FileOutlineView *) outlineView 85{ 86 return fOutline; 87} 88 89- (void) setTorrent: (Torrent *) torrent 90{ 91 fTorrent = torrent; 92 93 [fFileList setArray: [fTorrent fileList]]; 94 95 [fFilterText release]; 96 fFilterText = nil; 97 98 [fOutline reloadData]; 99 [fOutline deselectAll: nil]; //do this after reloading the data #4575 100} 101 102- (void) setFilterText: (NSString *) text 103{ 104 NSArray * components = [text betterComponentsSeparatedByCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]]; 105 if (!components || [components count] == 0) 106 { 107 text = nil; 108 components = nil; 109 } 110 111 if ((!text && !fFilterText) || (text && fFilterText && [text isEqualToString: fFilterText])) 112 return; 113 114 const BOOL onLion = [NSApp isOnLionOrBetter]; 115 116 if (onLion) 117 [fOutline beginUpdates]; 118 119 NSUInteger currentIndex = 0, totalCount = 0; 120 NSMutableArray * itemsToAdd = [NSMutableArray array]; 121 NSMutableIndexSet * itemsToAddIndexes = [NSMutableIndexSet indexSet]; 122 123 NSMutableDictionary * removedIndexesForParents = nil; //ugly, but we can't modify the actual file nodes 124 125 NSArray * tempList = !text ? [fTorrent fileList] : [fTorrent flatFileList]; 126 for (FileListNode * item in tempList) 127 { 128 __block BOOL filter = NO; 129 if (components) 130 { 131 [components enumerateObjectsWithOptions: NSEnumerationConcurrent usingBlock: ^(id obj, NSUInteger idx, BOOL * stop) { 132 if ([[item name] rangeOfString: (NSString *)obj options: (NSCaseInsensitiveSearch | NSDiacriticInsensitiveSearch)].location == NSNotFound) 133 { 134 filter = YES; 135 *stop = YES; 136 } 137 }]; 138 } 139 140 if (!filter) 141 { 142 FileListNode * parent = nil; 143 NSUInteger previousIndex = ![item isFolder] ? [self findFileNode: item inList: fFileList atIndexes: [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(currentIndex, [fFileList count]-currentIndex)] currentParent: nil finalParent: &parent] : NSNotFound; 144 145 if (previousIndex == NSNotFound) 146 { 147 [itemsToAdd addObject: item]; 148 [itemsToAddIndexes addIndex: totalCount]; 149 } 150 else 151 { 152 BOOL move = YES; 153 if (!parent) 154 { 155 if (previousIndex != currentIndex) 156 [fFileList moveObjectAtIndex: previousIndex toIndex: currentIndex]; 157 else 158 move = NO; 159 } 160 else 161 { 162 [fFileList insertObject: item atIndex: currentIndex]; 163 164 //figure out the index within the semi-edited table - UGLY 165 if (!removedIndexesForParents) 166 removedIndexesForParents = [NSMutableDictionary dictionary]; 167 168 NSMutableIndexSet * removedIndexes = [removedIndexesForParents objectForKey: parent]; 169 if (!removedIndexes) 170 { 171 removedIndexes = [NSMutableIndexSet indexSetWithIndex: previousIndex]; 172 [removedIndexesForParents setObject: removedIndexes forKey: parent]; 173 } 174 else 175 { 176 [removedIndexes addIndex: previousIndex]; 177 previousIndex -= [removedIndexes countOfIndexesInRange: NSMakeRange(0, previousIndex)]; 178 } 179 } 180 181 if (move && onLion) 182 [fOutline moveItemAtIndex: previousIndex inParent: parent toIndex: currentIndex inParent: nil]; 183 184 ++currentIndex; 185 } 186 187 ++totalCount; 188 } 189 } 190 191 //remove trailing items - those are the unused 192 if (currentIndex < [fFileList count]) 193 { 194 const NSRange removeRange = NSMakeRange(currentIndex, [fFileList count]-currentIndex); 195 [fFileList removeObjectsInRange: removeRange]; 196 if (onLion) 197 [fOutline removeItemsAtIndexes: [NSIndexSet indexSetWithIndexesInRange: removeRange] inParent: nil withAnimation: NSTableViewAnimationSlideDown]; 198 } 199 200 //add new items 201 [fFileList insertObjects: itemsToAdd atIndexes: itemsToAddIndexes]; 202 if (onLion) 203 [fOutline insertItemsAtIndexes: itemsToAddIndexes inParent: nil withAnimation: NSTableViewAnimationSlideUp]; 204 205 if (onLion) 206 [fOutline endUpdates]; 207 else 208 [fOutline reloadData]; 209 210 [fFilterText release]; 211 fFilterText = [text retain]; 212} 213 214- (void) refresh 215{ 216 [fTorrent updateFileStat]; 217 218 [fOutline setNeedsDisplay: YES]; 219} 220 221- (void) outlineViewSelectionDidChange: (NSNotification *) notification 222{ 223 if ([QLPreviewPanel sharedPreviewPanelExists] && [[QLPreviewPanel sharedPreviewPanel] isVisible]) 224 [[QLPreviewPanel sharedPreviewPanel] reloadData]; 225} 226 227- (NSInteger) outlineView: (NSOutlineView *) outlineView numberOfChildrenOfItem: (id) item 228{ 229 if (!item) 230 return fFileList ? [fFileList count] : 0; 231 else 232 { 233 FileListNode * node = (FileListNode *)item; 234 return [node isFolder] ? [[node children] count] : 0; 235 } 236} 237 238- (BOOL) outlineView: (NSOutlineView *) outlineView isItemExpandable: (id) item 239{ 240 return [(FileListNode *)item isFolder]; 241} 242 243- (id) outlineView: (NSOutlineView *) outlineView child: (NSInteger) index ofItem: (id) item 244{ 245 return [(item ? [(FileListNode *)item children] : fFileList) objectAtIndex: index]; 246} 247 248- (id) outlineView: (NSOutlineView *) outlineView objectValueForTableColumn: (NSTableColumn *) tableColumn byItem: (id) item 249{ 250 if ([[tableColumn identifier] isEqualToString: @"Check"]) 251 return [NSNumber numberWithInteger: [fTorrent checkForFiles: [(FileListNode *)item indexes]]]; 252 else 253 return item; 254} 255 256- (void) outlineView: (NSOutlineView *) outlineView willDisplayCell: (id) cell forTableColumn: (NSTableColumn *) tableColumn item: (id) item 257{ 258 NSString * identifier = [tableColumn identifier]; 259 if ([identifier isEqualToString: @"Check"]) 260 [cell setEnabled: [fTorrent canChangeDownloadCheckForFiles: [(FileListNode *)item indexes]]]; 261 else if ([identifier isEqualToString: @"Priority"]) 262 { 263 [cell setRepresentedObject: item]; 264 265 NSInteger hoveredRow = [fOutline hoveredRow]; 266 [(FilePriorityCell *)cell setHovered: hoveredRow != -1 && hoveredRow == [fOutline rowForItem: item]]; 267 } 268 else; 269} 270 271- (void) outlineView: (NSOutlineView *) outlineView setObjectValue: (id) object forTableColumn: (NSTableColumn *) tableColumn byItem: (id) item 272{ 273 NSString * identifier = [tableColumn identifier]; 274 if ([identifier isEqualToString: @"Check"]) 275 { 276 NSIndexSet * indexSet; 277 if ([NSEvent modifierFlags] & NSAlternateKeyMask) 278 indexSet = [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [fTorrent fileCount])]; 279 else 280 indexSet = [(FileListNode *)item indexes]; 281 282 [fTorrent setFileCheckState: [object intValue] != NSOffState ? NSOnState : NSOffState forIndexes: indexSet]; 283 [fOutline setNeedsDisplay: YES]; 284 285 [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateUI" object: nil]; 286 } 287} 288 289- (NSString *) outlineView: (NSOutlineView *) outlineView typeSelectStringForTableColumn: (NSTableColumn *) tableColumn item: (id) item 290{ 291 return [(FileListNode *)item name]; 292} 293 294- (NSString *) outlineView: (NSOutlineView *) outlineView toolTipForCell: (NSCell *) cell rect: (NSRectPointer) rect 295 tableColumn: (NSTableColumn *) tableColumn item: (id) item mouseLocation: (NSPoint) mouseLocation 296{ 297 NSString * ident = [tableColumn identifier]; 298 if ([ident isEqualToString: @"Name"]) 299 { 300 NSString * path = [fTorrent fileLocation: item]; 301 if (!path) 302 path = [[(FileListNode *)item path] stringByAppendingPathComponent: [(FileListNode *)item name]]; 303 return path; 304 } 305 else if ([ident isEqualToString: @"Check"]) 306 { 307 switch ([cell state]) 308 { 309 case NSOffState: 310 return NSLocalizedString(@"Don't Download", "files tab -> tooltip"); 311 case NSOnState: 312 return NSLocalizedString(@"Download", "files tab -> tooltip"); 313 case NSMixedState: 314 return NSLocalizedString(@"Download Some", "files tab -> tooltip"); 315 } 316 } 317 else if ([ident isEqualToString: @"Priority"]) 318 { 319 NSSet * priorities = [fTorrent filePrioritiesForIndexes: [(FileListNode *)item indexes]]; 320 switch ([priorities count]) 321 { 322 case 0: 323 return NSLocalizedString(@"Priority Not Available", "files tab -> tooltip"); 324 case 1: 325 switch ([[priorities anyObject] intValue]) 326 { 327 case TR_PRI_LOW: 328 return NSLocalizedString(@"Low Priority", "files tab -> tooltip"); 329 case TR_PRI_HIGH: 330 return NSLocalizedString(@"High Priority", "files tab -> tooltip"); 331 case TR_PRI_NORMAL: 332 return NSLocalizedString(@"Normal Priority", "files tab -> tooltip"); 333 } 334 break; 335 default: 336 return NSLocalizedString(@"Multiple Priorities", "files tab -> tooltip"); 337 } 338 } 339 else; 340 341 return nil; 342} 343 344- (CGFloat) outlineView: (NSOutlineView *) outlineView heightOfRowByItem: (id) item 345{ 346 if ([(FileListNode *)item isFolder]) 347 return ROW_SMALL_HEIGHT; 348 else 349 return [outlineView rowHeight]; 350} 351 352- (void) setCheck: (id) sender 353{ 354 NSInteger state = [sender tag] == FILE_UNCHECK_TAG ? NSOffState : NSOnState; 355 356 NSIndexSet * indexSet = [fOutline selectedRowIndexes]; 357 NSMutableIndexSet * itemIndexes = [NSMutableIndexSet indexSet]; 358 for (NSInteger i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i]) 359 [itemIndexes addIndexes: [[fOutline itemAtRow: i] indexes]]; 360 361 [fTorrent setFileCheckState: state forIndexes: itemIndexes]; 362 [fOutline setNeedsDisplay: YES]; 363} 364 365- (void) setOnlySelectedCheck: (id) sender 366{ 367 NSIndexSet * indexSet = [fOutline selectedRowIndexes]; 368 NSMutableIndexSet * itemIndexes = [NSMutableIndexSet indexSet]; 369 for (NSInteger i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i]) 370 [itemIndexes addIndexes: [[fOutline itemAtRow: i] indexes]]; 371 372 [fTorrent setFileCheckState: NSOnState forIndexes: itemIndexes]; 373 374 NSMutableIndexSet * remainingItemIndexes = [NSMutableIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [fTorrent fileCount])]; 375 [remainingItemIndexes removeIndexes: itemIndexes]; 376 [fTorrent setFileCheckState: NSOffState forIndexes: remainingItemIndexes]; 377 378 [fOutline setNeedsDisplay: YES]; 379} 380 381- (void) checkAll 382{ 383 NSIndexSet * indexSet = [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [fTorrent fileCount])]; 384 [fTorrent setFileCheckState: NSOnState forIndexes: indexSet]; 385 [fOutline setNeedsDisplay: YES]; 386} 387 388- (void) uncheckAll 389{ 390 NSIndexSet * indexSet = [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [fTorrent fileCount])]; 391 [fTorrent setFileCheckState: NSOffState forIndexes: indexSet]; 392 [fOutline setNeedsDisplay: YES]; 393} 394 395- (void) setPriority: (id) sender 396{ 397 tr_priority_t priority; 398 switch ([sender tag]) 399 { 400 case FILE_PRIORITY_HIGH_TAG: 401 priority = TR_PRI_HIGH; 402 break; 403 case FILE_PRIORITY_NORMAL_TAG: 404 priority = TR_PRI_NORMAL; 405 break; 406 case FILE_PRIORITY_LOW_TAG: 407 priority = TR_PRI_LOW; 408 } 409 410 NSIndexSet * indexSet = [fOutline selectedRowIndexes]; 411 NSMutableIndexSet * itemIndexes = [NSMutableIndexSet indexSet]; 412 for (NSInteger i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i]) 413 [itemIndexes addIndexes: [[fOutline itemAtRow: i] indexes]]; 414 415 [fTorrent setFilePriority: priority forIndexes: itemIndexes]; 416 [fOutline setNeedsDisplay: YES]; 417} 418 419- (void) revealFile: (id) sender 420{ 421 NSIndexSet * indexes = [fOutline selectedRowIndexes]; 422 NSMutableArray * paths = [NSMutableArray arrayWithCapacity: [indexes count]]; 423 for (NSUInteger i = [indexes firstIndex]; i != NSNotFound; i = [indexes indexGreaterThanIndex: i]) 424 { 425 NSString * path = [fTorrent fileLocation: [fOutline itemAtRow: i]]; 426 if (path) 427 [paths addObject: [NSURL fileURLWithPath: path]]; 428 } 429 430 if ([paths count] > 0) 431 [[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs: paths]; 432} 433 434#warning make real view controller (Leopard-only) so that Command-R will work 435- (BOOL) validateMenuItem: (NSMenuItem *) menuItem 436{ 437 if (!fTorrent) 438 return NO; 439 440 SEL action = [menuItem action]; 441 442 if (action == @selector(revealFile:)) 443 { 444 NSIndexSet * indexSet = [fOutline selectedRowIndexes]; 445 for (NSInteger i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i]) 446 if ([fTorrent fileLocation: [fOutline itemAtRow: i]] != nil) 447 return YES; 448 return NO; 449 } 450 451 if (action == @selector(setCheck:)) 452 { 453 if ([fOutline numberOfSelectedRows] == 0) 454 return NO; 455 456 NSIndexSet * indexSet = [fOutline selectedRowIndexes]; 457 NSMutableIndexSet * itemIndexes = [NSMutableIndexSet indexSet]; 458 for (NSInteger i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i]) 459 [itemIndexes addIndexes: [[fOutline itemAtRow: i] indexes]]; 460 461 NSInteger state = ([menuItem tag] == FILE_CHECK_TAG) ? NSOnState : NSOffState; 462 return [fTorrent checkForFiles: itemIndexes] != state && [fTorrent canChangeDownloadCheckForFiles: itemIndexes]; 463 } 464 465 if (action == @selector(setOnlySelectedCheck:)) 466 { 467 if ([fOutline numberOfSelectedRows] == 0) 468 return NO; 469 470 NSIndexSet * indexSet = [fOutline selectedRowIndexes]; 471 NSMutableIndexSet * itemIndexes = [NSMutableIndexSet indexSet]; 472 for (NSInteger i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i]) 473 [itemIndexes addIndexes: [[fOutline itemAtRow: i] indexes]]; 474 475 return [fTorrent canChangeDownloadCheckForFiles: itemIndexes]; 476 } 477 478 if (action == @selector(setPriority:)) 479 { 480 if ([fOutline numberOfSelectedRows] == 0) 481 { 482 [menuItem setState: NSOffState]; 483 return NO; 484 } 485 486 //determine which priorities are checked 487 NSIndexSet * indexSet = [fOutline selectedRowIndexes]; 488 tr_priority_t priority; 489 switch ([menuItem tag]) 490 { 491 case FILE_PRIORITY_HIGH_TAG: 492 priority = TR_PRI_HIGH; 493 break; 494 case FILE_PRIORITY_NORMAL_TAG: 495 priority = TR_PRI_NORMAL; 496 break; 497 case FILE_PRIORITY_LOW_TAG: 498 priority = TR_PRI_LOW; 499 break; 500 } 501 502 BOOL current = NO, canChange = NO; 503 for (NSInteger i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i]) 504 { 505 NSIndexSet * fileIndexSet = [[fOutline itemAtRow: i] indexes]; 506 if (![fTorrent canChangeDownloadCheckForFiles: fileIndexSet]) 507 continue; 508 509 canChange = YES; 510 if ([fTorrent hasFilePriority: priority forIndexes: fileIndexSet]) 511 { 512 current = YES; 513 break; 514 } 515 } 516 517 [menuItem setState: current ? NSOnState : NSOffState]; 518 return canChange; 519 } 520 521 return YES; 522} 523 524@end 525 526@implementation FileOutlineController (Private) 527 528- (NSMenu *) menu 529{ 530 NSMenu * menu = [[NSMenu alloc] initWithTitle: @"File Outline Menu"]; 531 532 //check and uncheck 533 NSMenuItem * item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString(@"Check Selected", "File Outline -> Menu") 534 action: @selector(setCheck:) keyEquivalent: @""]; 535 [item setTarget: self]; 536 [item setTag: FILE_CHECK_TAG]; 537 [menu addItem: item]; 538 [item release]; 539 540 item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString(@"Uncheck Selected", "File Outline -> Menu") 541 action: @selector(setCheck:) keyEquivalent: @""]; 542 [item setTarget: self]; 543 [item setTag: FILE_UNCHECK_TAG]; 544 [menu addItem: item]; 545 [item release]; 546 547 //only check selected 548 item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString(@"Only Check Selected", "File Outline -> Menu") 549 action: @selector(setOnlySelectedCheck:) keyEquivalent: @""]; 550 [item setTarget: self]; 551 [menu addItem: item]; 552 [item release]; 553 554 [menu addItem: [NSMenuItem separatorItem]]; 555 556 //priority 557 item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString(@"Priority", "File Outline -> Menu") action: NULL keyEquivalent: @""]; 558 NSMenu * priorityMenu = [[NSMenu alloc] initWithTitle: @"File Priority Menu"]; 559 [item setSubmenu: priorityMenu]; 560 [menu addItem: item]; 561 [item release]; 562 563 item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString(@"High", "File Outline -> Priority Menu") 564 action: @selector(setPriority:) keyEquivalent: @""]; 565 [item setTarget: self]; 566 [item setTag: FILE_PRIORITY_HIGH_TAG]; 567 [item setImage: [NSImage imageNamed: @"PriorityHighTemplate"]]; 568 [priorityMenu addItem: item]; 569 [item release]; 570 571 item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString(@"Normal", "File Outline -> Priority Menu") 572 action: @selector(setPriority:) keyEquivalent: @""]; 573 [item setTarget: self]; 574 [item setTag: FILE_PRIORITY_NORMAL_TAG]; 575 [item setImage: [NSImage imageNamed: @"PriorityNormalTemplate"]]; 576 [priorityMenu addItem: item]; 577 [item release]; 578 579 item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString(@"Low", "File Outline -> Priority Menu") 580 action: @selector(setPriority:) keyEquivalent: @""]; 581 [item setTarget: self]; 582 [item setTag: FILE_PRIORITY_LOW_TAG]; 583 [item setImage: [NSImage imageNamed: @"PriorityLowTemplate"]]; 584 [priorityMenu addItem: item]; 585 [item release]; 586 587 [priorityMenu release]; 588 589 [menu addItem: [NSMenuItem separatorItem]]; 590 591 //reveal in finder 592 item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString(@"Show in Finder", "File Outline -> Menu") 593 action: @selector(revealFile:) keyEquivalent: @""]; 594 [item setTarget: self]; 595 [menu addItem: item]; 596 [item release]; 597 598 return [menu autorelease]; 599} 600 601- (NSUInteger) findFileNode: (FileListNode *) node inList: (NSArray *) list atIndexes: (NSIndexSet *) indexes currentParent: (FileListNode *) currentParent finalParent: (FileListNode **) parent 602{ 603 NSAssert(![node isFolder], @"Looking up folder node!"); 604 605 __block NSUInteger retIndex = NSNotFound; 606 607 [list enumerateObjectsAtIndexes: indexes options: NSEnumerationConcurrent usingBlock: ^(id checkNode, NSUInteger index, BOOL * stop) { 608 if ([[checkNode indexes] containsIndex: [[node indexes] firstIndex]]) 609 { 610 if (![checkNode isFolder]) 611 { 612 NSAssert2([checkNode isEqualTo: node], @"Expected file nodes to be equal: %@ %@", checkNode, node); 613 614 *parent = currentParent; 615 retIndex = index; 616 } 617 else 618 { 619 const NSUInteger subIndex = [self findFileNode: node inList: [checkNode children] atIndexes: [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [[checkNode children] count])] currentParent: checkNode finalParent: parent]; 620 NSAssert(subIndex != NSNotFound, @"We didn't find an expected file node."); 621 retIndex = subIndex; 622 } 623 624 *stop = YES; 625 } 626 }]; 627 628 return retIndex; 629} 630 631@end 632