transmission/macosx/InfoWindowController.m

963 lines
34 KiB
Objective-C

/******************************************************************************
* $Id$
*
* Copyright (c) 2006 Transmission authors and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*****************************************************************************/
#import "InfoWindowController.h"
#import "StringAdditions.h"
#define MIN_WINDOW_WIDTH 300
#define MAX_WINDOW_WIDTH 5000
#define FILE_ROW_SMALL_HEIGHT 18.0
#define TAB_INFO_IDENT @"Info"
#define TAB_ACTIVITY_IDENT @"Activity"
#define TAB_PEERS_IDENT @"Peers"
#define TAB_FILES_IDENT @"Files"
#define TAB_OPTIONS_IDENT @"Options"
//15 spacing at the bottom of each tab
#define TAB_INFO_HEIGHT 284.0
#define TAB_ACTIVITY_HEIGHT 170.0
#define TAB_PEERS_HEIGHT 268.0
#define TAB_FILES_HEIGHT 268.0
#define TAB_OPTIONS_HEIGHT 142.0
#define INVALID -99
@interface InfoWindowController (Private)
- (void) updateInfoGeneral;
- (void) updateInfoActivity;
- (void) updateInfoPeers;
- (void) updateInfoSettings;
- (void) setWindowForTab: (NSString *) identifier animate: (BOOL) animate;
- (NSArray *) peerSortDescriptors;
- (void) setFileCheckState: (int) state forItem: (NSMutableDictionary *) item;
- (NSMutableDictionary *) resetFileCheckStateForItemParent: (NSMutableDictionary *) originalChild;
@end
@implementation InfoWindowController
- (id) initWithWindowNibName: (NSString *) name
{
if ((self = [super initWithWindowNibName: name]))
{
fAppIcon = [[NSApp applicationIconImage] copy];
fDotGreen = [NSImage imageNamed: @"GreenDot.tiff"];
fDotRed = [NSImage imageNamed: @"RedDot.tiff"];
}
return self;
}
- (void) awakeFromNib
{
//window location and size
NSPanel * window = (NSPanel *)[self window];
[window setBecomesKeyOnlyIfNeeded: YES];
[window setFrameAutosaveName: @"InspectorWindowFrame"];
[window setFrameUsingName: @"InspectorWindowFrame"];
//select tab
NSString * identifier = [[NSUserDefaults standardUserDefaults] stringForKey: @"InfoTab"];
if ([fTabView indexOfTabViewItemWithIdentifier: identifier] == NSNotFound)
identifier = TAB_INFO_IDENT;
[fTabView selectTabViewItemWithIdentifier: identifier];
[self setWindowForTab: identifier animate: NO];
//initially sort peer table by IP
if ([[fPeerTable sortDescriptors] count] == 0)
[fPeerTable setSortDescriptors: [NSArray arrayWithObject: [[fPeerTable tableColumnWithIdentifier: @"IP"]
sortDescriptorPrototype]]];
//set file table
[fFileOutline setDoubleAction: @selector(revealFile:)];
//set blank inspector
[self updateInfoForTorrents: [NSArray array]];
}
- (void) dealloc
{
[fTorrents release];
if (fPeers)
[fPeers release];
if (fFiles)
[fFiles release];
[fAppIcon release];
[super dealloc];
}
- (void) updateInfoForTorrents: (NSArray *) torrents
{
if (fTorrents)
[fTorrents release];
fTorrents = [torrents retain];
int numberSelected = [fTorrents count];
if (numberSelected != 1)
{
if (numberSelected > 0)
{
[fNameField setStringValue: [NSString stringWithFormat: NSLocalizedString(@"%d Torrents Selected",
"Inspector -> above tabs -> selected torrents"), numberSelected]];
uint64_t size = 0;
NSEnumerator * enumerator = [torrents objectEnumerator];
Torrent * torrent;
while ((torrent = [enumerator nextObject]))
size += [torrent size];
[fSizeField setStringValue: [NSString stringWithFormat: NSLocalizedString(@"%@ Total",
"Inspector -> above tabs -> total size (several torrents selected)"), [NSString stringForFileSize: size]]];
}
else
{
[fNameField setStringValue: NSLocalizedString(@"No Torrents Selected",
"Inspector -> above tabs -> selected torrents")];
[fSizeField setStringValue: @""];
[fDownloadedValidField setStringValue: @""];
[fDownloadedTotalField setStringValue: @""];
[fUploadedTotalField setStringValue: @""];
}
[fImageView setImage: fAppIcon];
[fNameField setToolTip: nil];
[fTrackerField setStringValue: @""];
[fTrackerField setToolTip: nil];
[fPiecesField setStringValue: @""];
[fHashField setStringValue: @""];
[fHashField setToolTip: nil];
[fSecureField setStringValue: @""];
[fCommentView setString: @""];
[fCreatorField setStringValue: @""];
[fDateCreatedField setStringValue: @""];
[fTorrentLocationField setStringValue: @""];
[fTorrentLocationField setToolTip: nil];
[fDataLocationField setStringValue: @""];
[fDataLocationField setToolTip: nil];
[fDateStartedField setStringValue: @""];
[fCommentView setSelectable: NO];
[fRevealDataButton setHidden: YES];
[fRevealTorrentButton setHidden: YES];
//don't allow empty fields to be selected
[fTrackerField setSelectable: NO];
[fHashField setSelectable: NO];
[fCreatorField setSelectable: NO];
[fTorrentLocationField setSelectable: NO];
[fDataLocationField setSelectable: NO];
[fStateField setStringValue: @""];
[fRatioField setStringValue: @""];
[fSeedersField setStringValue: @""];
[fLeechersField setStringValue: @""];
[fCompletedFromTrackerField setStringValue: @""];
[fConnectedPeersField setStringValue: @""];
[fDownloadingFromField setStringValue: @""];
[fUploadingToField setStringValue: @""];
[fSwarmSpeedField setStringValue: @""];
[fErrorMessageView setString: @""];
[fErrorMessageView setSelectable: NO];
[fPiecesView setTorrent: nil];
if (fPeers)
{
[fPeers release];
fPeers = nil;
}
if (fFiles)
{
[fFiles release];
fFiles = nil;
}
[fFileTableStatusField setStringValue: NSLocalizedString(@"info not available",
"Inspector -> Files tab -> bottom text (number of files)")];
}
else
{
Torrent * torrent = [fTorrents objectAtIndex: 0];
[fImageView setImage: [torrent icon]];
NSString * name = [torrent name];
[fNameField setStringValue: name];
[fNameField setToolTip: name];
[fSizeField setStringValue: [NSString stringForFileSize: [torrent size]]];
NSString * hashString = [torrent hashString],
* commentString = [torrent comment];
[fPiecesField setStringValue: [NSString stringWithFormat: @"%d, %@", [torrent pieceCount],
[NSString stringForFileSize: [torrent pieceSize]]]];
[fHashField setStringValue: hashString];
[fHashField setToolTip: hashString];
[fSecureField setStringValue: [torrent privateTorrent]
? NSLocalizedString(@"Private Torrent", "Inspector -> is private torrent")
: NSLocalizedString(@"Public Torrent", "Inspector -> is not private torrent")];
[fCommentView setString: commentString];
[fCreatorField setStringValue: [torrent creator]];
[fDateCreatedField setObjectValue: [torrent dateCreated]];
BOOL publicTorrent = [torrent publicTorrent];
[fTorrentLocationField setStringValue: publicTorrent
? [[torrent publicTorrentLocation] stringByAbbreviatingWithTildeInPath]
: NSLocalizedString(@"Transmission Support Folder", "Torrent -> location when deleting original")];
if (publicTorrent)
[fTorrentLocationField setToolTip: [NSString stringWithFormat: @"%@\n\n%@",
[torrent publicTorrentLocation], [torrent torrentLocation]]];
else
[fTorrentLocationField setToolTip: [torrent torrentLocation]];
[fDateStartedField setObjectValue: [torrent date]];
[fRevealDataButton setHidden: NO];
[fRevealTorrentButton setHidden: ![torrent publicTorrent]];
//allow these fields to be selected
[fTrackerField setSelectable: YES];
[fHashField setSelectable: YES];
[fCommentView setSelectable: YES];
[fCreatorField setSelectable: YES];
[fTorrentLocationField setSelectable: YES];
[fDataLocationField setSelectable: YES];
[fPiecesView setTorrent: torrent];
//set file table
[fFileOutline deselectAll: nil];
if (fFiles)
[fFiles release];
fFiles = [[torrent fileList] retain];
int fileCount = [torrent fileCount];
if (fileCount != 1)
[fFileTableStatusField setStringValue: [NSString stringWithFormat: NSLocalizedString(@"%d files total",
"Inspector -> Files tab -> bottom text (number of files)"), fileCount]];
else
[fFileTableStatusField setStringValue: NSLocalizedString(@"1 file total",
"Inspector -> Files tab -> bottom text (number of files)")];
}
//update stats and settings
[self updateInfoStats];
[self updateInfoSettings];
[fPeerTable reloadData];
[fFileOutline deselectAll: nil];
[fFileOutline reloadData];
}
- (void) updateInfoStats
{
if ([[[fTabView selectedTabViewItem] identifier] isEqualToString: TAB_ACTIVITY_IDENT])
[self updateInfoActivity];
else if ([[[fTabView selectedTabViewItem] identifier] isEqualToString: TAB_PEERS_IDENT])
[self updateInfoPeers];
else if ([[[fTabView selectedTabViewItem] identifier] isEqualToString: TAB_INFO_IDENT])
[self updateInfoGeneral];
else;
}
- (void) updateInfoGeneral
{
int numberSelected = [fTorrents count];
if (numberSelected != 1)
return;
Torrent * torrent = [fTorrents objectAtIndex: 0];
NSString * tracker = [[torrent trackerAddress] stringByAppendingString: [torrent trackerAddressAnnounce]];
[fTrackerField setStringValue: tracker];
[fTrackerField setToolTip: tracker];
NSString * location = [torrent dataLocation];
[fDataLocationField setStringValue: [location stringByAbbreviatingWithTildeInPath]];
[fDataLocationField setToolTip: location];
}
- (void) updateInfoActivity
{
int numberSelected = [fTorrents count];
if (numberSelected == 0)
return;
float downloadedValid = 0;
uint64_t downloadedTotal = 0, uploadedTotal = 0;
Torrent * torrent;
NSEnumerator * enumerator = [fTorrents objectEnumerator];
while ((torrent = [enumerator nextObject]))
{
downloadedValid += [torrent downloadedValid];
downloadedTotal += [torrent downloadedTotal];
uploadedTotal += [torrent uploadedTotal];
}
[fDownloadedValidField setStringValue: [NSString stringForFileSize: downloadedValid]];
[fDownloadedTotalField setStringValue: [NSString stringForFileSize: downloadedTotal]];
[fUploadedTotalField setStringValue: [NSString stringForFileSize: uploadedTotal]];
if (numberSelected == 1)
{
torrent = [fTorrents objectAtIndex: 0];
//append percentage to amount downloaded if 1 torrent
[fDownloadedValidField setStringValue: [[fDownloadedValidField stringValue]
stringByAppendingFormat: @" (%.2f%%)", 100.0 * [torrent progress]]];
[fStateField setStringValue: [torrent stateString]];
[fRatioField setStringValue: [NSString stringForRatio: [torrent ratio]]];
[fSwarmSpeedField setStringValue: [torrent isActive] ? [NSString stringForSpeed: [torrent swarmSpeed]] : @""];
NSString * errorMessage = [torrent errorMessage];
if (![errorMessage isEqualToString: [fErrorMessageView string]])
{
[fErrorMessageView setString: errorMessage];
[fErrorMessageView setSelectable: ![errorMessage isEqualToString: @""]];
}
[fPiecesView updateView: NO];
}
}
- (void) updateInfoPeers
{
if ([fTorrents count] != 1)
return;
Torrent * torrent = [fTorrents objectAtIndex: 0];
int seeders = [torrent seeders], leechers = [torrent leechers], downloaded = [torrent completedFromTracker];
[fSeedersField setStringValue: seeders < 0 ? @"" : [NSString stringWithInt: seeders]];
[fLeechersField setStringValue: leechers < 0 ? @"" : [NSString stringWithInt: leechers]];
[fCompletedFromTrackerField setStringValue: downloaded < 0 ? @"" : [NSString stringWithInt: downloaded]];
BOOL active = [torrent isActive];
[fConnectedPeersField setStringValue: active ? [NSString stringWithFormat: NSLocalizedString(@"%d (%d incoming)",
"Inspector -> Peers tab -> connected"),
[torrent totalPeers], [torrent totalPeersIncoming]]: @""];
[fDownloadingFromField setStringValue: active ? [NSString stringWithInt: [torrent peersUploading]] : @""];
[fUploadingToField setStringValue: active ? [NSString stringWithInt: [torrent peersDownloading]] : @""];
if (fPeers)
[fPeers release];
fPeers = [[[torrent peers] sortedArrayUsingDescriptors: [self peerSortDescriptors]] retain];
[fPeerTable reloadData];
}
- (void) updateInfoSettings
{
int numberSelected = [fTorrents count];
if (numberSelected > 0)
{
Torrent * torrent;
//set bandwidth limits
NSEnumerator * enumerator = [fTorrents objectEnumerator];
torrent = [enumerator nextObject]; //first torrent
int limitCustom = [torrent customLimitSetting] ? 1 : 0,
checkUpload = [torrent checkUpload] ? 1 : 0,
checkDownload = [torrent checkDownload] ? 1 : 0,
uploadLimit = [torrent uploadLimit],
downloadLimit = [torrent downloadLimit];
while ((limitCustom != INVALID
|| checkUpload != INVALID || uploadLimit != INVALID
|| checkDownload != INVALID || downloadLimit != INVALID)
&& (torrent = [enumerator nextObject]))
{
if (limitCustom != INVALID && limitCustom != ([torrent customLimitSetting] ? 1 : 0))
limitCustom = INVALID;
if (checkUpload != INVALID && checkUpload != ([torrent checkUpload] ? 1 : 0))
checkUpload = INVALID;
if (uploadLimit != INVALID && uploadLimit != [torrent uploadLimit])
uploadLimit = INVALID;
if (checkDownload != INVALID && checkDownload != ([torrent checkDownload] ? 1 : 0))
checkDownload = INVALID;
if (downloadLimit != INVALID && downloadLimit != [torrent downloadLimit])
downloadLimit = INVALID;
}
[fLimitCustomCheck setEnabled: YES];
[fLimitCustomCheck setState: limitCustom == INVALID ? NSMixedState
: (limitCustom == 1 ? NSOnState : NSOffState)];
[fUploadLimitCheck setEnabled: limitCustom == 1];
[fUploadLimitLabel setEnabled: limitCustom == 1];
[fUploadLimitCheck setState: checkUpload == INVALID ? NSMixedState
: (checkUpload == 1 ? NSOnState : NSOffState)];
[fDownloadLimitCheck setEnabled: limitCustom == 1];
[fDownloadLimitLabel setEnabled: limitCustom == 1];
[fDownloadLimitCheck setState: checkDownload == INVALID ? NSMixedState
: (checkDownload == 1 ? NSOnState : NSOffState)];
[fUploadLimitField setEnabled: limitCustom == 1 && checkUpload == 1];
if (uploadLimit != INVALID)
[fUploadLimitField setIntValue: uploadLimit];
else
[fUploadLimitField setStringValue: @""];
[fDownloadLimitField setEnabled: limitCustom == 1 && checkDownload == 1];
if (downloadLimit != INVALID)
[fDownloadLimitField setIntValue: downloadLimit];
else
[fDownloadLimitField setStringValue: @""];
//set ratio settings
enumerator = [fTorrents objectEnumerator];
torrent = [enumerator nextObject]; //first torrent
int ratioCustom = [torrent customRatioSetting] ? 1 : 0,
ratioStop = [torrent shouldStopAtRatio];
float ratioLimit = [torrent ratioLimit];
while ((ratioCustom != INVALID || ratioStop != INVALID || ratioLimit != INVALID)
&& (torrent = [enumerator nextObject]))
{
if (ratioCustom != INVALID && ratioCustom != ([torrent customRatioSetting] ? 1 : 0))
ratioCustom = INVALID;
if (ratioStop != INVALID && ratioStop != ([torrent shouldStopAtRatio] ? 1 : 0))
ratioStop = INVALID;
if (ratioLimit != INVALID && ratioLimit != [torrent ratioLimit])
ratioLimit = INVALID;
}
[fRatioCustomCheck setEnabled: YES];
[fRatioCustomCheck setState: ratioCustom == INVALID ? NSMixedState
: (ratioCustom == 1 ? NSOnState : NSOffState)];
[fRatioStopCheck setEnabled: ratioCustom == 1];
[fRatioStopCheck setState: ratioStop == INVALID ? NSMixedState
: (ratioStop == 1 ? NSOnState : NSOffState)];
[fRatioLimitField setEnabled: ratioCustom == 1 && ratioStop == 1];
if (ratioLimit != INVALID)
[fRatioLimitField setFloatValue: ratioLimit];
else
[fRatioLimitField setStringValue: @""];
}
else
{
[fLimitCustomCheck setEnabled: NO];
[fLimitCustomCheck setState: NSOffState];
[fUploadLimitCheck setEnabled: NO];
[fUploadLimitCheck setState: NSOffState];
[fUploadLimitField setEnabled: NO];
[fUploadLimitField setStringValue: @""];
[fUploadLimitLabel setEnabled: NO];
[fDownloadLimitCheck setEnabled: NO];
[fDownloadLimitCheck setState: NSOffState];
[fDownloadLimitField setEnabled: NO];
[fDownloadLimitField setStringValue: @""];
[fDownloadLimitLabel setEnabled: NO];
[fRatioCustomCheck setEnabled: NO];
[fRatioCustomCheck setState: NSOffState];
[fRatioStopCheck setEnabled: NO];
[fRatioStopCheck setState: NSOffState];
[fRatioLimitField setEnabled: NO];
[fRatioLimitField setStringValue: @""];
}
[self updateInfoStats];
}
- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
{
SEL action = [menuItem action];
if (action == @selector(revealFile:))
return [fFileOutline numberOfSelectedRows] > 0 &&
[[[fTabView selectedTabViewItem] identifier] isEqualToString: TAB_FILES_IDENT];
return YES;
}
- (NSRect) windowWillUseStandardFrame: (NSWindow *) window defaultFrame: (NSRect) defaultFrame
{
NSRect windowRect = [window frame];
windowRect.size.width = [window minSize].width;
return windowRect;
}
- (void) tabView: (NSTabView *) tabView didSelectTabViewItem: (NSTabViewItem *) tabViewItem
{
NSString * identifier = [tabViewItem identifier];
[self setWindowForTab: identifier animate: YES];
[[NSUserDefaults standardUserDefaults] setObject: identifier forKey: @"InfoTab"];
}
- (void) setWindowForTab: (NSString *) identifier animate: (BOOL) animate
{
[self updateInfoStats];
float height;
if ([identifier isEqualToString: TAB_ACTIVITY_IDENT])
{
height = TAB_ACTIVITY_HEIGHT;
[fPiecesView updateView: YES];
}
else if ([identifier isEqualToString: TAB_PEERS_IDENT])
height = TAB_PEERS_HEIGHT;
else if ([identifier isEqualToString: TAB_FILES_IDENT])
height = TAB_FILES_HEIGHT;
else if ([identifier isEqualToString: TAB_OPTIONS_IDENT])
height = TAB_OPTIONS_HEIGHT;
else
height = TAB_INFO_HEIGHT;
NSWindow * window = [self window];
NSRect frame = [window frame];
NSView * view = [[fTabView selectedTabViewItem] view];
float difference = height - [view frame].size.height;
frame.origin.y -= difference;
frame.size.height += difference;
if (animate)
{
[view setHidden: YES];
[window setFrame: frame display: YES animate: YES];
[view setHidden: NO];
}
else
[window setFrame: frame display: YES];
[window setMinSize: NSMakeSize(MIN_WINDOW_WIDTH, frame.size.height)];
[window setMaxSize: NSMakeSize(MAX_WINDOW_WIDTH, frame.size.height)];
}
- (void) setNextTab
{
if ([fTabView indexOfTabViewItem: [fTabView selectedTabViewItem]] == [fTabView numberOfTabViewItems] - 1)
[fTabView selectFirstTabViewItem: nil];
else
[fTabView selectNextTabViewItem: nil];
}
- (void) setPreviousTab
{
if ([fTabView indexOfTabViewItem: [fTabView selectedTabViewItem]] == 0)
[fTabView selectLastTabViewItem: nil];
else
[fTabView selectPreviousTabViewItem: nil];
}
- (int) numberOfRowsInTableView: (NSTableView *) tableView
{
if (tableView == fPeerTable)
return fPeers ? [fPeers count] : 0;
return 0;
}
- (id) tableView: (NSTableView *) tableView objectValueForTableColumn: (NSTableColumn *) column row: (int) row
{
if (tableView == fPeerTable)
{
NSString * ident = [column identifier];
NSDictionary * peer = [fPeers objectAtIndex: row];
if ([ident isEqualToString: @"Connected"])
return [[peer objectForKey: @"Connected"] boolValue] ? fDotGreen : fDotRed;
else if ([ident isEqualToString: @"Client"])
return [peer objectForKey: @"Client"];
else if ([ident isEqualToString: @"Progress"])
return [peer objectForKey: @"Progress"];
else if ([ident isEqualToString: @"UL To"])
return [[peer objectForKey: @"UL To"] boolValue]
? [NSString stringForSpeedAbbrev: [[peer objectForKey: @"UL To Rate"] floatValue]] : @"";
else if ([ident isEqualToString: @"DL From"])
return [[peer objectForKey: @"DL From"] boolValue]
? [NSString stringForSpeedAbbrev: [[peer objectForKey: @"DL From Rate"] floatValue]] : @"";
else
return [peer objectForKey: @"IP"];
}
return nil;
}
- (void) tableView: (NSTableView *) tableView didClickTableColumn: (NSTableColumn *) tableColumn
{
if (tableView == fPeerTable)
{
if (fPeers)
{
NSArray * oldPeers = fPeers;
fPeers = [[fPeers sortedArrayUsingDescriptors: [self peerSortDescriptors]] retain];
[oldPeers release];
[tableView reloadData];
}
}
}
- (BOOL) tableView: (NSTableView *) tableView shouldSelectRow:(int) row
{
return tableView != fPeerTable;
}
- (NSString *) tableView: (NSTableView *) tableView toolTipForCell: (NSCell *) cell rect: (NSRectPointer) rect
tableColumn: (NSTableColumn *) column row: (int) row mouseLocation: (NSPoint) mouseLocation
{
if (tableView == fPeerTable)
{
NSDictionary * peerDic = [fPeers objectAtIndex: row];
return [NSString stringWithFormat: NSLocalizedString(@"Progress: %.1f%%"
"\nPort: %@"
"\nFrom %@ connection", "Inspector -> Peers tab -> table row tooltip"),
[[peerDic objectForKey: @"Progress"] floatValue] * 100.0,
[peerDic objectForKey: @"Port"],
[[peerDic objectForKey: @"Incoming"] boolValue]
? NSLocalizedString(@"incoming", "Inspector -> Peers tab -> table row tooltip")
: NSLocalizedString(@"outgoing", "Inspector -> Peers tab -> table row tooltip")];
}
return nil;
}
- (int) outlineView: (NSOutlineView *) outlineView numberOfChildrenOfItem: (id) item
{
if (!item)
return fFiles ? [fFiles count] : 1;
return [[item objectForKey: @"IsFolder"] boolValue] ? [[item objectForKey: @"Children"] count] : 0;
}
- (BOOL) outlineView: (NSOutlineView *) outlineView isItemExpandable: (id) item
{
return item && [[item objectForKey: @"IsFolder"] boolValue];
}
- (id) outlineView: (NSOutlineView *) outlineView child: (int) index ofItem: (id) item
{
if (!fFiles)
return nil;
return [(item ? [item objectForKey: @"Children"] : fFiles) objectAtIndex: index];
}
- (id) outlineView: (NSOutlineView *) outlineView objectValueForTableColumn: (NSTableColumn *) tableColumn
byItem: (id) item
{
if (!item)
return nil;
if ([[tableColumn identifier] isEqualToString: @"Check"])
return [item objectForKey: @"Check"];
else
return item;
}
- (void) outlineView: (NSOutlineView *) outlineView willDisplayCell: (id) cell
forTableColumn: (NSTableColumn *) tableColumn item:(id) item
{
if ([[tableColumn identifier] isEqualToString: @"Name"])
{
if (!item)
return;
NSImage * icon = [[NSWorkspace sharedWorkspace] iconForFileType: ![[item objectForKey: @"IsFolder"] boolValue]
? [[item objectForKey: @"Name"] pathExtension] : NSFileTypeForHFSTypeCode('fldr')];
[cell setImage: icon];
}
else if ([[tableColumn identifier] isEqualToString: @"Check"])
{
[(NSButtonCell *)cell setImagePosition: item ? NSImageOnly : NSNoImage];
[cell setEnabled: NO];
}
else;
}
- (void) outlineView: (NSOutlineView *) outlineView setObjectValue: (id) object
forTableColumn: (NSTableColumn *) tableColumn byItem: (id) item
{
int state = [object intValue] != NSOffState;
[self setFileCheckState: state forItem: item];
NSMutableDictionary * topItem = [self resetFileCheckStateForItemParent: item];
[fFileOutline reloadItem: topItem reloadChildren: YES];
}
- (void) setFileCheckState: (int) state forItem: (NSMutableDictionary *) item
{
[item setObject: [NSNumber numberWithInt: state] forKey: @"Check"];
if (![[item objectForKey: @"IsFolder"] boolValue])
return;
NSMutableDictionary * child;
NSEnumerator * enumerator = [[item objectForKey: @"Children"] objectEnumerator];
while ((child = [enumerator nextObject]))
if ([[child objectForKey: @"Check"] intValue] != state)
[self setFileCheckState: state forItem: child];
}
- (NSMutableDictionary *) resetFileCheckStateForItemParent: (NSMutableDictionary *) originalChild
{
NSMutableDictionary * item;
if (!(item = [originalChild objectForKey: @"Parent"]))
return originalChild;
int state = INVALID;
NSMutableDictionary * child;
NSEnumerator * enumerator = [[item objectForKey: @"Children"] objectEnumerator];
while ((child = [enumerator nextObject]))
{
if (state == INVALID)
{
state = [[child objectForKey: @"Check"] intValue];
if (state == NSMixedState)
break;
}
else if (state != [[child objectForKey: @"Check"] intValue])
{
state = NSMixedState;
break;
}
else;
}
if ([[item objectForKey: @"Check"] intValue] != state)
{
[item setObject: [NSNumber numberWithInt: state] forKey: @"Check"];
return [self resetFileCheckStateForItemParent: item];
}
else
return originalChild;
}
- (NSString *) outlineView: (NSOutlineView *) outlineView toolTipForCell: (NSCell *) cell rect: (NSRectPointer) rect
tableColumn: (NSTableColumn *) tableColumn item: (id) item mouseLocation: (NSPoint) mouseLocation
{
if (!item)
return nil;
NSString * ident = [tableColumn identifier];
if ([ident isEqualToString: @"Name"])
return [[[fTorrents objectAtIndex: 0] downloadFolder] stringByAppendingPathComponent: [item objectForKey: @"Path"]];
else
return nil;
}
- (float) outlineView: (NSOutlineView *) outlineView heightOfRowByItem: (id) item
{
if ([[item objectForKey: @"IsFolder"] boolValue])
return FILE_ROW_SMALL_HEIGHT;
else
return [outlineView rowHeight];
}
- (BOOL) outlineView: (NSOutlineView *) outlineView shouldSelectItem: (id) item
{
return item != nil;
}
- (NSArray *) peerSortDescriptors
{
NSMutableArray * descriptors = [NSMutableArray array];
NSArray * oldDescriptors = [fPeerTable sortDescriptors];
if ([oldDescriptors count] > 0)
[descriptors addObject: [oldDescriptors objectAtIndex: 0]];
[descriptors addObject: [[fPeerTable tableColumnWithIdentifier: @"IP"] sortDescriptorPrototype]];
return descriptors;
}
- (void) revealTorrentFile: (id) sender
{
if ([fTorrents count] > 0)
[[fTorrents objectAtIndex: 0] revealPublicTorrent];
}
- (void) revealDataFile: (id) sender
{
if ([fTorrents count] > 0)
[[fTorrents objectAtIndex: 0] revealData];
}
- (void) revealFile: (id) sender
{
if (!fFiles)
return;
NSString * folder = [[fTorrents objectAtIndex: 0] downloadFolder];
NSIndexSet * indexes = [fFileOutline selectedRowIndexes];
int i;
for (i = [indexes firstIndex]; i != NSNotFound; i = [indexes indexGreaterThanIndex: i])
[[NSWorkspace sharedWorkspace] selectFile: [folder stringByAppendingPathComponent:
[[fFileOutline itemAtRow: i] objectForKey: @"Path"]] inFileViewerRootedAtPath: nil];
}
- (void) setLimitCustom: (id) sender
{
BOOL custom = [sender state] != NSOffState;
if (custom)
[sender setState: NSOnState];
Torrent * torrent;
NSEnumerator * enumerator = [fTorrents objectEnumerator];
while ((torrent = [enumerator nextObject]))
[torrent setCustomLimitSetting: custom];
[fUploadLimitCheck setEnabled: custom];
[fUploadLimitLabel setEnabled: custom];
[fUploadLimitField setEnabled: custom && [fUploadLimitCheck state] == NSOnState];
[fDownloadLimitCheck setEnabled: custom];
[fDownloadLimitLabel setEnabled: custom];
[fDownloadLimitField setEnabled: custom && [fDownloadLimitCheck state] == NSOnState];
}
- (void) setLimitCheck: (id) sender
{
BOOL upload = sender == fUploadLimitCheck,
limit = [sender state] != NSOffState;
if (limit)
[sender setState: NSOnState];
Torrent * torrent;
NSEnumerator * enumerator = [fTorrents objectEnumerator];
while ((torrent = [enumerator nextObject]))
upload ? [torrent setLimitUpload: limit] : [torrent setLimitDownload: limit];
NSTextField * field = upload ? fUploadLimitField : fDownloadLimitField;
[field setEnabled: limit];
}
- (void) setSpeedLimit: (id) sender
{
BOOL upload = sender == fUploadLimitField;
Torrent * torrent;
NSEnumerator * enumerator = [fTorrents objectEnumerator];
int limit = [sender intValue];
if (![[sender stringValue] isEqualToString: [NSString stringWithFormat: @"%i", limit]] || limit < 0)
{
NSBeep();
torrent = [enumerator nextObject]; //use first torrent
limit = upload ? [torrent uploadLimit] : [torrent downloadLimit];
while ((torrent = [enumerator nextObject]))
if (limit != upload ? [torrent uploadLimit] : [torrent downloadLimit])
{
[sender setStringValue: @""];
return;
}
[sender setIntValue: limit];
}
else
{
while ((torrent = [enumerator nextObject]))
upload ? [torrent setUploadLimit: limit] : [torrent setDownloadLimit: limit];
}
}
- (void) setRatioCustom: (id) sender
{
BOOL custom = [sender state] != NSOffState;
if (custom)
[sender setState: NSOnState];
Torrent * torrent;
NSEnumerator * enumerator = [fTorrents objectEnumerator];
while ((torrent = [enumerator nextObject]))
[torrent setCustomRatioSetting: custom];
[fRatioStopCheck setEnabled: custom];
[fRatioLimitField setEnabled: custom && [fRatioStopCheck state] == NSOnState];
}
- (void) setRatioSetting: (id) sender
{
BOOL enabled = [sender state] != NSOffState;
if (enabled)
[sender setState: NSOnState];
Torrent * torrent;
NSEnumerator * enumerator = [fTorrents objectEnumerator];
while ((torrent = [enumerator nextObject]))
[torrent setShouldStopAtRatio: enabled];
[fRatioLimitField setEnabled: enabled && [fRatioCustomCheck state] == NSOnState];
}
- (void) setRatioLimit: (id) sender
{
Torrent * torrent;
NSEnumerator * enumerator = [fTorrents objectEnumerator];
float ratioLimit = [sender floatValue];
if (![[sender stringValue] isEqualToString: [NSString stringWithFormat: @"%.2f", ratioLimit]] || ratioLimit < 0)
{
NSBeep();
float ratioLimit = [[enumerator nextObject] ratioLimit]; //use first torrent
while ((torrent = [enumerator nextObject]))
if (ratioLimit != [torrent ratioLimit])
{
[sender setStringValue: @""];
return;
}
[sender setFloatValue: ratioLimit];
}
else
{
while ((torrent = [enumerator nextObject]))
[torrent setRatioLimit: ratioLimit];
}
}
@end