source: trunk/macosx/InfoWindowController.m @ 10112

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

slight efficiency improvements for peers and webseeds in the inspector

  • Property svn:keywords set to Date Rev Author Id
File size: 67.9 KB
Line 
1/******************************************************************************
2 * $Id: InfoWindowController.m 10112 2010-02-06 15:42:18Z 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            [fPeers sortUsingDescriptors: [self peerSortDescriptors]];
739            [tableView reloadData];
740        }
741    }
742    else if (tableView == fWebSeedTable)
743    {
744        if (fWebSeeds)
745        {
746            [fWebSeeds sortUsingDescriptors: [fWebSeedTable sortDescriptors]];
747            [tableView reloadData];
748        }
749    }
750    else;
751}
752
753- (BOOL) tableView: (NSTableView *) tableView shouldSelectRow: (NSInteger) row
754{
755    return tableView == fTrackerTable;
756}
757
758- (void) tableViewSelectionDidChange: (NSNotification *) notification
759{
760    if ([notification object] == fTrackerTable)
761        [fTrackerAddRemoveControl setEnabled: [fTorrents count] == 1 && [fTrackerTable numberOfSelectedRows] > 0
762            forSegment: TRACKER_REMOVE_TAG];
763}
764
765- (BOOL) tableView: (NSTableView *) tableView isGroupRow: (NSInteger) row
766{
767    if (tableView == fTrackerTable)
768        return ![[fTrackers objectAtIndex: row] isKindOfClass: [TrackerNode class]] && [tableView editedRow] != row;
769    return NO;
770}
771
772- (NSString *) tableView: (NSTableView *) tableView toolTipForCell: (NSCell *) cell rect: (NSRectPointer) rect
773                tableColumn: (NSTableColumn *) column row: (NSInteger) row mouseLocation: (NSPoint) mouseLocation
774{
775    if (tableView == fPeerTable)
776    {
777        const BOOL multiple = [fTorrents count] > 1;
778       
779        NSDictionary * peer = [fPeers objectAtIndex: row];
780        NSMutableArray * components = [NSMutableArray arrayWithCapacity: multiple ? 6 : 5];
781       
782        if (multiple)
783            [components addObject: [peer objectForKey: @"Name"]];
784       
785        const CGFloat progress = [[peer objectForKey: @"Progress"] floatValue];
786        NSString * progressString = [NSString localizedStringWithFormat: NSLocalizedString(@"Progress: %.1f%%",
787                                        "Inspector -> Peers tab -> table row tooltip"), progress * 100.0];
788        if (progress < 1.0 && [[peer objectForKey: @"Seed"] boolValue])
789            progressString = [progressString stringByAppendingFormat: @" (%@)", NSLocalizedString(@"Partial Seed",
790                                "Inspector -> Peers tab -> table row tooltip")];
791        [components addObject: progressString];
792       
793        if ([[peer objectForKey: @"Encryption"] boolValue])
794            [components addObject: NSLocalizedString(@"Encrypted Connection", "Inspector -> Peers tab -> table row tooltip")];
795       
796        NSString * portString;
797        NSInteger port;
798        if ((port = [[peer objectForKey: @"Port"] intValue]) > 0)
799            portString = [NSString stringWithFormat: @"%d", port];
800        else
801            portString = NSLocalizedString(@"N/A", "Inspector -> Peers tab -> table row tooltip");
802        [components addObject: [NSString stringWithFormat: @"%@: %@", NSLocalizedString(@"Port",
803            "Inspector -> Peers tab -> table row tooltip"), portString]];
804       
805        const NSInteger peerFrom = [[peer objectForKey: @"From"] integerValue];
806        switch (peerFrom)
807        {
808            case TR_PEER_FROM_TRACKER:
809                [components addObject: NSLocalizedString(@"From: tracker", "Inspector -> Peers tab -> table row tooltip")];
810                break;
811            case TR_PEER_FROM_INCOMING:
812                [components addObject: NSLocalizedString(@"From: incoming connection", "Inspector -> Peers tab -> table row tooltip")];
813                break;
814            case TR_PEER_FROM_RESUME:
815                [components addObject: NSLocalizedString(@"From: cache", "Inspector -> Peers tab -> table row tooltip")];
816                break;
817            case TR_PEER_FROM_PEX:
818                [components addObject: NSLocalizedString(@"From: peer exchange", "Inspector -> Peers tab -> table row tooltip")];
819                break;
820            case TR_PEER_FROM_DHT:
821                [components addObject: NSLocalizedString(@"From: distributed hash table", "Inspector -> Peers tab -> table row tooltip")];
822                break;
823            case TR_PEER_FROM_LTEP:
824                [components addObject: NSLocalizedString(@"From: libtorrent extension protocol handshake",
825                                        "Inspector -> Peers tab -> table row tooltip")];
826                break;
827            default:
828                NSAssert1(NO, @"Peer from unknown source: %d", peerFrom);
829        }
830       
831        //determing status strings from flags
832        NSMutableArray * statusArray = [NSMutableArray arrayWithCapacity: 6];
833        NSString * flags = [peer objectForKey: @"Flags"];
834       
835        if ([flags rangeOfString: @"D"].location != NSNotFound)
836            [statusArray addObject: NSLocalizedString(@"Currently downloading (interested and not choked)",
837                "Inspector -> peer -> status")];
838        if ([flags rangeOfString: @"d"].location != NSNotFound)
839            [statusArray addObject: NSLocalizedString(@"You want to download, but peer does not want to send (interested and choked)",
840                "Inspector -> peer -> status")];
841        if ([flags rangeOfString: @"U"].location != NSNotFound)
842            [statusArray addObject: NSLocalizedString(@"Currently uploading (interested and not choked)",
843                "Inspector -> peer -> status")];
844        if ([flags rangeOfString: @"u"].location != NSNotFound)
845            [statusArray addObject: NSLocalizedString(@"Peer wants you to upload, but you do not want to (interested and choked)",
846                "Inspector -> peer -> status")];
847        if ([flags rangeOfString: @"K"].location != NSNotFound)
848            [statusArray addObject: NSLocalizedString(@"Peer is unchoking you, but you are not interested",
849                "Inspector -> peer -> status")];
850        if ([flags rangeOfString: @"?"].location != NSNotFound)
851            [statusArray addObject: NSLocalizedString(@"You unchoked the peer, but the peer is not interested",
852                "Inspector -> peer -> status")];
853       
854        if ([statusArray count] > 0)
855        {
856            NSString * statusStrings = [statusArray componentsJoinedByString: @"\n\n"];
857            [components addObject: [@"\n" stringByAppendingString: statusStrings]];
858        }
859       
860        return [components componentsJoinedByString: @"\n"];
861    }
862    else if (tableView == fTrackerTable)
863    {
864        id node = [fTrackers objectAtIndex: row];
865        if ([node isKindOfClass: [TrackerNode class]])
866            return [(TrackerNode *)node fullAnnounceAddress];
867    }
868    else if (tableView == fWebSeedTable)
869    {
870        if ([fTorrents count] > 1)
871            return [[fWebSeeds objectAtIndex: row] objectForKey: @"Name"];
872    }
873   
874    return nil;
875}
876
877- (void) tableView: (NSTableView *) tableView setObjectValue: (id) object forTableColumn: (NSTableColumn *) tableColumn
878    row: (NSInteger) row
879{
880    if (tableView != fTrackerTable)
881        return;
882   
883    Torrent * torrent= [fTorrents objectAtIndex: 0];
884   
885    BOOL added = NO;
886    for (NSString * tracker in [object componentsSeparatedByString: @"\n"])
887        if ([torrent addTrackerToNewTier: tracker])
888            added = YES;
889   
890    if (!added)
891        NSBeep();
892   
893    //reset table with either new or old value
894    [fTrackers release];
895    fTrackers = [[torrent allTrackerStats] retain];
896   
897    [fTrackerTable setTrackers: fTrackers];
898    [fTrackerTable reloadData];
899    [fTrackerTable deselectAll: self];
900   
901    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateUI" object: nil]; //incase sort by tracker
902}
903
904- (void) addRemoveTracker: (id) sender
905{
906    //don't allow add/remove when currently adding - it leads to weird results
907    if ([fTrackerTable editedRow] != -1)
908        return;
909   
910    if ([[sender cell] tagForSegment: [sender selectedSegment]] == TRACKER_REMOVE_TAG)
911        [self removeTrackers];
912    else
913        [self addTrackers];
914}
915
916- (NSArray *) quickLookURLs
917{
918    FileOutlineView * fileOutlineView = [fFileController outlineView];
919    Torrent * torrent = [fTorrents objectAtIndex: 0];
920    NSIndexSet * indexes = [fileOutlineView selectedRowIndexes];
921    NSMutableArray * urlArray = [NSMutableArray arrayWithCapacity: [indexes count]];
922   
923    for (NSUInteger i = [indexes firstIndex]; i != NSNotFound; i = [indexes indexGreaterThanIndex: i])
924    {
925        FileListNode * item = [fileOutlineView itemAtRow: i];
926        if ([self canQuickLookFile: item])
927            [urlArray addObject: [NSURL fileURLWithPath: [torrent fileLocation: item]]];
928    }
929   
930    return urlArray;
931}
932
933- (BOOL) canQuickLook
934{
935    if (fCurrentTabTag != TAB_FILES_TAG || ![[self window] isVisible] || [fTorrents count] != 1 || ![NSApp isOnSnowLeopardOrBetter])
936        return NO;
937   
938    Torrent * torrent = [fTorrents objectAtIndex: 0];
939    if (![torrent isFolder])
940        return NO;
941   
942    FileOutlineView * fileOutlineView = [fFileController outlineView];
943    NSIndexSet * indexes = [fileOutlineView selectedRowIndexes];
944   
945    for (NSUInteger i = [indexes firstIndex]; i != NSNotFound; i = [indexes indexGreaterThanIndex: i])
946        if ([self canQuickLookFile: [fileOutlineView itemAtRow: i]])
947            return YES;
948   
949    return NO;
950}
951
952#warning uncomment (in header too)
953- (NSRect) quickLookSourceFrameForPreviewItem: (id /*<QLPreviewItem>*/) item
954{
955    FileOutlineView * fileOutlineView = [fFileController outlineView];
956   
957    NSString * fullPath = [(NSURL *)item path];
958    Torrent * torrent = [fTorrents objectAtIndex: 0];
959    NSRange visibleRows = [fileOutlineView rowsInRect: [fileOutlineView bounds]];
960   
961    for (NSUInteger row = visibleRows.location; row < NSMaxRange(visibleRows); row++)
962    {
963        FileListNode * rowItem = [fileOutlineView itemAtRow: row];
964        if ([[torrent fileLocation: rowItem] isEqualToString: fullPath])
965        {
966            NSRect frame = [fileOutlineView iconRectForRow: row];
967           
968            if (!NSIntersectsRect([fileOutlineView visibleRect], frame))
969                return NSZeroRect;
970           
971            frame.origin = [fileOutlineView convertPoint: frame.origin toView: nil];
972            frame.origin = [[self window] convertBaseToScreen: frame.origin];
973            frame.origin.y -= frame.size.height;
974            return frame;
975        }
976    }
977   
978    return NSZeroRect;
979}
980
981- (void) setPiecesView: (id) sender
982{
983    [self setPiecesViewForAvailable: [sender selectedSegment] == PIECES_CONTROL_AVAILABLE];
984}
985
986- (void) setPiecesViewForAvailable: (BOOL) available
987{
988    [fPiecesControl setSelected: available forSegment: PIECES_CONTROL_AVAILABLE];
989    [fPiecesControl setSelected: !available forSegment: PIECES_CONTROL_PROGRESS];
990   
991    [[NSUserDefaults standardUserDefaults] setBool: available forKey: @"PiecesViewShowAvailability"];
992    [fPiecesView updateView];
993}
994
995- (void) revealDataFile: (id) sender
996{
997    Torrent * torrent = [fTorrents objectAtIndex: 0];
998    NSString * location = [torrent dataLocation];
999    if (!location)
1000        return;
1001   
1002    if ([NSApp isOnSnowLeopardOrBetter])
1003    {
1004        NSURL * file = [NSURL fileURLWithPath: location];
1005        [[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs: [NSArray arrayWithObject: file]];
1006    }
1007    else
1008        [[NSWorkspace sharedWorkspace] selectFile: location inFileViewerRootedAtPath: nil];
1009}
1010
1011- (void) setFileFilterText: (id) sender
1012{
1013    [fFileController setFilterText: [sender stringValue]];
1014}
1015
1016- (void) setUseSpeedLimit: (id) sender
1017{
1018    const BOOL upload = sender == fUploadLimitCheck;
1019   
1020    if ([sender state] == NSMixedState)
1021        [sender setState: NSOnState];
1022    const BOOL limit = [sender state] == NSOnState;
1023   
1024    for (Torrent * torrent in fTorrents)
1025        [torrent setUseSpeedLimit: limit upload: upload];
1026   
1027    NSTextField * field = upload ? fUploadLimitField : fDownloadLimitField;
1028    [field setEnabled: limit];
1029    if (limit)
1030    {
1031        [field selectText: self];
1032        [[self window] makeKeyAndOrderFront: self];
1033    }
1034   
1035    NSTextField * label = upload ? fUploadLimitLabel : fDownloadLimitLabel;
1036    [label setEnabled: limit];
1037}
1038
1039- (void) setUseGlobalSpeedLimit: (id) sender
1040{
1041    if ([sender state] == NSMixedState)
1042        [sender setState: NSOnState];
1043    const BOOL limit = [sender state] == NSOnState;
1044   
1045    for (Torrent * torrent in fTorrents)
1046        [torrent setUseGlobalSpeedLimit: limit];
1047}
1048
1049- (void) setSpeedLimit: (id) sender
1050{
1051    const BOOL upload = sender == fUploadLimitField;
1052    const NSInteger limit = [sender intValue];
1053   
1054    for (Torrent * torrent in fTorrents)
1055        [torrent setSpeedLimit: limit upload: upload];
1056}
1057
1058- (void) setRatioSetting: (id) sender
1059{
1060    NSInteger setting;
1061    bool single = NO;
1062    switch ([sender indexOfSelectedItem])
1063    {
1064        case OPTION_POPUP_LIMIT:
1065            setting = TR_RATIOLIMIT_SINGLE;
1066            single = YES;
1067            break;
1068        case OPTION_POPUP_NO_LIMIT:
1069            setting = TR_RATIOLIMIT_UNLIMITED;
1070            break;
1071        case OPTION_POPUP_GLOBAL:
1072            setting = TR_RATIOLIMIT_GLOBAL;
1073            break;
1074        default:
1075            NSAssert1(NO, @"Unknown option selected in ratio popup: %d", [sender indexOfSelectedItem]);
1076            return;
1077    }
1078   
1079    for (Torrent * torrent in fTorrents)
1080        [torrent setRatioSetting: setting];
1081   
1082    [fRatioLimitField setHidden: !single];
1083    if (single)
1084    {
1085        [fRatioLimitField selectText: self];
1086        [[self window] makeKeyAndOrderFront: self];
1087    }
1088}
1089
1090- (void) setRatioLimit: (id) sender
1091{
1092    CGFloat limit = [sender floatValue];
1093   
1094    for (Torrent * torrent in fTorrents)
1095        [torrent setRatioLimit: limit];
1096}
1097
1098- (void) setPriority: (id) sender
1099{
1100    tr_priority_t priority;
1101    switch ([sender indexOfSelectedItem])
1102    {
1103        case OPTION_POPUP_PRIORITY_HIGH:
1104            priority = TR_PRI_HIGH;
1105            break;
1106        case OPTION_POPUP_PRIORITY_NORMAL:
1107            priority = TR_PRI_NORMAL;
1108            break;
1109        case OPTION_POPUP_PRIORITY_LOW:
1110            priority = TR_PRI_LOW;
1111            break;
1112        default:
1113            NSAssert1(NO, @"Unknown option selected in priority popup: %d", [sender indexOfSelectedItem]);
1114            return;
1115    }
1116   
1117    for (Torrent * torrent in fTorrents)
1118        [torrent setPriority: priority];
1119   
1120    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateUI" object: nil];
1121}
1122
1123- (void) setPeersConnectLimit: (id) sender
1124{
1125    NSInteger limit = [sender intValue];
1126   
1127    for (Torrent * torrent in fTorrents)
1128        [torrent setMaxPeerConnect: limit];
1129}
1130
1131- (BOOL) control: (NSControl *) control textShouldBeginEditing: (NSText *) fieldEditor
1132{
1133    [fInitialString release];
1134    fInitialString = [[control stringValue] retain];
1135   
1136    return YES;
1137}
1138
1139- (BOOL) control: (NSControl *) control didFailToFormatString: (NSString *) string errorDescription: (NSString *) error
1140{
1141    NSBeep();
1142    if (fInitialString)
1143    {
1144        [control setStringValue: fInitialString];
1145        [fInitialString release];
1146        fInitialString = nil;
1147    }
1148    return NO;
1149}
1150
1151@end
1152
1153@implementation InfoWindowController (Private)
1154
1155- (void) resetInfo
1156{
1157    const NSUInteger numberSelected = [fTorrents count];
1158    if (numberSelected != 1)
1159    {
1160        if (numberSelected > 0)
1161        {
1162            [fImageView setImage: [NSImage imageNamed: NSImageNameMultipleDocuments]];
1163           
1164            [fNameField setStringValue: [NSString stringWithFormat: NSLocalizedString(@"%d Torrents Selected",
1165                                            "Inspector -> selected torrents"), numberSelected]];
1166       
1167            uint64_t size = 0;
1168            NSUInteger fileCount = 0, magnetCount = 0;
1169            for (Torrent * torrent in fTorrents)
1170            {
1171                size += [torrent size];
1172                fileCount += [torrent fileCount];
1173                if ([torrent isMagnet])
1174                    ++magnetCount;
1175            }
1176           
1177            NSMutableArray * fileStrings = [NSMutableArray arrayWithCapacity: 2];
1178            if (fileCount > 0)
1179            {
1180                NSString * fileString;
1181                if (fileCount == 1)
1182                    fileString = NSLocalizedString(@"1 file", "Inspector -> selected torrents");
1183                else
1184                    fileString = [NSString stringWithFormat: NSLocalizedString(@"%d files", "Inspector -> selected torrents"), fileCount];
1185                [fileStrings addObject: fileString];
1186            }
1187            if (magnetCount > 0)
1188            {
1189                NSString * magnetString;
1190                if (magnetCount == 1)
1191                    magnetString = NSLocalizedString(@"1 magnetized transfer", "Inspector -> selected torrents");
1192                else
1193                    magnetString = [NSString stringWithFormat: NSLocalizedString(@"%d magnetized transfers",
1194                                    "Inspector -> selected torrents"), magnetCount];
1195                [fileStrings addObject: magnetString];
1196            }
1197           
1198            NSString * fileString = [fileStrings componentsJoinedByString: @" + "];
1199           
1200            if (magnetCount < numberSelected)
1201            {
1202                [fBasicInfoField setStringValue: [NSString stringWithFormat: @"%@, %@", fileString,
1203                    [NSString stringWithFormat: NSLocalizedString(@"%@ total", "Inspector -> selected torrents"),
1204                        [NSString stringForFileSize: size]]]];
1205                [fBasicInfoField setToolTip: [NSString stringWithFormat: NSLocalizedString(@"%llu bytes", "Inspector -> selected torrents"),
1206                                                size]];
1207            }
1208            else
1209            {
1210                [fBasicInfoField setStringValue: fileString];
1211                [fBasicInfoField setToolTip: nil];
1212            }
1213        }
1214        else
1215        {
1216            [fImageView setImage: [NSImage imageNamed: @"NSApplicationIcon"]];
1217           
1218            [fNameField setStringValue: NSLocalizedString(@"No Torrents Selected", "Inspector -> selected torrents")];
1219            [fBasicInfoField setStringValue: @""];
1220            [fBasicInfoField setToolTip: @""];
1221   
1222            [fHaveField setStringValue: @""];
1223            [fDownloadedTotalField setStringValue: @""];
1224            [fUploadedTotalField setStringValue: @""];
1225            [fFailedHashField setStringValue: @""];
1226            [fDateActivityField setStringValue: @""];
1227            [fRatioField setStringValue: @""];
1228           
1229            //options fields
1230            [fUploadLimitCheck setEnabled: NO];
1231            [fUploadLimitCheck setState: NSOffState];
1232            [fUploadLimitField setEnabled: NO];
1233            [fUploadLimitLabel setEnabled: NO];
1234            [fUploadLimitField setStringValue: @""];
1235           
1236            [fDownloadLimitCheck setEnabled: NO];
1237            [fDownloadLimitCheck setState: NSOffState];
1238            [fDownloadLimitField setEnabled: NO];
1239            [fDownloadLimitLabel setEnabled: NO];
1240            [fDownloadLimitField setStringValue: @""];
1241           
1242            [fGlobalLimitCheck setEnabled: NO];
1243            [fGlobalLimitCheck setState: NSOffState];
1244           
1245            [fPriorityPopUp setEnabled: NO];
1246            [fPriorityPopUp selectItemAtIndex: -1];
1247           
1248            [fRatioPopUp setEnabled: NO];
1249            [fRatioPopUp selectItemAtIndex: -1];
1250            [fRatioLimitField setHidden: YES];
1251            [fRatioLimitField setStringValue: @""];
1252           
1253            [fPeersConnectField setEnabled: NO];
1254            [fPeersConnectField setStringValue: @""];
1255            [fPeersConnectLabel setEnabled: NO];
1256           
1257            [fPeers release];
1258            fPeers = nil;
1259            [fPeerTable reloadData];
1260        }
1261       
1262        [fFileController setTorrent: nil];
1263       
1264        [fNameField setToolTip: nil];
1265
1266        [fPiecesField setStringValue: @""];
1267        [fHashField setStringValue: @""];
1268        [fHashField setToolTip: nil];
1269        [fSecureField setStringValue: @""];
1270        [fCommentView setString: @""];
1271       
1272        [fCreatorField setStringValue: @""];
1273        [fDateCreatedField setStringValue: @""];
1274       
1275        [fDataLocationField setStringValue: @""];
1276        [fDataLocationField setToolTip: nil];
1277       
1278        [fRevealDataButton setHidden: YES];
1279       
1280        [fStateField setStringValue: @""];
1281        [fProgressField setStringValue: @""];
1282       
1283        [fErrorMessageView setString: @""];
1284       
1285        [fConnectedPeersField setStringValue: @""];
1286       
1287        [fDateAddedField setStringValue: @""];
1288        [fDateCompletedField setStringValue: @""];
1289       
1290        [fPiecesControl setSelected: NO forSegment: PIECES_CONTROL_AVAILABLE];
1291        [fPiecesControl setSelected: NO forSegment: PIECES_CONTROL_PROGRESS];
1292        [fPiecesControl setEnabled: NO];
1293        [fPiecesView setTorrent: nil];
1294       
1295        [fTrackerTable setTorrent: nil];
1296       
1297        [fTrackers release];
1298        fTrackers = nil;
1299       
1300        [fTrackerTable setTrackers: fTrackers];
1301        [fTrackerTable reloadData];
1302       
1303        [fTrackerAddRemoveControl setEnabled: NO forSegment: TRACKER_ADD_TAG];
1304        [fTrackerAddRemoveControl setEnabled: NO forSegment: TRACKER_REMOVE_TAG];
1305       
1306        [fFileFilterField setEnabled: NO];
1307    }
1308    else
1309    {
1310        Torrent * torrent = [fTorrents objectAtIndex: 0];
1311       
1312        [fFileController setTorrent: torrent];
1313       
1314        if ([NSApp isOnSnowLeopardOrBetter])
1315            [fImageView setImage: [torrent icon]];
1316        else
1317        {
1318            NSImage * icon = [[torrent icon] copy];
1319            [icon setFlipped: NO];
1320            [fImageView setImage: icon];
1321            [icon release];
1322        }
1323       
1324        NSString * name = [torrent name];
1325        [fNameField setStringValue: name];
1326        [fNameField setToolTip: name];
1327       
1328        if (![torrent isMagnet])
1329        {
1330            NSString * basicString = [NSString stringForFileSize: [torrent size]];
1331            if ([torrent isFolder])
1332            {
1333                NSString * fileString;
1334                NSInteger fileCount = [torrent fileCount];
1335                if (fileCount == 1)
1336                    fileString = NSLocalizedString(@"1 file", "Inspector -> selected torrents");
1337                else
1338                    fileString= [NSString stringWithFormat: NSLocalizedString(@"%d files", "Inspector -> selected torrents"), fileCount];
1339                basicString = [NSString stringWithFormat: @"%@, %@", fileString, basicString];
1340            }
1341            [fBasicInfoField setStringValue: basicString];
1342            [fBasicInfoField setToolTip: [NSString stringWithFormat: NSLocalizedString(@"%llu bytes", "Inspector -> selected torrents"),
1343                                            [torrent size]]];
1344        }
1345        else
1346        {
1347            [fBasicInfoField setStringValue: NSLocalizedString(@"Magnetized transfer", "Inspector -> selected torrents")];
1348            [fBasicInfoField setToolTip: nil];
1349        }
1350       
1351        NSString * piecesString = ![torrent isMagnet] ? [NSString stringWithFormat: @"%d, %@", [torrent pieceCount],
1352                                        [NSString stringForFileSize: [torrent pieceSize]]] : @"";
1353        [fPiecesField setStringValue: piecesString];
1354                                       
1355        NSString * hashString = [torrent hashString];
1356        [fHashField setStringValue: hashString];
1357        [fHashField setToolTip: hashString];
1358        [fSecureField setStringValue: [torrent privateTorrent]
1359                        ? NSLocalizedString(@"Private Torrent, PEX and DHT automatically disabled", "Inspector -> private torrent")
1360                        : NSLocalizedString(@"Public Torrent", "Inspector -> private torrent")];
1361       
1362        NSString * commentString = [torrent comment];
1363        [fCommentView setString: commentString];
1364       
1365        NSString * creatorString = [torrent creator];
1366        [fCreatorField setStringValue: creatorString];
1367        [fDateCreatedField setObjectValue: [torrent dateCreated]];
1368       
1369        [fDateAddedField setObjectValue: [torrent dateAdded]];
1370       
1371        //set pieces view
1372        BOOL piecesAvailableSegment = [[NSUserDefaults standardUserDefaults] boolForKey: @"PiecesViewShowAvailability"];
1373        [fPiecesControl setSelected: piecesAvailableSegment forSegment: PIECES_CONTROL_AVAILABLE];
1374        [fPiecesControl setSelected: !piecesAvailableSegment forSegment: PIECES_CONTROL_PROGRESS];
1375        [fPiecesControl setEnabled: YES];
1376        [fPiecesView setTorrent: torrent];
1377       
1378        [fTrackerTable setTorrent: torrent];
1379        [fTrackerTable deselectAll: self];
1380        [fTrackerAddRemoveControl setEnabled: YES forSegment: TRACKER_ADD_TAG];
1381        [fTrackerAddRemoveControl setEnabled: NO forSegment: TRACKER_REMOVE_TAG];
1382
1383        [fFileFilterField setEnabled: [torrent isFolder]];
1384    }
1385   
1386    [fFileFilterField setStringValue: @""];
1387   
1388    //reset webseeds here, since it might be hidden regardless of number selected
1389    BOOL hasWebSeeds = NO;
1390    for (Torrent * torrent in fTorrents)
1391    {
1392        if ([torrent webSeedCount] > 0)
1393        {
1394            hasWebSeeds = YES;
1395            break;
1396        }
1397    }
1398   
1399    if (!hasWebSeeds)
1400    {
1401        [fWebSeeds release];
1402        fWebSeeds = nil;
1403        [fWebSeedTable reloadData];
1404    }
1405    [self setWebSeedTableHidden: !hasWebSeeds animate: YES];
1406   
1407    //update stats and settings
1408    [self updateInfoStats];
1409    [self updateOptions];
1410}
1411
1412- (void) resetInfoForTorrent: (NSNotification *) notification
1413{
1414    if (fTorrents && [fTorrents containsObject: [notification object]])
1415        [self resetInfo];
1416}
1417
1418- (void) updateInfoGeneral
1419{   
1420    if ([fTorrents count] != 1)
1421        return;
1422   
1423    Torrent * torrent = [fTorrents objectAtIndex: 0];
1424   
1425    NSString * location = [torrent dataLocation];
1426    [fDataLocationField setStringValue: location ? [location stringByAbbreviatingWithTildeInPath] : @""];
1427    [fDataLocationField setToolTip: location ? location : @""];
1428   
1429    [fRevealDataButton setHidden: !location];
1430}
1431
1432- (void) updateInfoActivity
1433{
1434    NSInteger numberSelected = [fTorrents count];
1435    if (numberSelected == 0)
1436        return;
1437   
1438    uint64_t have = 0, haveVerified = 0, downloadedTotal = 0, uploadedTotal = 0, failedHash = 0;
1439    NSDate * lastActivity = nil;
1440    for (Torrent * torrent in fTorrents)
1441    {
1442        have += [torrent haveTotal];
1443        haveVerified += [torrent haveVerified];
1444        downloadedTotal += [torrent downloadedTotal];
1445        uploadedTotal += [torrent uploadedTotal];
1446        failedHash += [torrent failedHash];
1447       
1448        NSDate * nextLastActivity;
1449        if ((nextLastActivity = [torrent dateActivity]))
1450            lastActivity = lastActivity ? [lastActivity laterDate: nextLastActivity] : nextLastActivity;
1451    }
1452   
1453    if (have == 0)
1454        [fHaveField setStringValue: [NSString stringForFileSize: 0]];
1455    else
1456    {
1457        NSString * verifiedString = [NSString stringWithFormat: NSLocalizedString(@"%@ verified", "Inspector -> Activity tab -> have"),
1458                                        [NSString stringForFileSize: haveVerified]];
1459        if (have == haveVerified)
1460            [fHaveField setStringValue: verifiedString];
1461        else
1462            [fHaveField setStringValue: [NSString stringWithFormat: @"%@ (%@)", [NSString stringForFileSize: have], verifiedString]];
1463    }
1464   
1465    [fDownloadedTotalField setStringValue: [NSString stringForFileSize: downloadedTotal]];
1466    [fUploadedTotalField setStringValue: [NSString stringForFileSize: uploadedTotal]];
1467    [fFailedHashField setStringValue: [NSString stringForFileSize: failedHash]];
1468   
1469    [fDateActivityField setObjectValue: lastActivity];
1470   
1471    if (numberSelected == 1)
1472    {
1473        Torrent * torrent = [fTorrents objectAtIndex: 0];
1474       
1475        [fStateField setStringValue: [torrent stateString]];
1476       
1477        if ([torrent isFolder])
1478            [fProgressField setStringValue: [NSString localizedStringWithFormat: NSLocalizedString(@"%.2f%% (%.2f%% selected)",
1479                "Inspector -> Activity tab -> progress"), 100.0 * [torrent progress], 100.0 * [torrent progressDone]]];
1480        else
1481            [fProgressField setStringValue: [NSString localizedStringWithFormat: @"%.2f%%", 100.0 * [torrent progress]]];
1482           
1483        [fRatioField setStringValue: [NSString stringForRatio: [torrent ratio]]];
1484       
1485        NSString * errorMessage = [torrent errorMessage];
1486        if (![errorMessage isEqualToString: [fErrorMessageView string]])
1487            [fErrorMessageView setString: errorMessage];
1488       
1489        [fDateCompletedField setObjectValue: [torrent dateCompleted]];
1490       
1491        [fPiecesView updateView];
1492    }
1493    else if (numberSelected > 1)
1494    {
1495        [fRatioField setStringValue: [NSString stringForRatio: tr_getRatio(uploadedTotal, downloadedTotal)]];
1496    }
1497    else;
1498}
1499
1500- (void) updateInfoTracker
1501{
1502    if ([fTorrents count] == 0)
1503        return;
1504   
1505    //get updated tracker stats
1506    if ([fTrackerTable editedRow] == -1)
1507    {
1508        [fTrackers release];
1509       
1510        if ([fTorrents count] == 1)
1511            fTrackers = [[[fTorrents objectAtIndex: 0] allTrackerStats] retain];
1512        else
1513        {
1514            fTrackers = [[NSMutableArray alloc] init];
1515            for (Torrent * torrent in fTorrents)
1516                [fTrackers addObjectsFromArray: [torrent allTrackerStats]];
1517        }
1518       
1519        [fTrackerTable setTrackers: fTrackers];
1520        [fTrackerTable reloadData];
1521    }
1522    else
1523    {
1524        NSAssert1([fTorrents count] == 1, @"Attempting to add tracker with %d transfers selected", [fTorrents count]);
1525       
1526        if ([NSApp isOnSnowLeopardOrBetter])
1527        {
1528            NSIndexSet * addedIndexes = [NSIndexSet indexSetWithIndexesInRange: NSMakeRange([fTrackers count]-2, 2)];
1529            NSArray * tierAndTrackerBeingAdded = [fTrackers objectsAtIndexes: addedIndexes];
1530           
1531            [fTrackers release];
1532            fTrackers = [[[fTorrents objectAtIndex: 0] allTrackerStats] retain];
1533            [fTrackers addObjectsFromArray: tierAndTrackerBeingAdded];
1534           
1535            [fTrackerTable setTrackers: fTrackers];
1536           
1537            NSIndexSet * updateIndexes = [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [fTrackers count]-2)],
1538                    * columnIndexes = [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [[fTrackerTable tableColumns] count])];
1539            [fTrackerTable reloadDataForRowIndexes: updateIndexes columnIndexes: columnIndexes];
1540        }
1541    }
1542}
1543
1544- (void) updateInfoPeers
1545{
1546    if ([fTorrents count] == 0)
1547        return;
1548   
1549    if (!fPeers)
1550        fPeers = [[NSMutableArray alloc] init];
1551    else
1552        [fPeers removeAllObjects];
1553   
1554    NSUInteger known = 0, connected = 0, tracker = 0, incoming = 0, cache = 0, pex = 0, dht = 0, ltep = 0,
1555                toUs = 0, fromUs = 0;
1556    BOOL anyActive = false;
1557    for (Torrent * torrent in fTorrents)
1558    {
1559        [fPeers addObjectsFromArray: [torrent peers]];
1560       
1561        known += [torrent totalPeersKnown];
1562       
1563        if ([torrent isActive])
1564        {
1565            anyActive = YES;
1566            const NSUInteger connectedThis = [torrent totalPeersConnected];
1567            if (connectedThis > 0)
1568            {
1569                connected += [torrent totalPeersConnected];
1570                tracker += [torrent totalPeersTracker];
1571                incoming += [torrent totalPeersIncoming];
1572                cache += [torrent totalPeersCache];
1573                pex += [torrent totalPeersPex];
1574                dht += [torrent totalPeersDHT];
1575                ltep += [torrent totalPeersLTEP];
1576               
1577                toUs += [torrent peersSendingToUs];
1578                fromUs += [torrent peersGettingFromUs];
1579            }
1580        }
1581    }
1582   
1583    NSString * knownString = [NSString stringWithFormat: NSLocalizedString(@"%d known", "Inspector -> Peers tab -> peers"), known];
1584    if (anyActive)
1585    {
1586        NSString * connectedText = [NSString stringWithFormat: NSLocalizedString(@"%d Connected", "Inspector -> Peers tab -> peers"),
1587                                    connected];
1588       
1589        if (connected > 0)
1590        {
1591            NSMutableArray * fromComponents = [NSMutableArray arrayWithCapacity: 6];
1592            if (tracker > 0)
1593                [fromComponents addObject: [NSString stringWithFormat:
1594                                        NSLocalizedString(@"%d tracker", "Inspector -> Peers tab -> peers"), tracker]];
1595            if (incoming > 0)
1596                [fromComponents addObject: [NSString stringWithFormat:
1597                                        NSLocalizedString(@"%d incoming", "Inspector -> Peers tab -> peers"), incoming]];
1598            if (cache > 0)
1599                [fromComponents addObject: [NSString stringWithFormat:
1600                                        NSLocalizedString(@"%d cache", "Inspector -> Peers tab -> peers"), cache]];
1601            if (pex > 0)
1602                [fromComponents addObject: [NSString stringWithFormat:
1603                                        NSLocalizedString(@"%d PEX", "Inspector -> Peers tab -> peers"), pex]];
1604            if (dht > 0)
1605                [fromComponents addObject: [NSString stringWithFormat:
1606                                        NSLocalizedString(@"%d DHT", "Inspector -> Peers tab -> peers"), dht]];
1607            if (ltep > 0)
1608                [fromComponents addObject: [NSString stringWithFormat:
1609                                        NSLocalizedString(@"%d LTEP", "Inspector -> Peers tab -> peers"), ltep]];
1610           
1611            NSMutableArray * upDownComponents = [NSMutableArray arrayWithCapacity: 3];
1612            if (toUs > 0)
1613                [upDownComponents addObject: [NSString stringWithFormat:
1614                                        NSLocalizedString(@"DL from %d", "Inspector -> Peers tab -> peers"), toUs]];
1615            if (fromUs > 0)
1616                [upDownComponents addObject: [NSString stringWithFormat:
1617                                        NSLocalizedString(@"UL to %d", "Inspector -> Peers tab -> peers"), fromUs]];
1618            [upDownComponents addObject: knownString];
1619           
1620            connectedText = [connectedText stringByAppendingFormat: @": %@\n%@", [fromComponents componentsJoinedByString: @", "],
1621                                [upDownComponents componentsJoinedByString: @", "]];
1622        }
1623        else
1624            connectedText = [connectedText stringByAppendingFormat: @"\n%@", knownString];
1625       
1626        [fConnectedPeersField setStringValue: connectedText];
1627    }
1628    else
1629    {
1630        NSString * activeString;
1631        if ([fTorrents count] == 1)
1632            activeString = NSLocalizedString(@"Transfer Not Active", "Inspector -> Peers tab -> peers");
1633        else
1634            activeString = NSLocalizedString(@"Transfers Not Active", "Inspector -> Peers tab -> peers");
1635       
1636        NSString * connectedText = [NSString stringWithFormat: @"%@\n%@", activeString, knownString];
1637        [fConnectedPeersField setStringValue: connectedText];
1638    }
1639   
1640    [fPeers sortUsingDescriptors: [self peerSortDescriptors]];
1641    [fPeerTable reloadData];
1642   
1643    if (!fWebSeeds)
1644        fWebSeeds = [[NSMutableArray alloc] init];
1645    else
1646        [fWebSeeds removeAllObjects];
1647   
1648    for (Torrent * torrent in fTorrents)
1649        [fWebSeeds addObjectsFromArray: [torrent webSeeds]];
1650   
1651    [fWebSeeds sortUsingDescriptors: [fWebSeedTable sortDescriptors]];
1652    [fWebSeedTable reloadData];
1653}
1654
1655- (void) updateInfoFiles
1656{
1657    if ([fTorrents count] == 1)
1658        [fFileController reloadData];
1659}
1660
1661- (NSView *) tabViewForTag: (NSInteger) tag
1662{
1663    switch (tag)
1664    {
1665        case TAB_INFO_TAG:
1666            return fInfoView;
1667        case TAB_ACTIVITY_TAG:
1668            return fActivityView;
1669        case TAB_TRACKER_TAG:
1670            return fTrackerView;
1671        case TAB_PEERS_TAG:
1672            return fPeersView;
1673        case TAB_FILES_TAG:
1674            return fFilesView;
1675        case TAB_OPTIONS_TAG:
1676            return fOptionsView;
1677        default:
1678            NSAssert1(NO, @"Unknown tab view for tag: %d", tag);
1679            return nil;
1680    }
1681}
1682
1683- (void) setWebSeedTableHidden: (BOOL) hide animate: (BOOL) animate
1684{
1685    if (fCurrentTabTag != TAB_PEERS_TAG || ![[self window] isVisible])
1686        animate = NO;
1687   
1688    if (fWebSeedTableAnimation)
1689    {
1690        [fWebSeedTableAnimation stopAnimation];
1691        [fWebSeedTableAnimation release];
1692        fWebSeedTableAnimation = nil;
1693    }
1694   
1695    NSRect webSeedFrame = [[fWebSeedTable enclosingScrollView] frame];
1696    NSRect peerFrame = [[fPeerTable enclosingScrollView] frame];
1697   
1698    if (hide)
1699    {
1700        CGFloat webSeedFrameMaxY = NSMaxY(webSeedFrame);
1701        webSeedFrame.size.height = 0;
1702        webSeedFrame.origin.y = webSeedFrameMaxY;
1703       
1704        peerFrame.size.height = webSeedFrameMaxY - peerFrame.origin.y;
1705    }
1706    else
1707    {
1708        webSeedFrame.origin.y -= fWebSeedTableHeight - webSeedFrame.size.height;
1709        webSeedFrame.size.height = fWebSeedTableHeight;
1710       
1711        peerFrame.size.height = (webSeedFrame.origin.y - fSpaceBetweenWebSeedAndPeer) - peerFrame.origin.y;
1712    }
1713   
1714    [[fWebSeedTable enclosingScrollView] setHidden: NO]; //this is needed for some reason
1715   
1716    //actually resize tables
1717    if (animate)
1718    {
1719        NSDictionary * webSeedDict = [NSDictionary dictionaryWithObjectsAndKeys:
1720                                    [fWebSeedTable enclosingScrollView], NSViewAnimationTargetKey,
1721                                    [NSValue valueWithRect: [[fWebSeedTable enclosingScrollView] frame]], NSViewAnimationStartFrameKey,
1722                                    [NSValue valueWithRect: webSeedFrame], NSViewAnimationEndFrameKey, nil],
1723                    * peerDict = [NSDictionary dictionaryWithObjectsAndKeys:
1724                                    [fPeerTable enclosingScrollView], NSViewAnimationTargetKey,
1725                                    [NSValue valueWithRect: [[fPeerTable enclosingScrollView] frame]], NSViewAnimationStartFrameKey,
1726                                    [NSValue valueWithRect: peerFrame], NSViewAnimationEndFrameKey, nil];
1727       
1728        fWebSeedTableAnimation = [[NSViewAnimation alloc] initWithViewAnimations:
1729                                        [NSArray arrayWithObjects: webSeedDict, peerDict, nil]];
1730        [fWebSeedTableAnimation setDuration: 0.125];
1731        [fWebSeedTableAnimation setAnimationBlockingMode: NSAnimationNonblocking];
1732        [fWebSeedTableAnimation setDelegate: self];
1733       
1734        [fWebSeedTableAnimation startAnimation];
1735    }
1736    else
1737    {
1738        [[fWebSeedTable enclosingScrollView] setFrame: webSeedFrame];
1739        [[fPeerTable enclosingScrollView] setFrame: peerFrame];
1740    }
1741}
1742
1743- (NSArray *) peerSortDescriptors
1744{
1745    NSMutableArray * descriptors = [NSMutableArray arrayWithCapacity: 2];
1746   
1747    NSArray * oldDescriptors = [fPeerTable sortDescriptors];
1748    BOOL useSecond = YES, asc = YES;
1749    if ([oldDescriptors count] > 0)
1750    {
1751        NSSortDescriptor * descriptor = [oldDescriptors objectAtIndex: 0];
1752        [descriptors addObject: descriptor];
1753       
1754        if ((useSecond = ![[descriptor key] isEqualToString: @"IP"]))
1755            asc = [descriptor ascending];
1756    }
1757   
1758    //sort by IP after primary sort
1759    if (useSecond)
1760    {
1761        NSSortDescriptor * secondDescriptor = [[NSSortDescriptor alloc] initWithKey: @"IP" ascending: asc
1762                                                                        selector: @selector(compareNumeric:)];
1763        [descriptors addObject: secondDescriptor];
1764        [secondDescriptor release];
1765    }
1766   
1767    return descriptors;
1768}
1769
1770- (BOOL) canQuickLookFile: (FileListNode *) item
1771{
1772    Torrent * torrent = [fTorrents objectAtIndex: 0];
1773    return ([item isFolder] || [torrent fileProgress: item] >= 1.0) && [torrent fileLocation: item];
1774}
1775
1776#warning doesn't like blank addresses
1777- (void) addTrackers
1778{
1779    [[self window] makeKeyWindow];
1780   
1781    [fTrackers addObject: NSLocalizedString(@"New Tier", "inspector -> add tracker")];
1782    [fTrackers addObject: @""];
1783   
1784    [fTrackerTable setTrackers: fTrackers];
1785    [fTrackerTable reloadData];
1786    [fTrackerTable selectRowIndexes: [NSIndexSet indexSetWithIndex: [fTrackers count]-1] byExtendingSelection: NO];
1787    [fTrackerTable editColumn: [fTrackerTable columnWithIdentifier: @"Tracker"] row: [fTrackers count]-1 withEvent: nil select: YES];
1788}
1789
1790- (void) removeTrackers
1791{
1792    NSMutableIndexSet * removeIdentifiers = [NSMutableIndexSet indexSet];
1793   
1794    NSIndexSet * selectedIndexes = [fTrackerTable selectedRowIndexes];
1795    BOOL groupSelected = NO;
1796    for (NSUInteger i = 0; i < [fTrackers count]; ++i)
1797    {
1798        id object = [fTrackers objectAtIndex: i];
1799        if ([object isKindOfClass: [TrackerNode class]])
1800        {
1801            if (groupSelected || [selectedIndexes containsIndex: i])
1802                [removeIdentifiers addIndex: [(TrackerNode *)object identifier]];
1803        }
1804        else
1805        {
1806            groupSelected = [selectedIndexes containsIndex: i];
1807            if (!groupSelected && i > [selectedIndexes lastIndex])
1808                break;
1809        }
1810    }
1811   
1812    NSAssert([removeIdentifiers count] > 0, @"Trying to remove no trackers.");
1813   
1814    if ([[NSUserDefaults standardUserDefaults] boolForKey: @"WarningRemoveTrackers"])
1815    {
1816        NSAlert * alert = [[NSAlert alloc] init];
1817       
1818        if ([removeIdentifiers count] > 1)
1819        {
1820            [alert setMessageText: [NSString stringWithFormat: NSLocalizedString(@"Are you sure you want to remove %d trackers?",
1821                                                                "Remove trackers alert -> title"), [removeIdentifiers count]]];
1822            [alert setInformativeText: NSLocalizedString(@"Once removed, Transmission will no longer attempt to contact them."
1823                                        " This cannot be undone.", "Remove trackers alert -> message")];
1824        }
1825        else
1826        {
1827            [alert setMessageText: NSLocalizedString(@"Are you sure you want to remove this tracker?", "Remove trackers alert -> title")];
1828            [alert setInformativeText: NSLocalizedString(@"Once removed, Transmission will no longer attempt to contact it."
1829                                        " This cannot be undone.", "Remove trackers alert -> message")];
1830        }
1831       
1832        [alert addButtonWithTitle: NSLocalizedString(@"Remove", "Remove trackers alert -> button")];
1833        [alert addButtonWithTitle: NSLocalizedString(@"Cancel", "Remove trackers alert -> button")];
1834       
1835        [alert setShowsSuppressionButton: YES];
1836
1837        NSInteger result = [alert runModal];
1838        if ([[alert suppressionButton] state] == NSOnState)
1839            [[NSUserDefaults standardUserDefaults] setBool: NO forKey: @"WarningRemoveTrackers"];
1840        [alert release];
1841       
1842        if (result != NSAlertFirstButtonReturn)
1843            return;
1844    }
1845   
1846    Torrent * torrent = [fTorrents objectAtIndex: 0];
1847    [torrent removeTrackersWithIdentifiers: removeIdentifiers];
1848   
1849    //reset table with either new or old value
1850    [fTrackers release];
1851    fTrackers = [[torrent allTrackerStats] retain];
1852   
1853    [fTrackerTable setTrackers: fTrackers];
1854    [fTrackerTable reloadData];
1855    [fTrackerTable deselectAll: self];
1856   
1857    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateUI" object: nil]; //incase sort by tracker
1858}
1859
1860@end
Note: See TracBrowser for help on using the repository browser.