1 /******************************************************************************
2 * $Id: FileOutlineController.m 13251 2012-03-13 02:52:11Z livings124 $
4 * Copyright (c) 2008-2012 Transmission authors and contributors
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:
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
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 *****************************************************************************/
25 #import "FileOutlineController.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>
35 #define ROW_SMALL_HEIGHT 18.0
45 FILE_PRIORITY_HIGH_TAG,
46 FILE_PRIORITY_NORMAL_TAG,
48 } filePriorityMenuTag;
50 @interface FileOutlineController (Private)
54 - (NSUInteger) findFileNode: (FileListNode *) node inList: (NSArray *) list atIndexes: (NSIndexSet *) range currentParent: (FileListNode *) currentParent finalParent: (FileListNode **) parent;
58 @implementation FileOutlineController
62 fFileList = [[NSMutableArray alloc] init];
64 [fOutline setDoubleAction: @selector(revealFile:)];
65 [fOutline setTarget: self];
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")];
71 [fOutline setMenu: [self menu]];
73 [self setTorrent: nil];
79 [fFilterText release];
84 - (FileOutlineView *) outlineView
89 - (void) setTorrent: (Torrent *) torrent
93 [fFileList setArray: [fTorrent fileList]];
95 [fFilterText release];
98 [fOutline reloadData];
99 [fOutline deselectAll: nil]; //do this after reloading the data #4575
102 - (void) setFilterText: (NSString *) text
104 NSArray * components = [text betterComponentsSeparatedByCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
105 if (!components || [components count] == 0)
111 if ((!text && !fFilterText) || (text && fFilterText && [text isEqualToString: fFilterText]))
114 const BOOL onLion = [NSApp isOnLionOrBetter];
117 [fOutline beginUpdates];
119 NSUInteger currentIndex = 0, totalCount = 0;
120 NSMutableArray * itemsToAdd = [NSMutableArray array];
121 NSMutableIndexSet * itemsToAddIndexes = [NSMutableIndexSet indexSet];
123 NSMutableDictionary * removedIndexesForParents = nil; //ugly, but we can't modify the actual file nodes
125 NSArray * tempList = !text ? [fTorrent fileList] : [fTorrent flatFileList];
126 for (FileListNode * item in tempList)
128 __block BOOL filter = NO;
131 [components enumerateObjectsWithOptions: NSEnumerationConcurrent usingBlock: ^(id obj, NSUInteger idx, BOOL * stop) {
132 if ([[item name] rangeOfString: (NSString *)obj options: (NSCaseInsensitiveSearch | NSDiacriticInsensitiveSearch)].location == NSNotFound)
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;
145 if (previousIndex == NSNotFound)
147 [itemsToAdd addObject: item];
148 [itemsToAddIndexes addIndex: totalCount];
155 if (previousIndex != currentIndex)
156 [fFileList moveObjectAtIndex: previousIndex toIndex: currentIndex];
162 [fFileList insertObject: item atIndex: currentIndex];
164 //figure out the index within the semi-edited table - UGLY
165 if (!removedIndexesForParents)
166 removedIndexesForParents = [NSMutableDictionary dictionary];
168 NSMutableIndexSet * removedIndexes = [removedIndexesForParents objectForKey: parent];
171 removedIndexes = [NSMutableIndexSet indexSetWithIndex: previousIndex];
172 [removedIndexesForParents setObject: removedIndexes forKey: parent];
176 [removedIndexes addIndex: previousIndex];
177 previousIndex -= [removedIndexes countOfIndexesInRange: NSMakeRange(0, previousIndex)];
182 [fOutline moveItemAtIndex: previousIndex inParent: parent toIndex: currentIndex inParent: nil];
191 //remove trailing items - those are the unused
192 if (currentIndex < [fFileList count])
194 const NSRange removeRange = NSMakeRange(currentIndex, [fFileList count]-currentIndex);
195 [fFileList removeObjectsInRange: removeRange];
197 [fOutline removeItemsAtIndexes: [NSIndexSet indexSetWithIndexesInRange: removeRange] inParent: nil withAnimation: NSTableViewAnimationSlideDown];
201 [fFileList insertObjects: itemsToAdd atIndexes: itemsToAddIndexes];
203 [fOutline insertItemsAtIndexes: itemsToAddIndexes inParent: nil withAnimation: NSTableViewAnimationSlideUp];
206 [fOutline endUpdates];
208 [fOutline reloadData];
210 [fFilterText release];
211 fFilterText = [text retain];
216 [fTorrent updateFileStat];
218 [fOutline setNeedsDisplay: YES];
221 - (void) outlineViewSelectionDidChange: (NSNotification *) notification
223 if ([QLPreviewPanel sharedPreviewPanelExists] && [[QLPreviewPanel sharedPreviewPanel] isVisible])
224 [[QLPreviewPanel sharedPreviewPanel] reloadData];
227 - (NSInteger) outlineView: (NSOutlineView *) outlineView numberOfChildrenOfItem: (id) item
230 return fFileList ? [fFileList count] : 0;
233 FileListNode * node = (FileListNode *)item;
234 return [node isFolder] ? [[node children] count] : 0;
238 - (BOOL) outlineView: (NSOutlineView *) outlineView isItemExpandable: (id) item
240 return [(FileListNode *)item isFolder];
243 - (id) outlineView: (NSOutlineView *) outlineView child: (NSInteger) index ofItem: (id) item
245 return [(item ? [(FileListNode *)item children] : fFileList) objectAtIndex: index];
248 - (id) outlineView: (NSOutlineView *) outlineView objectValueForTableColumn: (NSTableColumn *) tableColumn byItem: (id) item
250 if ([[tableColumn identifier] isEqualToString: @"Check"])
251 return [NSNumber numberWithInteger: [fTorrent checkForFiles: [(FileListNode *)item indexes]]];
256 - (void) outlineView: (NSOutlineView *) outlineView willDisplayCell: (id) cell forTableColumn: (NSTableColumn *) tableColumn item: (id) item
258 NSString * identifier = [tableColumn identifier];
259 if ([identifier isEqualToString: @"Check"])
260 [cell setEnabled: [fTorrent canChangeDownloadCheckForFiles: [(FileListNode *)item indexes]]];
261 else if ([identifier isEqualToString: @"Priority"])
263 [cell setRepresentedObject: item];
265 NSInteger hoveredRow = [fOutline hoveredRow];
266 [(FilePriorityCell *)cell setHovered: hoveredRow != -1 && hoveredRow == [fOutline rowForItem: item]];
271 - (void) outlineView: (NSOutlineView *) outlineView setObjectValue: (id) object forTableColumn: (NSTableColumn *) tableColumn byItem: (id) item
273 NSString * identifier = [tableColumn identifier];
274 if ([identifier isEqualToString: @"Check"])
276 NSIndexSet * indexSet;
277 if ([NSEvent modifierFlags] & NSAlternateKeyMask)
278 indexSet = [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [fTorrent fileCount])];
280 indexSet = [(FileListNode *)item indexes];
282 [fTorrent setFileCheckState: [object intValue] != NSOffState ? NSOnState : NSOffState forIndexes: indexSet];
283 [fOutline setNeedsDisplay: YES];
285 [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateUI" object: nil];
289 - (NSString *) outlineView: (NSOutlineView *) outlineView typeSelectStringForTableColumn: (NSTableColumn *) tableColumn item: (id) item
291 return [(FileListNode *)item name];
294 - (NSString *) outlineView: (NSOutlineView *) outlineView toolTipForCell: (NSCell *) cell rect: (NSRectPointer) rect
295 tableColumn: (NSTableColumn *) tableColumn item: (id) item mouseLocation: (NSPoint) mouseLocation
297 NSString * ident = [tableColumn identifier];
298 if ([ident isEqualToString: @"Name"])
300 NSString * path = [fTorrent fileLocation: item];
302 path = [[(FileListNode *)item path] stringByAppendingPathComponent: [(FileListNode *)item name]];
305 else if ([ident isEqualToString: @"Check"])
307 switch ([cell state])
310 return NSLocalizedString(@"Don't Download", "files tab -> tooltip");
312 return NSLocalizedString(@"Download", "files tab -> tooltip");
314 return NSLocalizedString(@"Download Some", "files tab -> tooltip");
317 else if ([ident isEqualToString: @"Priority"])
319 NSSet * priorities = [fTorrent filePrioritiesForIndexes: [(FileListNode *)item indexes]];
320 switch ([priorities count])
323 return NSLocalizedString(@"Priority Not Available", "files tab -> tooltip");
325 switch ([[priorities anyObject] intValue])
328 return NSLocalizedString(@"Low Priority", "files tab -> tooltip");
330 return NSLocalizedString(@"High Priority", "files tab -> tooltip");
332 return NSLocalizedString(@"Normal Priority", "files tab -> tooltip");
336 return NSLocalizedString(@"Multiple Priorities", "files tab -> tooltip");
344 - (CGFloat) outlineView: (NSOutlineView *) outlineView heightOfRowByItem: (id) item
346 if ([(FileListNode *)item isFolder])
347 return ROW_SMALL_HEIGHT;
349 return [outlineView rowHeight];
352 - (void) setCheck: (id) sender
354 NSInteger state = [sender tag] == FILE_UNCHECK_TAG ? NSOffState : NSOnState;
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]];
361 [fTorrent setFileCheckState: state forIndexes: itemIndexes];
362 [fOutline setNeedsDisplay: YES];
365 - (void) setOnlySelectedCheck: (id) sender
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]];
372 [fTorrent setFileCheckState: NSOnState forIndexes: itemIndexes];
374 NSMutableIndexSet * remainingItemIndexes = [NSMutableIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [fTorrent fileCount])];
375 [remainingItemIndexes removeIndexes: itemIndexes];
376 [fTorrent setFileCheckState: NSOffState forIndexes: remainingItemIndexes];
378 [fOutline setNeedsDisplay: YES];
381 - (void) setPriority: (id) sender
383 tr_priority_t priority;
384 switch ([sender tag])
386 case FILE_PRIORITY_HIGH_TAG:
387 priority = TR_PRI_HIGH;
389 case FILE_PRIORITY_NORMAL_TAG:
390 priority = TR_PRI_NORMAL;
392 case FILE_PRIORITY_LOW_TAG:
393 priority = TR_PRI_LOW;
396 NSIndexSet * indexSet = [fOutline selectedRowIndexes];
397 NSMutableIndexSet * itemIndexes = [NSMutableIndexSet indexSet];
398 for (NSInteger i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
399 [itemIndexes addIndexes: [[fOutline itemAtRow: i] indexes]];
401 [fTorrent setFilePriority: priority forIndexes: itemIndexes];
402 [fOutline setNeedsDisplay: YES];
405 - (void) revealFile: (id) sender
407 NSIndexSet * indexes = [fOutline selectedRowIndexes];
408 NSMutableArray * paths = [NSMutableArray arrayWithCapacity: [indexes count]];
409 for (NSUInteger i = [indexes firstIndex]; i != NSNotFound; i = [indexes indexGreaterThanIndex: i])
411 NSString * path = [fTorrent fileLocation: [fOutline itemAtRow: i]];
413 [paths addObject: [NSURL fileURLWithPath: path]];
416 if ([paths count] > 0)
417 [[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs: paths];
420 #warning make real view controller (Leopard-only) so that Command-R will work
421 - (BOOL) validateMenuItem: (NSMenuItem *) menuItem
426 SEL action = [menuItem action];
428 if (action == @selector(revealFile:))
430 NSIndexSet * indexSet = [fOutline selectedRowIndexes];
431 for (NSInteger i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
432 if ([fTorrent fileLocation: [fOutline itemAtRow: i]] != nil)
437 if (action == @selector(setCheck:))
439 if ([fOutline numberOfSelectedRows] == 0)
442 NSIndexSet * indexSet = [fOutline selectedRowIndexes];
443 NSMutableIndexSet * itemIndexes = [NSMutableIndexSet indexSet];
444 for (NSInteger i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
445 [itemIndexes addIndexes: [[fOutline itemAtRow: i] indexes]];
447 NSInteger state = ([menuItem tag] == FILE_CHECK_TAG) ? NSOnState : NSOffState;
448 return [fTorrent checkForFiles: itemIndexes] != state && [fTorrent canChangeDownloadCheckForFiles: itemIndexes];
451 if (action == @selector(setOnlySelectedCheck:))
453 if ([fOutline numberOfSelectedRows] == 0)
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]];
461 return [fTorrent canChangeDownloadCheckForFiles: itemIndexes];
464 if (action == @selector(setPriority:))
466 if ([fOutline numberOfSelectedRows] == 0)
468 [menuItem setState: NSOffState];
472 //determine which priorities are checked
473 NSIndexSet * indexSet = [fOutline selectedRowIndexes];
474 tr_priority_t priority;
475 switch ([menuItem tag])
477 case FILE_PRIORITY_HIGH_TAG:
478 priority = TR_PRI_HIGH;
480 case FILE_PRIORITY_NORMAL_TAG:
481 priority = TR_PRI_NORMAL;
483 case FILE_PRIORITY_LOW_TAG:
484 priority = TR_PRI_LOW;
488 BOOL current = NO, canChange = NO;
489 for (NSInteger i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
491 NSIndexSet * fileIndexSet = [[fOutline itemAtRow: i] indexes];
492 if (![fTorrent canChangeDownloadCheckForFiles: fileIndexSet])
496 if ([fTorrent hasFilePriority: priority forIndexes: fileIndexSet])
503 [menuItem setState: current ? NSOnState : NSOffState];
512 @implementation FileOutlineController (Private)
516 NSMenu * menu = [[NSMenu alloc] initWithTitle: @"File Outline Menu"];
519 NSMenuItem * item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString(@"Check Selected", "File Outline -> Menu")
520 action: @selector(setCheck:) keyEquivalent: @""];
521 [item setTarget: self];
522 [item setTag: FILE_CHECK_TAG];
523 [menu addItem: item];
526 item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString(@"Uncheck Selected", "File Outline -> Menu")
527 action: @selector(setCheck:) keyEquivalent: @""];
528 [item setTarget: self];
529 [item setTag: FILE_UNCHECK_TAG];
530 [menu addItem: item];
533 //only check selected
534 item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString(@"Only Check Selected", "File Outline -> Menu")
535 action: @selector(setOnlySelectedCheck:) keyEquivalent: @""];
536 [item setTarget: self];
537 [menu addItem: item];
540 [menu addItem: [NSMenuItem separatorItem]];
543 item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString(@"Priority", "File Outline -> Menu") action: NULL keyEquivalent: @""];
544 NSMenu * priorityMenu = [[NSMenu alloc] initWithTitle: @"File Priority Menu"];
545 [item setSubmenu: priorityMenu];
546 [menu addItem: item];
549 item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString(@"High", "File Outline -> Priority Menu")
550 action: @selector(setPriority:) keyEquivalent: @""];
551 [item setTarget: self];
552 [item setTag: FILE_PRIORITY_HIGH_TAG];
553 [item setImage: [NSImage imageNamed: @"PriorityHighTemplate.png"]];
554 [priorityMenu addItem: item];
557 item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString(@"Normal", "File Outline -> Priority Menu")
558 action: @selector(setPriority:) keyEquivalent: @""];
559 [item setTarget: self];
560 [item setTag: FILE_PRIORITY_NORMAL_TAG];
561 [item setImage: [NSImage imageNamed: @"PriorityNormalTemplate.png"]];
562 [priorityMenu addItem: item];
565 item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString(@"Low", "File Outline -> Priority Menu")
566 action: @selector(setPriority:) keyEquivalent: @""];
567 [item setTarget: self];
568 [item setTag: FILE_PRIORITY_LOW_TAG];
569 [item setImage: [NSImage imageNamed: @"PriorityLowTemplate.png"]];
570 [priorityMenu addItem: item];
573 [priorityMenu release];
575 [menu addItem: [NSMenuItem separatorItem]];
578 item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString(@"Show in Finder", "File Outline -> Menu")
579 action: @selector(revealFile:) keyEquivalent: @""];
580 [item setTarget: self];
581 [menu addItem: item];
584 return [menu autorelease];
587 - (NSUInteger) findFileNode: (FileListNode *) node inList: (NSArray *) list atIndexes: (NSIndexSet *) indexes currentParent: (FileListNode *) currentParent finalParent: (FileListNode **) parent
589 NSAssert(![node isFolder], @"Looking up folder node!");
591 __block NSUInteger retIndex = NSNotFound;
593 [list enumerateObjectsAtIndexes: indexes options: NSEnumerationConcurrent usingBlock: ^(id checkNode, NSUInteger index, BOOL * stop) {
594 if ([[checkNode indexes] containsIndex: [[node indexes] firstIndex]])
596 if (![checkNode isFolder])
598 NSAssert2([checkNode isEqualTo: node], @"Expected file nodes to be equal: %@ %@", checkNode, node);
600 *parent = currentParent;
605 const NSUInteger subIndex = [self findFileNode: node inList: [checkNode children] atIndexes: [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [[checkNode children] count])] currentParent: checkNode finalParent: parent];
606 NSAssert(subIndex != NSNotFound, @"We didn't find an expected file node.");