source: trunk/macosx/InfoWindowController.m @ 10105

Last change on this file since 10105 was 10105, checked in by livings124, 12 years ago

improve how tracker tiers are sent to the inspector code

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