source: trunk/macosx/InfoWindowController.m @ 9156

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

better use of the cache object for tracker favicons, so if an icon is removed from the cache, it will be fetched again the next time it's needed

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