source: trunk/macosx/InfoWindowController.m @ 7246

Last change on this file since 7246 was 7246, checked in by livings124, 13 years ago

show a checkmark in the peer inspector for partial seeds and reflect that status in the tooltip

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