source: trunk/macosx/InfoWindowController.m @ 9123

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

#2414 Upside-down dock icons in Inspector under Leopard

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