source: trunk/macosx/InfoWindowController.m @ 9124

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

use 10.6's localizedStandardCompare: when sorting by name

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