source: trunk/macosx/InfoWindowController.m @ 9186

Last change on this file since 9186 was 9186, checked in by livings124, 8 years ago

show tracker icons on 10.5

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