source: trunk/macosx/InfoWindowController.m @ 9583

Last change on this file since 9583 was 9583, checked in by charles, 13 years ago

(trunk mac) maybe fix the mac build

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