source: trunk/macosx/InfoWindowController.m @ 9152

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

remove unneeded check for user-added tier

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