source: trunk/macosx/InfoWindowController.m @ 7290

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

#1395 - patch from Waldorf: Bonjour support for Web Interface

  • Property svn:keywords set to Date Rev Author Id
File size: 65.0 KB
Line 
1/******************************************************************************
2 * $Id: InfoWindowController.m 7290 2008-12-06 01:18:25Z 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        NSString * progressString = [NSString localizedStringWithFormat: NSLocalizedString(@"Progress: %.1f%%",
985                                        "Inspector -> Peers tab -> table row tooltip"), progress * 100.0];
986        if (progress < 1.0 && [[peer objectForKey: @"Seed"] boolValue])
987            progressString = [progressString stringByAppendingFormat: @" (%@)", NSLocalizedString(@"Partial Seed",
988                                "Inspector -> Peers tab -> table row tooltip")];
989        [components addObject: progressString];
990       
991        if ([[peer objectForKey: @"Encryption"] boolValue])
992            [components addObject: NSLocalizedString(@"Encrypted Connection", "Inspector -> Peers tab -> table row tooltip")];
993       
994        NSString * portString;
995        NSInteger port;
996        if ((port = [[peer objectForKey: @"Port"] intValue]) > 0)
997            portString = [NSString stringWithFormat: @"%d", port];
998        else
999            portString = NSLocalizedString(@"N/A", "Inspector -> Peers tab -> table row tooltip");
1000        [components addObject: [NSString stringWithFormat: @"%@: %@", NSLocalizedString(@"Port",
1001            "Inspector -> Peers tab -> table row tooltip"), portString]];
1002       
1003        switch ([[peer objectForKey: @"From"] intValue])
1004        {
1005            case TR_PEER_FROM_TRACKER:
1006                [components addObject: NSLocalizedString(@"From: tracker", "Inspector -> Peers tab -> table row tooltip")];
1007                break;
1008            case TR_PEER_FROM_INCOMING:
1009                [components addObject: NSLocalizedString(@"From: incoming connection", "Inspector -> Peers tab -> table row tooltip")];
1010                break;
1011            case TR_PEER_FROM_CACHE:
1012                [components addObject: NSLocalizedString(@"From: cache", "Inspector -> Peers tab -> table row tooltip")];
1013                break;
1014            case TR_PEER_FROM_PEX:
1015                [components addObject: NSLocalizedString(@"From: peer exchange", "Inspector -> Peers tab -> table row tooltip")];
1016                break;
1017        }
1018       
1019        //determing status strings from flags
1020        NSMutableArray * statusArray = [NSMutableArray arrayWithCapacity: 6];
1021        NSString * flags = [peer objectForKey: @"Flags"];
1022       
1023        if ([flags rangeOfString: @"D"].location != NSNotFound)
1024            [statusArray addObject: NSLocalizedString(@"Currently downloading (interested and not choked)",
1025                "Inspector -> peer -> status")];
1026        if ([flags rangeOfString: @"d"].location != NSNotFound)
1027            [statusArray addObject: NSLocalizedString(@"You want to download, but peer does not want to send (interested and choked)",
1028                "Inspector -> peer -> status")];
1029        if ([flags rangeOfString: @"U"].location != NSNotFound)
1030            [statusArray addObject: NSLocalizedString(@"Currently uploading (interested and not choked)",
1031                "Inspector -> peer -> status")];
1032        if ([flags rangeOfString: @"u"].location != NSNotFound)
1033            [statusArray addObject: NSLocalizedString(@"Peer wants you to upload, but you do not want to (interested and choked)",
1034                "Inspector -> peer -> status")];
1035        if ([flags rangeOfString: @"K"].location != NSNotFound)
1036            [statusArray addObject: NSLocalizedString(@"Peer is unchoking you, but you are not interested",
1037                "Inspector -> peer -> status")];
1038        if ([flags rangeOfString: @"?"].location != NSNotFound)
1039            [statusArray addObject: NSLocalizedString(@"You unchoked the peer, but the peer is not interested",
1040                "Inspector -> peer -> status")];
1041       
1042        if ([statusArray count] > 0)
1043        {
1044            NSString * statusStrings = [statusArray componentsJoinedByString: @"\n\n"];
1045            [components addObject: [@"\n" stringByAppendingString: statusStrings]];
1046        }
1047       
1048        return [components componentsJoinedByString: @"\n"];
1049    }
1050    return nil;
1051}
1052
1053- (void) tableView: (NSTableView *) tableView setObjectValue: (id) object forTableColumn: (NSTableColumn *) tableColumn
1054    row: (NSInteger) row
1055{
1056    if (tableView != fTrackerTable)
1057        return;
1058   
1059    [fTrackers replaceObjectAtIndex: row withObject: object];
1060   
1061    Torrent * torrent= [fTorrents objectAtIndex: 0];
1062    if (![torrent updateAllTrackersForAdd: fTrackers])
1063        NSBeep();
1064   
1065    //reset table with either new or old value
1066    [fTrackers release];
1067    fTrackers = [[torrent allTrackers: YES] retain];
1068    [fTrackerTable deselectAll: self];
1069   
1070    [fTrackerTable setTrackers: fTrackers];
1071    [fTrackerTable reloadData];
1072}
1073
1074- (void) addRemoveTracker: (id) sender
1075{
1076    //don't allow add/remove when currently adding - it leads to weird results
1077    if ([fTrackerTable editedRow] != -1)
1078        return;
1079   
1080    if ([[sender cell] tagForSegment: [sender selectedSegment]] == TRACKER_REMOVE_TAG)
1081        [self removeTrackers];
1082    else
1083        [self addTrackers];
1084}
1085
1086- (BOOL) tableView: (NSTableView *) tableView shouldEditTableColumn: (NSTableColumn *) tableColumn row: (NSInteger) row
1087{
1088    if (tableView != fTrackerTable)
1089        return NO;
1090   
1091    //only allow modification of custom-added trackers
1092    if ([[fTrackers objectAtIndex: row] isKindOfClass: [NSNumber class]] || ![[fTorrents objectAtIndex: 0] hasAddedTrackers])
1093        return NO;
1094   
1095    NSUInteger i;
1096    for (i = row-1; ![[fTrackers objectAtIndex: i] isKindOfClass: [NSNumber class]]; i--);
1097   
1098    return [[fTrackers objectAtIndex: i] intValue] == 0;
1099}
1100
1101- (BOOL) shouldQuickLookFileView
1102{
1103    return [[self window] isVisible] && fCurrentTabTag == TAB_FILES_TAG && [[fFileController outlineView] numberOfSelectedRows] > 0;
1104}
1105
1106- (NSArray *) quickLookURLs
1107{
1108    FileOutlineView * fileOutlineView = [fFileController outlineView];
1109    Torrent * torrent = [fTorrents objectAtIndex: 0];
1110    NSString * folder = [torrent downloadFolder];
1111    NSIndexSet * indexes = [fileOutlineView selectedRowIndexes];
1112    NSMutableArray * urlArray = [NSMutableArray arrayWithCapacity: [indexes count]];
1113   
1114    for (NSUInteger i = [indexes firstIndex]; i != NSNotFound; i = [indexes indexGreaterThanIndex: i])
1115    {
1116        FileListNode * item = [fileOutlineView itemAtRow: i];
1117        if ([self canQuickLookFile: item])
1118            [urlArray addObject: [NSURL fileURLWithPath: [folder stringByAppendingPathComponent: [item fullPath]]]];
1119    }
1120   
1121    return urlArray;
1122}
1123
1124- (BOOL) canQuickLook
1125{
1126    FileOutlineView * fileOutlineView = [fFileController outlineView];
1127    NSIndexSet * indexes = [fileOutlineView selectedRowIndexes];
1128   
1129    for (NSUInteger i = [indexes firstIndex]; i != NSNotFound; i = [indexes indexGreaterThanIndex: i])
1130        if ([self canQuickLookFile: [fileOutlineView itemAtRow: i]])
1131            return YES;
1132   
1133    return NO;
1134}
1135
1136- (NSRect) quickLookFrameWithURL: (NSURL *) url
1137{
1138    FileOutlineView * fileOutlineView = [fFileController outlineView];
1139   
1140    NSString * fullPath = [url path];
1141    NSString * folder = [[fTorrents objectAtIndex: 0] downloadFolder];
1142    NSRange visibleRows = [fileOutlineView rowsInRect: [fileOutlineView bounds]];
1143   
1144    for (NSUInteger row = visibleRows.location; row < NSMaxRange(visibleRows); row++)
1145    {
1146        FileListNode * rowItem = [fileOutlineView itemAtRow: row];
1147        if ([[folder stringByAppendingPathComponent: [rowItem fullPath]] isEqualToString: fullPath])
1148        {
1149            NSRect frame = [fileOutlineView iconRectForRow: row];
1150            frame.origin = [fileOutlineView convertPoint: frame.origin toView: nil];
1151            frame.origin = [[self window] convertBaseToScreen: frame.origin];
1152            frame.origin.y -= frame.size.height;
1153            return frame;
1154        }
1155    }
1156   
1157    return NSZeroRect;
1158}
1159
1160- (void) setPiecesView: (id) sender
1161{
1162    [self setPiecesViewForAvailable: [sender selectedSegment] == PIECES_CONTROL_AVAILABLE];
1163}
1164
1165- (void) setPiecesViewForAvailable: (BOOL) available
1166{
1167    [fPiecesControl setSelected: available forSegment: PIECES_CONTROL_AVAILABLE];
1168    [fPiecesControl setSelected: !available forSegment: PIECES_CONTROL_PROGRESS];
1169   
1170    [[NSUserDefaults standardUserDefaults] setBool: available forKey: @"PiecesViewShowAvailability"];
1171    [fPiecesView updateView];
1172}
1173
1174- (void) revealTorrentFile: (id) sender
1175{
1176    if ([fTorrents count] > 0)
1177        [[fTorrents objectAtIndex: 0] revealPublicTorrent];
1178}
1179
1180- (void) revealDataFile: (id) sender
1181{
1182    if ([fTorrents count] > 0)
1183        [[fTorrents objectAtIndex: 0] revealData];
1184}
1185
1186- (void) setSpeedMode: (id) sender
1187{
1188    BOOL upload = sender == fUploadLimitPopUp;
1189    NSInteger mode;
1190    switch ([sender indexOfSelectedItem])
1191    {
1192        case OPTION_POPUP_LIMIT:
1193            mode = TR_SPEEDLIMIT_SINGLE;
1194            break;
1195        case OPTION_POPUP_NO_LIMIT:
1196            mode = TR_SPEEDLIMIT_UNLIMITED;
1197            break;
1198        case OPTION_POPUP_GLOBAL:
1199            mode = TR_SPEEDLIMIT_GLOBAL;
1200            break;
1201        default:
1202            return;
1203    }
1204   
1205    Torrent * torrent;
1206    NSEnumerator * enumerator = [fTorrents objectEnumerator];
1207    while ((torrent = [enumerator nextObject]))
1208        [torrent setSpeedMode: mode upload: upload];
1209   
1210    NSTextField * field = upload ? fUploadLimitField : fDownloadLimitField;
1211   
1212    BOOL single = mode == TR_SPEEDLIMIT_SINGLE;
1213    [field setHidden: !single];
1214    if (single)
1215    {
1216        [field selectText: self];
1217        [[self window] makeKeyAndOrderFront:self];
1218    }
1219   
1220    NSTextField * label = upload ? fUploadLimitLabel : fDownloadLimitLabel;
1221    [label setHidden: !single];
1222}
1223
1224- (void) setSpeedLimit: (id) sender
1225{
1226    BOOL upload = sender == fUploadLimitField;
1227    NSInteger limit = [sender intValue];
1228   
1229    Torrent * torrent;
1230    NSEnumerator * enumerator = [fTorrents objectEnumerator];
1231   
1232    while ((torrent = [enumerator nextObject]))
1233        [torrent setSpeedLimit: limit upload: upload];
1234}
1235
1236- (void) setRatioSetting: (id) sender
1237{
1238    NSInteger setting;
1239    switch ([sender indexOfSelectedItem])
1240    {
1241        case OPTION_POPUP_LIMIT:
1242            setting = NSOnState;
1243            break;
1244        case OPTION_POPUP_NO_LIMIT:
1245            setting = NSOffState;
1246            break;
1247        case OPTION_POPUP_GLOBAL:
1248            setting = NSMixedState;
1249            break;
1250        default:
1251            return;
1252    }
1253   
1254    Torrent * torrent;
1255    NSEnumerator * enumerator = [fTorrents objectEnumerator];
1256    while ((torrent = [enumerator nextObject]))
1257        [torrent setRatioSetting: setting];
1258   
1259    BOOL single = setting == NSOnState;
1260    [fRatioLimitField setHidden: !single];
1261    if (single)
1262    {
1263        [fRatioLimitField selectText: self];
1264        [[self window] makeKeyAndOrderFront:self];
1265    }
1266   
1267    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateUI" object: nil];
1268}
1269
1270- (void) setRatioLimit: (id) sender
1271{
1272    CGFloat limit = [sender floatValue];
1273   
1274    Torrent * torrent;
1275    NSEnumerator * enumerator = [fTorrents objectEnumerator];
1276    while ((torrent = [enumerator nextObject]))
1277        [torrent setRatioLimit: limit];
1278}
1279
1280- (void) setPeersConnectLimit: (id) sender
1281{
1282    NSInteger limit = [sender intValue];
1283   
1284    Torrent * torrent;
1285    NSEnumerator * enumerator = [fTorrents objectEnumerator];
1286    while ((torrent = [enumerator nextObject]))
1287        [torrent setMaxPeerConnect: limit];
1288}
1289
1290
1291- (BOOL) control: (NSControl *) control textShouldBeginEditing: (NSText *) fieldEditor
1292{
1293    [fInitialString release];
1294    fInitialString = [[control stringValue] retain];
1295   
1296    return YES;
1297}
1298
1299- (BOOL) control: (NSControl *) control didFailToFormatString: (NSString *) string errorDescription: (NSString *) error
1300{
1301    NSBeep();
1302    if (fInitialString)
1303    {
1304        [control setStringValue: fInitialString];
1305        [fInitialString release];
1306        fInitialString = nil;
1307    }
1308    return NO;
1309}
1310
1311@end
1312
1313@implementation InfoWindowController (Private)
1314
1315- (void) updateInfoGeneral
1316{   
1317    if ([fTorrents count] != 1)
1318        return;
1319   
1320    Torrent * torrent = [fTorrents objectAtIndex: 0];
1321   
1322    [fTrackerField setStringValue: [torrent trackerAddressAnnounce]];
1323   
1324    NSString * location = [torrent dataLocation];
1325    [fDataLocationField setStringValue: [location stringByAbbreviatingWithTildeInPath]];
1326    [fDataLocationField setToolTip: location];
1327}
1328
1329- (void) updateInfoActivity
1330{
1331    NSInteger numberSelected = [fTorrents count];
1332    if (numberSelected == 0)
1333        return;
1334   
1335    uint64_t have = 0, haveVerified = 0, downloadedTotal = 0, uploadedTotal = 0, failedHash = 0;
1336    NSDate * lastActivity = nil;
1337    Torrent * torrent;
1338    NSEnumerator * enumerator = [fTorrents objectEnumerator];
1339    while ((torrent = [enumerator nextObject]))
1340    {
1341        have += [torrent haveTotal];
1342        haveVerified += [torrent haveVerified];
1343        downloadedTotal += [torrent downloadedTotal];
1344        uploadedTotal += [torrent uploadedTotal];
1345        failedHash += [torrent failedHash];
1346       
1347        NSDate * nextLastActivity;
1348        if ((nextLastActivity = [torrent dateActivity]))
1349            lastActivity = lastActivity ? [lastActivity laterDate: nextLastActivity] : nextLastActivity;
1350    }
1351   
1352    if (have == 0)
1353        [fHaveField setStringValue: [NSString stringForFileSize: 0]];
1354    else
1355    {
1356        NSString * verifiedString = [NSString stringWithFormat: NSLocalizedString(@"%@ verified", "Inspector -> Activity tab -> have"),
1357                                        [NSString stringForFileSize: haveVerified]];
1358        if (have == haveVerified)
1359            [fHaveField setStringValue: verifiedString];
1360        else
1361            [fHaveField setStringValue: [NSString stringWithFormat: @"%@ (%@)", [NSString stringForFileSize: have], verifiedString]];
1362    }
1363   
1364    [fDownloadedTotalField setStringValue: [NSString stringForFileSize: downloadedTotal]];
1365    [fUploadedTotalField setStringValue: [NSString stringForFileSize: uploadedTotal]];
1366    [fFailedHashField setStringValue: [NSString stringForFileSize: failedHash]];
1367   
1368    [fDateActivityField setObjectValue: lastActivity];
1369   
1370    if (numberSelected == 1)
1371    {
1372        torrent = [fTorrents objectAtIndex: 0];
1373       
1374        [fStateField setStringValue: [torrent stateString]];
1375       
1376        if ([torrent isFolder])
1377            [fProgressField setStringValue: [NSString localizedStringWithFormat: NSLocalizedString(@"%.2f%% (%.2f%% selected)",
1378                "Inspector -> Activity tab -> progress"), 100.0 * [torrent progress], 100.0 * [torrent progressDone]]];
1379        else
1380            [fProgressField setStringValue: [NSString localizedStringWithFormat: @"%.2f%%", 100.0 * [torrent progress]]];
1381           
1382        [fRatioField setStringValue: [NSString stringForRatio: [torrent ratio]]];
1383        [fSwarmSpeedField setStringValue: [torrent isActive] ? [NSString stringForSpeed: [torrent swarmSpeed]] : @""];
1384       
1385        NSString * errorMessage = [torrent errorMessage];
1386        if (![errorMessage isEqualToString: [fErrorMessageView string]])
1387        {
1388            [fErrorMessageView setString: errorMessage];
1389            [fErrorMessageView setSelectable: ![errorMessage isEqualToString: @""]];
1390        }
1391       
1392        [fDateCompletedField setObjectValue: [torrent dateCompleted]];
1393       
1394        [fPiecesView updateView];
1395    }
1396    else if (numberSelected > 1)
1397    {
1398        [fRatioField setStringValue: [NSString stringForRatio: tr_getRatio(uploadedTotal, downloadedTotal)]];
1399    }
1400    else;
1401}
1402
1403#warning reload table when necessary?
1404- (void) updateInfoTracker
1405{
1406    if ([fTorrents count] != 1)
1407        return;
1408    Torrent * torrent = [fTorrents objectAtIndex: 0];
1409   
1410    //announce fields
1411    NSString * announceAddress = [torrent trackerAddressAnnounce];
1412    [fAnnounceAddressField setStringValue: announceAddress];
1413    [fAnnounceAddressField setToolTip: announceAddress];
1414   
1415    [fAnnounceLastField setObjectValue: [torrent lastAnnounceTime]];
1416   
1417    NSString * announceResponse = [torrent announceResponse];
1418    [fAnnounceResponseField setStringValue: announceResponse];
1419    [fAnnounceResponseField setToolTip: announceResponse];
1420    [fAnnounceResponseField setSelectable: ![announceResponse isEqualToString: @""]];
1421   
1422    NSInteger announceNext = [torrent nextAnnounceTime];
1423    NSString * announceNextString;
1424    switch (announceNext)
1425    {
1426        case STAT_TIME_NOW:
1427            announceNextString = [NSLocalizedString(@"In progress", "Inspector -> tracker tab") stringByAppendingEllipsis];
1428            break;
1429        case STAT_TIME_NONE:
1430            announceNextString = @"";
1431            break;
1432        default:
1433            announceNextString = [NSString timeString: announceNext showSeconds: YES];
1434    }
1435    [fAnnounceNextField setStringValue: announceNextString];
1436   
1437    //scrape fields
1438    NSString * scrapeAddress;
1439    if ((scrapeAddress = [torrent trackerAddressScrape]))
1440    {
1441        [fScrapeAddressField setStringValue: scrapeAddress];
1442        [fScrapeAddressField setToolTip: scrapeAddress];
1443    }
1444    else
1445    {
1446        [fScrapeAddressField setStringValue: @""];
1447        [fScrapeAddressField setToolTip: @""];
1448    }
1449   
1450    [fScrapeLastField setObjectValue: [torrent lastScrapeTime]];
1451   
1452    NSString * scrapeResponse = [torrent scrapeResponse];
1453    [fScrapeResponseField setStringValue: scrapeResponse];
1454    [fScrapeResponseField setToolTip: scrapeResponse];
1455    [fScrapeResponseField setSelectable: ![scrapeResponse isEqualToString: @""]];
1456   
1457    NSInteger scrapeNext = [torrent nextScrapeTime];
1458    NSString * scrapeNextString;
1459    switch (scrapeNext)
1460    {
1461        case STAT_TIME_NOW:
1462            scrapeNextString = [NSLocalizedString(@"In progress", "Inspector -> tracker tab") stringByAppendingEllipsis];
1463            break;
1464        case STAT_TIME_NONE:
1465            scrapeNextString = @"";
1466            break;
1467        default:
1468            scrapeNextString = [NSString timeString: scrapeNext showSeconds: YES];
1469    }
1470    [fScrapeNextField setStringValue: scrapeNextString];
1471}
1472
1473- (void) updateInfoPeers
1474{
1475    if ([fTorrents count] != 1)
1476        return;
1477    Torrent * torrent = [fTorrents objectAtIndex: 0];
1478   
1479    NSInteger seeders = [torrent seeders], leechers = [torrent leechers], completed = [torrent completedFromTracker];
1480    [fSeedersField setStringValue: seeders >= 0 ? [NSString stringWithFormat: @"%d", seeders] : @""];
1481    [fLeechersField setStringValue: leechers >= 0 ? [NSString stringWithFormat: @"%d", leechers] : @""];
1482    [fCompletedFromTrackerField setStringValue: completed >= 0 ? [NSString stringWithFormat: @"%d", completed] : @""];
1483   
1484    BOOL active = [torrent isActive];
1485   
1486    if (active)
1487    {
1488        NSInteger total = [torrent totalPeersConnected];
1489        NSString * connected = [NSString stringWithFormat:
1490                                NSLocalizedString(@"%d Connected", "Inspector -> Peers tab -> peers"), total];
1491       
1492        if (total > 0)
1493        {
1494            NSMutableArray * components = [NSMutableArray arrayWithCapacity: 4];
1495            NSInteger count;
1496            if ((count = [torrent totalPeersTracker]) > 0)
1497                [components addObject: [NSString stringWithFormat:
1498                                        NSLocalizedString(@"%d tracker", "Inspector -> Peers tab -> peers"), count]];
1499            if ((count = [torrent totalPeersIncoming]) > 0)
1500                [components addObject: [NSString stringWithFormat:
1501                                        NSLocalizedString(@"%d incoming", "Inspector -> Peers tab -> peers"), count]];
1502            if ((count = [torrent totalPeersPex]) > 0)
1503                [components addObject: [NSString stringWithFormat:
1504                                        NSLocalizedString(@"%d PEX", "Inspector -> Peers tab -> peers"), count]];
1505            if ((count = [torrent totalPeersCache]) > 0)
1506                [components addObject: [NSString stringWithFormat:
1507                                        NSLocalizedString(@"%d cache", "Inspector -> Peers tab -> peers"), count]];
1508           
1509            connected = [connected stringByAppendingFormat: @": %@", [components componentsJoinedByString: @", "]];
1510        }
1511       
1512        [fConnectedPeersField setStringValue: connected];
1513       
1514        [fDownloadingFromField setIntValue: [torrent peersSendingToUs]];
1515        [fUploadingToField setIntValue: [torrent peersGettingFromUs]];
1516    }
1517    else
1518    {
1519        [fConnectedPeersField setStringValue: @""];
1520        [fDownloadingFromField setStringValue: @""];
1521        [fUploadingToField setStringValue: @""];
1522    }
1523   
1524    [fKnownField setIntValue: [torrent totalPeersKnown]];
1525   
1526    [fPeers release];
1527    fPeers = [[[torrent peers] sortedArrayUsingDescriptors: [self peerSortDescriptors]] retain];
1528    [fPeerTable reloadData];
1529   
1530    if ([torrent webSeedCount] > 0)
1531    {
1532        [fWebSeeds release];
1533        fWebSeeds = [[[torrent webSeeds] sortedArrayUsingDescriptors: [fWebSeedTable sortDescriptors]] retain];
1534        [fWebSeedTable reloadData];
1535    }
1536}
1537
1538- (void) updateInfoFiles
1539{
1540    if ([fTorrents count] == 1)
1541        [fFileController reloadData];
1542}
1543
1544- (NSView *) tabViewForTag: (NSInteger) tag
1545{
1546    switch (tag)
1547    {
1548        case TAB_INFO_TAG:
1549            return fInfoView;
1550        case TAB_ACTIVITY_TAG:
1551            return fActivityView;
1552        case TAB_TRACKER_TAG:
1553            return fTrackerView;
1554        case TAB_PEERS_TAG:
1555            return fPeersView;
1556        case TAB_FILES_TAG:
1557            return fFilesView;
1558        case TAB_OPTIONS_TAG:
1559            return fOptionsView;
1560        default:
1561            return nil;
1562    }
1563}
1564
1565- (void) setWebSeedTableHidden: (BOOL) hide animate: (BOOL) animate
1566{
1567    if (fCurrentTabTag != TAB_PEERS_TAG || ![[self window] isVisible])
1568        animate = NO;
1569   
1570    if (fWebSeedTableAnimation)
1571    {
1572        [fWebSeedTableAnimation stopAnimation];
1573        [fWebSeedTableAnimation release];
1574        fWebSeedTableAnimation = nil;
1575    }
1576   
1577    NSRect webSeedFrame = [[fWebSeedTable enclosingScrollView] frame];
1578    NSRect peerFrame = [[fPeerTable enclosingScrollView] frame];
1579   
1580    if (hide)
1581    {
1582        CGFloat webSeedFrameMaxY = NSMaxY(webSeedFrame);
1583        webSeedFrame.size.height = 0;
1584        webSeedFrame.origin.y = webSeedFrameMaxY;
1585       
1586        peerFrame.size.height = webSeedFrameMaxY - peerFrame.origin.y;
1587    }
1588    else
1589    {
1590        webSeedFrame.origin.y -= fWebSeedTableHeight - webSeedFrame.size.height;
1591        webSeedFrame.size.height = fWebSeedTableHeight;
1592       
1593        peerFrame.size.height = (webSeedFrame.origin.y - fSpaceBetweenWebSeedAndPeer) - peerFrame.origin.y;
1594    }
1595   
1596    [[fWebSeedTable enclosingScrollView] setHidden: NO]; //this is needed for some reason
1597   
1598    //actually resize tables
1599    if (animate)
1600    {
1601        NSDictionary * webSeedDict = [NSDictionary dictionaryWithObjectsAndKeys:
1602                                    [fWebSeedTable enclosingScrollView], NSViewAnimationTargetKey,
1603                                    [NSValue valueWithRect: [[fWebSeedTable enclosingScrollView] frame]], NSViewAnimationStartFrameKey,
1604                                    [NSValue valueWithRect: webSeedFrame], NSViewAnimationEndFrameKey, nil],
1605                    * peerDict = [NSDictionary dictionaryWithObjectsAndKeys:
1606                                    [fPeerTable enclosingScrollView], NSViewAnimationTargetKey,
1607                                    [NSValue valueWithRect: [[fPeerTable enclosingScrollView] frame]], NSViewAnimationStartFrameKey,
1608                                    [NSValue valueWithRect: peerFrame], NSViewAnimationEndFrameKey, nil];
1609       
1610        fWebSeedTableAnimation = [[NSViewAnimation alloc] initWithViewAnimations:
1611                                        [NSArray arrayWithObjects: webSeedDict, peerDict, nil]];
1612        [fWebSeedTableAnimation setDuration: 0.125];
1613        [fWebSeedTableAnimation setAnimationBlockingMode: NSAnimationNonblocking];
1614        [fWebSeedTableAnimation setDelegate: self];
1615       
1616        [fWebSeedTableAnimation startAnimation];
1617    }
1618    else
1619    {
1620        [[fWebSeedTable enclosingScrollView] setFrame: webSeedFrame];
1621        [[fPeerTable enclosingScrollView] setFrame: peerFrame];
1622    }
1623}
1624
1625- (NSArray *) peerSortDescriptors
1626{
1627    NSMutableArray * descriptors = [NSMutableArray arrayWithCapacity: 2];
1628   
1629    NSArray * oldDescriptors = [fPeerTable sortDescriptors];
1630    BOOL useSecond = YES, asc = YES;
1631    if ([oldDescriptors count] > 0)
1632    {
1633        NSSortDescriptor * descriptor = [oldDescriptors objectAtIndex: 0];
1634        [descriptors addObject: descriptor];
1635       
1636        if ((useSecond = ![[descriptor key] isEqualToString: @"IP"]))
1637            asc = [descriptor ascending];
1638    }
1639   
1640    //sort by IP after primary sort
1641    if (useSecond)
1642    {
1643        NSSortDescriptor * secondDescriptor = [[NSSortDescriptor alloc] initWithKey: @"IP" ascending: asc
1644                                                                        selector: @selector(compareNumeric:)];
1645        [descriptors addObject: secondDescriptor];
1646        [secondDescriptor release];
1647    }
1648   
1649    return descriptors;
1650}
1651
1652- (BOOL) canQuickLookFile: (FileListNode *) item
1653{
1654    Torrent * torrent = [fTorrents objectAtIndex: 0];
1655   
1656    if (![[NSFileManager defaultManager] fileExistsAtPath: [[torrent downloadFolder] stringByAppendingPathComponent: [item fullPath]]])
1657        return NO;
1658   
1659    return [item isFolder] || [torrent fileProgress: item] == 1.0;
1660}
1661
1662- (void) addTrackers
1663{
1664    [[self window] makeKeyWindow];
1665   
1666    NSUInteger index = 1;
1667    if ([[fTorrents objectAtIndex: 0] hasAddedTrackers])
1668    {
1669        for (; index < [fTrackers count]; index++)
1670            if ([[fTrackers objectAtIndex: index] isKindOfClass: [NSNumber class]])
1671                break;
1672    }
1673    else
1674        [fTrackers insertObject: [NSNumber numberWithInt: 0] atIndex: 0];
1675   
1676    [fTrackers insertObject: @"" atIndex: index];
1677    [fTrackerTable reloadData];
1678    [fTrackerTable selectRow: index byExtendingSelection: NO];
1679    [fTrackerTable editColumn: 0 row: index withEvent: nil select: YES];
1680}
1681
1682- (void) removeTrackers
1683{
1684    NSMutableIndexSet * indexes = [[[fTrackerTable selectedRowIndexes] mutableCopy] autorelease];
1685   
1686    //get all rows to remove and determine if any built-in trackers are being remove
1687    NSUInteger i = 0, numberBuiltIn = 0;
1688    while (i < [fTrackers count])
1689    {
1690        BOOL builtIn = i != 0 || [[fTrackers objectAtIndex: i] intValue] != 0;
1691       
1692        //if a group is selected, remove all trackers in the group
1693        if ([indexes containsIndex: i])
1694        {
1695            for (i = i+1; i < [fTrackers count] && ![[fTrackers objectAtIndex: i] isKindOfClass: [NSNumber class]]; i++)
1696            {
1697                [indexes addIndex: i];
1698                if (builtIn)
1699                    numberBuiltIn++;
1700            }
1701        }
1702        //remove empty groups
1703        else
1704        {
1705            BOOL allSelected = YES;
1706            NSUInteger j;
1707            for (j = i+1; j < [fTrackers count] && ![[fTrackers objectAtIndex: j] isKindOfClass: [NSNumber class]]; j++)
1708            {
1709                if (![indexes containsIndex: j])
1710                    allSelected = NO;
1711                else if (builtIn)
1712                    numberBuiltIn++;
1713                else;
1714            }
1715           
1716            if (allSelected)
1717                [indexes addIndex: i];
1718           
1719            i = j;
1720        }
1721    }
1722   
1723    if ([fTrackers count] == [indexes count])
1724    {
1725        NSBeep();
1726        return;
1727    }
1728   
1729    Torrent * torrent = [fTorrents objectAtIndex: 0];
1730   
1731    //determine if removing trackers built into the torrent
1732    if (numberBuiltIn > 0 && [[NSUserDefaults standardUserDefaults] boolForKey: @"WarningRemoveBuiltInTracker"])
1733    {
1734        NSAlert * alert = [[NSAlert alloc] init];
1735       
1736        if (numberBuiltIn > 1)
1737        {
1738            [alert setMessageText: [NSString stringWithFormat:
1739                                    NSLocalizedString(@"Are you sure you want to remove %d built-in trackers?",
1740                                    "Remove built-in tracker alert -> title"), numberBuiltIn]];
1741            [alert setInformativeText: NSLocalizedString(@"These tracker addresses are part of the torrent file."
1742                " Once removed, Transmission will no longer attempt to contact them.", "Remove built-in tracker alert -> message")];
1743        }
1744        else
1745        {
1746            [alert setMessageText: NSLocalizedString(@"Are you sure you want to remove a built-in tracker?",
1747                                    "Remove built-in tracker alert -> title")];
1748            [alert setInformativeText: NSLocalizedString(@"The tracker address is part of the torrent file."
1749                " Once removed, Transmission will no longer attempt to contact it.", "Remove built-in tracker alert -> message")];
1750        }
1751       
1752        [alert addButtonWithTitle: NSLocalizedString(@"Remove", "Remove built-in tracker alert -> button")];
1753        [alert addButtonWithTitle: NSLocalizedString(@"Cancel", "Remove built-in tracker alert -> button")];
1754       
1755        BOOL onLeopard = [NSApp isOnLeopardOrBetter];
1756        if (onLeopard)
1757            [alert setShowsSuppressionButton: YES];
1758        else
1759            [alert addButtonWithTitle: NSLocalizedString(@"Don't Alert Again", "Remove built-in tracker alert -> button")];
1760
1761        NSInteger result = [alert runModal];
1762        if ((onLeopard ? [[alert suppressionButton] state] == NSOnState : result == NSAlertThirdButtonReturn))
1763            [[NSUserDefaults standardUserDefaults] setBool: NO forKey: @"WarningRemoveBuiltInTracker"];
1764        [alert release];
1765       
1766        if (result != NSAlertFirstButtonReturn)
1767            return;
1768    }
1769   
1770    [fTrackers removeObjectsAtIndexes: indexes];
1771   
1772    [torrent updateAllTrackersForRemove: fTrackers];
1773    [fTrackerTable deselectAll: self];
1774   
1775    //reset table with either new or old value
1776    [fTrackers release];
1777    fTrackers = [[torrent allTrackers: YES] retain];
1778   
1779    [fTrackerTable setTrackers: fTrackers];
1780    [fTrackerTable reloadData];
1781}
1782
1783@end
Note: See TracBrowser for help on using the repository browser.