source: trunk/macosx/InfoWindowController.m @ 10212

Last change on this file since 10212 was 10212, checked in by livings124, 12 years ago

when adding a new tracker, display tier as "New Tier"

  • Property svn:keywords set to Date Rev Author Id
File size: 69.0 KB
Line 
1/******************************************************************************
2 * $Id: InfoWindowController.m 10212 2010-02-15 19:26:23Z livings124 $
3 *
4 * Copyright (c) 2006-2010 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 "InfoWindowController.h"
26#import "FileListNode.h"
27#import "FileOutlineController.h"
28#import "FileOutlineView.h"
29#import "InfoTabButtonCell.h"
30#import "NSApplicationAdditions.h"
31#import "NSStringAdditions.h"
32#import "PeerProgressIndicatorCell.h"
33#import "PiecesView.h"
34#import "Torrent.h"
35#import "TrackerCell.h"
36#import "TrackerNode.h"
37#import "TrackerTableView.h"
38#include "utils.h" //tr_getRatio()
39
40#define TAB_INFO_IDENT @"Info"
41#define TAB_ACTIVITY_IDENT @"Activity"
42#define TAB_TRACKER_IDENT @"Tracker"
43#define TAB_PEERS_IDENT @"Peers"
44#define TAB_FILES_IDENT @"Files"
45#define TAB_OPTIONS_IDENT @"Options"
46
47#define TAB_MIN_HEIGHT 250
48
49#define TRACKER_GROUP_SEPARATOR_HEIGHT 14.0
50
51#define PIECES_CONTROL_PROGRESS 0
52#define PIECES_CONTROL_AVAILABLE 1
53
54#define OPTION_POPUP_GLOBAL 0
55#define OPTION_POPUP_NO_LIMIT 1
56#define OPTION_POPUP_LIMIT 2
57
58#define OPTION_POPUP_PRIORITY_HIGH 0
59#define OPTION_POPUP_PRIORITY_NORMAL 1
60#define OPTION_POPUP_PRIORITY_LOW 2
61
62#define INVALID -99
63
64#define TRACKER_ADD_TAG 0
65#define TRACKER_REMOVE_TAG 1
66
67typedef enum
68{
69    TAB_INFO_TAG = 0,
70    TAB_ACTIVITY_TAG = 1,
71    TAB_TRACKER_TAG = 2,
72    TAB_PEERS_TAG = 3,
73    TAB_FILES_TAG = 4,
74    TAB_OPTIONS_TAG = 5
75} tabTag;
76
77@interface InfoWindowController (Private)
78
79- (void) resetInfo;
80- (void) resetInfoForTorrent: (NSNotification *) notification;
81
82- (void) updateInfoGeneral;
83- (void) updateInfoActivity;
84- (void) updateInfoTracker;
85- (void) updateInfoPeers;
86- (void) updateInfoFiles;
87
88- (NSView *) tabViewForTag: (NSInteger) tag;
89- (void) setWebSeedTableHidden: (BOOL) hide animate: (BOOL) animate;
90- (NSArray *) peerSortDescriptors;
91
92- (BOOL) canQuickLookFile: (FileListNode *) item;
93
94- (void) addTrackers;
95- (void) removeTrackers;
96
97@end
98
99@implementation InfoWindowController
100
101- (id) init
102{
103    if ((self = [super initWithWindowNibName: @"InfoWindow"]))
104    {
105        fTrackerCell = [[TrackerCell alloc] init];
106    }
107   
108    return self;
109}
110
111- (void) awakeFromNib
112{
113    //window location and size
114    NSPanel * window = (NSPanel *)[self window];
115   
116    CGFloat windowHeight = [window frame].size.height;
117   
118    [window setFrameAutosaveName: @"InspectorWindow"];
119    [window setFrameUsingName: @"InspectorWindow"];
120   
121    NSRect windowRect = [window frame];
122    windowRect.origin.y -= windowHeight - windowRect.size.height;
123    windowRect.size.height = windowHeight;
124    [window setFrame: windowRect display: NO];
125   
126    [window setBecomesKeyOnlyIfNeeded: YES];
127   
128    //set tab images and tooltips
129    [[fTabMatrix cellWithTag: TAB_INFO_TAG] setIcon: [NSImage imageNamed: @"InfoGeneral.png"]];
130    [[fTabMatrix cellWithTag: TAB_ACTIVITY_TAG] setIcon: [NSImage imageNamed: @"InfoActivity.png"]];
131    [[fTabMatrix cellWithTag: TAB_TRACKER_TAG] setIcon: [NSImage imageNamed: @"InfoTracker.png"]];
132    [[fTabMatrix cellWithTag: TAB_PEERS_TAG] setIcon: [NSImage imageNamed: @"InfoPeers.png"]];
133    [[fTabMatrix cellWithTag: TAB_FILES_TAG] setIcon: [NSImage imageNamed: @"InfoFiles.png"]];
134    [[fTabMatrix cellWithTag: TAB_OPTIONS_TAG] setIcon: [NSImage imageNamed: @"InfoOptions.png"]];
135   
136    //set selected tab
137    fCurrentTabTag = INVALID;
138    NSString * identifier = [[NSUserDefaults standardUserDefaults] stringForKey: @"InspectorSelected"];
139    NSInteger tag;
140    if ([identifier isEqualToString: TAB_INFO_IDENT])
141        tag = TAB_INFO_TAG;
142    else if ([identifier isEqualToString: TAB_ACTIVITY_IDENT])
143        tag = TAB_ACTIVITY_TAG;
144    else if ([identifier isEqualToString: TAB_TRACKER_IDENT])
145        tag = TAB_TRACKER_TAG;
146    else if ([identifier isEqualToString: TAB_PEERS_IDENT])
147        tag = TAB_PEERS_TAG;
148    else if ([identifier isEqualToString: TAB_FILES_IDENT])
149        tag = TAB_FILES_TAG;
150    else if ([identifier isEqualToString: TAB_OPTIONS_IDENT])
151        tag = TAB_OPTIONS_TAG;
152    else //safety
153    {
154        [[NSUserDefaults standardUserDefaults] setObject: TAB_INFO_IDENT forKey: @"InspectorSelected"];
155        tag = TAB_INFO_TAG;
156    }
157    [fTabMatrix selectCellWithTag: tag];
158    [self setTab: nil];
159   
160    if (![NSApp isOnSnowLeopardOrBetter])
161    {
162        //reset images for reveal button, since the images are also used in the main table
163        NSImage * revealOn = [[NSImage imageNamed: @"RevealOn.png"] copy],
164                * revealOff = [[NSImage imageNamed: @"RevealOff.png"] copy];
165       
166        [revealOn setFlipped: NO];
167        [revealOff setFlipped: NO];
168       
169        [fRevealDataButton setImage: revealOff];
170        [fRevealDataButton setAlternateImage: revealOn];
171       
172        [revealOn release];
173        [revealOff release];
174    }
175   
176    //initially sort peer table by IP
177    if ([[fPeerTable sortDescriptors] count] == 0)
178        [fPeerTable setSortDescriptors: [NSArray arrayWithObject: [[fPeerTable tableColumnWithIdentifier: @"IP"]
179                                            sortDescriptorPrototype]]];
180   
181    //initially sort webseed table by address
182    if ([[fWebSeedTable sortDescriptors] count] == 0)
183        [fWebSeedTable setSortDescriptors: [NSArray arrayWithObject: [[fWebSeedTable tableColumnWithIdentifier: @"Address"]
184                                            sortDescriptorPrototype]]];
185   
186    //set table header tool tips
187    [[fPeerTable tableColumnWithIdentifier: @"Encryption"] setHeaderToolTip: NSLocalizedString(@"Encrypted Connection",
188                                                                        "inspector -> peer table -> header tool tip")];
189    [[fPeerTable tableColumnWithIdentifier: @"Progress"] setHeaderToolTip: NSLocalizedString(@"Available",
190                                                                        "inspector -> peer table -> header tool tip")];
191    [[fPeerTable tableColumnWithIdentifier: @"UL To"] setHeaderToolTip: NSLocalizedString(@"Uploading To Peer",
192                                                                        "inspector -> peer table -> header tool tip")];
193    [[fPeerTable tableColumnWithIdentifier: @"DL From"] setHeaderToolTip: NSLocalizedString(@"Downloading From Peer",
194                                                                        "inspector -> peer table -> header tool tip")];
195   
196    [[fWebSeedTable tableColumnWithIdentifier: @"DL From"] setHeaderToolTip: NSLocalizedString(@"Downloading From Web Seed",
197                                                                        "inspector -> web seed table -> header tool tip")];
198   
199    //prepare for animating peer table and web seed table
200    NSRect webSeedTableFrame = [[fWebSeedTable enclosingScrollView] frame];
201    fWebSeedTableHeight = webSeedTableFrame.size.height;
202    fSpaceBetweenWebSeedAndPeer = webSeedTableFrame.origin.y - NSMaxY([[fPeerTable enclosingScrollView] frame]);
203   
204    [self setWebSeedTableHidden: YES animate: NO];
205   
206    //set blank inspector
207    [self setInfoForTorrents: [NSArray array]];
208   
209    //allow for update notifications
210    NSNotificationCenter * nc = [NSNotificationCenter defaultCenter];
211    [nc addObserver: self selector: @selector(resetInfoForTorrent:) name: @"ResetInspector" object: nil];
212    [nc addObserver: self selector: @selector(updateInfoStats) name: @"UpdateStats" object: nil];
213    [nc addObserver: self selector: @selector(updateOptions) name: @"UpdateOptions" object: nil];
214}
215
216- (void) dealloc
217{
218    //save resizeable view height
219    NSString * resizeSaveKey = nil;
220    switch (fCurrentTabTag)
221    {
222        case TAB_TRACKER_TAG:
223            resizeSaveKey = @"InspectorContentHeightTracker";
224            break;
225        case TAB_PEERS_TAG:
226            resizeSaveKey = @"InspectorContentHeightPeers";
227            break;
228        case TAB_FILES_TAG:
229            resizeSaveKey = @"InspectorContentHeightFiles";
230            break;
231    }
232    if (resizeSaveKey)
233        [[NSUserDefaults standardUserDefaults] setFloat: [[self tabViewForTag: fCurrentTabTag] frame].size.height forKey: resizeSaveKey];
234   
235    [[NSNotificationCenter defaultCenter] removeObserver: self];
236   
237    [fTorrents release];
238    [fPeers release];
239    [fWebSeeds release];
240    [fTrackers release];
241   
242    [fWebSeedTableAnimation release];
243   
244    [fTrackerCell release];
245   
246    [fPreviewPanel release];
247   
248    [super dealloc];
249}
250
251- (void) setInfoForTorrents: (NSArray *) torrents
252{
253    if (fTorrents && [fTorrents isEqualToArray: torrents])
254        return;
255   
256    [fTorrents release];
257    fTorrents = [torrents retain];
258   
259    [self resetInfo];
260}
261
262- (void) updateInfoStats
263{
264    switch ([fTabMatrix selectedTag])
265    {
266        case TAB_INFO_TAG:
267            [self updateInfoGeneral];
268            break;
269        case TAB_ACTIVITY_TAG:
270            [self updateInfoActivity];
271            break;
272        case TAB_TRACKER_TAG:
273            [self updateInfoTracker];
274            break;
275        case TAB_PEERS_TAG:
276            [self updateInfoPeers];
277            break;
278        case TAB_FILES_TAG:
279            [self updateInfoFiles];
280            break;
281    }
282}
283
284- (void) updateOptions
285{
286    if ([fTorrents count] == 0)
287        return;
288   
289    //get bandwidth info
290    NSEnumerator * enumerator = [fTorrents objectEnumerator];
291    Torrent * torrent = [enumerator nextObject]; //first torrent
292   
293    NSInteger uploadUseSpeedLimit = [torrent usesSpeedLimit: YES] ? NSOnState : NSOffState,
294                uploadSpeedLimit = [torrent speedLimit: YES],
295                downloadUseSpeedLimit = [torrent usesSpeedLimit: NO] ? NSOnState : NSOffState,
296                downloadSpeedLimit = [torrent speedLimit: NO],
297                globalUseSpeedLimit = [torrent usesGlobalSpeedLimit] ? NSOnState : NSOffState;
298   
299    while ((torrent = [enumerator nextObject])
300            && (uploadUseSpeedLimit != NSMixedState || uploadSpeedLimit != INVALID
301                || downloadUseSpeedLimit != NSMixedState || downloadSpeedLimit != INVALID
302                || globalUseSpeedLimit != NSMixedState))
303    {
304        if (uploadUseSpeedLimit != NSMixedState && uploadUseSpeedLimit != ([torrent usesSpeedLimit: YES] ? NSOnState : NSOffState))
305            uploadUseSpeedLimit = NSMixedState;
306       
307        if (uploadSpeedLimit != INVALID && uploadSpeedLimit != [torrent speedLimit: YES])
308            uploadSpeedLimit = INVALID;
309       
310        if (downloadUseSpeedLimit != NSMixedState && downloadUseSpeedLimit != ([torrent usesSpeedLimit: NO] ? NSOnState : NSOffState))
311            downloadUseSpeedLimit = NSMixedState;
312       
313        if (downloadSpeedLimit != INVALID && downloadSpeedLimit != [torrent speedLimit: NO])
314            downloadSpeedLimit = INVALID;
315       
316        if (globalUseSpeedLimit != NSMixedState && globalUseSpeedLimit != ([torrent usesGlobalSpeedLimit] ? NSOnState : NSOffState))
317            globalUseSpeedLimit = NSMixedState;
318    }
319   
320    //set upload view
321    [fUploadLimitCheck setState: uploadUseSpeedLimit];
322    [fUploadLimitCheck setEnabled: YES];
323   
324    [fUploadLimitLabel setEnabled: uploadUseSpeedLimit == NSOnState];
325    [fUploadLimitField setEnabled: uploadUseSpeedLimit == NSOnState];
326    if (uploadSpeedLimit != INVALID)
327        [fUploadLimitField setIntValue: uploadSpeedLimit];
328    else
329        [fUploadLimitField setStringValue: @""];
330   
331    //set download view
332    [fDownloadLimitCheck setState: downloadUseSpeedLimit];
333    [fDownloadLimitCheck setEnabled: YES];
334   
335    [fDownloadLimitLabel setEnabled: downloadUseSpeedLimit == NSOnState];
336    [fDownloadLimitField setEnabled: downloadUseSpeedLimit == NSOnState];
337    if (downloadSpeedLimit != INVALID)
338        [fDownloadLimitField setIntValue: downloadSpeedLimit];
339    else
340        [fDownloadLimitField setStringValue: @""];
341   
342    //set global check
343    [fGlobalLimitCheck setState: globalUseSpeedLimit];
344    [fGlobalLimitCheck setEnabled: YES];
345   
346    //get ratio info
347    enumerator = [fTorrents objectEnumerator];
348    torrent = [enumerator nextObject]; //first torrent
349   
350    NSInteger checkRatio = [torrent ratioSetting];
351    CGFloat ratioLimit = [torrent ratioLimit];
352   
353    while ((torrent = [enumerator nextObject]) && (checkRatio != INVALID || ratioLimit != INVALID))
354    {
355        if (checkRatio != INVALID && checkRatio != [torrent ratioSetting])
356            checkRatio = INVALID;
357       
358        if (ratioLimit != INVALID && ratioLimit != [torrent ratioLimit])
359            ratioLimit = INVALID;
360    }
361   
362    //set ratio view
363    NSInteger index;
364    if (checkRatio == TR_RATIOLIMIT_SINGLE)
365        index = OPTION_POPUP_LIMIT;
366    else if (checkRatio == TR_RATIOLIMIT_UNLIMITED)
367        index = OPTION_POPUP_NO_LIMIT;
368    else if (checkRatio == TR_RATIOLIMIT_GLOBAL)
369        index = OPTION_POPUP_GLOBAL;
370    else
371        index = -1;
372    [fRatioPopUp selectItemAtIndex: index];
373    [fRatioPopUp setEnabled: YES];
374   
375    [fRatioLimitField setHidden: checkRatio != TR_RATIOLIMIT_SINGLE];
376    if (ratioLimit != INVALID)
377        [fRatioLimitField setFloatValue: ratioLimit];
378    else
379        [fRatioLimitField setStringValue: @""];
380   
381    //get priority info
382    enumerator = [fTorrents objectEnumerator];
383    torrent = [enumerator nextObject]; //first torrent
384   
385    NSInteger priority = [torrent priority];
386   
387    while ((torrent = [enumerator nextObject]) && priority != INVALID)
388    {
389        if (priority != INVALID && priority != [torrent priority])
390            priority = INVALID;
391    }
392   
393    //set priority view
394    if (priority == TR_PRI_HIGH)
395        index = OPTION_POPUP_PRIORITY_HIGH;
396    else if (priority == TR_PRI_NORMAL)
397        index = OPTION_POPUP_PRIORITY_NORMAL;
398    else if (priority == TR_PRI_LOW)
399        index = OPTION_POPUP_PRIORITY_LOW;
400    else
401        index = -1;
402    [fPriorityPopUp selectItemAtIndex: index];
403    [fPriorityPopUp setEnabled: YES];
404   
405    //get peer info
406    enumerator = [fTorrents objectEnumerator];
407    torrent = [enumerator nextObject]; //first torrent
408   
409    NSInteger maxPeers = [torrent maxPeerConnect];
410   
411    while ((torrent = [enumerator nextObject]))
412    {
413        if (maxPeers != [torrent maxPeerConnect])
414        {
415            maxPeers = INVALID;
416            break;
417        }
418    }
419   
420    //set peer view
421    [fPeersConnectField setEnabled: YES];
422    [fPeersConnectLabel setEnabled: YES];
423    if (maxPeers != INVALID)
424        [fPeersConnectField setIntValue: maxPeers];
425    else
426        [fPeersConnectField setStringValue: @""];
427}
428
429- (NSRect) windowWillUseStandardFrame: (NSWindow *) window defaultFrame: (NSRect) defaultFrame
430{
431    NSRect windowRect = [window frame];
432    windowRect.size.width = [window minSize].width;
433    return windowRect;
434}
435
436- (void) animationDidEnd: (NSAnimation *) animation
437{
438    if (animation == fWebSeedTableAnimation)
439    {
440        [fWebSeedTableAnimation release];
441        fWebSeedTableAnimation = nil;
442    }
443}
444
445- (NSSize) windowWillResize: (NSWindow *) window toSize: (NSSize) proposedFrameSize
446{
447    //this is an edge-case - just stop the animation (stopAnimation jumps to end frame)
448    if (fWebSeedTableAnimation)
449    {
450        [fWebSeedTableAnimation stopAnimation];
451        [fWebSeedTableAnimation release];
452        fWebSeedTableAnimation = nil;
453    }
454   
455    return proposedFrameSize;
456}
457
458- (void) windowWillClose: (NSNotification *) notification
459{
460    if ([NSApp isOnSnowLeopardOrBetter] && fCurrentTabTag == TAB_FILES_TAG
461        && ([QLPreviewPanelSL sharedPreviewPanelExists] && [[QLPreviewPanelSL sharedPreviewPanel] isVisible]))
462        [[QLPreviewPanelSL sharedPreviewPanel] reloadData];
463}
464
465- (void) setTab: (id) sender
466{
467    const NSInteger oldTabTag = fCurrentTabTag;
468    fCurrentTabTag = [fTabMatrix selectedTag];
469    if (fCurrentTabTag == oldTabTag)
470        return;
471   
472    [self updateInfoStats];
473   
474    //take care of old view
475    CGFloat oldHeight = 0;
476    NSString * oldResizeSaveKey = nil;
477    if (oldTabTag != INVALID)
478    {
479        //deselect old tab item
480        [(InfoTabButtonCell *)[fTabMatrix cellWithTag: oldTabTag] setSelectedTab: NO];
481       
482        switch (oldTabTag)
483        {
484            case TAB_ACTIVITY_TAG:
485                [fPiecesView clearView];
486                break;
487           
488            case TAB_TRACKER_TAG:
489                [fTrackers release];
490                fTrackers = nil;
491               
492                oldResizeSaveKey = @"InspectorContentHeightTracker";
493                break;
494           
495            case TAB_PEERS_TAG:
496                //if in the middle of animating, just stop and resize immediately
497                if (fWebSeedTableAnimation)
498                    [self setWebSeedTableHidden: !fWebSeeds animate: NO];
499               
500                [fPeers release];
501                fPeers = nil;
502                [fWebSeeds release];
503                fWebSeeds = nil;
504               
505                oldResizeSaveKey = @"InspectorContentHeightPeers";
506                break;
507           
508            case TAB_FILES_TAG:
509                oldResizeSaveKey = @"InspectorContentHeightFiles";
510                break;
511        }
512       
513        NSView * oldView = [self tabViewForTag: oldTabTag];
514        oldHeight = [oldView frame].size.height;
515        if (oldResizeSaveKey)
516            [[NSUserDefaults standardUserDefaults] setFloat: oldHeight forKey: oldResizeSaveKey];
517       
518        //remove old view
519        [oldView setHidden: YES];
520        [oldView removeFromSuperview];
521    }
522   
523    //set new tab item
524    NSView * view = [self tabViewForTag: fCurrentTabTag];
525   
526    NSString * resizeSaveKey = nil;
527    NSString * identifier, * title;
528    switch (fCurrentTabTag)
529    {
530        case TAB_INFO_TAG:
531            identifier = TAB_INFO_IDENT;
532            title = NSLocalizedString(@"General Info", "Inspector -> title");
533            break;
534        case TAB_ACTIVITY_TAG:
535            identifier = TAB_ACTIVITY_IDENT;
536            title = NSLocalizedString(@"Activity", "Inspector -> title");
537            break;
538        case TAB_TRACKER_TAG:
539            identifier = TAB_TRACKER_IDENT;
540            title = NSLocalizedString(@"Trackers", "Inspector -> title");
541            resizeSaveKey = @"InspectorContentHeightTracker";
542            break;
543        case TAB_PEERS_TAG:
544            identifier = TAB_PEERS_IDENT;
545            title = NSLocalizedString(@"Peers", "Inspector -> title");
546            resizeSaveKey = @"InspectorContentHeightPeers";
547            break;
548        case TAB_FILES_TAG:
549            identifier = TAB_FILES_IDENT;
550            title = NSLocalizedString(@"Files", "Inspector -> title");
551            resizeSaveKey = @"InspectorContentHeightFiles";
552            break;
553        case TAB_OPTIONS_TAG:
554            identifier = TAB_OPTIONS_IDENT;
555            title = NSLocalizedString(@"Options", "Inspector -> title");
556            break;
557        default:
558            NSAssert1(NO, @"Unknown info tab selected: %d", fCurrentTabTag);
559            return;
560    }
561   
562    [[NSUserDefaults standardUserDefaults] setObject: identifier forKey: @"InspectorSelected"];
563   
564    NSWindow * window = [self window];
565   
566    [window setTitle: [NSString stringWithFormat: @"%@ - %@", title, NSLocalizedString(@"Torrent Inspector", "Inspector -> title")]];
567   
568    //selected tab item
569    [(InfoTabButtonCell *)[fTabMatrix selectedCell] setSelectedTab: YES];
570   
571    NSRect windowRect = [window frame], viewRect = [view frame];
572   
573    if (resizeSaveKey)
574    {
575        CGFloat height = [[NSUserDefaults standardUserDefaults] floatForKey: resizeSaveKey];
576        if (height != 0.0)
577            viewRect.size.height = MAX(height, TAB_MIN_HEIGHT);
578    }
579   
580    CGFloat difference = (viewRect.size.height - oldHeight) * [window userSpaceScaleFactor];
581    windowRect.origin.y -= difference;
582    windowRect.size.height += difference;
583   
584    if (resizeSaveKey)
585    {
586        if (!oldResizeSaveKey)
587        {
588            [window setMinSize: NSMakeSize([window minSize].width, windowRect.size.height - viewRect.size.height + TAB_MIN_HEIGHT)];
589            [window setMaxSize: NSMakeSize(FLT_MAX, FLT_MAX)];
590        }
591    }
592    else
593    {
594        [window setMinSize: NSMakeSize([window minSize].width, windowRect.size.height)];
595        [window setMaxSize: NSMakeSize(FLT_MAX, windowRect.size.height)];
596    }
597   
598    viewRect.size.width = windowRect.size.width;
599    [view setFrame: viewRect];
600   
601    [window setFrame: windowRect display: YES animate: oldTabTag != INVALID];
602    [[window contentView] addSubview: view];
603    [view setHidden: NO];
604   
605    if ([NSApp isOnSnowLeopardOrBetter] && (fCurrentTabTag == TAB_FILES_TAG || oldTabTag == TAB_FILES_TAG)
606        && ([QLPreviewPanelSL sharedPreviewPanelExists] && [[QLPreviewPanelSL sharedPreviewPanel] isVisible]))
607        [[QLPreviewPanelSL sharedPreviewPanel] reloadData];
608}
609
610- (void) setNextTab
611{
612    NSInteger tag = [fTabMatrix selectedTag]+1;
613    if (tag >= [fTabMatrix numberOfColumns])
614        tag = 0;
615   
616    [fTabMatrix selectCellWithTag: tag];
617    [self setTab: nil];
618}
619
620- (void) setPreviousTab
621{
622    NSInteger tag = [fTabMatrix selectedTag]-1;
623    if (tag < 0)
624        tag = [fTabMatrix numberOfColumns]-1;
625   
626    [fTabMatrix selectCellWithTag: tag];
627    [self setTab: nil];
628}
629
630- (NSInteger) numberOfRowsInTableView: (NSTableView *) tableView
631{
632    if (tableView == fPeerTable)
633        return fPeers ? [fPeers count] : 0;
634    else if (tableView == fWebSeedTable)
635        return fWebSeeds ? [fWebSeeds count] : 0;
636    else if (tableView == fTrackerTable)
637        return fTrackers ? [fTrackers count] : 0;
638    return 0;
639}
640
641- (id) tableView: (NSTableView *) tableView objectValueForTableColumn: (NSTableColumn *) column row: (NSInteger) row
642{
643    if (tableView == fPeerTable)
644    {
645        NSString * ident = [column identifier];
646        NSDictionary * peer = [fPeers objectAtIndex: row];
647       
648        if ([ident isEqualToString: @"Encryption"])
649            return [[peer objectForKey: @"Encryption"] boolValue] ? [NSImage imageNamed: @"Lock.png"] : nil;
650        else if ([ident isEqualToString: @"Client"])
651            return [peer objectForKey: @"Client"];
652        else if  ([ident isEqualToString: @"Progress"])
653            return [peer objectForKey: @"Progress"];
654        else if ([ident isEqualToString: @"UL To"])
655        {
656            NSNumber * rate;
657            return (rate = [peer objectForKey: @"UL To Rate"]) ? [NSString stringForSpeedAbbrev: [rate floatValue]] : @"";
658        }
659        else if ([ident isEqualToString: @"DL From"])
660        {
661            NSNumber * rate;
662            return (rate = [peer objectForKey: @"DL From Rate"]) ? [NSString stringForSpeedAbbrev: [rate floatValue]] : @"";
663        }
664        else
665            return [peer objectForKey: @"IP"];
666    }
667    else if (tableView == fWebSeedTable)
668    {
669        NSString * ident = [column identifier];
670        NSDictionary * webSeed = [fWebSeeds objectAtIndex: row];
671       
672        if ([ident isEqualToString: @"DL From"])
673        {
674            NSNumber * rate;
675            return (rate = [webSeed objectForKey: @"DL From Rate"]) ? [NSString stringForSpeedAbbrev: [rate floatValue]] : @"";
676        }
677        else
678            return [webSeed objectForKey: @"Address"];
679    }
680    else if (tableView == fTrackerTable)
681    {
682        id item = [fTrackers objectAtIndex: row];
683       
684        if ([item isKindOfClass: [NSDictionary class]])
685        {
686            const NSInteger tier = [[item objectForKey: @"Tier"] integerValue];
687            NSString * tierString = tier == -1 ? NSLocalizedString(@"New Tier", "Inspector -> tracker table")
688                                    : [NSString stringWithFormat: NSLocalizedString(@"Tier %d", "Inspector -> tracker table"), tier];
689           
690            if ([fTorrents count] > 1)
691                tierString = [tierString stringByAppendingFormat: @" - %@", [item objectForKey: @"Name"]];
692            return tierString;
693        }
694        else
695            return item; //TrackerNode or NSString
696    }
697    return nil;
698}
699
700- (NSCell *) tableView: (NSTableView *) tableView dataCellForTableColumn: (NSTableColumn *) tableColumn row: (NSInteger) row
701{
702    if (tableView == fTrackerTable)
703    {
704        const BOOL tracker = [[fTrackers objectAtIndex: row] isKindOfClass: [TrackerNode class]];
705        return tracker ? fTrackerCell : [tableColumn dataCellForRow: row];
706    }
707   
708    return nil;
709}
710
711- (CGFloat) tableView: (NSTableView *) tableView heightOfRow: (NSInteger) row
712{
713    if (tableView == fTrackerTable)
714    {
715        //check for NSDictionay instead of TrackerNode because of display issue when adding a row
716        if ([[fTrackers objectAtIndex: row] isKindOfClass: [NSDictionary class]])
717            return TRACKER_GROUP_SEPARATOR_HEIGHT;
718    }
719   
720    return [tableView rowHeight];
721}
722
723- (void) tableView: (NSTableView *) tableView willDisplayCell: (id) cell forTableColumn: (NSTableColumn *) tableColumn
724    row: (NSInteger) row
725{
726    if (tableView == fPeerTable)
727    {
728        NSString * ident = [tableColumn identifier];
729       
730        if  ([ident isEqualToString: @"Progress"])
731        {
732            NSDictionary * peer = [fPeers objectAtIndex: row];
733            [(PeerProgressIndicatorCell *)cell setSeed: [[peer objectForKey: @"Seed"] boolValue]];
734        }
735    }
736}
737
738- (void) tableView: (NSTableView *) tableView didClickTableColumn: (NSTableColumn *) tableColumn
739{
740    if (tableView == fPeerTable)
741    {
742        if (fPeers)
743        {
744            [fPeers sortUsingDescriptors: [self peerSortDescriptors]];
745            [tableView reloadData];
746        }
747    }
748    else if (tableView == fWebSeedTable)
749    {
750        if (fWebSeeds)
751        {
752            [fWebSeeds sortUsingDescriptors: [fWebSeedTable sortDescriptors]];
753            [tableView reloadData];
754        }
755    }
756    else;
757}
758
759- (BOOL) tableView: (NSTableView *) tableView shouldSelectRow: (NSInteger) row
760{
761    return tableView == fTrackerTable;
762}
763
764- (void) tableViewSelectionDidChange: (NSNotification *) notification
765{
766    if ([notification object] == fTrackerTable)
767        [fTrackerAddRemoveControl setEnabled: [fTrackerTable numberOfSelectedRows] > 0 forSegment: TRACKER_REMOVE_TAG];
768}
769
770- (BOOL) tableView: (NSTableView *) tableView isGroupRow: (NSInteger) row
771{
772    if (tableView == fTrackerTable)
773        return ![[fTrackers objectAtIndex: row] isKindOfClass: [TrackerNode class]] && [tableView editedRow] != row;
774    return NO;
775}
776
777- (NSString *) tableView: (NSTableView *) tableView toolTipForCell: (NSCell *) cell rect: (NSRectPointer) rect
778                tableColumn: (NSTableColumn *) column row: (NSInteger) row mouseLocation: (NSPoint) mouseLocation
779{
780    if (tableView == fPeerTable)
781    {
782        const BOOL multiple = [fTorrents count] > 1;
783       
784        NSDictionary * peer = [fPeers objectAtIndex: row];
785        NSMutableArray * components = [NSMutableArray arrayWithCapacity: multiple ? 6 : 5];
786       
787        if (multiple)
788            [components addObject: [peer objectForKey: @"Name"]];
789       
790        const CGFloat progress = [[peer objectForKey: @"Progress"] floatValue];
791        NSString * progressString = [NSString localizedStringWithFormat: NSLocalizedString(@"Progress: %.1f%%",
792                                        "Inspector -> Peers tab -> table row tooltip"), progress * 100.0];
793        if (progress < 1.0 && [[peer objectForKey: @"Seed"] boolValue])
794            progressString = [progressString stringByAppendingFormat: @" (%@)", NSLocalizedString(@"Partial Seed",
795                                "Inspector -> Peers tab -> table row tooltip")];
796        [components addObject: progressString];
797       
798        if ([[peer objectForKey: @"Encryption"] boolValue])
799            [components addObject: NSLocalizedString(@"Encrypted Connection", "Inspector -> Peers tab -> table row tooltip")];
800       
801        NSString * portString;
802        NSInteger port;
803        if ((port = [[peer objectForKey: @"Port"] intValue]) > 0)
804            portString = [NSString stringWithFormat: @"%d", port];
805        else
806            portString = NSLocalizedString(@"N/A", "Inspector -> Peers tab -> table row tooltip");
807        [components addObject: [NSString stringWithFormat: @"%@: %@", NSLocalizedString(@"Port",
808            "Inspector -> Peers tab -> table row tooltip"), portString]];
809       
810        const NSInteger peerFrom = [[peer objectForKey: @"From"] integerValue];
811        switch (peerFrom)
812        {
813            case TR_PEER_FROM_TRACKER:
814                [components addObject: NSLocalizedString(@"From: tracker", "Inspector -> Peers tab -> table row tooltip")];
815                break;
816            case TR_PEER_FROM_INCOMING:
817                [components addObject: NSLocalizedString(@"From: incoming connection", "Inspector -> Peers tab -> table row tooltip")];
818                break;
819            case TR_PEER_FROM_RESUME:
820                [components addObject: NSLocalizedString(@"From: cache", "Inspector -> Peers tab -> table row tooltip")];
821                break;
822            case TR_PEER_FROM_PEX:
823                [components addObject: NSLocalizedString(@"From: peer exchange", "Inspector -> Peers tab -> table row tooltip")];
824                break;
825            case TR_PEER_FROM_DHT:
826                [components addObject: NSLocalizedString(@"From: distributed hash table", "Inspector -> Peers tab -> table row tooltip")];
827                break;
828            case TR_PEER_FROM_LTEP:
829                [components addObject: NSLocalizedString(@"From: libtorrent extension protocol handshake",
830                                        "Inspector -> Peers tab -> table row tooltip")];
831                break;
832            default:
833                NSAssert1(NO, @"Peer from unknown source: %d", peerFrom);
834        }
835       
836        //determing status strings from flags
837        NSMutableArray * statusArray = [NSMutableArray arrayWithCapacity: 6];
838        NSString * flags = [peer objectForKey: @"Flags"];
839       
840        if ([flags rangeOfString: @"D"].location != NSNotFound)
841            [statusArray addObject: NSLocalizedString(@"Currently downloading (interested and not choked)",
842                "Inspector -> peer -> status")];
843        if ([flags rangeOfString: @"d"].location != NSNotFound)
844            [statusArray addObject: NSLocalizedString(@"You want to download, but peer does not want to send (interested and choked)",
845                "Inspector -> peer -> status")];
846        if ([flags rangeOfString: @"U"].location != NSNotFound)
847            [statusArray addObject: NSLocalizedString(@"Currently uploading (interested and not choked)",
848                "Inspector -> peer -> status")];
849        if ([flags rangeOfString: @"u"].location != NSNotFound)
850            [statusArray addObject: NSLocalizedString(@"Peer wants you to upload, but you do not want to (interested and choked)",
851                "Inspector -> peer -> status")];
852        if ([flags rangeOfString: @"K"].location != NSNotFound)
853            [statusArray addObject: NSLocalizedString(@"Peer is unchoking you, but you are not interested",
854                "Inspector -> peer -> status")];
855        if ([flags rangeOfString: @"?"].location != NSNotFound)
856            [statusArray addObject: NSLocalizedString(@"You unchoked the peer, but the peer is not interested",
857                "Inspector -> peer -> status")];
858       
859        if ([statusArray count] > 0)
860        {
861            NSString * statusStrings = [statusArray componentsJoinedByString: @"\n\n"];
862            [components addObject: [@"\n" stringByAppendingString: statusStrings]];
863        }
864       
865        return [components componentsJoinedByString: @"\n"];
866    }
867    else if (tableView == fTrackerTable)
868    {
869        id node = [fTrackers objectAtIndex: row];
870        if ([node isKindOfClass: [TrackerNode class]])
871            return [(TrackerNode *)node fullAnnounceAddress];
872    }
873    else if (tableView == fWebSeedTable)
874    {
875        if ([fTorrents count] > 1)
876            return [[fWebSeeds objectAtIndex: row] objectForKey: @"Name"];
877    }
878   
879    return nil;
880}
881
882- (void) tableView: (NSTableView *) tableView setObjectValue: (id) object forTableColumn: (NSTableColumn *) tableColumn
883    row: (NSInteger) row
884{
885    if (tableView != fTrackerTable)
886        return;
887   
888    Torrent * torrent= [fTorrents objectAtIndex: 0];
889   
890    BOOL added = NO;
891    for (NSString * tracker in [object componentsSeparatedByString: @"\n"])
892        if ([torrent addTrackerToNewTier: tracker])
893            added = YES;
894   
895    if (!added)
896        NSBeep();
897   
898    //reset table with either new or old value
899    [fTrackers release];
900    fTrackers = [[torrent allTrackerStats] retain];
901   
902    [fTrackerTable setTrackers: fTrackers];
903    [fTrackerTable reloadData];
904    [fTrackerTable deselectAll: self];
905   
906    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateUI" object: nil]; //incase sort by tracker
907}
908
909- (void) addRemoveTracker: (id) sender
910{
911    //don't allow add/remove when currently adding - it leads to weird results
912    if ([fTrackerTable editedRow] != -1)
913        return;
914   
915    if ([[sender cell] tagForSegment: [sender selectedSegment]] == TRACKER_REMOVE_TAG)
916        [self removeTrackers];
917    else
918        [self addTrackers];
919}
920
921- (NSArray *) quickLookURLs
922{
923    FileOutlineView * fileOutlineView = [fFileController outlineView];
924    Torrent * torrent = [fTorrents objectAtIndex: 0];
925    NSIndexSet * indexes = [fileOutlineView selectedRowIndexes];
926    NSMutableArray * urlArray = [NSMutableArray arrayWithCapacity: [indexes count]];
927   
928    for (NSUInteger i = [indexes firstIndex]; i != NSNotFound; i = [indexes indexGreaterThanIndex: i])
929    {
930        FileListNode * item = [fileOutlineView itemAtRow: i];
931        if ([self canQuickLookFile: item])
932            [urlArray addObject: [NSURL fileURLWithPath: [torrent fileLocation: item]]];
933    }
934   
935    return urlArray;
936}
937
938- (BOOL) canQuickLook
939{
940    if (fCurrentTabTag != TAB_FILES_TAG || ![[self window] isVisible] || [fTorrents count] != 1 || ![NSApp isOnSnowLeopardOrBetter])
941        return NO;
942   
943    Torrent * torrent = [fTorrents objectAtIndex: 0];
944    if (![torrent isFolder])
945        return NO;
946   
947    FileOutlineView * fileOutlineView = [fFileController outlineView];
948    NSIndexSet * indexes = [fileOutlineView selectedRowIndexes];
949   
950    for (NSUInteger i = [indexes firstIndex]; i != NSNotFound; i = [indexes indexGreaterThanIndex: i])
951        if ([self canQuickLookFile: [fileOutlineView itemAtRow: i]])
952            return YES;
953   
954    return NO;
955}
956
957#warning uncomment (in header too)
958- (NSRect) quickLookSourceFrameForPreviewItem: (id /*<QLPreviewItem>*/) item
959{
960    FileOutlineView * fileOutlineView = [fFileController outlineView];
961   
962    NSString * fullPath = [(NSURL *)item path];
963    Torrent * torrent = [fTorrents objectAtIndex: 0];
964    NSRange visibleRows = [fileOutlineView rowsInRect: [fileOutlineView bounds]];
965   
966    for (NSUInteger row = visibleRows.location; row < NSMaxRange(visibleRows); row++)
967    {
968        FileListNode * rowItem = [fileOutlineView itemAtRow: row];
969        if ([[torrent fileLocation: rowItem] isEqualToString: fullPath])
970        {
971            NSRect frame = [fileOutlineView iconRectForRow: row];
972           
973            if (!NSIntersectsRect([fileOutlineView visibleRect], frame))
974                return NSZeroRect;
975           
976            frame.origin = [fileOutlineView convertPoint: frame.origin toView: nil];
977            frame.origin = [[self window] convertBaseToScreen: frame.origin];
978            frame.origin.y -= frame.size.height;
979            return frame;
980        }
981    }
982   
983    return NSZeroRect;
984}
985
986- (void) setPiecesView: (id) sender
987{
988    [self setPiecesViewForAvailable: [sender selectedSegment] == PIECES_CONTROL_AVAILABLE];
989}
990
991- (void) setPiecesViewForAvailable: (BOOL) available
992{
993    [fPiecesControl setSelected: available forSegment: PIECES_CONTROL_AVAILABLE];
994    [fPiecesControl setSelected: !available forSegment: PIECES_CONTROL_PROGRESS];
995   
996    [[NSUserDefaults standardUserDefaults] setBool: available forKey: @"PiecesViewShowAvailability"];
997    [fPiecesView updateView];
998}
999
1000- (void) revealDataFile: (id) sender
1001{
1002    Torrent * torrent = [fTorrents objectAtIndex: 0];
1003    NSString * location = [torrent dataLocation];
1004    if (!location)
1005        return;
1006   
1007    if ([NSApp isOnSnowLeopardOrBetter])
1008    {
1009        NSURL * file = [NSURL fileURLWithPath: location];
1010        [[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs: [NSArray arrayWithObject: file]];
1011    }
1012    else
1013        [[NSWorkspace sharedWorkspace] selectFile: location inFileViewerRootedAtPath: nil];
1014}
1015
1016- (void) setFileFilterText: (id) sender
1017{
1018    [fFileController setFilterText: [sender stringValue]];
1019}
1020
1021- (void) setUseSpeedLimit: (id) sender
1022{
1023    const BOOL upload = sender == fUploadLimitCheck;
1024   
1025    if ([sender state] == NSMixedState)
1026        [sender setState: NSOnState];
1027    const BOOL limit = [sender state] == NSOnState;
1028   
1029    for (Torrent * torrent in fTorrents)
1030        [torrent setUseSpeedLimit: limit upload: upload];
1031   
1032    NSTextField * field = upload ? fUploadLimitField : fDownloadLimitField;
1033    [field setEnabled: limit];
1034    if (limit)
1035    {
1036        [field selectText: self];
1037        [[self window] makeKeyAndOrderFront: self];
1038    }
1039   
1040    NSTextField * label = upload ? fUploadLimitLabel : fDownloadLimitLabel;
1041    [label setEnabled: limit];
1042}
1043
1044- (void) setUseGlobalSpeedLimit: (id) sender
1045{
1046    if ([sender state] == NSMixedState)
1047        [sender setState: NSOnState];
1048    const BOOL limit = [sender state] == NSOnState;
1049   
1050    for (Torrent * torrent in fTorrents)
1051        [torrent setUseGlobalSpeedLimit: limit];
1052}
1053
1054- (void) setSpeedLimit: (id) sender
1055{
1056    const BOOL upload = sender == fUploadLimitField;
1057    const NSInteger limit = [sender intValue];
1058   
1059    for (Torrent * torrent in fTorrents)
1060        [torrent setSpeedLimit: limit upload: upload];
1061}
1062
1063- (void) setRatioSetting: (id) sender
1064{
1065    NSInteger setting;
1066    bool single = NO;
1067    switch ([sender indexOfSelectedItem])
1068    {
1069        case OPTION_POPUP_LIMIT:
1070            setting = TR_RATIOLIMIT_SINGLE;
1071            single = YES;
1072            break;
1073        case OPTION_POPUP_NO_LIMIT:
1074            setting = TR_RATIOLIMIT_UNLIMITED;
1075            break;
1076        case OPTION_POPUP_GLOBAL:
1077            setting = TR_RATIOLIMIT_GLOBAL;
1078            break;
1079        default:
1080            NSAssert1(NO, @"Unknown option selected in ratio popup: %d", [sender indexOfSelectedItem]);
1081            return;
1082    }
1083   
1084    for (Torrent * torrent in fTorrents)
1085        [torrent setRatioSetting: setting];
1086   
1087    [fRatioLimitField setHidden: !single];
1088    if (single)
1089    {
1090        [fRatioLimitField selectText: self];
1091        [[self window] makeKeyAndOrderFront: self];
1092    }
1093}
1094
1095- (void) setRatioLimit: (id) sender
1096{
1097    CGFloat limit = [sender floatValue];
1098   
1099    for (Torrent * torrent in fTorrents)
1100        [torrent setRatioLimit: limit];
1101}
1102
1103- (void) setPriority: (id) sender
1104{
1105    tr_priority_t priority;
1106    switch ([sender indexOfSelectedItem])
1107    {
1108        case OPTION_POPUP_PRIORITY_HIGH:
1109            priority = TR_PRI_HIGH;
1110            break;
1111        case OPTION_POPUP_PRIORITY_NORMAL:
1112            priority = TR_PRI_NORMAL;
1113            break;
1114        case OPTION_POPUP_PRIORITY_LOW:
1115            priority = TR_PRI_LOW;
1116            break;
1117        default:
1118            NSAssert1(NO, @"Unknown option selected in priority popup: %d", [sender indexOfSelectedItem]);
1119            return;
1120    }
1121   
1122    for (Torrent * torrent in fTorrents)
1123        [torrent setPriority: priority];
1124   
1125    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateUI" object: nil];
1126}
1127
1128- (void) setPeersConnectLimit: (id) sender
1129{
1130    NSInteger limit = [sender intValue];
1131   
1132    for (Torrent * torrent in fTorrents)
1133        [torrent setMaxPeerConnect: limit];
1134}
1135
1136- (BOOL) control: (NSControl *) control textShouldBeginEditing: (NSText *) fieldEditor
1137{
1138    [fInitialString release];
1139    fInitialString = [[control stringValue] retain];
1140   
1141    return YES;
1142}
1143
1144- (BOOL) control: (NSControl *) control didFailToFormatString: (NSString *) string errorDescription: (NSString *) error
1145{
1146    NSBeep();
1147    if (fInitialString)
1148    {
1149        [control setStringValue: fInitialString];
1150        [fInitialString release];
1151        fInitialString = nil;
1152    }
1153    return NO;
1154}
1155
1156@end
1157
1158@implementation InfoWindowController (Private)
1159
1160- (void) resetInfo
1161{
1162    const NSUInteger numberSelected = [fTorrents count];
1163    if (numberSelected != 1)
1164    {
1165        if (numberSelected > 0)
1166        {
1167            [fImageView setImage: [NSImage imageNamed: NSImageNameMultipleDocuments]];
1168           
1169            [fNameField setStringValue: [NSString stringWithFormat: NSLocalizedString(@"%d Torrents Selected",
1170                                            "Inspector -> selected torrents"), numberSelected]];
1171       
1172            uint64_t size = 0;
1173            NSUInteger fileCount = 0, magnetCount = 0;
1174            for (Torrent * torrent in fTorrents)
1175            {
1176                size += [torrent size];
1177                fileCount += [torrent fileCount];
1178                if ([torrent isMagnet])
1179                    ++magnetCount;
1180            }
1181           
1182            NSMutableArray * fileStrings = [NSMutableArray arrayWithCapacity: 2];
1183            if (fileCount > 0)
1184            {
1185                NSString * fileString;
1186                if (fileCount == 1)
1187                    fileString = NSLocalizedString(@"1 file", "Inspector -> selected torrents");
1188                else
1189                    fileString = [NSString stringWithFormat: NSLocalizedString(@"%d files", "Inspector -> selected torrents"), fileCount];
1190                [fileStrings addObject: fileString];
1191            }
1192            if (magnetCount > 0)
1193            {
1194                NSString * magnetString;
1195                if (magnetCount == 1)
1196                    magnetString = NSLocalizedString(@"1 magnetized transfer", "Inspector -> selected torrents");
1197                else
1198                    magnetString = [NSString stringWithFormat: NSLocalizedString(@"%d magnetized transfers",
1199                                    "Inspector -> selected torrents"), magnetCount];
1200                [fileStrings addObject: magnetString];
1201            }
1202           
1203            NSString * fileString = [fileStrings componentsJoinedByString: @" + "];
1204           
1205            if (magnetCount < numberSelected)
1206            {
1207                [fBasicInfoField setStringValue: [NSString stringWithFormat: @"%@, %@", fileString,
1208                    [NSString stringWithFormat: NSLocalizedString(@"%@ total", "Inspector -> selected torrents"),
1209                        [NSString stringForFileSize: size]]]];
1210                [fBasicInfoField setToolTip: [NSString stringWithFormat: NSLocalizedString(@"%llu bytes", "Inspector -> selected torrents"),
1211                                                size]];
1212            }
1213            else
1214            {
1215                [fBasicInfoField setStringValue: fileString];
1216                [fBasicInfoField setToolTip: nil];
1217            }
1218           
1219            [fTrackerTable deselectAll: self];
1220        }
1221        else
1222        {
1223            [fImageView setImage: [NSImage imageNamed: @"NSApplicationIcon"]];
1224           
1225            [fNameField setStringValue: NSLocalizedString(@"No Torrents Selected", "Inspector -> selected torrents")];
1226            [fBasicInfoField setStringValue: @""];
1227            [fBasicInfoField setToolTip: @""];
1228   
1229            [fHaveField setStringValue: @""];
1230            [fDownloadedTotalField setStringValue: @""];
1231            [fUploadedTotalField setStringValue: @""];
1232            [fFailedHashField setStringValue: @""];
1233            [fDateActivityField setStringValue: @""];
1234            [fRatioField setStringValue: @""];
1235           
1236            //options fields
1237            [fUploadLimitCheck setEnabled: NO];
1238            [fUploadLimitCheck setState: NSOffState];
1239            [fUploadLimitField setEnabled: NO];
1240            [fUploadLimitLabel setEnabled: NO];
1241            [fUploadLimitField setStringValue: @""];
1242           
1243            [fDownloadLimitCheck setEnabled: NO];
1244            [fDownloadLimitCheck setState: NSOffState];
1245            [fDownloadLimitField setEnabled: NO];
1246            [fDownloadLimitLabel setEnabled: NO];
1247            [fDownloadLimitField setStringValue: @""];
1248           
1249            [fGlobalLimitCheck setEnabled: NO];
1250            [fGlobalLimitCheck setState: NSOffState];
1251           
1252            [fPriorityPopUp setEnabled: NO];
1253            [fPriorityPopUp selectItemAtIndex: -1];
1254           
1255            [fRatioPopUp setEnabled: NO];
1256            [fRatioPopUp selectItemAtIndex: -1];
1257            [fRatioLimitField setHidden: YES];
1258            [fRatioLimitField setStringValue: @""];
1259           
1260            [fPeersConnectField setEnabled: NO];
1261            [fPeersConnectField setStringValue: @""];
1262            [fPeersConnectLabel setEnabled: NO];
1263           
1264            [fPeers release];
1265            fPeers = nil;
1266            [fPeerTable reloadData];
1267           
1268            [fTrackers release];
1269            fTrackers = nil;
1270           
1271            [fTrackerTable setTrackers: nil];
1272            [fTrackerTable reloadData];
1273        }
1274       
1275        [fFileController setTorrent: nil];
1276       
1277        [fNameField setToolTip: nil];
1278
1279        [fPiecesField setStringValue: @""];
1280        [fHashField setStringValue: @""];
1281        [fHashField setToolTip: nil];
1282        [fSecureField setStringValue: @""];
1283        [fCommentView setString: @""];
1284       
1285        [fCreatorField setStringValue: @""];
1286        [fDateCreatedField setStringValue: @""];
1287       
1288        [fDataLocationField setStringValue: @""];
1289        [fDataLocationField setToolTip: nil];
1290       
1291        [fRevealDataButton setHidden: YES];
1292       
1293        [fStateField setStringValue: @""];
1294        [fProgressField setStringValue: @""];
1295       
1296        [fErrorMessageView setString: @""];
1297       
1298        [fConnectedPeersField setStringValue: @""];
1299       
1300        [fDateAddedField setStringValue: @""];
1301        [fDateCompletedField setStringValue: @""];
1302       
1303        [fPiecesControl setSelected: NO forSegment: PIECES_CONTROL_AVAILABLE];
1304        [fPiecesControl setSelected: NO forSegment: PIECES_CONTROL_PROGRESS];
1305        [fPiecesControl setEnabled: NO];
1306        [fPiecesView setTorrent: nil];
1307       
1308        [fTrackerTable setTorrent: nil];
1309       
1310        [fTrackerAddRemoveControl setEnabled: NO forSegment: TRACKER_ADD_TAG];
1311        [fTrackerAddRemoveControl setEnabled: NO forSegment: TRACKER_REMOVE_TAG];
1312       
1313        [fFileFilterField setEnabled: NO];
1314    }
1315    else
1316    {
1317        Torrent * torrent = [fTorrents objectAtIndex: 0];
1318       
1319        [fFileController setTorrent: torrent];
1320       
1321        if ([NSApp isOnSnowLeopardOrBetter])
1322            [fImageView setImage: [torrent icon]];
1323        else
1324        {
1325            NSImage * icon = [[torrent icon] copy];
1326            [icon setFlipped: NO];
1327            [fImageView setImage: icon];
1328            [icon release];
1329        }
1330       
1331        NSString * name = [torrent name];
1332        [fNameField setStringValue: name];
1333        [fNameField setToolTip: name];
1334       
1335        if (![torrent isMagnet])
1336        {
1337            NSString * basicString = [NSString stringForFileSize: [torrent size]];
1338            if ([torrent isFolder])
1339            {
1340                NSString * fileString;
1341                NSInteger fileCount = [torrent fileCount];
1342                if (fileCount == 1)
1343                    fileString = NSLocalizedString(@"1 file", "Inspector -> selected torrents");
1344                else
1345                    fileString= [NSString stringWithFormat: NSLocalizedString(@"%d files", "Inspector -> selected torrents"), fileCount];
1346                basicString = [NSString stringWithFormat: @"%@, %@", fileString, basicString];
1347            }
1348            [fBasicInfoField setStringValue: basicString];
1349            [fBasicInfoField setToolTip: [NSString stringWithFormat: NSLocalizedString(@"%llu bytes", "Inspector -> selected torrents"),
1350                                            [torrent size]]];
1351        }
1352        else
1353        {
1354            [fBasicInfoField setStringValue: NSLocalizedString(@"Magnetized transfer", "Inspector -> selected torrents")];
1355            [fBasicInfoField setToolTip: nil];
1356        }
1357       
1358        NSString * piecesString = ![torrent isMagnet] ? [NSString stringWithFormat: @"%d, %@", [torrent pieceCount],
1359                                        [NSString stringForFileSize: [torrent pieceSize]]] : @"";
1360        [fPiecesField setStringValue: piecesString];
1361                                       
1362        NSString * hashString = [torrent hashString];
1363        [fHashField setStringValue: hashString];
1364        [fHashField setToolTip: hashString];
1365        [fSecureField setStringValue: [torrent privateTorrent]
1366                        ? NSLocalizedString(@"Private Torrent, PEX and DHT automatically disabled", "Inspector -> private torrent")
1367                        : NSLocalizedString(@"Public Torrent", "Inspector -> private torrent")];
1368       
1369        NSString * commentString = [torrent comment];
1370        [fCommentView setString: commentString];
1371       
1372        NSString * creatorString = [torrent creator];
1373        [fCreatorField setStringValue: creatorString];
1374        [fDateCreatedField setObjectValue: [torrent dateCreated]];
1375       
1376        [fDateAddedField setObjectValue: [torrent dateAdded]];
1377       
1378        //set pieces view
1379        BOOL piecesAvailableSegment = [[NSUserDefaults standardUserDefaults] boolForKey: @"PiecesViewShowAvailability"];
1380        [fPiecesControl setSelected: piecesAvailableSegment forSegment: PIECES_CONTROL_AVAILABLE];
1381        [fPiecesControl setSelected: !piecesAvailableSegment forSegment: PIECES_CONTROL_PROGRESS];
1382        [fPiecesControl setEnabled: YES];
1383        [fPiecesView setTorrent: torrent];
1384       
1385        [fTrackerTable setTorrent: torrent];
1386        [fTrackerTable deselectAll: self];
1387        [fTrackerAddRemoveControl setEnabled: YES forSegment: TRACKER_ADD_TAG];
1388        [fTrackerAddRemoveControl setEnabled: NO forSegment: TRACKER_REMOVE_TAG];
1389
1390        [fFileFilterField setEnabled: [torrent isFolder]];
1391    }
1392   
1393    [fFileFilterField setStringValue: @""];
1394   
1395    //reset webseeds here, since it might be hidden regardless of number selected
1396    BOOL hasWebSeeds = NO;
1397    for (Torrent * torrent in fTorrents)
1398    {
1399        if ([torrent webSeedCount] > 0)
1400        {
1401            hasWebSeeds = YES;
1402            break;
1403        }
1404    }
1405   
1406    if (!hasWebSeeds)
1407    {
1408        [fWebSeeds release];
1409        fWebSeeds = nil;
1410        [fWebSeedTable reloadData];
1411    }
1412    [self setWebSeedTableHidden: !hasWebSeeds animate: YES];
1413   
1414    //update stats and settings
1415    [self updateInfoStats];
1416    [self updateOptions];
1417}
1418
1419- (void) resetInfoForTorrent: (NSNotification *) notification
1420{
1421    if (fTorrents && [fTorrents containsObject: [notification object]])
1422        [self resetInfo];
1423}
1424
1425- (void) updateInfoGeneral
1426{   
1427    if ([fTorrents count] != 1)
1428        return;
1429   
1430    Torrent * torrent = [fTorrents objectAtIndex: 0];
1431   
1432    NSString * location = [torrent dataLocation];
1433    [fDataLocationField setStringValue: location ? [location stringByAbbreviatingWithTildeInPath] : @""];
1434    [fDataLocationField setToolTip: location ? location : @""];
1435   
1436    [fRevealDataButton setHidden: !location];
1437}
1438
1439- (void) updateInfoActivity
1440{
1441    NSInteger numberSelected = [fTorrents count];
1442    if (numberSelected == 0)
1443        return;
1444   
1445    uint64_t have = 0, haveVerified = 0, downloadedTotal = 0, uploadedTotal = 0, failedHash = 0;
1446    NSDate * lastActivity = nil;
1447    for (Torrent * torrent in fTorrents)
1448    {
1449        have += [torrent haveTotal];
1450        haveVerified += [torrent haveVerified];
1451        downloadedTotal += [torrent downloadedTotal];
1452        uploadedTotal += [torrent uploadedTotal];
1453        failedHash += [torrent failedHash];
1454       
1455        NSDate * nextLastActivity;
1456        if ((nextLastActivity = [torrent dateActivity]))
1457            lastActivity = lastActivity ? [lastActivity laterDate: nextLastActivity] : nextLastActivity;
1458    }
1459   
1460    if (have == 0)
1461        [fHaveField setStringValue: [NSString stringForFileSize: 0]];
1462    else
1463    {
1464        NSString * verifiedString = [NSString stringWithFormat: NSLocalizedString(@"%@ verified", "Inspector -> Activity tab -> have"),
1465                                        [NSString stringForFileSize: haveVerified]];
1466        if (have == haveVerified)
1467            [fHaveField setStringValue: verifiedString];
1468        else
1469            [fHaveField setStringValue: [NSString stringWithFormat: @"%@ (%@)", [NSString stringForFileSize: have], verifiedString]];
1470    }
1471   
1472    [fDownloadedTotalField setStringValue: [NSString stringForFileSize: downloadedTotal]];
1473    [fUploadedTotalField setStringValue: [NSString stringForFileSize: uploadedTotal]];
1474    [fFailedHashField setStringValue: [NSString stringForFileSize: failedHash]];
1475   
1476    [fDateActivityField setObjectValue: lastActivity];
1477   
1478    if (numberSelected == 1)
1479    {
1480        Torrent * torrent = [fTorrents objectAtIndex: 0];
1481       
1482        [fStateField setStringValue: [torrent stateString]];
1483       
1484        if ([torrent isFolder])
1485            [fProgressField setStringValue: [NSString localizedStringWithFormat: NSLocalizedString(@"%.2f%% (%.2f%% selected)",
1486                "Inspector -> Activity tab -> progress"), 100.0 * [torrent progress], 100.0 * [torrent progressDone]]];
1487        else
1488            [fProgressField setStringValue: [NSString localizedStringWithFormat: @"%.2f%%", 100.0 * [torrent progress]]];
1489           
1490        [fRatioField setStringValue: [NSString stringForRatio: [torrent ratio]]];
1491       
1492        NSString * errorMessage = [torrent errorMessage];
1493        if (![errorMessage isEqualToString: [fErrorMessageView string]])
1494            [fErrorMessageView setString: errorMessage];
1495       
1496        [fDateCompletedField setObjectValue: [torrent dateCompleted]];
1497       
1498        [fPiecesView updateView];
1499    }
1500    else if (numberSelected > 1)
1501    {
1502        [fRatioField setStringValue: [NSString stringForRatio: tr_getRatio(uploadedTotal, downloadedTotal)]];
1503    }
1504    else;
1505}
1506
1507- (void) updateInfoTracker
1508{
1509    if ([fTorrents count] == 0)
1510        return;
1511   
1512    //get updated tracker stats
1513    if ([fTrackerTable editedRow] == -1)
1514    {
1515        [fTrackers release];
1516       
1517        if ([fTorrents count] == 1)
1518            fTrackers = [[[fTorrents objectAtIndex: 0] allTrackerStats] retain];
1519        else
1520        {
1521            fTrackers = [[NSMutableArray alloc] init];
1522            for (Torrent * torrent in fTorrents)
1523                [fTrackers addObjectsFromArray: [torrent allTrackerStats]];
1524        }
1525       
1526        [fTrackerTable setTrackers: fTrackers];
1527        [fTrackerTable reloadData];
1528    }
1529    else
1530    {
1531        NSAssert1([fTorrents count] == 1, @"Attempting to add tracker with %d transfers selected", [fTorrents count]);
1532       
1533        if ([NSApp isOnSnowLeopardOrBetter])
1534        {
1535            NSIndexSet * addedIndexes = [NSIndexSet indexSetWithIndexesInRange: NSMakeRange([fTrackers count]-2, 2)];
1536            NSArray * tierAndTrackerBeingAdded = [fTrackers objectsAtIndexes: addedIndexes];
1537           
1538            [fTrackers release];
1539            fTrackers = [[[fTorrents objectAtIndex: 0] allTrackerStats] retain];
1540            [fTrackers addObjectsFromArray: tierAndTrackerBeingAdded];
1541           
1542            [fTrackerTable setTrackers: fTrackers];
1543           
1544            NSIndexSet * updateIndexes = [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [fTrackers count]-2)],
1545                    * columnIndexes = [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [[fTrackerTable tableColumns] count])];
1546            [fTrackerTable reloadDataForRowIndexes: updateIndexes columnIndexes: columnIndexes];
1547        }
1548    }
1549}
1550
1551- (void) updateInfoPeers
1552{
1553    if ([fTorrents count] == 0)
1554        return;
1555   
1556    if (!fPeers)
1557        fPeers = [[NSMutableArray alloc] init];
1558    else
1559        [fPeers removeAllObjects];
1560   
1561    if (!fWebSeeds)
1562        fWebSeeds = [[NSMutableArray alloc] init];
1563    else
1564        [fWebSeeds removeAllObjects];
1565   
1566    NSUInteger known = 0, connected = 0, tracker = 0, incoming = 0, cache = 0, pex = 0, dht = 0, ltep = 0,
1567                toUs = 0, fromUs = 0;
1568    BOOL anyActive = false;
1569    for (Torrent * torrent in fTorrents)
1570    {
1571        if ([torrent webSeedCount] > 0)
1572            [fWebSeeds addObjectsFromArray: [torrent webSeeds]];
1573       
1574        known += [torrent totalPeersKnown];
1575       
1576        if ([torrent isActive])
1577        {
1578            anyActive = YES;
1579            [fPeers addObjectsFromArray: [torrent peers]];
1580           
1581            const NSUInteger connectedThis = [torrent totalPeersConnected];
1582            if (connectedThis > 0)
1583            {
1584                connected += [torrent totalPeersConnected];
1585                tracker += [torrent totalPeersTracker];
1586                incoming += [torrent totalPeersIncoming];
1587                cache += [torrent totalPeersCache];
1588                pex += [torrent totalPeersPex];
1589                dht += [torrent totalPeersDHT];
1590                ltep += [torrent totalPeersLTEP];
1591               
1592                toUs += [torrent peersSendingToUs];
1593                fromUs += [torrent peersGettingFromUs];
1594            }
1595        }
1596    }
1597   
1598    [fPeers sortUsingDescriptors: [self peerSortDescriptors]];
1599    [fPeerTable reloadData];
1600   
1601    [fWebSeeds sortUsingDescriptors: [fWebSeedTable sortDescriptors]];
1602    [fWebSeedTable reloadData];
1603   
1604    NSString * knownString = [NSString stringWithFormat: NSLocalizedString(@"%d known", "Inspector -> Peers tab -> peers"), known];
1605    if (anyActive)
1606    {
1607        NSString * connectedText = [NSString stringWithFormat: NSLocalizedString(@"%d Connected", "Inspector -> Peers tab -> peers"),
1608                                    connected];
1609       
1610        if (connected > 0)
1611        {
1612            NSMutableArray * fromComponents = [NSMutableArray arrayWithCapacity: 6];
1613            if (tracker > 0)
1614                [fromComponents addObject: [NSString stringWithFormat:
1615                                        NSLocalizedString(@"%d tracker", "Inspector -> Peers tab -> peers"), tracker]];
1616            if (incoming > 0)
1617                [fromComponents addObject: [NSString stringWithFormat:
1618                                        NSLocalizedString(@"%d incoming", "Inspector -> Peers tab -> peers"), incoming]];
1619            if (cache > 0)
1620                [fromComponents addObject: [NSString stringWithFormat:
1621                                        NSLocalizedString(@"%d cache", "Inspector -> Peers tab -> peers"), cache]];
1622            if (pex > 0)
1623                [fromComponents addObject: [NSString stringWithFormat:
1624                                        NSLocalizedString(@"%d PEX", "Inspector -> Peers tab -> peers"), pex]];
1625            if (dht > 0)
1626                [fromComponents addObject: [NSString stringWithFormat:
1627                                        NSLocalizedString(@"%d DHT", "Inspector -> Peers tab -> peers"), dht]];
1628            if (ltep > 0)
1629                [fromComponents addObject: [NSString stringWithFormat:
1630                                        NSLocalizedString(@"%d LTEP", "Inspector -> Peers tab -> peers"), ltep]];
1631           
1632            NSMutableArray * upDownComponents = [NSMutableArray arrayWithCapacity: 3];
1633            if (toUs > 0)
1634                [upDownComponents addObject: [NSString stringWithFormat:
1635                                        NSLocalizedString(@"DL from %d", "Inspector -> Peers tab -> peers"), toUs]];
1636            if (fromUs > 0)
1637                [upDownComponents addObject: [NSString stringWithFormat:
1638                                        NSLocalizedString(@"UL to %d", "Inspector -> Peers tab -> peers"), fromUs]];
1639            [upDownComponents addObject: knownString];
1640           
1641            connectedText = [connectedText stringByAppendingFormat: @": %@\n%@", [fromComponents componentsJoinedByString: @", "],
1642                                [upDownComponents componentsJoinedByString: @", "]];
1643        }
1644        else
1645            connectedText = [connectedText stringByAppendingFormat: @"\n%@", knownString];
1646       
1647        [fConnectedPeersField setStringValue: connectedText];
1648    }
1649    else
1650    {
1651        NSString * activeString;
1652        if ([fTorrents count] == 1)
1653            activeString = NSLocalizedString(@"Transfer Not Active", "Inspector -> Peers tab -> peers");
1654        else
1655            activeString = NSLocalizedString(@"Transfers Not Active", "Inspector -> Peers tab -> peers");
1656       
1657        NSString * connectedText = [activeString stringByAppendingFormat: @"\n%@", knownString];
1658        [fConnectedPeersField setStringValue: connectedText];
1659    }
1660}
1661
1662- (void) updateInfoFiles
1663{
1664    if ([fTorrents count] == 1)
1665        [fFileController reloadData];
1666}
1667
1668- (NSView *) tabViewForTag: (NSInteger) tag
1669{
1670    switch (tag)
1671    {
1672        case TAB_INFO_TAG:
1673            return fInfoView;
1674        case TAB_ACTIVITY_TAG:
1675            return fActivityView;
1676        case TAB_TRACKER_TAG:
1677            return fTrackerView;
1678        case TAB_PEERS_TAG:
1679            return fPeersView;
1680        case TAB_FILES_TAG:
1681            return fFilesView;
1682        case TAB_OPTIONS_TAG:
1683            return fOptionsView;
1684        default:
1685            NSAssert1(NO, @"Unknown tab view for tag: %d", tag);
1686            return nil;
1687    }
1688}
1689
1690- (void) setWebSeedTableHidden: (BOOL) hide animate: (BOOL) animate
1691{
1692    if (fCurrentTabTag != TAB_PEERS_TAG || ![[self window] isVisible])
1693        animate = NO;
1694   
1695    if (fWebSeedTableAnimation)
1696    {
1697        [fWebSeedTableAnimation stopAnimation];
1698        [fWebSeedTableAnimation release];
1699        fWebSeedTableAnimation = nil;
1700    }
1701   
1702    NSRect webSeedFrame = [[fWebSeedTable enclosingScrollView] frame];
1703    NSRect peerFrame = [[fPeerTable enclosingScrollView] frame];
1704   
1705    if (hide)
1706    {
1707        CGFloat webSeedFrameMaxY = NSMaxY(webSeedFrame);
1708        webSeedFrame.size.height = 0;
1709        webSeedFrame.origin.y = webSeedFrameMaxY;
1710       
1711        peerFrame.size.height = webSeedFrameMaxY - peerFrame.origin.y;
1712    }
1713    else
1714    {
1715        webSeedFrame.origin.y -= fWebSeedTableHeight - webSeedFrame.size.height;
1716        webSeedFrame.size.height = fWebSeedTableHeight;
1717       
1718        peerFrame.size.height = (webSeedFrame.origin.y - fSpaceBetweenWebSeedAndPeer) - peerFrame.origin.y;
1719    }
1720   
1721    [[fWebSeedTable enclosingScrollView] setHidden: NO]; //this is needed for some reason
1722   
1723    //actually resize tables
1724    if (animate)
1725    {
1726        NSDictionary * webSeedDict = [NSDictionary dictionaryWithObjectsAndKeys:
1727                                    [fWebSeedTable enclosingScrollView], NSViewAnimationTargetKey,
1728                                    [NSValue valueWithRect: [[fWebSeedTable enclosingScrollView] frame]], NSViewAnimationStartFrameKey,
1729                                    [NSValue valueWithRect: webSeedFrame], NSViewAnimationEndFrameKey, nil],
1730                    * peerDict = [NSDictionary dictionaryWithObjectsAndKeys:
1731                                    [fPeerTable enclosingScrollView], NSViewAnimationTargetKey,
1732                                    [NSValue valueWithRect: [[fPeerTable enclosingScrollView] frame]], NSViewAnimationStartFrameKey,
1733                                    [NSValue valueWithRect: peerFrame], NSViewAnimationEndFrameKey, nil];
1734       
1735        fWebSeedTableAnimation = [[NSViewAnimation alloc] initWithViewAnimations:
1736                                        [NSArray arrayWithObjects: webSeedDict, peerDict, nil]];
1737        [fWebSeedTableAnimation setDuration: 0.125];
1738        [fWebSeedTableAnimation setAnimationBlockingMode: NSAnimationNonblocking];
1739        [fWebSeedTableAnimation setDelegate: self];
1740       
1741        [fWebSeedTableAnimation startAnimation];
1742    }
1743    else
1744    {
1745        [[fWebSeedTable enclosingScrollView] setFrame: webSeedFrame];
1746        [[fPeerTable enclosingScrollView] setFrame: peerFrame];
1747    }
1748}
1749
1750- (NSArray *) peerSortDescriptors
1751{
1752    NSMutableArray * descriptors = [NSMutableArray arrayWithCapacity: 2];
1753   
1754    NSArray * oldDescriptors = [fPeerTable sortDescriptors];
1755    BOOL useSecond = YES, asc = YES;
1756    if ([oldDescriptors count] > 0)
1757    {
1758        NSSortDescriptor * descriptor = [oldDescriptors objectAtIndex: 0];
1759        [descriptors addObject: descriptor];
1760       
1761        if ((useSecond = ![[descriptor key] isEqualToString: @"IP"]))
1762            asc = [descriptor ascending];
1763    }
1764   
1765    //sort by IP after primary sort
1766    if (useSecond)
1767    {
1768        NSSortDescriptor * secondDescriptor = [[NSSortDescriptor alloc] initWithKey: @"IP" ascending: asc
1769                                                                        selector: @selector(compareNumeric:)];
1770        [descriptors addObject: secondDescriptor];
1771        [secondDescriptor release];
1772    }
1773   
1774    return descriptors;
1775}
1776
1777- (BOOL) canQuickLookFile: (FileListNode *) item
1778{
1779    Torrent * torrent = [fTorrents objectAtIndex: 0];
1780    return ([item isFolder] || [torrent fileProgress: item] >= 1.0) && [torrent fileLocation: item];
1781}
1782
1783#warning doesn't like blank addresses
1784- (void) addTrackers
1785{
1786    [[self window] makeKeyWindow];
1787   
1788    NSAssert1([fTorrents count] == 1, @"Attempting to add tracker with %d transfers selected", [fTorrents count]);
1789   
1790    [fTrackers addObject: [NSDictionary dictionaryWithObject: [NSNumber numberWithInteger: -1] forKey: @"Tier"]];
1791    [fTrackers addObject: @""];
1792   
1793    [fTrackerTable setTrackers: fTrackers];
1794    [fTrackerTable reloadData];
1795    [fTrackerTable selectRowIndexes: [NSIndexSet indexSetWithIndex: [fTrackers count]-1] byExtendingSelection: NO];
1796    [fTrackerTable editColumn: [fTrackerTable columnWithIdentifier: @"Tracker"] row: [fTrackers count]-1 withEvent: nil select: YES];
1797}
1798
1799- (void) removeTrackers
1800{
1801    NSMutableDictionary * removeIdentifiers = [NSMutableDictionary dictionary];
1802   
1803    NSIndexSet * selectedIndexes = [fTrackerTable selectedRowIndexes];
1804    BOOL groupSelected = NO;
1805    for (NSUInteger i = 0; i < [fTrackers count]; ++i)
1806    {
1807        id object = [fTrackers objectAtIndex: i];
1808        if ([object isKindOfClass: [TrackerNode class]])
1809        {
1810            if (groupSelected || [selectedIndexes containsIndex: i])
1811            {
1812                Torrent * torrent = [(TrackerNode *)object torrent];
1813                NSMutableIndexSet * removeIndexSet;
1814                if (!(removeIndexSet = [removeIdentifiers objectForKey: torrent]))
1815                {
1816                    removeIndexSet = [NSMutableIndexSet indexSet];
1817                    [removeIdentifiers setObject: removeIndexSet forKey: torrent];
1818                }
1819               
1820                [removeIndexSet addIndex: [(TrackerNode *)object identifier]];
1821            }
1822        }
1823        else
1824        {
1825            groupSelected = [selectedIndexes containsIndex: i];
1826            if (!groupSelected && i > [selectedIndexes lastIndex])
1827                break;
1828        }
1829    }
1830   
1831    NSAssert([removeIdentifiers count] > 0, @"Trying to remove no trackers.");
1832   
1833    if ([[NSUserDefaults standardUserDefaults] boolForKey: @"WarningRemoveTrackers"])
1834    {
1835        NSAlert * alert = [[NSAlert alloc] init];
1836       
1837        if ([removeIdentifiers count] > 1)
1838        {
1839            [alert setMessageText: [NSString stringWithFormat: NSLocalizedString(@"Are you sure you want to remove %d trackers?",
1840                                                                "Remove trackers alert -> title"), [removeIdentifiers count]]];
1841            [alert setInformativeText: NSLocalizedString(@"Once removed, Transmission will no longer attempt to contact them."
1842                                        " This cannot be undone.", "Remove trackers alert -> message")];
1843        }
1844        else
1845        {
1846            [alert setMessageText: NSLocalizedString(@"Are you sure you want to remove this tracker?", "Remove trackers alert -> title")];
1847            [alert setInformativeText: NSLocalizedString(@"Once removed, Transmission will no longer attempt to contact it."
1848                                        " This cannot be undone.", "Remove trackers alert -> message")];
1849        }
1850       
1851        [alert addButtonWithTitle: NSLocalizedString(@"Remove", "Remove trackers alert -> button")];
1852        [alert addButtonWithTitle: NSLocalizedString(@"Cancel", "Remove trackers alert -> button")];
1853       
1854        [alert setShowsSuppressionButton: YES];
1855
1856        NSInteger result = [alert runModal];
1857        if ([[alert suppressionButton] state] == NSOnState)
1858            [[NSUserDefaults standardUserDefaults] setBool: NO forKey: @"WarningRemoveTrackers"];
1859        [alert release];
1860       
1861        if (result != NSAlertFirstButtonReturn)
1862            return;
1863    }
1864   
1865    for (Torrent * torrent in removeIdentifiers)
1866        [torrent removeTrackersWithIdentifiers: [removeIdentifiers objectForKey: torrent]];
1867   
1868    //reset table with either new or old value
1869    [fTrackers release];
1870    fTrackers = [[NSMutableArray alloc] init];
1871    for (Torrent * torrent in fTorrents)
1872        [fTrackers addObjectsFromArray: [torrent allTrackerStats]];
1873   
1874    [fTrackerTable setTrackers: fTrackers];
1875    [fTrackerTable reloadData];
1876    [fTrackerTable deselectAll: self];
1877   
1878    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateUI" object: nil]; //incase sort by tracker
1879}
1880
1881@end
Note: See TracBrowser for help on using the repository browser.