source: trunk/macosx/InfoWindowController.m @ 9126

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

show a warning whenever removing any tracker, regardless of if it's built-in

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