diff --git a/macosx/FileOutlineController.h b/macosx/FileOutlineController.h index 8599b6348..0f61988b6 100644 --- a/macosx/FileOutlineController.h +++ b/macosx/FileOutlineController.h @@ -30,11 +30,13 @@ @interface FileOutlineController : NSObject { Torrent * fTorrent; - NSArray * fFileList; + NSMutableArray * fFileList; IBOutlet FileOutlineView * fOutline; NSString * fFilterText; + + NSLock * fLock; } - (FileOutlineView *) outlineView; diff --git a/macosx/FileOutlineController.m b/macosx/FileOutlineController.m index 1c3d2c5a9..0078e6a18 100644 --- a/macosx/FileOutlineController.m +++ b/macosx/FileOutlineController.m @@ -28,6 +28,7 @@ #import "FilePriorityCell.h" #import "FileListNode.h" #import "NSApplicationAdditions.h" +#import "NSMutableArrayAdditions.h" #import #define ROW_SMALL_HEIGHT 18.0 @@ -49,23 +50,28 @@ typedef enum - (NSMenu *) menu; +- (NSUInteger) findFileNode: (FileListNode *) node inList: (NSArray *) list inRange: (NSRange) range currentParent: (FileListNode *) currentParent finalParent: (FileListNode **) parent; + @end @implementation FileOutlineController - (void) awakeFromNib { + fFileList = [[NSMutableArray alloc] init]; + [fOutline setDoubleAction: @selector(revealFile:)]; [fOutline setTarget: self]; //set table header tool tips - [[fOutline tableColumnWithIdentifier: @"Check"] setHeaderToolTip: NSLocalizedString(@"Download", - "file table -> header tool tip")]; - [[fOutline tableColumnWithIdentifier: @"Priority"] setHeaderToolTip: NSLocalizedString(@"Priority", - "file table -> header tool tip")]; + [[fOutline tableColumnWithIdentifier: @"Check"] setHeaderToolTip: NSLocalizedString(@"Download", "file table -> header tool tip")]; + [[fOutline tableColumnWithIdentifier: @"Priority"] setHeaderToolTip: NSLocalizedString(@"Priority", "file table -> header tool tip")]; [fOutline setMenu: [self menu]]; + #warning needed? + fLock = [[NSLock alloc] init]; + [self setTorrent: nil]; } @@ -73,6 +79,9 @@ typedef enum { [fFileList release]; [fFilterText release]; + + [fLock release]; + [super dealloc]; } @@ -85,48 +94,131 @@ typedef enum { fTorrent = torrent; - [fFileList release]; - fFileList = [[fTorrent fileList] retain]; + [fFileList setArray: [fTorrent fileList]]; [fFilterText release]; fFilterText = nil; + [fLock lock]; + [fOutline deselectAll: nil]; [fOutline reloadData]; + + [fLock unlock]; } - (void) setFilterText: (NSString *) text { + [fLock lock]; + if ([text isEqualToString: @""]) text = nil; if ((!text && !fFilterText) || (text && fFilterText && [text isEqualToString: fFilterText])) + { + [fLock unlock]; return; + } + + const BOOL onLion = [NSApp isOnLionOrBetter]; + + if (onLion) + [fOutline beginUpdates]; + + NSUInteger currentIndex = 0, totalCount = 0; + NSMutableArray * itemsToAdd = [NSMutableArray array]; + NSMutableIndexSet * itemsToAddIndexes = [NSMutableIndexSet indexSet]; + + NSMutableDictionary * removedIndexesForParents = nil; //ugly, but we can't modify the actual file nodes + + NSArray * tempList = !text ? [fTorrent fileList] : [fTorrent flatFileList]; + for (FileListNode * item in tempList) + { + if (!text || [[item name] rangeOfString: text options: (NSCaseInsensitiveSearch | NSDiacriticInsensitiveSearch)].location != NSNotFound) + { + FileListNode * parent = nil; + NSUInteger previousIndex = ![item isFolder] ? [self findFileNode: item inList: fFileList inRange: NSMakeRange(currentIndex, [fFileList count]-currentIndex) currentParent: nil finalParent: &parent] : NSNotFound; + + if (previousIndex == NSNotFound) + { + [itemsToAdd addObject: item]; + [itemsToAddIndexes addIndex: totalCount]; + } + else + { + BOOL move = YES; + if (!parent) + { + if (previousIndex != currentIndex) + [fFileList moveObjectAtIndex: previousIndex toIndex: currentIndex]; + else + move = NO; + } + else + { + [fFileList insertObject: item atIndex: currentIndex]; + + //figure out the index within the semi-edited table - UGLY + if (!removedIndexesForParents) + removedIndexesForParents = [NSMutableDictionary dictionary]; + + NSMutableIndexSet * removedIndexes = [removedIndexesForParents objectForKey: parent]; + if (!removedIndexes) + { + removedIndexes = [NSMutableIndexSet indexSetWithIndex: previousIndex]; + [removedIndexesForParents setObject: removedIndexes forKey: parent]; + } + else + { + [removedIndexes addIndex: previousIndex]; + previousIndex -= [removedIndexes countOfIndexesInRange: NSMakeRange(0, previousIndex)]; + } + } + + if (move && onLion) + [fOutline moveItemAtIndex: previousIndex inParent: parent toIndex: currentIndex inParent: nil]; + + ++currentIndex; + } + + ++totalCount; + } + } + + //remove trailing items - those are the unused + if (currentIndex < [fFileList count]) + { + const NSRange removeRange = NSMakeRange(currentIndex, [fFileList count]-currentIndex); + [fFileList removeObjectsInRange: removeRange]; + if (onLion) + [fOutline removeItemsAtIndexes: [NSIndexSet indexSetWithIndexesInRange: removeRange] inParent: nil withAnimation: NSTableViewAnimationSlideDown]; + } + + //add new items + [fFileList insertObjects: itemsToAdd atIndexes: itemsToAddIndexes]; + if (onLion) + [fOutline insertItemsAtIndexes: itemsToAddIndexes inParent: nil withAnimation: NSTableViewAnimationSlideUp]; + + if (onLion) + [fOutline endUpdates]; + else + [fOutline reloadData]; [fFilterText release]; fFilterText = [text retain]; - [fFileList release]; - if (!fFilterText) - fFileList = [[fTorrent fileList] retain]; - else - { - NSMutableArray * list = [NSMutableArray arrayWithCapacity: [fTorrent fileCount]]; - - for (FileListNode * node in [fTorrent flatFileList]) - if ([[node name] rangeOfString: fFilterText options: (NSCaseInsensitiveSearch | NSDiacriticInsensitiveSearch)].location != NSNotFound) - [list addObject: node]; - - fFileList = [[NSArray alloc] initWithArray: list]; - } - - [fOutline reloadData]; + [fLock unlock]; } - (void) reloadData { [fTorrent updateFileStat]; + + [fLock lock]; + [fOutline reloadData]; + + [fLock unlock]; } - (void) outlineViewSelectionDidChange: (NSNotification *) notification @@ -265,6 +357,8 @@ typedef enum - (void) setCheck: (id) sender { + [fLock lock]; + NSInteger state = [sender tag] == FILE_UNCHECK_TAG ? NSOffState : NSOnState; NSIndexSet * indexSet = [fOutline selectedRowIndexes]; @@ -274,10 +368,14 @@ typedef enum [fTorrent setFileCheckState: state forIndexes: itemIndexes]; [fOutline reloadData]; + + [fLock unlock]; } - (void) setOnlySelectedCheck: (id) sender { + [fLock lock]; + NSIndexSet * indexSet = [fOutline selectedRowIndexes]; NSMutableIndexSet * itemIndexes = [NSMutableIndexSet indexSet]; for (NSInteger i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i]) @@ -290,6 +388,8 @@ typedef enum [fTorrent setFileCheckState: NSOffState forIndexes: remainingItemIndexes]; [fOutline reloadData]; + + [fLock unlock]; } - (void) setPriority: (id) sender @@ -307,6 +407,8 @@ typedef enum priority = TR_PRI_LOW; } + [fLock lock]; + NSIndexSet * indexSet = [fOutline selectedRowIndexes]; NSMutableIndexSet * itemIndexes = [NSMutableIndexSet indexSet]; for (NSInteger i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i]) @@ -314,10 +416,14 @@ typedef enum [fTorrent setFilePriority: priority forIndexes: itemIndexes]; [fOutline reloadData]; + + [fLock unlock]; } - (void) revealFile: (id) sender { + [fLock lock]; + NSIndexSet * indexes = [fOutline selectedRowIndexes]; if ([NSApp isOnSnowLeopardOrBetter]) { @@ -341,6 +447,8 @@ typedef enum [[NSWorkspace sharedWorkspace] selectFile: path inFileViewerRootedAtPath: nil]; } } + + [fLock unlock]; } #warning make real view controller (Leopard-only) so that Command-R will work @@ -510,4 +618,28 @@ typedef enum return [menu autorelease]; } +- (NSUInteger) findFileNode: (FileListNode *) node inList: (NSArray *) list inRange: (NSRange) range currentParent: (FileListNode *) currentParent finalParent: (FileListNode **) parent +{ + NSAssert(![node isFolder], @"Looking up folder node!"); + + const NSUInteger nodeIndex = [[node indexes] firstIndex]; + for (NSUInteger index = range.location; index < NSMaxRange(range); ++index) + { + FileListNode * checkNode = [list objectAtIndex: index]; + if ([checkNode isEqualTo: node]) + { + *parent = currentParent; + return index; + } + else if ([checkNode isFolder] && [[checkNode indexes] containsIndex: nodeIndex]) + { + const NSUInteger subIndex = [self findFileNode: node inList: [checkNode children] inRange: NSMakeRange(0, [[checkNode children] count]) currentParent: checkNode finalParent: parent]; + NSAssert(subIndex != NSNotFound, @"We didn't find an expected file node."); + return subIndex; + } + } + + return NSNotFound; +} + @end