source: trunk/macosx/InfoWindowController.m @ 9154

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

fix manually adding trackers

  • Property svn:keywords set to Date Rev Author Id
File size: 68.4 KB
Line 
1/******************************************************************************
2 * $Id: InfoWindowController.m 9154 2009-09-21 03:56:57Z 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        fTrackerIconLoaded = [[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    [fTrackerIconLoaded 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            NSURL * favIconUrl;
933            if ([hostComponents count] > 1)
934                favIconUrl = [NSURL URLWithString: [NSString stringWithFormat: @"%@://%@.%@/favicon.ico", [address scheme],
935                                [hostComponents objectAtIndex: [hostComponents count] - 2], [hostComponents lastObject]]];
936            else
937                favIconUrl = [NSURL URLWithString: [NSString stringWithFormat: @"%@://%@/favicon.ico", [address scheme],
938                                [hostComponents lastObject]]];
939           
940            NSImage * icon = nil;
941            if ([fTrackerIconLoaded containsObject: favIconUrl])
942                icon = [fTrackerIconCache objectForKey: favIconUrl];
943            else
944                [NSThread detachNewThreadSelector: @selector(loadTrackerIcon:) toTarget: self withObject: favIconUrl];
945           
946            return icon;
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: (NSURL *) favIconUrl
957{
958    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
959   
960    [fTrackerIconLoaded addObject: favIconUrl];
961   
962    NSImage * icon = [[NSImage alloc] initWithContentsOfURL: favIconUrl];
963    if (icon)
964    {
965        [fTrackerIconCache setObject: icon forKey: favIconUrl];
966        [icon release];
967    }
968   
969    [pool drain];
970}
971
972- (NSCell *)tableView: (NSTableView *) tableView dataCellForTableColumn: (NSTableColumn *) tableColumn row: (NSInteger) row
973{
974    if (tableView == fTrackerTable)
975    {
976        //group row the full column width
977        if (!tableColumn && [[fTrackers objectAtIndex: row] isKindOfClass: [NSNumber class]])
978            return [[tableView tableColumnWithIdentifier: @"Address"] dataCell];
979    }
980   
981    return nil;
982}
983
984- (void) tableView: (NSTableView *) tableView willDisplayCell: (id) cell forTableColumn: (NSTableColumn *) tableColumn
985    row: (NSInteger) row
986{
987    if (tableView == fPeerTable)
988    {
989        NSString * ident = [tableColumn identifier];
990       
991        if  ([ident isEqualToString: @"Progress"])
992        {
993            NSDictionary * peer = [fPeers objectAtIndex: row];
994            [(PeerProgressIndicatorCell *)cell setSeed: [[peer objectForKey: @"Seed"] boolValue]];
995        }
996    }
997}
998
999- (void) tableView: (NSTableView *) tableView didClickTableColumn: (NSTableColumn *) tableColumn
1000{
1001    if (tableView == fPeerTable)
1002    {
1003        if (fPeers)
1004        {
1005            NSArray * oldPeers = fPeers;
1006            fPeers = [[fPeers sortedArrayUsingDescriptors: [self peerSortDescriptors]] retain];
1007            [oldPeers release];
1008            [tableView reloadData];
1009        }
1010    }
1011    else if (tableView == fWebSeedTable)
1012    {
1013        if (fWebSeeds)
1014        {
1015            NSArray * oldWebSeeds = fWebSeeds;
1016            fWebSeeds = [[fWebSeeds sortedArrayUsingDescriptors: [fWebSeedTable sortDescriptors]] retain];
1017            [oldWebSeeds release];
1018            [tableView reloadData];
1019        }
1020    }
1021    else;
1022}
1023
1024- (BOOL) tableView: (NSTableView *) tableView shouldSelectRow: (NSInteger) row
1025{
1026    return tableView == fTrackerTable;
1027}
1028
1029- (void) tableViewSelectionDidChange: (NSNotification *) notification
1030{
1031    if ([notification object] == fTrackerTable)
1032    {
1033        NSInteger numSelected = [fTrackerTable numberOfSelectedRows];
1034        [fTrackerAddRemoveControl setEnabled: numSelected > 0 forSegment: TRACKER_REMOVE_TAG];
1035    }
1036}
1037
1038- (BOOL) tableView: (NSTableView *) tableView isGroupRow: (NSInteger) row
1039{
1040    if (tableView == fTrackerTable)
1041        return [[fTrackers objectAtIndex: row] isKindOfClass: [NSNumber class]];
1042    return NO;
1043}
1044
1045- (NSString *) tableView: (NSTableView *) tableView toolTipForCell: (NSCell *) cell rect: (NSRectPointer) rect
1046                tableColumn: (NSTableColumn *) column row: (NSInteger) row mouseLocation: (NSPoint) mouseLocation
1047{
1048    if (tableView == fPeerTable)
1049    {
1050        NSDictionary * peer = [fPeers objectAtIndex: row];
1051        NSMutableArray * components = [NSMutableArray arrayWithCapacity: 5];
1052       
1053        CGFloat progress = [[peer objectForKey: @"Progress"] floatValue];
1054        NSString * progressString = [NSString localizedStringWithFormat: NSLocalizedString(@"Progress: %.1f%%",
1055                                        "Inspector -> Peers tab -> table row tooltip"), progress * 100.0];
1056        if (progress < 1.0 && [[peer objectForKey: @"Seed"] boolValue])
1057            progressString = [progressString stringByAppendingFormat: @" (%@)", NSLocalizedString(@"Partial Seed",
1058                                "Inspector -> Peers tab -> table row tooltip")];
1059        [components addObject: progressString];
1060       
1061        if ([[peer objectForKey: @"Encryption"] boolValue])
1062            [components addObject: NSLocalizedString(@"Encrypted Connection", "Inspector -> Peers tab -> table row tooltip")];
1063       
1064        NSString * portString;
1065        NSInteger port;
1066        if ((port = [[peer objectForKey: @"Port"] intValue]) > 0)
1067            portString = [NSString stringWithFormat: @"%d", port];
1068        else
1069            portString = NSLocalizedString(@"N/A", "Inspector -> Peers tab -> table row tooltip");
1070        [components addObject: [NSString stringWithFormat: @"%@: %@", NSLocalizedString(@"Port",
1071            "Inspector -> Peers tab -> table row tooltip"), portString]];
1072       
1073        switch ([[peer objectForKey: @"From"] intValue])
1074        {
1075            case TR_PEER_FROM_TRACKER:
1076                [components addObject: NSLocalizedString(@"From: tracker", "Inspector -> Peers tab -> table row tooltip")];
1077                break;
1078            case TR_PEER_FROM_INCOMING:
1079                [components addObject: NSLocalizedString(@"From: incoming connection", "Inspector -> Peers tab -> table row tooltip")];
1080                break;
1081            case TR_PEER_FROM_CACHE:
1082                [components addObject: NSLocalizedString(@"From: cache", "Inspector -> Peers tab -> table row tooltip")];
1083                break;
1084            case TR_PEER_FROM_PEX:
1085                [components addObject: NSLocalizedString(@"From: peer exchange", "Inspector -> Peers tab -> table row tooltip")];
1086                break;
1087            case TR_PEER_FROM_DHT:
1088                [components addObject: NSLocalizedString(@"From: distributed hash table", "Inspector -> Peers tab -> table row tooltip")];
1089                break;
1090        }
1091       
1092        //determing status strings from flags
1093        NSMutableArray * statusArray = [NSMutableArray arrayWithCapacity: 6];
1094        NSString * flags = [peer objectForKey: @"Flags"];
1095       
1096        if ([flags rangeOfString: @"D"].location != NSNotFound)
1097            [statusArray addObject: NSLocalizedString(@"Currently downloading (interested and not choked)",
1098                "Inspector -> peer -> status")];
1099        if ([flags rangeOfString: @"d"].location != NSNotFound)
1100            [statusArray addObject: NSLocalizedString(@"You want to download, but peer does not want to send (interested and choked)",
1101                "Inspector -> peer -> status")];
1102        if ([flags rangeOfString: @"U"].location != NSNotFound)
1103            [statusArray addObject: NSLocalizedString(@"Currently uploading (interested and not choked)",
1104                "Inspector -> peer -> status")];
1105        if ([flags rangeOfString: @"u"].location != NSNotFound)
1106            [statusArray addObject: NSLocalizedString(@"Peer wants you to upload, but you do not want to (interested and choked)",
1107                "Inspector -> peer -> status")];
1108        if ([flags rangeOfString: @"K"].location != NSNotFound)
1109            [statusArray addObject: NSLocalizedString(@"Peer is unchoking you, but you are not interested",
1110                "Inspector -> peer -> status")];
1111        if ([flags rangeOfString: @"?"].location != NSNotFound)
1112            [statusArray addObject: NSLocalizedString(@"You unchoked the peer, but the peer is not interested",
1113                "Inspector -> peer -> status")];
1114       
1115        if ([statusArray count] > 0)
1116        {
1117            NSString * statusStrings = [statusArray componentsJoinedByString: @"\n\n"];
1118            [components addObject: [@"\n" stringByAppendingString: statusStrings]];
1119        }
1120       
1121        return [components componentsJoinedByString: @"\n"];
1122    }
1123    return nil;
1124}
1125
1126- (void) tableView: (NSTableView *) tableView setObjectValue: (id) object forTableColumn: (NSTableColumn *) tableColumn
1127    row: (NSInteger) row
1128{
1129    if (tableView != fTrackerTable)
1130        return;
1131   
1132    [fTrackers replaceObjectAtIndex: row withObject: object];
1133   
1134    Torrent * torrent= [fTorrents objectAtIndex: 0];
1135    if (![torrent updateAllTrackersForAdd: fTrackers])
1136        NSBeep();
1137   
1138    //reset table with either new or old value
1139    [fTrackers release];
1140    fTrackers = [[torrent allTrackers: YES] retain];
1141    [fTrackerTable deselectAll: self];
1142   
1143    [fTrackerTable setTrackers: fTrackers];
1144    [fTrackerTable reloadData];
1145}
1146
1147- (void) addRemoveTracker: (id) sender
1148{
1149    //don't allow add/remove when currently adding - it leads to weird results
1150    if ([fTrackerTable editedRow] != -1)
1151        return;
1152   
1153    if ([[sender cell] tagForSegment: [sender selectedSegment]] == TRACKER_REMOVE_TAG)
1154        [self removeTrackers];
1155    else
1156        [self addTrackers];
1157}
1158
1159- (BOOL) tableView: (NSTableView *) tableView shouldEditTableColumn: (NSTableColumn *) tableColumn row: (NSInteger) row
1160{
1161    if (tableView != fTrackerTable)
1162        return NO;
1163   
1164    NSUInteger i;
1165    for (i = row-1; ![[fTrackers objectAtIndex: i] isKindOfClass: [NSNumber class]]; i--);
1166   
1167    return [[fTrackers objectAtIndex: i] intValue] == 0;
1168}
1169
1170- (BOOL) acceptsPreviewPanelControl: (QLPreviewPanel *) panel
1171{
1172    return fCurrentTabTag == TAB_FILES_TAG && [self canQuickLook];
1173}
1174
1175- (void) beginPreviewPanelControl: (QLPreviewPanel *) panel
1176{
1177    fPreviewPanel = [panel retain];
1178    fPreviewPanel.delegate = self;
1179    fPreviewPanel.dataSource = self;
1180}
1181
1182- (void) endPreviewPanelControl: (QLPreviewPanel *) panel
1183{
1184    [fPreviewPanel release];
1185    fPreviewPanel = nil;
1186}
1187
1188- (NSInteger) numberOfPreviewItemsInPreviewPanel: (QLPreviewPanel *) panel
1189{
1190    return [[self quickLookURLs] count];
1191}
1192
1193- (id <QLPreviewItem>) previewPanel: (QLPreviewPanel *)panel previewItemAtIndex: (NSInteger) index
1194{
1195    return [[self quickLookURLs] objectAtIndex: index];
1196}
1197
1198- (BOOL) previewPanel: (QLPreviewPanel *) panel handleEvent: (NSEvent *) event
1199{
1200    if ([event type] == NSKeyDown)
1201    {
1202        [super keyDown: event];
1203        return YES;
1204    }
1205   
1206    return NO;
1207}
1208
1209- (NSRect) previewPanel: (QLPreviewPanel *) panel sourceFrameOnScreenForPreviewItem: (id <QLPreviewItem>) item
1210{
1211    FileOutlineView * fileOutlineView = [fFileController outlineView];
1212   
1213    NSString * fullPath = [(NSURL *) item path];
1214    NSString * folder = [[fTorrents objectAtIndex: 0] downloadFolder];
1215    NSRange visibleRows = [fileOutlineView rowsInRect: [fileOutlineView bounds]];
1216   
1217    for (NSUInteger row = visibleRows.location; row < NSMaxRange(visibleRows); row++)
1218    {
1219        FileListNode * rowItem = [fileOutlineView itemAtRow: row];
1220        if ([[folder stringByAppendingPathComponent: [rowItem fullPath]] isEqualToString: fullPath])
1221        {
1222            NSRect frame = [fileOutlineView iconRectForRow: row];
1223            frame.origin = [fileOutlineView convertPoint: frame.origin toView: nil];
1224            frame.origin = [[self window] convertBaseToScreen: frame.origin];
1225            frame.origin.y -= frame.size.height;
1226            return frame;
1227        }
1228    }
1229   
1230    return NSZeroRect;
1231}
1232
1233- (void) setPiecesView: (id) sender
1234{
1235    [self setPiecesViewForAvailable: [sender selectedSegment] == PIECES_CONTROL_AVAILABLE];
1236}
1237
1238- (void) setPiecesViewForAvailable: (BOOL) available
1239{
1240    [fPiecesControl setSelected: available forSegment: PIECES_CONTROL_AVAILABLE];
1241    [fPiecesControl setSelected: !available forSegment: PIECES_CONTROL_PROGRESS];
1242   
1243    [[NSUserDefaults standardUserDefaults] setBool: available forKey: @"PiecesViewShowAvailability"];
1244    [fPiecesView updateView];
1245}
1246
1247- (void) revealDataFile: (id) sender
1248{
1249    if ([fTorrents count] > 0)
1250    {
1251        Torrent * torrent = [fTorrents objectAtIndex: 0];
1252        if ([NSApp isOnSnowLeopardOrBetter])
1253        {
1254            NSURL * file = [NSURL fileURLWithPath: [torrent dataLocation]];
1255            [[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs: [NSArray arrayWithObject: file]];
1256        }
1257        else
1258            [[NSWorkspace sharedWorkspace] selectFile: [torrent dataLocation] inFileViewerRootedAtPath: nil];
1259    }
1260}
1261
1262- (void) setFileFilterText: (id) sender
1263{
1264    [fFileController setFilterText: [sender stringValue]];
1265}
1266
1267- (void) setUseSpeedLimit: (id) sender
1268{
1269    const BOOL upload = sender == fUploadLimitCheck;
1270   
1271    if ([sender state] == NSMixedState)
1272        [sender setState: NSOnState];
1273    const BOOL limit = [sender state] == NSOnState;
1274   
1275    for (Torrent * torrent in fTorrents)
1276        [torrent setUseSpeedLimit: limit upload: upload];
1277   
1278    NSTextField * field = upload ? fUploadLimitField : fDownloadLimitField;
1279    [field setEnabled: limit];
1280    if (limit)
1281    {
1282        [field selectText: self];
1283        [[self window] makeKeyAndOrderFront: self];
1284    }
1285   
1286    NSTextField * label = upload ? fUploadLimitLabel : fDownloadLimitLabel;
1287    [label setEnabled: limit];
1288}
1289
1290- (void) setUseGlobalSpeedLimit: (id) sender
1291{
1292    if ([sender state] == NSMixedState)
1293        [sender setState: NSOnState];
1294    const BOOL limit = [sender state] == NSOnState;
1295   
1296    for (Torrent * torrent in fTorrents)
1297        [torrent setUseGlobalSpeedLimit: limit];
1298}
1299
1300- (void) setSpeedLimit: (id) sender
1301{
1302    const BOOL upload = sender == fUploadLimitField;
1303    const NSInteger limit = [sender intValue];
1304   
1305    for (Torrent * torrent in fTorrents)
1306        [torrent setSpeedLimit: limit upload: upload];
1307}
1308
1309- (void) setRatioSetting: (id) sender
1310{
1311    NSInteger setting;
1312    bool single = NO;
1313    switch ([sender indexOfSelectedItem])
1314    {
1315        case OPTION_POPUP_LIMIT:
1316            setting = TR_RATIOLIMIT_SINGLE;
1317            single = YES;
1318            break;
1319        case OPTION_POPUP_NO_LIMIT:
1320            setting = TR_RATIOLIMIT_UNLIMITED;
1321            break;
1322        case OPTION_POPUP_GLOBAL:
1323            setting = TR_RATIOLIMIT_GLOBAL;
1324            break;
1325        default:
1326            return;
1327    }
1328   
1329    for (Torrent * torrent in fTorrents)
1330        [torrent setRatioSetting: setting];
1331   
1332    [fRatioLimitField setHidden: !single];
1333    if (single)
1334    {
1335        [fRatioLimitField selectText: self];
1336        [[self window] makeKeyAndOrderFront: self];
1337    }
1338}
1339
1340- (void) setRatioLimit: (id) sender
1341{
1342    CGFloat limit = [sender floatValue];
1343   
1344    for (Torrent * torrent in fTorrents)
1345        [torrent setRatioLimit: limit];
1346}
1347
1348- (void) setPriority: (id) sender
1349{
1350    tr_priority_t priority;
1351    switch ([sender indexOfSelectedItem])
1352    {
1353        case OPTION_POPUP_PRIORITY_HIGH:
1354            priority = TR_PRI_HIGH;
1355            break;
1356        case OPTION_POPUP_PRIORITY_NORMAL:
1357            priority = TR_PRI_NORMAL;
1358            break;
1359        case OPTION_POPUP_PRIORITY_LOW:
1360            priority = TR_PRI_LOW;
1361            break;
1362        default:
1363            return;
1364    }
1365   
1366    for (Torrent * torrent in fTorrents)
1367        [torrent setPriority: priority];
1368   
1369    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateUI" object: nil];
1370}
1371
1372- (void) setPeersConnectLimit: (id) sender
1373{
1374    NSInteger limit = [sender intValue];
1375   
1376    for (Torrent * torrent in fTorrents)
1377        [torrent setMaxPeerConnect: limit];
1378}
1379
1380- (BOOL) control: (NSControl *) control textShouldBeginEditing: (NSText *) fieldEditor
1381{
1382    [fInitialString release];
1383    fInitialString = [[control stringValue] retain];
1384   
1385    return YES;
1386}
1387
1388- (BOOL) control: (NSControl *) control didFailToFormatString: (NSString *) string errorDescription: (NSString *) error
1389{
1390    NSBeep();
1391    if (fInitialString)
1392    {
1393        [control setStringValue: fInitialString];
1394        [fInitialString release];
1395        fInitialString = nil;
1396    }
1397    return NO;
1398}
1399
1400@end
1401
1402@implementation InfoWindowController (Private)
1403
1404- (void) updateInfoGeneral
1405{   
1406    if ([fTorrents count] != 1)
1407        return;
1408   
1409    Torrent * torrent = [fTorrents objectAtIndex: 0];
1410   
1411    [fTrackerField setStringValue: [torrent trackerAddressAnnounce]];
1412   
1413    NSString * location = [torrent dataLocation];
1414    [fDataLocationField setStringValue: [location stringByAbbreviatingWithTildeInPath]];
1415    [fDataLocationField setToolTip: location];
1416}
1417
1418- (void) updateInfoActivity
1419{
1420    NSInteger numberSelected = [fTorrents count];
1421    if (numberSelected == 0)
1422        return;
1423   
1424    uint64_t have = 0, haveVerified = 0, downloadedTotal = 0, uploadedTotal = 0, failedHash = 0;
1425    NSDate * lastActivity = nil;
1426    for (Torrent * torrent in fTorrents)
1427    {
1428        have += [torrent haveTotal];
1429        haveVerified += [torrent haveVerified];
1430        downloadedTotal += [torrent downloadedTotal];
1431        uploadedTotal += [torrent uploadedTotal];
1432        failedHash += [torrent failedHash];
1433       
1434        NSDate * nextLastActivity;
1435        if ((nextLastActivity = [torrent dateActivity]))
1436            lastActivity = lastActivity ? [lastActivity laterDate: nextLastActivity] : nextLastActivity;
1437    }
1438   
1439    if (have == 0)
1440        [fHaveField setStringValue: [NSString stringForFileSize: 0]];
1441    else
1442    {
1443        NSString * verifiedString = [NSString stringWithFormat: NSLocalizedString(@"%@ verified", "Inspector -> Activity tab -> have"),
1444                                        [NSString stringForFileSize: haveVerified]];
1445        if (have == haveVerified)
1446            [fHaveField setStringValue: verifiedString];
1447        else
1448            [fHaveField setStringValue: [NSString stringWithFormat: @"%@ (%@)", [NSString stringForFileSize: have], verifiedString]];
1449    }
1450   
1451    [fDownloadedTotalField setStringValue: [NSString stringForFileSize: downloadedTotal]];
1452    [fUploadedTotalField setStringValue: [NSString stringForFileSize: uploadedTotal]];
1453    [fFailedHashField setStringValue: [NSString stringForFileSize: failedHash]];
1454   
1455    [fDateActivityField setObjectValue: lastActivity];
1456   
1457    if (numberSelected == 1)
1458    {
1459        Torrent * torrent = [fTorrents objectAtIndex: 0];
1460       
1461        [fStateField setStringValue: [torrent stateString]];
1462       
1463        if ([torrent isFolder])
1464            [fProgressField setStringValue: [NSString localizedStringWithFormat: NSLocalizedString(@"%.2f%% (%.2f%% selected)",
1465                "Inspector -> Activity tab -> progress"), 100.0 * [torrent progress], 100.0 * [torrent progressDone]]];
1466        else
1467            [fProgressField setStringValue: [NSString localizedStringWithFormat: @"%.2f%%", 100.0 * [torrent progress]]];
1468           
1469        [fRatioField setStringValue: [NSString stringForRatio: [torrent ratio]]];
1470        [fSwarmSpeedField setStringValue: [torrent isActive] ? [NSString stringForSpeed: [torrent swarmSpeed]] : @""];
1471       
1472        NSString * errorMessage = [torrent errorMessage];
1473        if (![errorMessage isEqualToString: [fErrorMessageView string]])
1474        {
1475            [fErrorMessageView setString: errorMessage];
1476            [fErrorMessageView setSelectable: ![errorMessage isEqualToString: @""]];
1477        }
1478       
1479        [fDateCompletedField setObjectValue: [torrent dateCompleted]];
1480       
1481        [fPiecesView updateView];
1482    }
1483    else if (numberSelected > 1)
1484    {
1485        [fRatioField setStringValue: [NSString stringForRatio: tr_getRatio(uploadedTotal, downloadedTotal)]];
1486    }
1487    else;
1488}
1489
1490#warning reload table when necessary?
1491- (void) updateInfoTracker
1492{
1493    if ([fTorrents count] != 1)
1494        return;
1495    Torrent * torrent = [fTorrents objectAtIndex: 0];
1496   
1497    //announce fields
1498    NSString * announceAddress = [torrent trackerAddressAnnounce];
1499    [fAnnounceAddressField setStringValue: announceAddress];
1500    [fAnnounceAddressField setToolTip: announceAddress];
1501   
1502    [fAnnounceLastField setObjectValue: [torrent lastAnnounceTime]];
1503   
1504    NSString * announceResponse = [torrent announceResponse];
1505    [fAnnounceResponseField setStringValue: announceResponse];
1506    [fAnnounceResponseField setToolTip: announceResponse];
1507    [fAnnounceResponseField setSelectable: ![announceResponse isEqualToString: @""]];
1508   
1509    NSInteger announceNext = [torrent nextAnnounceTime];
1510    NSString * announceNextString;
1511    switch (announceNext)
1512    {
1513        case STAT_TIME_NOW:
1514            announceNextString = [NSLocalizedString(@"In progress", "Inspector -> tracker tab") stringByAppendingEllipsis];
1515            break;
1516        case STAT_TIME_NONE:
1517            announceNextString = @"";
1518            break;
1519        default:
1520            announceNextString = [NSString timeString: announceNext showSeconds: YES];
1521    }
1522    [fAnnounceNextField setStringValue: announceNextString];
1523   
1524    //scrape fields
1525    NSString * scrapeAddress;
1526    if ((scrapeAddress = [torrent trackerAddressScrape]))
1527    {
1528        [fScrapeAddressField setStringValue: scrapeAddress];
1529        [fScrapeAddressField setToolTip: scrapeAddress];
1530    }
1531    else
1532    {
1533        [fScrapeAddressField setStringValue: @""];
1534        [fScrapeAddressField setToolTip: @""];
1535    }
1536   
1537    [fScrapeLastField setObjectValue: [torrent lastScrapeTime]];
1538   
1539    NSString * scrapeResponse = [torrent scrapeResponse];
1540    [fScrapeResponseField setStringValue: scrapeResponse];
1541    [fScrapeResponseField setToolTip: scrapeResponse];
1542    [fScrapeResponseField setSelectable: ![scrapeResponse isEqualToString: @""]];
1543   
1544    NSInteger scrapeNext = [torrent nextScrapeTime];
1545    NSString * scrapeNextString;
1546    switch (scrapeNext)
1547    {
1548        case STAT_TIME_NOW:
1549            scrapeNextString = [NSLocalizedString(@"In progress", "Inspector -> tracker tab") stringByAppendingEllipsis];
1550            break;
1551        case STAT_TIME_NONE:
1552            scrapeNextString = @"";
1553            break;
1554        default:
1555            scrapeNextString = [NSString timeString: scrapeNext showSeconds: YES];
1556    }
1557    [fScrapeNextField setStringValue: scrapeNextString];
1558}
1559
1560- (void) updateInfoPeers
1561{
1562    if ([fTorrents count] != 1)
1563        return;
1564    Torrent * torrent = [fTorrents objectAtIndex: 0];
1565   
1566    NSInteger seeders = [torrent seeders], leechers = [torrent leechers], completed = [torrent completedFromTracker];
1567    [fSeedersField setStringValue: seeders >= 0 ? [NSString stringWithFormat: @"%d", seeders] : @""];
1568    [fLeechersField setStringValue: leechers >= 0 ? [NSString stringWithFormat: @"%d", leechers] : @""];
1569    [fCompletedFromTrackerField setStringValue: completed >= 0 ? [NSString stringWithFormat: @"%d", completed] : @""];
1570   
1571    BOOL active = [torrent isActive];
1572   
1573    if (active)
1574    {
1575        NSInteger total = [torrent totalPeersConnected];
1576        NSString * connected = [NSString stringWithFormat:
1577                                NSLocalizedString(@"%d Connected", "Inspector -> Peers tab -> peers"), total];
1578       
1579        if (total > 0)
1580        {
1581            NSMutableArray * components = [NSMutableArray arrayWithCapacity: 5];
1582            NSInteger count;
1583            if ((count = [torrent totalPeersTracker]) > 0)
1584                [components addObject: [NSString stringWithFormat:
1585                                        NSLocalizedString(@"%d tracker", "Inspector -> Peers tab -> peers"), count]];
1586            if ((count = [torrent totalPeersIncoming]) > 0)
1587                [components addObject: [NSString stringWithFormat:
1588                                        NSLocalizedString(@"%d incoming", "Inspector -> Peers tab -> peers"), count]];
1589            if ((count = [torrent totalPeersCache]) > 0)
1590                [components addObject: [NSString stringWithFormat:
1591                                        NSLocalizedString(@"%d cache", "Inspector -> Peers tab -> peers"), count]];
1592            if ((count = [torrent totalPeersPex]) > 0)
1593                [components addObject: [NSString stringWithFormat:
1594                                        NSLocalizedString(@"%d PEX", "Inspector -> Peers tab -> peers"), count]];
1595            if ((count = [torrent totalPeersDHT]) > 0)
1596                [components addObject: [NSString stringWithFormat:
1597                                        NSLocalizedString(@"%d DHT", "Inspector -> Peers tab -> peers"), count]];
1598           
1599            connected = [connected stringByAppendingFormat: @": %@", [components componentsJoinedByString: @", "]];
1600        }
1601       
1602        [fConnectedPeersField setStringValue: connected];
1603       
1604        [fDownloadingFromField setIntValue: [torrent peersSendingToUs]];
1605        [fUploadingToField setIntValue: [torrent peersGettingFromUs]];
1606    }
1607    else
1608    {
1609        [fConnectedPeersField setStringValue: @""];
1610        [fDownloadingFromField setStringValue: @""];
1611        [fUploadingToField setStringValue: @""];
1612    }
1613   
1614    [fKnownField setIntValue: [torrent totalPeersKnown]];
1615   
1616    [fPeers release];
1617    fPeers = [[[torrent peers] sortedArrayUsingDescriptors: [self peerSortDescriptors]] retain];
1618    [fPeerTable reloadData];
1619   
1620    if ([torrent webSeedCount] > 0)
1621    {
1622        [fWebSeeds release];
1623        fWebSeeds = [[[torrent webSeeds] sortedArrayUsingDescriptors: [fWebSeedTable sortDescriptors]] retain];
1624        [fWebSeedTable reloadData];
1625    }
1626}
1627
1628- (void) updateInfoFiles
1629{
1630    if ([fTorrents count] == 1)
1631        [fFileController reloadData];
1632}
1633
1634- (NSView *) tabViewForTag: (NSInteger) tag
1635{
1636    switch (tag)
1637    {
1638        case TAB_INFO_TAG:
1639            return fInfoView;
1640        case TAB_ACTIVITY_TAG:
1641            return fActivityView;
1642        case TAB_TRACKER_TAG:
1643            return fTrackerView;
1644        case TAB_PEERS_TAG:
1645            return fPeersView;
1646        case TAB_FILES_TAG:
1647            return fFilesView;
1648        case TAB_OPTIONS_TAG:
1649            return fOptionsView;
1650        default:
1651            return nil;
1652    }
1653}
1654
1655- (void) setWebSeedTableHidden: (BOOL) hide animate: (BOOL) animate
1656{
1657    if (fCurrentTabTag != TAB_PEERS_TAG || ![[self window] isVisible])
1658        animate = NO;
1659   
1660    if (fWebSeedTableAnimation)
1661    {
1662        [fWebSeedTableAnimation stopAnimation];
1663        [fWebSeedTableAnimation release];
1664        fWebSeedTableAnimation = nil;
1665    }
1666   
1667    NSRect webSeedFrame = [[fWebSeedTable enclosingScrollView] frame];
1668    NSRect peerFrame = [[fPeerTable enclosingScrollView] frame];
1669   
1670    if (hide)
1671    {
1672        CGFloat webSeedFrameMaxY = NSMaxY(webSeedFrame);
1673        webSeedFrame.size.height = 0;
1674        webSeedFrame.origin.y = webSeedFrameMaxY;
1675       
1676        peerFrame.size.height = webSeedFrameMaxY - peerFrame.origin.y;
1677    }
1678    else
1679    {
1680        webSeedFrame.origin.y -= fWebSeedTableHeight - webSeedFrame.size.height;
1681        webSeedFrame.size.height = fWebSeedTableHeight;
1682       
1683        peerFrame.size.height = (webSeedFrame.origin.y - fSpaceBetweenWebSeedAndPeer) - peerFrame.origin.y;
1684    }
1685   
1686    [[fWebSeedTable enclosingScrollView] setHidden: NO]; //this is needed for some reason
1687   
1688    //actually resize tables
1689    if (animate)
1690    {
1691        NSDictionary * webSeedDict = [NSDictionary dictionaryWithObjectsAndKeys:
1692                                    [fWebSeedTable enclosingScrollView], NSViewAnimationTargetKey,
1693                                    [NSValue valueWithRect: [[fWebSeedTable enclosingScrollView] frame]], NSViewAnimationStartFrameKey,
1694                                    [NSValue valueWithRect: webSeedFrame], NSViewAnimationEndFrameKey, nil],
1695                    * peerDict = [NSDictionary dictionaryWithObjectsAndKeys:
1696                                    [fPeerTable enclosingScrollView], NSViewAnimationTargetKey,
1697                                    [NSValue valueWithRect: [[fPeerTable enclosingScrollView] frame]], NSViewAnimationStartFrameKey,
1698                                    [NSValue valueWithRect: peerFrame], NSViewAnimationEndFrameKey, nil];
1699       
1700        fWebSeedTableAnimation = [[NSViewAnimation alloc] initWithViewAnimations:
1701                                        [NSArray arrayWithObjects: webSeedDict, peerDict, nil]];
1702        [fWebSeedTableAnimation setDuration: 0.125];
1703        [fWebSeedTableAnimation setAnimationBlockingMode: NSAnimationNonblocking];
1704        [fWebSeedTableAnimation setDelegate: self];
1705       
1706        [fWebSeedTableAnimation startAnimation];
1707    }
1708    else
1709    {
1710        [[fWebSeedTable enclosingScrollView] setFrame: webSeedFrame];
1711        [[fPeerTable enclosingScrollView] setFrame: peerFrame];
1712    }
1713}
1714
1715- (NSArray *) peerSortDescriptors
1716{
1717    NSMutableArray * descriptors = [NSMutableArray arrayWithCapacity: 2];
1718   
1719    NSArray * oldDescriptors = [fPeerTable sortDescriptors];
1720    BOOL useSecond = YES, asc = YES;
1721    if ([oldDescriptors count] > 0)
1722    {
1723        NSSortDescriptor * descriptor = [oldDescriptors objectAtIndex: 0];
1724        [descriptors addObject: descriptor];
1725       
1726        if ((useSecond = ![[descriptor key] isEqualToString: @"IP"]))
1727            asc = [descriptor ascending];
1728    }
1729   
1730    //sort by IP after primary sort
1731    if (useSecond)
1732    {
1733        NSSortDescriptor * secondDescriptor = [[NSSortDescriptor alloc] initWithKey: @"IP" ascending: asc
1734                                                                        selector: @selector(compareNumeric:)];
1735        [descriptors addObject: secondDescriptor];
1736        [secondDescriptor release];
1737    }
1738   
1739    return descriptors;
1740}
1741
1742- (NSArray *) quickLookURLs
1743{
1744    FileOutlineView * fileOutlineView = [fFileController outlineView];
1745    Torrent * torrent = [fTorrents objectAtIndex: 0];
1746    NSString * folder = [torrent downloadFolder];
1747    NSIndexSet * indexes = [fileOutlineView selectedRowIndexes];
1748    NSMutableArray * urlArray = [NSMutableArray arrayWithCapacity: [indexes count]];
1749   
1750    for (NSUInteger i = [indexes firstIndex]; i != NSNotFound; i = [indexes indexGreaterThanIndex: i])
1751    {
1752        FileListNode * item = [fileOutlineView itemAtRow: i];
1753        if ([self canQuickLookFile: item])
1754            [urlArray addObject: [NSURL fileURLWithPath: [folder stringByAppendingPathComponent: [item fullPath]]]];
1755    }
1756   
1757    return urlArray;
1758}
1759
1760- (BOOL) canQuickLook
1761{
1762    if (![NSApp isOnSnowLeopardOrBetter])
1763        return NO;
1764   
1765    FileOutlineView * fileOutlineView = [fFileController outlineView];
1766    NSIndexSet * indexes = [fileOutlineView selectedRowIndexes];
1767   
1768    for (NSUInteger i = [indexes firstIndex]; i != NSNotFound; i = [indexes indexGreaterThanIndex: i])
1769        if ([self canQuickLookFile: [fileOutlineView itemAtRow: i]])
1770            return YES;
1771   
1772    return NO;
1773}
1774
1775- (BOOL) canQuickLookFile: (FileListNode *) item
1776{
1777    Torrent * torrent = [fTorrents objectAtIndex: 0];
1778   
1779    if (![[NSFileManager defaultManager] fileExistsAtPath: [[torrent downloadFolder] stringByAppendingPathComponent: [item fullPath]]])
1780        return NO;
1781   
1782    return [item isFolder] || [torrent fileProgress: item] == 1.0;
1783}
1784
1785#warning doesn't like blank addresses
1786- (void) addTrackers
1787{
1788    [[self window] makeKeyWindow];
1789   
1790    NSUInteger tierCount = 0;
1791    for (id trackerObject in fTrackers)
1792        if ([trackerObject isKindOfClass: [NSNumber class]])
1793            tierCount++;
1794   
1795    [fTrackers addObject: [NSNumber numberWithInt: tierCount+1]];
1796    [fTrackers addObject: @""];
1797   
1798    [fTrackerTable reloadData];
1799    [fTrackerTable selectRowIndexes: [NSIndexSet indexSetWithIndex: [fTrackers count]-1] byExtendingSelection: NO];
1800    [fTrackerTable editColumn: [fTrackerTable columnWithIdentifier: @"Address"] row: [fTrackers count]-1 withEvent: nil select: YES];
1801}
1802
1803- (void) removeTrackers
1804{
1805    NSMutableIndexSet * indexes = [[[fTrackerTable selectedRowIndexes] mutableCopy] autorelease];
1806   
1807    //get all rows to remove
1808    NSUInteger i = 0, trackerCount = 0;
1809    while (i < [fTrackers count])
1810    {
1811        //if a group is selected, remove all trackers in the group
1812        if ([indexes containsIndex: i])
1813        {
1814            for (i = i+1; i < [fTrackers count] && ![[fTrackers objectAtIndex: i] isKindOfClass: [NSNumber class]]; i++)
1815            {
1816                [indexes addIndex: i];
1817                trackerCount++;
1818            }
1819        }
1820        //remove empty groups
1821        else
1822        {
1823            BOOL allSelected = YES;
1824            NSUInteger j;
1825            for (j = i+1; j < [fTrackers count] && ![[fTrackers objectAtIndex: j] isKindOfClass: [NSNumber class]]; j++)
1826            {
1827                if (![indexes containsIndex: j])
1828                    allSelected = NO;
1829                else
1830                    trackerCount++;
1831            }
1832           
1833            if (allSelected)
1834                [indexes addIndex: i];
1835           
1836            i = j;
1837        }
1838    }
1839   
1840    if ([fTrackers count] == [indexes count])
1841    {
1842        NSBeep();
1843        return;
1844    }
1845   
1846    if ([[NSUserDefaults standardUserDefaults] boolForKey: @"WarningRemoveTrackers"])
1847    {
1848        NSAlert * alert = [[NSAlert alloc] init];
1849       
1850        if (trackerCount > 1)
1851        {
1852            [alert setMessageText: [NSString stringWithFormat: NSLocalizedString(@"Are you sure you want to remove %d trackers?",
1853                                                                "Remove trackers alert -> title"), trackerCount]];
1854            [alert setInformativeText: NSLocalizedString(@"Once removed, Transmission will no longer attempt to contact them."
1855                                        " This cannot be undone.", "Remove trackers alert -> message")];
1856        }
1857        else
1858        {
1859            [alert setMessageText: NSLocalizedString(@"Are you sure you want to remove this tracker?", "Remove trackers alert -> title")];
1860            [alert setInformativeText: NSLocalizedString(@"Once removed, Transmission will no longer attempt to contact it."
1861                                        " This cannot be undone.", "Remove trackers alert -> message")];
1862        }
1863       
1864        [alert addButtonWithTitle: NSLocalizedString(@"Remove", "Remove trackers alert -> button")];
1865        [alert addButtonWithTitle: NSLocalizedString(@"Cancel", "Remove trackers alert -> button")];
1866       
1867        [alert setShowsSuppressionButton: YES];
1868
1869        NSInteger result = [alert runModal];
1870        if ([[alert suppressionButton] state] == NSOnState)
1871            [[NSUserDefaults standardUserDefaults] setBool: NO forKey: @"WarningRemoveTrackers"];
1872        [alert release];
1873       
1874        if (result != NSAlertFirstButtonReturn)
1875            return;
1876    }
1877   
1878    [fTrackers removeObjectsAtIndexes: indexes];
1879   
1880    Torrent * torrent = [fTorrents objectAtIndex: 0];
1881    [torrent updateAllTrackersForRemove: fTrackers];
1882    [fTrackerTable deselectAll: self];
1883   
1884    //reset table with either new or old value
1885    [fTrackers release];
1886    fTrackers = [[torrent allTrackers: YES] retain];
1887   
1888    [fTrackerTable setTrackers: fTrackers];
1889    [fTrackerTable reloadData];
1890}
1891
1892@end
Note: See TracBrowser for help on using the repository browser.