source: trunk/macosx/InfoWindowController.m @ 7892

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

have the mac ui use libT's ratio settings (attempt 1); when seed ratio is reached in libT, set the seed ratio setting to "seed forever"

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