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