source: trunk/macosx/InfoWindowController.m @ 10097

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

#2860 Display trackers for all selected torrents in the inspector

  • Property svn:keywords set to Date Rev Author Id
File size: 66.3 KB
Line 
1/******************************************************************************
2 * $Id: InfoWindowController.m 10097 2010-02-05 02:51:42Z 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                oldResizeSaveKey = @"InspectorContentHeightTracker";
490                break;
491           
492            case TAB_PEERS_TAG:
493                //if in the middle of animating, just stop and resize immediately
494                if (fWebSeedTableAnimation)
495                    [self setWebSeedTableHidden: !fWebSeeds animate: NO];
496               
497                [fPeers release];
498                fPeers = nil;
499                [fWebSeeds release];
500                fWebSeeds = nil;
501               
502                oldResizeSaveKey = @"InspectorContentHeightPeers";
503                break;
504           
505            case TAB_FILES_TAG:
506                oldResizeSaveKey = @"InspectorContentHeightFiles";
507                break;
508        }
509       
510        NSView * oldView = [self tabViewForTag: oldTabTag];
511        oldHeight = [oldView frame].size.height;
512        if (oldResizeSaveKey)
513            [[NSUserDefaults standardUserDefaults] setFloat: oldHeight forKey: oldResizeSaveKey];
514       
515        //remove old view
516        [oldView setHidden: YES];
517        [oldView removeFromSuperview];
518    }
519   
520    //set new tab item
521    NSView * view = [self tabViewForTag: fCurrentTabTag];
522   
523    NSString * resizeSaveKey = nil;
524    NSString * identifier, * title;
525    switch (fCurrentTabTag)
526    {
527        case TAB_INFO_TAG:
528            identifier = TAB_INFO_IDENT;
529            title = NSLocalizedString(@"General Info", "Inspector -> title");
530            break;
531        case TAB_ACTIVITY_TAG:
532            identifier = TAB_ACTIVITY_IDENT;
533            title = NSLocalizedString(@"Activity", "Inspector -> title");
534            break;
535        case TAB_TRACKER_TAG:
536            identifier = TAB_TRACKER_IDENT;
537            title = NSLocalizedString(@"Trackers", "Inspector -> title");
538            resizeSaveKey = @"InspectorContentHeightTracker";
539            break;
540        case TAB_PEERS_TAG:
541            identifier = TAB_PEERS_IDENT;
542            title = NSLocalizedString(@"Peers", "Inspector -> title");
543            resizeSaveKey = @"InspectorContentHeightPeers";
544            break;
545        case TAB_FILES_TAG:
546            identifier = TAB_FILES_IDENT;
547            title = NSLocalizedString(@"Files", "Inspector -> title");
548            resizeSaveKey = @"InspectorContentHeightFiles";
549            break;
550        case TAB_OPTIONS_TAG:
551            identifier = TAB_OPTIONS_IDENT;
552            title = NSLocalizedString(@"Options", "Inspector -> title");
553            break;
554        default:
555            NSAssert1(NO, @"Unknown info tab selected: %d", fCurrentTabTag);
556            return;
557    }
558   
559    [[NSUserDefaults standardUserDefaults] setObject: identifier forKey: @"InspectorSelected"];
560   
561    NSWindow * window = [self window];
562   
563    [window setTitle: [NSString stringWithFormat: @"%@ - %@", title, NSLocalizedString(@"Torrent Inspector", "Inspector -> title")]];
564   
565    //selected tab item
566    [(InfoTabButtonCell *)[fTabMatrix selectedCell] setSelectedTab: YES];
567   
568    NSRect windowRect = [window frame], viewRect = [view frame];
569   
570    if (resizeSaveKey)
571    {
572        CGFloat height = [[NSUserDefaults standardUserDefaults] floatForKey: resizeSaveKey];
573        if (height != 0.0)
574            viewRect.size.height = MAX(height, TAB_MIN_HEIGHT);
575    }
576   
577    CGFloat difference = (viewRect.size.height - oldHeight) * [window userSpaceScaleFactor];
578    windowRect.origin.y -= difference;
579    windowRect.size.height += difference;
580   
581    if (resizeSaveKey)
582    {
583        if (!oldResizeSaveKey)
584        {
585            [window setMinSize: NSMakeSize([window minSize].width, windowRect.size.height - viewRect.size.height + TAB_MIN_HEIGHT)];
586            [window setMaxSize: NSMakeSize(FLT_MAX, FLT_MAX)];
587        }
588    }
589    else
590    {
591        [window setMinSize: NSMakeSize([window minSize].width, windowRect.size.height)];
592        [window setMaxSize: NSMakeSize(FLT_MAX, windowRect.size.height)];
593    }
594   
595    viewRect.size.width = windowRect.size.width;
596    [view setFrame: viewRect];
597   
598    [window setFrame: windowRect display: YES animate: oldTabTag != INVALID];
599    [[window contentView] addSubview: view];
600    [view setHidden: NO];
601   
602    if ([NSApp isOnSnowLeopardOrBetter] && (fCurrentTabTag == TAB_FILES_TAG || oldTabTag == TAB_FILES_TAG)
603        && ([QLPreviewPanelSL sharedPreviewPanelExists] && [[QLPreviewPanelSL sharedPreviewPanel] isVisible]))
604        [[QLPreviewPanelSL sharedPreviewPanel] reloadData];
605}
606
607- (void) setNextTab
608{
609    NSInteger tag = [fTabMatrix selectedTag]+1;
610    if (tag >= [fTabMatrix numberOfColumns])
611        tag = 0;
612   
613    [fTabMatrix selectCellWithTag: tag];
614    [self setTab: nil];
615}
616
617- (void) setPreviousTab
618{
619    NSInteger tag = [fTabMatrix selectedTag]-1;
620    if (tag < 0)
621        tag = [fTabMatrix numberOfColumns]-1;
622   
623    [fTabMatrix selectCellWithTag: tag];
624    [self setTab: nil];
625}
626
627- (NSInteger) numberOfRowsInTableView: (NSTableView *) tableView
628{
629    if (tableView == fPeerTable)
630        return fPeers ? [fPeers count] : 0;
631    else if (tableView == fWebSeedTable)
632        return fWebSeeds ? [fWebSeeds count] : 0;
633    else if (tableView == fTrackerTable)
634        return fTrackers ? [fTrackers count] : 0;
635    return 0;
636}
637
638- (id) tableView: (NSTableView *) tableView objectValueForTableColumn: (NSTableColumn *) column row: (NSInteger) row
639{
640    if (tableView == fPeerTable)
641    {
642        NSString * ident = [column identifier];
643        NSDictionary * peer = [fPeers objectAtIndex: row];
644       
645        if ([ident isEqualToString: @"Encryption"])
646            return [[peer objectForKey: @"Encryption"] boolValue] ? [NSImage imageNamed: @"Lock.png"] : nil;
647        else if ([ident isEqualToString: @"Client"])
648            return [peer objectForKey: @"Client"];
649        else if  ([ident isEqualToString: @"Progress"])
650            return [peer objectForKey: @"Progress"];
651        else if ([ident isEqualToString: @"UL To"])
652        {
653            NSNumber * rate;
654            return (rate = [peer objectForKey: @"UL To Rate"]) ? [NSString stringForSpeedAbbrev: [rate floatValue]] : @"";
655        }
656        else if ([ident isEqualToString: @"DL From"])
657        {
658            NSNumber * rate;
659            return (rate = [peer objectForKey: @"DL From Rate"]) ? [NSString stringForSpeedAbbrev: [rate floatValue]] : @"";
660        }
661        else
662            return [peer objectForKey: @"IP"];
663    }
664    else if (tableView == fWebSeedTable)
665    {
666        NSString * ident = [column identifier];
667        NSDictionary * webSeed = [fWebSeeds objectAtIndex: row];
668       
669        if ([ident isEqualToString: @"DL From"])
670        {
671            NSNumber * rate;
672            return (rate = [webSeed objectForKey: @"DL From Rate"]) ? [NSString stringForSpeedAbbrev: [rate floatValue]] : @"";
673        }
674        else
675            return [webSeed objectForKey: @"Address"];
676    }
677    else if (tableView == fTrackerTable)
678        return [fTrackers objectAtIndex: row];
679    return nil;
680}
681
682- (NSCell *) tableView: (NSTableView *) tableView dataCellForTableColumn: (NSTableColumn *) tableColumn row: (NSInteger) row
683{
684    if (tableView == fTrackerTable)
685    {
686        const BOOL tracker = [[fTrackers objectAtIndex: row] isKindOfClass: [TrackerNode class]];
687        return tracker ? fTrackerCell : [tableColumn dataCellForRow: row];
688    }
689   
690    return nil;
691}
692
693- (CGFloat) tableView: (NSTableView *) tableView heightOfRow: (NSInteger) row
694{
695    if (tableView == fTrackerTable)
696    {
697        if (![[fTrackers objectAtIndex: row] isKindOfClass: [TrackerNode class]] && [tableView editedRow] != row)
698            return TRACKER_GROUP_SEPARATOR_HEIGHT;
699    }
700   
701    return [tableView rowHeight];
702}
703
704- (void) tableView: (NSTableView *) tableView willDisplayCell: (id) cell forTableColumn: (NSTableColumn *) tableColumn
705    row: (NSInteger) row
706{
707    if (tableView == fPeerTable)
708    {
709        NSString * ident = [tableColumn identifier];
710       
711        if  ([ident isEqualToString: @"Progress"])
712        {
713            NSDictionary * peer = [fPeers objectAtIndex: row];
714            [(PeerProgressIndicatorCell *)cell setSeed: [[peer objectForKey: @"Seed"] boolValue]];
715        }
716    }
717}
718
719- (void) tableView: (NSTableView *) tableView didClickTableColumn: (NSTableColumn *) tableColumn
720{
721    if (tableView == fPeerTable)
722    {
723        if (fPeers)
724        {
725            NSArray * oldPeers = fPeers;
726            fPeers = [[fPeers sortedArrayUsingDescriptors: [self peerSortDescriptors]] retain];
727            [oldPeers release];
728            [tableView reloadData];
729        }
730    }
731    else if (tableView == fWebSeedTable)
732    {
733        if (fWebSeeds)
734        {
735            NSArray * oldWebSeeds = fWebSeeds;
736            fWebSeeds = [[fWebSeeds sortedArrayUsingDescriptors: [fWebSeedTable sortDescriptors]] retain];
737            [oldWebSeeds release];
738            [tableView reloadData];
739        }
740    }
741    else;
742}
743
744- (BOOL) tableView: (NSTableView *) tableView shouldSelectRow: (NSInteger) row
745{
746    return tableView == fTrackerTable;
747}
748
749- (void) tableViewSelectionDidChange: (NSNotification *) notification
750{
751    if ([notification object] == fTrackerTable)
752        [fTrackerAddRemoveControl setEnabled: [fTorrents count] == 1 && [fTrackerTable numberOfSelectedRows] > 0
753            forSegment: TRACKER_REMOVE_TAG];
754}
755
756- (BOOL) tableView: (NSTableView *) tableView isGroupRow: (NSInteger) row
757{
758    if (tableView == fTrackerTable)
759        return ![[fTrackers objectAtIndex: row] isKindOfClass: [TrackerNode class]] && [tableView editedRow] != row;
760    return NO;
761}
762
763- (NSString *) tableView: (NSTableView *) tableView toolTipForCell: (NSCell *) cell rect: (NSRectPointer) rect
764                tableColumn: (NSTableColumn *) column row: (NSInteger) row mouseLocation: (NSPoint) mouseLocation
765{
766    if (tableView == fPeerTable)
767    {
768        NSDictionary * peer = [fPeers objectAtIndex: row];
769        NSMutableArray * components = [NSMutableArray arrayWithCapacity: 5];
770       
771        CGFloat progress = [[peer objectForKey: @"Progress"] floatValue];
772        NSString * progressString = [NSString localizedStringWithFormat: NSLocalizedString(@"Progress: %.1f%%",
773                                        "Inspector -> Peers tab -> table row tooltip"), progress * 100.0];
774        if (progress < 1.0 && [[peer objectForKey: @"Seed"] boolValue])
775            progressString = [progressString stringByAppendingFormat: @" (%@)", NSLocalizedString(@"Partial Seed",
776                                "Inspector -> Peers tab -> table row tooltip")];
777        [components addObject: progressString];
778       
779        if ([[peer objectForKey: @"Encryption"] boolValue])
780            [components addObject: NSLocalizedString(@"Encrypted Connection", "Inspector -> Peers tab -> table row tooltip")];
781       
782        NSString * portString;
783        NSInteger port;
784        if ((port = [[peer objectForKey: @"Port"] intValue]) > 0)
785            portString = [NSString stringWithFormat: @"%d", port];
786        else
787            portString = NSLocalizedString(@"N/A", "Inspector -> Peers tab -> table row tooltip");
788        [components addObject: [NSString stringWithFormat: @"%@: %@", NSLocalizedString(@"Port",
789            "Inspector -> Peers tab -> table row tooltip"), portString]];
790       
791        const NSInteger peerFrom = [[peer objectForKey: @"From"] integerValue];
792        switch (peerFrom)
793        {
794            case TR_PEER_FROM_TRACKER:
795                [components addObject: NSLocalizedString(@"From: tracker", "Inspector -> Peers tab -> table row tooltip")];
796                break;
797            case TR_PEER_FROM_INCOMING:
798                [components addObject: NSLocalizedString(@"From: incoming connection", "Inspector -> Peers tab -> table row tooltip")];
799                break;
800            case TR_PEER_FROM_RESUME:
801                [components addObject: NSLocalizedString(@"From: cache", "Inspector -> Peers tab -> table row tooltip")];
802                break;
803            case TR_PEER_FROM_PEX:
804                [components addObject: NSLocalizedString(@"From: peer exchange", "Inspector -> Peers tab -> table row tooltip")];
805                break;
806            case TR_PEER_FROM_DHT:
807                [components addObject: NSLocalizedString(@"From: distributed hash table", "Inspector -> Peers tab -> table row tooltip")];
808                break;
809            case TR_PEER_FROM_LTEP:
810                [components addObject: NSLocalizedString(@"From: libtorrent extension protocol handshake",
811                                        "Inspector -> Peers tab -> table row tooltip")];
812                break;
813            default:
814                NSAssert1(NO, @"Peer from unknown source: %d", peerFrom);
815        }
816       
817        //determing status strings from flags
818        NSMutableArray * statusArray = [NSMutableArray arrayWithCapacity: 6];
819        NSString * flags = [peer objectForKey: @"Flags"];
820       
821        if ([flags rangeOfString: @"D"].location != NSNotFound)
822            [statusArray addObject: NSLocalizedString(@"Currently downloading (interested and not choked)",
823                "Inspector -> peer -> status")];
824        if ([flags rangeOfString: @"d"].location != NSNotFound)
825            [statusArray addObject: NSLocalizedString(@"You want to download, but peer does not want to send (interested and choked)",
826                "Inspector -> peer -> status")];
827        if ([flags rangeOfString: @"U"].location != NSNotFound)
828            [statusArray addObject: NSLocalizedString(@"Currently uploading (interested and not choked)",
829                "Inspector -> peer -> status")];
830        if ([flags rangeOfString: @"u"].location != NSNotFound)
831            [statusArray addObject: NSLocalizedString(@"Peer wants you to upload, but you do not want to (interested and choked)",
832                "Inspector -> peer -> status")];
833        if ([flags rangeOfString: @"K"].location != NSNotFound)
834            [statusArray addObject: NSLocalizedString(@"Peer is unchoking you, but you are not interested",
835                "Inspector -> peer -> status")];
836        if ([flags rangeOfString: @"?"].location != NSNotFound)
837            [statusArray addObject: NSLocalizedString(@"You unchoked the peer, but the peer is not interested",
838                "Inspector -> peer -> status")];
839       
840        if ([statusArray count] > 0)
841        {
842            NSString * statusStrings = [statusArray componentsJoinedByString: @"\n\n"];
843            [components addObject: [@"\n" stringByAppendingString: statusStrings]];
844        }
845       
846        return [components componentsJoinedByString: @"\n"];
847    }
848    else if (tableView == fTrackerTable)
849    {
850        id node = [fTrackers objectAtIndex: row];
851        if ([node isKindOfClass: [TrackerNode class]])
852            return [(TrackerNode *)node fullAnnounceAddress];
853    }
854   
855    return nil;
856}
857
858- (void) tableView: (NSTableView *) tableView setObjectValue: (id) object forTableColumn: (NSTableColumn *) tableColumn
859    row: (NSInteger) row
860{
861    if (tableView != fTrackerTable)
862        return;
863   
864    Torrent * torrent= [fTorrents objectAtIndex: 0];
865   
866    BOOL added = NO;
867    for (NSString * tracker in [object componentsSeparatedByString: @"\n"])
868        if ([torrent addTrackerToNewTier: tracker])
869            added = YES;
870   
871    if (!added)
872        NSBeep();
873   
874    //reset table with either new or old value
875    [fTrackers release];
876    fTrackers = [[torrent allTrackerStatsWithTransferName: NO] retain];
877   
878    [fTrackerTable setTrackers: fTrackers];
879    [fTrackerTable reloadData];
880    [fTrackerTable deselectAll: self];
881   
882    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateUI" object: nil]; //incase sort by tracker
883}
884
885- (void) addRemoveTracker: (id) sender
886{
887    //don't allow add/remove when currently adding - it leads to weird results
888    if ([fTrackerTable editedRow] != -1)
889        return;
890   
891    if ([[sender cell] tagForSegment: [sender selectedSegment]] == TRACKER_REMOVE_TAG)
892        [self removeTrackers];
893    else
894        [self addTrackers];
895}
896
897- (NSArray *) quickLookURLs
898{
899    FileOutlineView * fileOutlineView = [fFileController outlineView];
900    Torrent * torrent = [fTorrents objectAtIndex: 0];
901    NSIndexSet * indexes = [fileOutlineView selectedRowIndexes];
902    NSMutableArray * urlArray = [NSMutableArray arrayWithCapacity: [indexes count]];
903   
904    for (NSUInteger i = [indexes firstIndex]; i != NSNotFound; i = [indexes indexGreaterThanIndex: i])
905    {
906        FileListNode * item = [fileOutlineView itemAtRow: i];
907        if ([self canQuickLookFile: item])
908            [urlArray addObject: [NSURL fileURLWithPath: [torrent fileLocation: item]]];
909    }
910   
911    return urlArray;
912}
913
914- (BOOL) canQuickLook
915{
916    if (fCurrentTabTag != TAB_FILES_TAG || ![[self window] isVisible] || [fTorrents count] != 1 || ![NSApp isOnSnowLeopardOrBetter])
917        return NO;
918   
919    Torrent * torrent = [fTorrents objectAtIndex: 0];
920    if (![torrent isFolder])
921        return NO;
922   
923    FileOutlineView * fileOutlineView = [fFileController outlineView];
924    NSIndexSet * indexes = [fileOutlineView selectedRowIndexes];
925   
926    for (NSUInteger i = [indexes firstIndex]; i != NSNotFound; i = [indexes indexGreaterThanIndex: i])
927        if ([self canQuickLookFile: [fileOutlineView itemAtRow: i]])
928            return YES;
929   
930    return NO;
931}
932
933#warning uncomment (in header too)
934- (NSRect) quickLookSourceFrameForPreviewItem: (id /*<QLPreviewItem>*/) item
935{
936    FileOutlineView * fileOutlineView = [fFileController outlineView];
937   
938    NSString * fullPath = [(NSURL *)item path];
939    Torrent * torrent = [fTorrents objectAtIndex: 0];
940    NSRange visibleRows = [fileOutlineView rowsInRect: [fileOutlineView bounds]];
941   
942    for (NSUInteger row = visibleRows.location; row < NSMaxRange(visibleRows); row++)
943    {
944        FileListNode * rowItem = [fileOutlineView itemAtRow: row];
945        if ([[torrent fileLocation: rowItem] isEqualToString: fullPath])
946        {
947            NSRect frame = [fileOutlineView iconRectForRow: row];
948           
949            if (!NSIntersectsRect([fileOutlineView visibleRect], frame))
950                return NSZeroRect;
951           
952            frame.origin = [fileOutlineView convertPoint: frame.origin toView: nil];
953            frame.origin = [[self window] convertBaseToScreen: frame.origin];
954            frame.origin.y -= frame.size.height;
955            return frame;
956        }
957    }
958   
959    return NSZeroRect;
960}
961
962- (void) setPiecesView: (id) sender
963{
964    [self setPiecesViewForAvailable: [sender selectedSegment] == PIECES_CONTROL_AVAILABLE];
965}
966
967- (void) setPiecesViewForAvailable: (BOOL) available
968{
969    [fPiecesControl setSelected: available forSegment: PIECES_CONTROL_AVAILABLE];
970    [fPiecesControl setSelected: !available forSegment: PIECES_CONTROL_PROGRESS];
971   
972    [[NSUserDefaults standardUserDefaults] setBool: available forKey: @"PiecesViewShowAvailability"];
973    [fPiecesView updateView];
974}
975
976- (void) revealDataFile: (id) sender
977{
978    Torrent * torrent = [fTorrents objectAtIndex: 0];
979    NSString * location = [torrent dataLocation];
980    if (!location)
981        return;
982   
983    if ([NSApp isOnSnowLeopardOrBetter])
984    {
985        NSURL * file = [NSURL fileURLWithPath: location];
986        [[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs: [NSArray arrayWithObject: file]];
987    }
988    else
989        [[NSWorkspace sharedWorkspace] selectFile: location inFileViewerRootedAtPath: nil];
990}
991
992- (void) setFileFilterText: (id) sender
993{
994    [fFileController setFilterText: [sender stringValue]];
995}
996
997- (void) setUseSpeedLimit: (id) sender
998{
999    const BOOL upload = sender == fUploadLimitCheck;
1000   
1001    if ([sender state] == NSMixedState)
1002        [sender setState: NSOnState];
1003    const BOOL limit = [sender state] == NSOnState;
1004   
1005    for (Torrent * torrent in fTorrents)
1006        [torrent setUseSpeedLimit: limit upload: upload];
1007   
1008    NSTextField * field = upload ? fUploadLimitField : fDownloadLimitField;
1009    [field setEnabled: limit];
1010    if (limit)
1011    {
1012        [field selectText: self];
1013        [[self window] makeKeyAndOrderFront: self];
1014    }
1015   
1016    NSTextField * label = upload ? fUploadLimitLabel : fDownloadLimitLabel;
1017    [label setEnabled: limit];
1018}
1019
1020- (void) setUseGlobalSpeedLimit: (id) sender
1021{
1022    if ([sender state] == NSMixedState)
1023        [sender setState: NSOnState];
1024    const BOOL limit = [sender state] == NSOnState;
1025   
1026    for (Torrent * torrent in fTorrents)
1027        [torrent setUseGlobalSpeedLimit: limit];
1028}
1029
1030- (void) setSpeedLimit: (id) sender
1031{
1032    const BOOL upload = sender == fUploadLimitField;
1033    const NSInteger limit = [sender intValue];
1034   
1035    for (Torrent * torrent in fTorrents)
1036        [torrent setSpeedLimit: limit upload: upload];
1037}
1038
1039- (void) setRatioSetting: (id) sender
1040{
1041    NSInteger setting;
1042    bool single = NO;
1043    switch ([sender indexOfSelectedItem])
1044    {
1045        case OPTION_POPUP_LIMIT:
1046            setting = TR_RATIOLIMIT_SINGLE;
1047            single = YES;
1048            break;
1049        case OPTION_POPUP_NO_LIMIT:
1050            setting = TR_RATIOLIMIT_UNLIMITED;
1051            break;
1052        case OPTION_POPUP_GLOBAL:
1053            setting = TR_RATIOLIMIT_GLOBAL;
1054            break;
1055        default:
1056            NSAssert1(NO, @"Unknown option selected in ratio popup: %d", [sender indexOfSelectedItem]);
1057            return;
1058    }
1059   
1060    for (Torrent * torrent in fTorrents)
1061        [torrent setRatioSetting: setting];
1062   
1063    [fRatioLimitField setHidden: !single];
1064    if (single)
1065    {
1066        [fRatioLimitField selectText: self];
1067        [[self window] makeKeyAndOrderFront: self];
1068    }
1069}
1070
1071- (void) setRatioLimit: (id) sender
1072{
1073    CGFloat limit = [sender floatValue];
1074   
1075    for (Torrent * torrent in fTorrents)
1076        [torrent setRatioLimit: limit];
1077}
1078
1079- (void) setPriority: (id) sender
1080{
1081    tr_priority_t priority;
1082    switch ([sender indexOfSelectedItem])
1083    {
1084        case OPTION_POPUP_PRIORITY_HIGH:
1085            priority = TR_PRI_HIGH;
1086            break;
1087        case OPTION_POPUP_PRIORITY_NORMAL:
1088            priority = TR_PRI_NORMAL;
1089            break;
1090        case OPTION_POPUP_PRIORITY_LOW:
1091            priority = TR_PRI_LOW;
1092            break;
1093        default:
1094            NSAssert1(NO, @"Unknown option selected in priority popup: %d", [sender indexOfSelectedItem]);
1095            return;
1096    }
1097   
1098    for (Torrent * torrent in fTorrents)
1099        [torrent setPriority: priority];
1100   
1101    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateUI" object: nil];
1102}
1103
1104- (void) setPeersConnectLimit: (id) sender
1105{
1106    NSInteger limit = [sender intValue];
1107   
1108    for (Torrent * torrent in fTorrents)
1109        [torrent setMaxPeerConnect: limit];
1110}
1111
1112- (BOOL) control: (NSControl *) control textShouldBeginEditing: (NSText *) fieldEditor
1113{
1114    [fInitialString release];
1115    fInitialString = [[control stringValue] retain];
1116   
1117    return YES;
1118}
1119
1120- (BOOL) control: (NSControl *) control didFailToFormatString: (NSString *) string errorDescription: (NSString *) error
1121{
1122    NSBeep();
1123    if (fInitialString)
1124    {
1125        [control setStringValue: fInitialString];
1126        [fInitialString release];
1127        fInitialString = nil;
1128    }
1129    return NO;
1130}
1131
1132@end
1133
1134@implementation InfoWindowController (Private)
1135
1136- (void) resetInfo
1137{
1138    const NSUInteger numberSelected = [fTorrents count];
1139    if (numberSelected != 1)
1140    {
1141        if (numberSelected > 0)
1142        {
1143            [fImageView setImage: [NSImage imageNamed: NSImageNameMultipleDocuments]];
1144           
1145            [fNameField setStringValue: [NSString stringWithFormat: NSLocalizedString(@"%d Torrents Selected",
1146                                            "Inspector -> selected torrents"), numberSelected]];
1147       
1148            uint64_t size = 0;
1149            NSUInteger fileCount = 0, magnetCount = 0;
1150            for (Torrent * torrent in fTorrents)
1151            {
1152                size += [torrent size];
1153                fileCount += [torrent fileCount];
1154                if ([torrent isMagnet])
1155                    ++magnetCount;
1156            }
1157           
1158            NSMutableArray * fileStrings = [NSMutableArray arrayWithCapacity: 2];
1159            if (fileCount > 0)
1160            {
1161                NSString * fileString;
1162                if (fileCount == 1)
1163                    fileString = NSLocalizedString(@"1 file", "Inspector -> selected torrents");
1164                else
1165                    fileString = [NSString stringWithFormat: NSLocalizedString(@"%d files", "Inspector -> selected torrents"), fileCount];
1166                [fileStrings addObject: fileString];
1167            }
1168            if (magnetCount > 0)
1169            {
1170                NSString * magnetString;
1171                if (magnetCount == 1)
1172                    magnetString = NSLocalizedString(@"1 magnetized transfer", "Inspector -> selected torrents");
1173                else
1174                    magnetString = [NSString stringWithFormat: NSLocalizedString(@"%d magnetized transfers",
1175                                    "Inspector -> selected torrents"), magnetCount];
1176                [fileStrings addObject: magnetString];
1177            }
1178           
1179            NSString * fileString = [fileStrings componentsJoinedByString: @" + "];
1180           
1181            if (magnetCount < numberSelected)
1182            {
1183                [fBasicInfoField setStringValue: [NSString stringWithFormat: @"%@, %@", fileString,
1184                    [NSString stringWithFormat: NSLocalizedString(@"%@ total", "Inspector -> selected torrents"),
1185                        [NSString stringForFileSize: size]]]];
1186                [fBasicInfoField setToolTip: [NSString stringWithFormat: NSLocalizedString(@"%llu bytes", "Inspector -> selected torrents"),
1187                                                size]];
1188            }
1189            else
1190            {
1191                [fBasicInfoField setStringValue: fileString];
1192                [fBasicInfoField setToolTip: nil];
1193            }
1194        }
1195        else
1196        {
1197            [fImageView setImage: [NSImage imageNamed: @"NSApplicationIcon"]];
1198           
1199            [fNameField setStringValue: NSLocalizedString(@"No Torrents Selected", "Inspector -> selected torrents")];
1200            [fBasicInfoField setStringValue: @""];
1201            [fBasicInfoField setToolTip: @""];
1202   
1203            [fHaveField setStringValue: @""];
1204            [fDownloadedTotalField setStringValue: @""];
1205            [fUploadedTotalField setStringValue: @""];
1206            [fFailedHashField setStringValue: @""];
1207            [fDateActivityField setStringValue: @""];
1208            [fRatioField setStringValue: @""];
1209           
1210            //options fields
1211            [fUploadLimitCheck setEnabled: NO];
1212            [fUploadLimitCheck setState: NSOffState];
1213            [fUploadLimitField setEnabled: NO];
1214            [fUploadLimitLabel setEnabled: NO];
1215            [fUploadLimitField setStringValue: @""];
1216           
1217            [fDownloadLimitCheck setEnabled: NO];
1218            [fDownloadLimitCheck setState: NSOffState];
1219            [fDownloadLimitField setEnabled: NO];
1220            [fDownloadLimitLabel setEnabled: NO];
1221            [fDownloadLimitField setStringValue: @""];
1222           
1223            [fGlobalLimitCheck setEnabled: NO];
1224            [fGlobalLimitCheck setState: NSOffState];
1225           
1226            [fPriorityPopUp setEnabled: NO];
1227            [fPriorityPopUp selectItemAtIndex: -1];
1228           
1229            [fRatioPopUp setEnabled: NO];
1230            [fRatioPopUp selectItemAtIndex: -1];
1231            [fRatioLimitField setHidden: YES];
1232            [fRatioLimitField setStringValue: @""];
1233           
1234            [fPeersConnectField setEnabled: NO];
1235            [fPeersConnectField setStringValue: @""];
1236            [fPeersConnectLabel setEnabled: NO];
1237        }
1238       
1239        [fFileController setTorrent: nil];
1240       
1241        [fNameField setToolTip: nil];
1242
1243        [fPiecesField setStringValue: @""];
1244        [fHashField setStringValue: @""];
1245        [fHashField setToolTip: nil];
1246        [fSecureField setStringValue: @""];
1247        [fCommentView setString: @""];
1248       
1249        [fCreatorField setStringValue: @""];
1250        [fDateCreatedField setStringValue: @""];
1251       
1252        [fDataLocationField setStringValue: @""];
1253        [fDataLocationField setToolTip: nil];
1254       
1255        [fRevealDataButton setHidden: YES];
1256       
1257        [fStateField setStringValue: @""];
1258        [fProgressField setStringValue: @""];
1259       
1260        [fErrorMessageView setString: @""];
1261       
1262        [fConnectedPeersField setStringValue: @""];
1263       
1264        [fDateAddedField setStringValue: @""];
1265        [fDateCompletedField setStringValue: @""];
1266       
1267        [fPiecesControl setSelected: NO forSegment: PIECES_CONTROL_AVAILABLE];
1268        [fPiecesControl setSelected: NO forSegment: PIECES_CONTROL_PROGRESS];
1269        [fPiecesControl setEnabled: NO];
1270        [fPiecesView setTorrent: nil];
1271       
1272        [fPeers release];
1273        fPeers = nil;
1274        [fPeerTable reloadData];
1275       
1276        [fWebSeeds release];
1277        fWebSeeds = nil;
1278        [fWebSeedTable reloadData];
1279        [self setWebSeedTableHidden: YES animate: YES];
1280       
1281        [fTrackerTable setTorrent: nil];
1282       
1283        [fTrackers release];
1284        fTrackers = nil;
1285       
1286        [fTrackerTable setTrackers: fTrackers];
1287        [fTrackerTable reloadData];
1288       
1289        [fTrackerAddRemoveControl setEnabled: NO forSegment: TRACKER_ADD_TAG];
1290        [fTrackerAddRemoveControl setEnabled: NO forSegment: TRACKER_REMOVE_TAG];
1291       
1292        [fFileFilterField setEnabled: NO];
1293    }
1294    else
1295    {
1296        Torrent * torrent = [fTorrents objectAtIndex: 0];
1297       
1298        [fFileController setTorrent: torrent];
1299       
1300        if ([NSApp isOnSnowLeopardOrBetter])
1301            [fImageView setImage: [torrent icon]];
1302        else
1303        {
1304            NSImage * icon = [[torrent icon] copy];
1305            [icon setFlipped: NO];
1306            [fImageView setImage: icon];
1307            [icon release];
1308        }
1309       
1310        NSString * name = [torrent name];
1311        [fNameField setStringValue: name];
1312        [fNameField setToolTip: name];
1313       
1314        if (![torrent isMagnet])
1315        {
1316            NSString * basicString = [NSString stringForFileSize: [torrent size]];
1317            if ([torrent isFolder])
1318            {
1319                NSString * fileString;
1320                NSInteger fileCount = [torrent fileCount];
1321                if (fileCount == 1)
1322                    fileString = NSLocalizedString(@"1 file", "Inspector -> selected torrents");
1323                else
1324                    fileString= [NSString stringWithFormat: NSLocalizedString(@"%d files", "Inspector -> selected torrents"), fileCount];
1325                basicString = [NSString stringWithFormat: @"%@, %@", fileString, basicString];
1326            }
1327            [fBasicInfoField setStringValue: basicString];
1328            [fBasicInfoField setToolTip: [NSString stringWithFormat: NSLocalizedString(@"%llu bytes", "Inspector -> selected torrents"),
1329                                            [torrent size]]];
1330        }
1331        else
1332        {
1333            [fBasicInfoField setStringValue: NSLocalizedString(@"Magnetized transfer", "Inspector -> selected torrents")];
1334            [fBasicInfoField setToolTip: nil];
1335        }
1336       
1337        NSString * piecesString = ![torrent isMagnet] ? [NSString stringWithFormat: @"%d, %@", [torrent pieceCount],
1338                                        [NSString stringForFileSize: [torrent pieceSize]]] : @"";
1339        [fPiecesField setStringValue: piecesString];
1340                                       
1341        NSString * hashString = [torrent hashString];
1342        [fHashField setStringValue: hashString];
1343        [fHashField setToolTip: hashString];
1344        [fSecureField setStringValue: [torrent privateTorrent]
1345                        ? NSLocalizedString(@"Private Torrent, PEX and DHT automatically disabled", "Inspector -> private torrent")
1346                        : NSLocalizedString(@"Public Torrent", "Inspector -> private torrent")];
1347       
1348        NSString * commentString = [torrent comment];
1349        [fCommentView setString: commentString];
1350       
1351        NSString * creatorString = [torrent creator];
1352        [fCreatorField setStringValue: creatorString];
1353        [fDateCreatedField setObjectValue: [torrent dateCreated]];
1354       
1355        [fDateAddedField setObjectValue: [torrent dateAdded]];
1356       
1357        //set pieces view
1358        BOOL piecesAvailableSegment = [[NSUserDefaults standardUserDefaults] boolForKey: @"PiecesViewShowAvailability"];
1359        [fPiecesControl setSelected: piecesAvailableSegment forSegment: PIECES_CONTROL_AVAILABLE];
1360        [fPiecesControl setSelected: !piecesAvailableSegment forSegment: PIECES_CONTROL_PROGRESS];
1361        [fPiecesControl setEnabled: YES];
1362        [fPiecesView setTorrent: torrent];
1363       
1364        //get webseeds for table - if no webseeds for this torrent, clear the table
1365        BOOL hasWebSeeds = [torrent webSeedCount] > 0;
1366        [self setWebSeedTableHidden: !hasWebSeeds animate: YES];
1367        if (!hasWebSeeds)
1368        {
1369            [fWebSeeds release];
1370            fWebSeeds = nil;
1371            [fWebSeedTable reloadData];
1372        }
1373       
1374        [fTrackerTable setTorrent: torrent];
1375        [fTrackerTable deselectAll: self];
1376        [fTrackerAddRemoveControl setEnabled: YES forSegment: TRACKER_ADD_TAG];
1377        [fTrackerAddRemoveControl setEnabled: NO forSegment: TRACKER_REMOVE_TAG];
1378
1379        [fFileFilterField setEnabled: [torrent isFolder]];
1380    }
1381   
1382    [fFileFilterField setStringValue: @""];
1383   
1384    //update stats and settings
1385    [self updateInfoStats];
1386    [self updateOptions];
1387}
1388
1389- (void) resetInfoForTorrent: (NSNotification *) notification
1390{
1391    if (fTorrents && [fTorrents containsObject: [notification object]])
1392        [self resetInfo];
1393}
1394
1395- (void) updateInfoGeneral
1396{   
1397    if ([fTorrents count] != 1)
1398        return;
1399   
1400    Torrent * torrent = [fTorrents objectAtIndex: 0];
1401   
1402    NSString * location = [torrent dataLocation];
1403    [fDataLocationField setStringValue: location ? [location stringByAbbreviatingWithTildeInPath] : @""];
1404    [fDataLocationField setToolTip: location ? location : @""];
1405   
1406    [fRevealDataButton setHidden: !location];
1407}
1408
1409- (void) updateInfoActivity
1410{
1411    NSInteger numberSelected = [fTorrents count];
1412    if (numberSelected == 0)
1413        return;
1414   
1415    uint64_t have = 0, haveVerified = 0, downloadedTotal = 0, uploadedTotal = 0, failedHash = 0;
1416    NSDate * lastActivity = nil;
1417    for (Torrent * torrent in fTorrents)
1418    {
1419        have += [torrent haveTotal];
1420        haveVerified += [torrent haveVerified];
1421        downloadedTotal += [torrent downloadedTotal];
1422        uploadedTotal += [torrent uploadedTotal];
1423        failedHash += [torrent failedHash];
1424       
1425        NSDate * nextLastActivity;
1426        if ((nextLastActivity = [torrent dateActivity]))
1427            lastActivity = lastActivity ? [lastActivity laterDate: nextLastActivity] : nextLastActivity;
1428    }
1429   
1430    if (have == 0)
1431        [fHaveField setStringValue: [NSString stringForFileSize: 0]];
1432    else
1433    {
1434        NSString * verifiedString = [NSString stringWithFormat: NSLocalizedString(@"%@ verified", "Inspector -> Activity tab -> have"),
1435                                        [NSString stringForFileSize: haveVerified]];
1436        if (have == haveVerified)
1437            [fHaveField setStringValue: verifiedString];
1438        else
1439            [fHaveField setStringValue: [NSString stringWithFormat: @"%@ (%@)", [NSString stringForFileSize: have], verifiedString]];
1440    }
1441   
1442    [fDownloadedTotalField setStringValue: [NSString stringForFileSize: downloadedTotal]];
1443    [fUploadedTotalField setStringValue: [NSString stringForFileSize: uploadedTotal]];
1444    [fFailedHashField setStringValue: [NSString stringForFileSize: failedHash]];
1445   
1446    [fDateActivityField setObjectValue: lastActivity];
1447   
1448    if (numberSelected == 1)
1449    {
1450        Torrent * torrent = [fTorrents objectAtIndex: 0];
1451       
1452        [fStateField setStringValue: [torrent stateString]];
1453       
1454        if ([torrent isFolder])
1455            [fProgressField setStringValue: [NSString localizedStringWithFormat: NSLocalizedString(@"%.2f%% (%.2f%% selected)",
1456                "Inspector -> Activity tab -> progress"), 100.0 * [torrent progress], 100.0 * [torrent progressDone]]];
1457        else
1458            [fProgressField setStringValue: [NSString localizedStringWithFormat: @"%.2f%%", 100.0 * [torrent progress]]];
1459           
1460        [fRatioField setStringValue: [NSString stringForRatio: [torrent ratio]]];
1461       
1462        NSString * errorMessage = [torrent errorMessage];
1463        if (![errorMessage isEqualToString: [fErrorMessageView string]])
1464            [fErrorMessageView setString: errorMessage];
1465       
1466        [fDateCompletedField setObjectValue: [torrent dateCompleted]];
1467       
1468        [fPiecesView updateView];
1469    }
1470    else if (numberSelected > 1)
1471    {
1472        [fRatioField setStringValue: [NSString stringForRatio: tr_getRatio(uploadedTotal, downloadedTotal)]];
1473    }
1474    else;
1475}
1476
1477- (void) updateInfoTracker
1478{
1479    if ([fTorrents count] == 0)
1480        return;
1481   
1482    //get updated tracker stats
1483    if ([fTrackerTable editedRow] == -1)
1484    {
1485        const BOOL multiple = [fTorrents count] != 1;
1486       
1487        [fTrackers release];
1488        fTrackers = [[NSMutableArray alloc] init];
1489        for (Torrent * torrent in fTorrents)
1490            [fTrackers addObjectsFromArray: [torrent allTrackerStatsWithTransferName: multiple]];
1491       
1492        [fTrackerTable setTrackers: fTrackers];
1493        [fTrackerTable reloadData];
1494    }
1495    else
1496    {
1497        NSAssert1([fTorrents count] == 1, @"Attempting to add tracker with %d transfers selected", [fTorrents count]);
1498       
1499        if ([NSApp isOnSnowLeopardOrBetter])
1500        {
1501            NSIndexSet * addedIndexes = [NSIndexSet indexSetWithIndexesInRange: NSMakeRange([fTrackers count]-2, 2)];
1502            NSArray * tierAndTrackerBeingAdded = [fTrackers objectsAtIndexes: addedIndexes];
1503           
1504            [fTrackers release];
1505            fTrackers = [[[fTorrents objectAtIndex: 0] allTrackerStatsWithTransferName: NO] retain];
1506            [fTrackers addObjectsFromArray: tierAndTrackerBeingAdded];
1507           
1508            [fTrackerTable setTrackers: fTrackers];
1509           
1510            NSIndexSet * updateIndexes = [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [fTrackers count]-2)],
1511                    * columnIndexes = [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [[fTrackerTable tableColumns] count])];
1512            [fTrackerTable reloadDataForRowIndexes: updateIndexes columnIndexes: columnIndexes];
1513        }
1514    }
1515}
1516
1517- (void) updateInfoPeers
1518{
1519    if ([fTorrents count] != 1)
1520        return;
1521    Torrent * torrent = [fTorrents objectAtIndex: 0];
1522   
1523    NSString * knownString = [NSString stringWithFormat: NSLocalizedString(@"%d known", "Inspector -> Peers tab -> peers"),
1524                                [torrent totalPeersKnown]];
1525    if ([torrent isActive])
1526    {
1527        const NSInteger total = [torrent totalPeersConnected];
1528        NSString * connectedText = [NSString stringWithFormat: NSLocalizedString(@"%d Connected", "Inspector -> Peers tab -> peers"),
1529                                    total];
1530       
1531        if (total > 0)
1532        {
1533            NSMutableArray * fromComponents = [NSMutableArray arrayWithCapacity: 5];
1534            NSInteger count;
1535            if ((count = [torrent totalPeersTracker]) > 0)
1536                [fromComponents addObject: [NSString stringWithFormat:
1537                                        NSLocalizedString(@"%d tracker", "Inspector -> Peers tab -> peers"), count]];
1538            if ((count = [torrent totalPeersIncoming]) > 0)
1539                [fromComponents addObject: [NSString stringWithFormat:
1540                                        NSLocalizedString(@"%d incoming", "Inspector -> Peers tab -> peers"), count]];
1541            if ((count = [torrent totalPeersCache]) > 0)
1542                [fromComponents addObject: [NSString stringWithFormat:
1543                                        NSLocalizedString(@"%d cache", "Inspector -> Peers tab -> peers"), count]];
1544            if ((count = [torrent totalPeersPex]) > 0)
1545                [fromComponents addObject: [NSString stringWithFormat:
1546                                        NSLocalizedString(@"%d PEX", "Inspector -> Peers tab -> peers"), count]];
1547            if ((count = [torrent totalPeersDHT]) > 0)
1548                [fromComponents addObject: [NSString stringWithFormat:
1549                                        NSLocalizedString(@"%d DHT", "Inspector -> Peers tab -> peers"), count]];
1550            if ((count = [torrent totalPeersLTEP]) > 0)
1551                [fromComponents addObject: [NSString stringWithFormat:
1552                                        NSLocalizedString(@"%d LTEP", "Inspector -> Peers tab -> peers"), count]];
1553           
1554            NSMutableArray * upDownComponents = [NSMutableArray arrayWithCapacity: 3];
1555            if ((count = [torrent peersSendingToUs]) > 0)
1556                [upDownComponents addObject: [NSString stringWithFormat:
1557                                        NSLocalizedString(@"DL from %d", "Inspector -> Peers tab -> peers"), count]];
1558            if ((count = [torrent peersGettingFromUs]) > 0)
1559                [upDownComponents addObject: [NSString stringWithFormat:
1560                                        NSLocalizedString(@"UL to %d", "Inspector -> Peers tab -> peers"), count]];
1561            [upDownComponents addObject: knownString];
1562           
1563            connectedText = [connectedText stringByAppendingFormat: @": %@\n%@", [fromComponents componentsJoinedByString: @", "],
1564                                [upDownComponents componentsJoinedByString: @", "]];
1565        }
1566        else
1567            connectedText = [connectedText stringByAppendingFormat: @"\n%@", knownString];
1568       
1569        [fConnectedPeersField setStringValue: connectedText];
1570    }
1571    else
1572    {
1573        NSString * connectedText = [NSString stringWithFormat: @"%@\n%@",
1574                                    NSLocalizedString(@"Not Connected", "Inspector -> Peers tab -> peers"), knownString];
1575        [fConnectedPeersField setStringValue: connectedText];
1576    }
1577   
1578    [fPeers release];
1579    fPeers = [[[torrent peers] sortedArrayUsingDescriptors: [self peerSortDescriptors]] retain];
1580    [fPeerTable reloadData];
1581   
1582    if ([torrent webSeedCount] > 0)
1583    {
1584        [fWebSeeds release];
1585        fWebSeeds = [[[torrent webSeeds] sortedArrayUsingDescriptors: [fWebSeedTable sortDescriptors]] retain];
1586        [fWebSeedTable reloadData];
1587    }
1588}
1589
1590- (void) updateInfoFiles
1591{
1592    if ([fTorrents count] == 1)
1593        [fFileController reloadData];
1594}
1595
1596- (NSView *) tabViewForTag: (NSInteger) tag
1597{
1598    switch (tag)
1599    {
1600        case TAB_INFO_TAG:
1601            return fInfoView;
1602        case TAB_ACTIVITY_TAG:
1603            return fActivityView;
1604        case TAB_TRACKER_TAG:
1605            return fTrackerView;
1606        case TAB_PEERS_TAG:
1607            return fPeersView;
1608        case TAB_FILES_TAG:
1609            return fFilesView;
1610        case TAB_OPTIONS_TAG:
1611            return fOptionsView;
1612        default:
1613            NSAssert1(NO, @"Unknown tab view for tag: %d", tag);
1614            return nil;
1615    }
1616}
1617
1618- (void) setWebSeedTableHidden: (BOOL) hide animate: (BOOL) animate
1619{
1620    if (fCurrentTabTag != TAB_PEERS_TAG || ![[self window] isVisible])
1621        animate = NO;
1622   
1623    if (fWebSeedTableAnimation)
1624    {
1625        [fWebSeedTableAnimation stopAnimation];
1626        [fWebSeedTableAnimation release];
1627        fWebSeedTableAnimation = nil;
1628    }
1629   
1630    NSRect webSeedFrame = [[fWebSeedTable enclosingScrollView] frame];
1631    NSRect peerFrame = [[fPeerTable enclosingScrollView] frame];
1632   
1633    if (hide)
1634    {
1635        CGFloat webSeedFrameMaxY = NSMaxY(webSeedFrame);
1636        webSeedFrame.size.height = 0;
1637        webSeedFrame.origin.y = webSeedFrameMaxY;
1638       
1639        peerFrame.size.height = webSeedFrameMaxY - peerFrame.origin.y;
1640    }
1641    else
1642    {
1643        webSeedFrame.origin.y -= fWebSeedTableHeight - webSeedFrame.size.height;
1644        webSeedFrame.size.height = fWebSeedTableHeight;
1645       
1646        peerFrame.size.height = (webSeedFrame.origin.y - fSpaceBetweenWebSeedAndPeer) - peerFrame.origin.y;
1647    }
1648   
1649    [[fWebSeedTable enclosingScrollView] setHidden: NO]; //this is needed for some reason
1650   
1651    //actually resize tables
1652    if (animate)
1653    {
1654        NSDictionary * webSeedDict = [NSDictionary dictionaryWithObjectsAndKeys:
1655                                    [fWebSeedTable enclosingScrollView], NSViewAnimationTargetKey,
1656                                    [NSValue valueWithRect: [[fWebSeedTable enclosingScrollView] frame]], NSViewAnimationStartFrameKey,
1657                                    [NSValue valueWithRect: webSeedFrame], NSViewAnimationEndFrameKey, nil],
1658                    * peerDict = [NSDictionary dictionaryWithObjectsAndKeys:
1659                                    [fPeerTable enclosingScrollView], NSViewAnimationTargetKey,
1660                                    [NSValue valueWithRect: [[fPeerTable enclosingScrollView] frame]], NSViewAnimationStartFrameKey,
1661                                    [NSValue valueWithRect: peerFrame], NSViewAnimationEndFrameKey, nil];
1662       
1663        fWebSeedTableAnimation = [[NSViewAnimation alloc] initWithViewAnimations:
1664                                        [NSArray arrayWithObjects: webSeedDict, peerDict, nil]];
1665        [fWebSeedTableAnimation setDuration: 0.125];
1666        [fWebSeedTableAnimation setAnimationBlockingMode: NSAnimationNonblocking];
1667        [fWebSeedTableAnimation setDelegate: self];
1668       
1669        [fWebSeedTableAnimation startAnimation];
1670    }
1671    else
1672    {
1673        [[fWebSeedTable enclosingScrollView] setFrame: webSeedFrame];
1674        [[fPeerTable enclosingScrollView] setFrame: peerFrame];
1675    }
1676}
1677
1678- (NSArray *) peerSortDescriptors
1679{
1680    NSMutableArray * descriptors = [NSMutableArray arrayWithCapacity: 2];
1681   
1682    NSArray * oldDescriptors = [fPeerTable sortDescriptors];
1683    BOOL useSecond = YES, asc = YES;
1684    if ([oldDescriptors count] > 0)
1685    {
1686        NSSortDescriptor * descriptor = [oldDescriptors objectAtIndex: 0];
1687        [descriptors addObject: descriptor];
1688       
1689        if ((useSecond = ![[descriptor key] isEqualToString: @"IP"]))
1690            asc = [descriptor ascending];
1691    }
1692   
1693    //sort by IP after primary sort
1694    if (useSecond)
1695    {
1696        NSSortDescriptor * secondDescriptor = [[NSSortDescriptor alloc] initWithKey: @"IP" ascending: asc
1697                                                                        selector: @selector(compareNumeric:)];
1698        [descriptors addObject: secondDescriptor];
1699        [secondDescriptor release];
1700    }
1701   
1702    return descriptors;
1703}
1704
1705- (BOOL) canQuickLookFile: (FileListNode *) item
1706{
1707    Torrent * torrent = [fTorrents objectAtIndex: 0];
1708    return ([item isFolder] || [torrent fileProgress: item] >= 1.0) && [torrent fileLocation: item];
1709}
1710
1711#warning doesn't like blank addresses
1712- (void) addTrackers
1713{
1714    [[self window] makeKeyWindow];
1715   
1716    [fTrackers addObject: NSLocalizedString(@"New Tier", "inspector -> add tracker")];
1717    [fTrackers addObject: @""];
1718   
1719    [fTrackerTable setTrackers: fTrackers];
1720    [fTrackerTable reloadData];
1721    [fTrackerTable selectRowIndexes: [NSIndexSet indexSetWithIndex: [fTrackers count]-1] byExtendingSelection: NO];
1722    [fTrackerTable editColumn: [fTrackerTable columnWithIdentifier: @"Tracker"] row: [fTrackers count]-1 withEvent: nil select: YES];
1723}
1724
1725- (void) removeTrackers
1726{
1727    NSMutableIndexSet * removeIdentifiers = [NSMutableIndexSet indexSet];
1728   
1729    NSIndexSet * selectedIndexes = [fTrackerTable selectedRowIndexes];
1730    BOOL groupSelected = NO;
1731    for (NSUInteger i = 0; i < [fTrackers count]; ++i)
1732    {
1733        id object = [fTrackers objectAtIndex: i];
1734        if ([object isKindOfClass: [TrackerNode class]])
1735        {
1736            if (groupSelected || [selectedIndexes containsIndex: i])
1737                [removeIdentifiers addIndex: [(TrackerNode *)object identifier]];
1738        }
1739        else
1740        {
1741            groupSelected = [selectedIndexes containsIndex: i];
1742            if (!groupSelected && i > [selectedIndexes lastIndex])
1743                break;
1744        }
1745    }
1746   
1747    NSAssert([removeIdentifiers count] > 0, @"Trying to remove no trackers.");
1748   
1749    if ([[NSUserDefaults standardUserDefaults] boolForKey: @"WarningRemoveTrackers"])
1750    {
1751        NSAlert * alert = [[NSAlert alloc] init];
1752       
1753        if ([removeIdentifiers count] > 1)
1754        {
1755            [alert setMessageText: [NSString stringWithFormat: NSLocalizedString(@"Are you sure you want to remove %d trackers?",
1756                                                                "Remove trackers alert -> title"), [removeIdentifiers count]]];
1757            [alert setInformativeText: NSLocalizedString(@"Once removed, Transmission will no longer attempt to contact them."
1758                                        " This cannot be undone.", "Remove trackers alert -> message")];
1759        }
1760        else
1761        {
1762            [alert setMessageText: NSLocalizedString(@"Are you sure you want to remove this tracker?", "Remove trackers alert -> title")];
1763            [alert setInformativeText: NSLocalizedString(@"Once removed, Transmission will no longer attempt to contact it."
1764                                        " This cannot be undone.", "Remove trackers alert -> message")];
1765        }
1766       
1767        [alert addButtonWithTitle: NSLocalizedString(@"Remove", "Remove trackers alert -> button")];
1768        [alert addButtonWithTitle: NSLocalizedString(@"Cancel", "Remove trackers alert -> button")];
1769       
1770        [alert setShowsSuppressionButton: YES];
1771
1772        NSInteger result = [alert runModal];
1773        if ([[alert suppressionButton] state] == NSOnState)
1774            [[NSUserDefaults standardUserDefaults] setBool: NO forKey: @"WarningRemoveTrackers"];
1775        [alert release];
1776       
1777        if (result != NSAlertFirstButtonReturn)
1778            return;
1779    }
1780   
1781    Torrent * torrent = [fTorrents objectAtIndex: 0];
1782    [torrent removeTrackersWithIdentifiers: removeIdentifiers];
1783   
1784    //reset table with either new or old value
1785    [fTrackers release];
1786    fTrackers = [[torrent allTrackerStatsWithTransferName: NO] retain];
1787   
1788    [fTrackerTable setTrackers: fTrackers];
1789    [fTrackerTable reloadData];
1790    [fTrackerTable deselectAll: self];
1791   
1792    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateUI" object: nil]; //incase sort by tracker
1793}
1794
1795@end
Note: See TracBrowser for help on using the repository browser.