source: trunk/macosx/InfoWindowController.m @ 6076

Last change on this file since 6076 was 6076, checked in by livings124, 14 years ago

update the peer inspector tab to show web seeders

  • Property svn:keywords set to Date Rev Author Id
File size: 57.1 KB
Line 
1/******************************************************************************
2 * $Id: InfoWindowController.m 6076 2008-06-07 23:38:05Z livings124 $
3 *
4 * Copyright (c) 2006-2008 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 "InfoTabButtonCell.h"
27#import "FileOutlineView.h"
28#import "FileListNode.h"
29#import "TrackerTableView.h"
30#import "QuickLookController.h"
31#import "NSApplicationAdditions.h"
32#import "NSStringAdditions.h"
33
34#define TAB_INFO_IDENT @"Info"
35#define TAB_ACTIVITY_IDENT @"Activity"
36#define TAB_TRACKER_IDENT @"Tracker"
37#define TAB_PEERS_IDENT @"Peers"
38#define TAB_FILES_IDENT @"Files"
39#define TAB_OPTIONS_IDENT @"Options"
40
41#define TAB_MIN_HEIGHT 250
42
43#define PIECES_CONTROL_PROGRESS 0
44#define PIECES_CONTROL_AVAILABLE 1
45
46#define OPTION_POPUP_GLOBAL 0
47#define OPTION_POPUP_NO_LIMIT 1
48#define OPTION_POPUP_LIMIT 2
49
50#define INVALID -99
51
52#define TRACKER_ADD_TAG 0
53#define TRACKER_REMOVE_TAG 1
54
55typedef enum
56{
57    TAB_INFO_TAG = 0,
58    TAB_ACTIVITY_TAG = 1,
59    TAB_TRACKER_TAG = 2,
60    TAB_PEERS_TAG = 3,
61    TAB_FILES_TAG = 4,
62    TAB_OPTIONS_TAG = 5
63} tabTag;
64
65@interface InfoWindowController (Private)
66
67- (void) updateInfoGeneral;
68- (void) updateInfoActivity;
69- (void) updateInfoTracker;
70- (void) updateInfoPeers;
71- (void) updateInfoFiles;
72
73- (NSView *) tabViewForTag: (int) tag;
74- (NSArray *) peerSortDescriptors;
75
76- (void) addTrackers;
77- (void) removeTrackers;
78
79@end
80
81@implementation InfoWindowController
82
83- (id) init
84{
85    return [super initWithWindowNibName: @"InfoWindow"];
86}
87
88- (void) awakeFromNib
89{
90    //window location and size
91    NSPanel * window = (NSPanel *)[self window];
92   
93    float windowHeight = [window frame].size.height;
94   
95    [window setFrameAutosaveName: @"InspectorWindow"];
96    [window setFrameUsingName: @"InspectorWindow"];
97   
98    NSRect windowRect = [window frame];
99    windowRect.origin.y -= windowHeight - windowRect.size.height;
100    windowRect.size.height = windowHeight;
101    [window setFrame: windowRect display: NO];
102   
103    [window setBecomesKeyOnlyIfNeeded: YES];
104   
105    //set tab images and tooltips
106    [[fTabMatrix cellWithTag: TAB_INFO_TAG] setIcon: [NSImage imageNamed: @"InfoGeneral.png"]];
107    [[fTabMatrix cellWithTag: TAB_ACTIVITY_TAG] setIcon: [NSImage imageNamed: @"InfoActivity.png"]];
108    [[fTabMatrix cellWithTag: TAB_TRACKER_TAG] setIcon: [NSImage imageNamed: @"InfoTracker.png"]];
109    [[fTabMatrix cellWithTag: TAB_PEERS_TAG] setIcon: [NSImage imageNamed: @"InfoPeers.png"]];
110    [[fTabMatrix cellWithTag: TAB_FILES_TAG] setIcon: [NSImage imageNamed: @"InfoFiles.png"]];
111    [[fTabMatrix cellWithTag: TAB_OPTIONS_TAG] setIcon: [NSImage imageNamed: @"InfoOptions.png"]];
112   
113    //set selected tab
114    fCurrentTabTag = INVALID;
115    NSString * identifier = [[NSUserDefaults standardUserDefaults] stringForKey: @"InspectorSelected"];
116    int tag;
117    if ([identifier isEqualToString: TAB_INFO_IDENT])
118        tag = TAB_INFO_TAG;
119    else if ([identifier isEqualToString: TAB_ACTIVITY_IDENT])
120        tag = TAB_ACTIVITY_TAG;
121    else if ([identifier isEqualToString: TAB_TRACKER_IDENT])
122        tag = TAB_TRACKER_TAG;
123    else if ([identifier isEqualToString: TAB_PEERS_IDENT])
124        tag = TAB_PEERS_TAG;
125    else if ([identifier isEqualToString: TAB_FILES_IDENT])
126        tag = TAB_FILES_TAG;
127    else if ([identifier isEqualToString: TAB_OPTIONS_IDENT])
128        tag = TAB_OPTIONS_TAG;
129    else //safety
130    {
131        [[NSUserDefaults standardUserDefaults] setObject: TAB_INFO_IDENT forKey: @"InspectorSelected"];
132        tag = TAB_INFO_TAG;
133    }
134    [fTabMatrix selectCellWithTag: tag];
135    [self setTab: nil];
136   
137    //reset images for reveal buttons, since the images are also used in the main table
138    NSImage * revealOn = [[NSImage imageNamed: @"RevealOn.png"] copy],
139            * revealOff = [[NSImage imageNamed: @"RevealOff.png"] copy];
140    [revealOn setFlipped: NO];
141    [revealOff setFlipped: NO];
142   
143    [fRevealDataButton setImage: revealOff];
144    [fRevealDataButton setAlternateImage: revealOn];
145    [fRevealTorrentButton setImage: revealOff];
146    [fRevealTorrentButton setAlternateImage: revealOn];
147   
148    [revealOn release];
149    [revealOff release];
150   
151    //initially sort peer table by IP
152    if ([[fPeerTable sortDescriptors] count] == 0)
153        [fPeerTable setSortDescriptors: [NSArray arrayWithObject: [[fPeerTable tableColumnWithIdentifier: @"IP"]
154                                            sortDescriptorPrototype]]];
155   
156    //set table header tool tips
157    if ([NSApp isOnLeopardOrBetter])
158    {
159        [[fPeerTable tableColumnWithIdentifier: @"Encryption"] setHeaderToolTip: NSLocalizedString(@"Encrypted Connection",
160                                                                            "inspector -> peer table -> header tool tip")];
161        [[fPeerTable tableColumnWithIdentifier: @"Progress"] setHeaderToolTip: NSLocalizedString(@"Available",
162                                                                            "inspector -> peer table -> header tool tip")];
163        [[fPeerTable tableColumnWithIdentifier: @"UL To"] setHeaderToolTip: NSLocalizedString(@"Uploading To Peer",
164                                                                            "inspector -> peer table -> header tool tip")];
165        [[fPeerTable tableColumnWithIdentifier: @"DL From"] setHeaderToolTip: NSLocalizedString(@"Downloading From Peer",
166                                                                            "inspector -> peer table -> header tool tip")];
167    }
168    else
169    {
170        [fTrackerAddRemoveControl sizeToFit];
171        [fTrackerAddRemoveControl setLabel: @"+" forSegment: TRACKER_ADD_TAG];
172        [fTrackerAddRemoveControl setLabel: @"-" forSegment: TRACKER_REMOVE_TAG];
173    }
174   
175    //set blank inspector
176    [self setInfoForTorrents: [NSArray array]];
177   
178    //allow for update notifications
179    NSNotificationCenter * nc = [NSNotificationCenter defaultCenter];
180    [nc addObserver: self selector: @selector(updateInfoStats) name: @"UpdateStats" object: nil];
181    [nc addObserver: self selector: @selector(updateOptions) name: @"UpdateOptions" object: nil];
182}
183
184- (void) dealloc
185{
186    //save resizeable view height
187    NSString * resizeSaveKey = nil;
188    switch (fCurrentTabTag)
189    {
190        case TAB_TRACKER_TAG:
191            resizeSaveKey = @"InspectorContentHeightTracker";
192            break;
193        case TAB_PEERS_TAG:
194            resizeSaveKey = @"InspectorContentHeightPeers";
195            break;
196        case TAB_FILES_TAG:
197            resizeSaveKey = @"InspectorContentHeightFiles";
198            break;
199    }
200    if (resizeSaveKey)
201        [[NSUserDefaults standardUserDefaults] setFloat: [[self tabViewForTag: fCurrentTabTag] frame].size.height forKey: resizeSaveKey];
202   
203    [[NSNotificationCenter defaultCenter] removeObserver: self];
204   
205    [fTorrents release];
206    [fPeers release];
207    [fWebSeeds release];
208    [fTrackers release];
209   
210    [super dealloc];
211}
212
213- (void) setInfoForTorrents: (NSArray *) torrents
214{
215    if (fTorrents && [fTorrents isEqualToArray: torrents])
216        return;
217   
218    [fTorrents release];
219    fTorrents = [torrents retain];
220
221    int numberSelected = [fTorrents count];
222    if (numberSelected != 1)
223    {
224        if (numberSelected > 0)
225        {
226            [fImageView setImage: [NSImage imageNamed: [NSApp isOnLeopardOrBetter]
227                                    ? NSImageNameMultipleDocuments : @"NSApplicationIcon"]];
228           
229            [fNameField setStringValue: [NSString stringWithFormat: NSLocalizedString(@"%d Torrents Selected",
230                                            "Inspector -> selected torrents"), numberSelected]];
231       
232            uint64_t size = 0;
233            int fileCount = 0;
234            NSEnumerator * enumerator = [torrents objectEnumerator];
235            Torrent * torrent;
236            while ((torrent = [enumerator nextObject]))
237            {
238                size += [torrent size];
239                fileCount += [torrent fileCount];
240            }
241           
242            [fBasicInfoField setStringValue: [NSString stringWithFormat: @"%@, %@",
243                [NSString stringWithFormat: NSLocalizedString(@"%d files", "Inspector -> selected torrents"), fileCount],
244                [NSString stringWithFormat: NSLocalizedString(@"%@ total", "Inspector -> selected torrents"),
245                [NSString stringForFileSize: size]]]];
246            [fBasicInfoField setToolTip: [NSString stringWithFormat: NSLocalizedString(@"%llu bytes", "Inspector -> selected torrents"),
247                                            size]];
248        }
249        else
250        {
251            [fImageView setImage: [NSImage imageNamed: @"NSApplicationIcon"]];
252           
253            [fNameField setStringValue: NSLocalizedString(@"No Torrents Selected", "Inspector -> selected torrents")];
254            [fBasicInfoField setStringValue: @""];
255            [fBasicInfoField setToolTip: @""];
256   
257            [fHaveField setStringValue: @""];
258            [fDownloadedTotalField setStringValue: @""];
259            [fUploadedTotalField setStringValue: @""];
260            [fFailedHashField setStringValue: @""];
261            [fDateActivityField setStringValue: @""];
262           
263            //options fields
264            [fUploadLimitPopUp setEnabled: NO];
265            [fUploadLimitPopUp selectItemAtIndex: -1];
266            [fUploadLimitField setHidden: YES];
267            [fUploadLimitLabel setHidden: YES];
268            [fUploadLimitField setStringValue: @""];
269           
270            [fDownloadLimitPopUp setEnabled: NO];
271            [fDownloadLimitPopUp selectItemAtIndex: -1];
272            [fDownloadLimitField setHidden: YES];
273            [fDownloadLimitLabel setHidden: YES];
274            [fDownloadLimitField setStringValue: @""];
275           
276            [fRatioPopUp setEnabled: NO];
277            [fRatioPopUp selectItemAtIndex: -1];
278            [fRatioLimitField setHidden: YES];
279            [fRatioLimitField setStringValue: @""];
280           
281            [fPeersConnectField setEnabled: NO];
282            [fPeersConnectField setStringValue: @""];
283        }
284       
285        [fFileController setTorrent: nil];
286       
287        [fNameField setToolTip: nil];
288
289        [fTrackerField setStringValue: @""];
290        [fPiecesField setStringValue: @""];
291        [fHashField setStringValue: @""];
292        [fHashField setToolTip: nil];
293        [fSecureField setStringValue: @""];
294        [fCommentView setString: @""];
295       
296        [fCreatorField setStringValue: @""];
297        [fDateCreatedField setStringValue: @""];
298        [fCommentView setSelectable: NO];
299       
300        [fTorrentLocationField setStringValue: @""];
301        [fTorrentLocationField setToolTip: nil];
302        [fDataLocationField setStringValue: @""];
303        [fDataLocationField setToolTip: nil];
304       
305        [fRevealDataButton setHidden: YES];
306        [fRevealTorrentButton setHidden: YES];
307       
308        //don't allow empty fields to be selected
309        [fTrackerField setSelectable: NO];
310        [fHashField setSelectable: NO];
311        [fCreatorField setSelectable: NO];
312        [fTorrentLocationField setSelectable: NO];
313        [fDataLocationField setSelectable: NO];
314       
315        [fStateField setStringValue: @""];
316        [fProgressField setStringValue: @""];
317        [fRatioField setStringValue: @""];
318       
319        [fSwarmSpeedField setStringValue: @""];
320        [fErrorMessageView setString: @""];
321        [fErrorMessageView setSelectable: NO];
322       
323        [fAnnounceAddressField setStringValue: @""];
324        [fAnnounceAddressField setToolTip: nil];
325        [fAnnounceAddressField setSelectable: NO];
326        [fAnnounceLastField setStringValue: @""];
327        [fAnnounceResponseField setStringValue: @""];
328        [fAnnounceResponseField setToolTip: nil];
329        [fAnnounceResponseField setSelectable: NO];
330        [fAnnounceNextField setStringValue: @""];
331       
332        [fScrapeAddressField setStringValue: @""];
333        [fScrapeAddressField setToolTip: nil];
334        [fScrapeAddressField setSelectable: NO];
335        [fScrapeLastField setStringValue: @""];
336        [fScrapeResponseField setStringValue: @""];
337        [fScrapeResponseField setToolTip: nil];
338        [fScrapeResponseField setSelectable: NO];
339        [fScrapeNextField setStringValue: @""];
340       
341        [fConnectedPeersField setStringValue: @""];
342        [fDownloadingFromField setStringValue: @""];
343        [fUploadingToField setStringValue: @""];
344        [fKnownField setStringValue: @""];
345        [fSeedersField setStringValue: @""];
346        [fLeechersField setStringValue: @""];
347        [fCompletedFromTrackerField setStringValue: @""];
348       
349        [fDateAddedField setStringValue: @""];
350        [fDateCompletedField setStringValue: @""];
351       
352        [fPiecesControl setSelected: NO forSegment: PIECES_CONTROL_AVAILABLE];
353        [fPiecesControl setSelected: NO forSegment: PIECES_CONTROL_PROGRESS];
354        [fPiecesControl setEnabled: NO];
355        [fPiecesView setTorrent: nil];
356       
357        [fPeers release];
358        fPeers = nil;
359       
360        [fWebSeeds release];
361        fWebSeeds = nil;
362       
363        [fTrackers release];
364        fTrackers = nil;
365       
366        [fTrackerAddRemoveControl setEnabled: NO forSegment: TRACKER_ADD_TAG];
367        [fTrackerAddRemoveControl setEnabled: NO forSegment: TRACKER_REMOVE_TAG];
368    }
369    else
370    {   
371        Torrent * torrent = [fTorrents objectAtIndex: 0];
372       
373        [fFileController setTorrent: torrent];
374       
375        NSImage * icon = [[torrent icon] copy];
376        [icon setFlipped: NO];
377        [fImageView setImage: icon];
378        [icon release];
379       
380        NSString * name = [torrent name];
381        [fNameField setStringValue: name];
382        [fNameField setToolTip: name];
383       
384        NSString * basicString = [NSString stringForFileSize: [torrent size]];
385        if ([torrent isFolder])
386        {
387            NSString * fileString;
388            int fileCount = [torrent fileCount];
389            if (fileCount == 1)
390                fileString = NSLocalizedString(@"1 file", "Inspector -> selected torrents");
391            else
392                fileString= [NSString stringWithFormat: NSLocalizedString(@"%d files", "Inspector -> selected torrents"), fileCount];
393            basicString = [NSString stringWithFormat: @"%@, %@", fileString, basicString];
394        }
395        [fBasicInfoField setStringValue: basicString];
396        [fBasicInfoField setToolTip: [NSString stringWithFormat: NSLocalizedString(@"%llu bytes", "Inspector -> selected torrents"),
397                                        [torrent size]]];
398       
399        NSString * hashString = [torrent hashString];
400        [fPiecesField setStringValue: [NSString stringWithFormat: @"%d, %@", [torrent pieceCount],
401                                        [NSString stringForFileSize: [torrent pieceSize]]]];
402        [fHashField setStringValue: hashString];
403        [fHashField setToolTip: hashString];
404        [fSecureField setStringValue: [torrent privateTorrent]
405                        ? NSLocalizedString(@"Private Torrent, PEX automatically disabled", "Inspector -> private torrent")
406                        : NSLocalizedString(@"Public Torrent", "Inspector -> private torrent")];
407       
408        NSString * commentString = [torrent comment];
409        [fCommentView setString: commentString];
410       
411        NSString * creatorString = [torrent creator];
412        [fCreatorField setStringValue: creatorString];
413        [fDateCreatedField setObjectValue: [torrent dateCreated]];
414       
415        BOOL publicTorrent = [torrent publicTorrent];
416        [fTorrentLocationField setStringValue: publicTorrent
417                    ? [[torrent publicTorrentLocation] stringByAbbreviatingWithTildeInPath]
418                    : NSLocalizedString(@"Transmission Support Folder", "Torrent -> location when deleting original")];
419        if (publicTorrent)
420            [fTorrentLocationField setToolTip: [[torrent publicTorrentLocation] stringByAppendingFormat: @"\n\n%@",
421                                                [torrent torrentLocation]]];
422        else
423            [fTorrentLocationField setToolTip: [torrent torrentLocation]];
424       
425        [fDateAddedField setObjectValue: [torrent dateAdded]];
426       
427        [fRevealDataButton setHidden: NO];
428        [fRevealTorrentButton setHidden: ![torrent publicTorrent]];
429       
430        //allow these fields to be selected
431        [fTrackerField setSelectable: YES];
432        [fHashField setSelectable: YES];
433        [fCommentView setSelectable: ![commentString isEqualToString: @""]];
434        [fCreatorField setSelectable: ![creatorString isEqualToString: @""]];
435        [fTorrentLocationField setSelectable: YES];
436        [fDataLocationField setSelectable: YES];
437        [fAnnounceAddressField setSelectable: YES];
438        [fScrapeAddressField setSelectable: YES];
439       
440        //set pieces view
441        BOOL piecesAvailableSegment = [[NSUserDefaults standardUserDefaults] boolForKey: @"PiecesViewShowAvailability"];
442        [fPiecesControl setSelected: piecesAvailableSegment forSegment: PIECES_CONTROL_AVAILABLE];
443        [fPiecesControl setSelected: !piecesAvailableSegment forSegment: PIECES_CONTROL_PROGRESS];
444        [fPiecesControl setEnabled: YES];
445        [fPiecesView setTorrent: torrent];
446       
447        //get webseers for table
448        [fWebSeeds release];
449        fWebSeeds = [[torrent webSeeders] retain];
450       
451        //get trackers for table
452        [fTrackers release];
453        fTrackers = [[torrent allTrackers: YES] retain];
454        [fTrackerTable deselectAll: self];
455       
456        [fTrackerAddRemoveControl setEnabled: YES forSegment: TRACKER_ADD_TAG];
457        [fTrackerAddRemoveControl setEnabled: NO forSegment: TRACKER_REMOVE_TAG];
458    }
459   
460    //update stats and settings
461    [self updateInfoStats];
462    [self updateOptions];
463   
464    [fTrackerTable setTrackers: fTrackers];
465    [fTrackerTable reloadData];
466   
467    [fWebSeedTable reloadData];
468    #warning check if should just be reloaded when not 1 torrent
469    [fPeerTable reloadData];
470}
471
472- (void) updateInfoStats
473{
474    switch ([fTabMatrix selectedTag])
475    {
476        case TAB_INFO_TAG:
477            [self updateInfoGeneral];
478            break;
479        case TAB_ACTIVITY_TAG:
480            [self updateInfoActivity];
481            break;
482        case TAB_TRACKER_TAG:
483            [self updateInfoTracker];
484            break;
485        case TAB_PEERS_TAG:
486            [self updateInfoPeers];
487            break;
488        case TAB_FILES_TAG:
489            [self updateInfoFiles];
490            break;
491    }
492}
493
494- (void) updateOptions
495{
496    if ([fTorrents count] == 0)
497        return;
498   
499    //get bandwidth info
500    NSEnumerator * enumerator = [fTorrents objectEnumerator];
501    Torrent * torrent = [enumerator nextObject]; //first torrent
502   
503    int uploadSpeedMode = [torrent speedMode: YES],
504        uploadSpeedLimit = [torrent speedLimit: YES],
505        downloadSpeedMode = [torrent speedMode: NO],
506        downloadSpeedLimit = [torrent speedLimit: NO];
507   
508    while ((torrent = [enumerator nextObject])
509            && (uploadSpeedMode != INVALID || uploadSpeedLimit != INVALID
510                || downloadSpeedMode != INVALID || downloadSpeedLimit != INVALID))
511    {
512        if (uploadSpeedMode != INVALID && uploadSpeedMode != [torrent speedMode: YES])
513            uploadSpeedMode = INVALID;
514       
515        if (uploadSpeedLimit != INVALID && uploadSpeedLimit != [torrent speedLimit: YES])
516            uploadSpeedLimit = INVALID;
517       
518        if (downloadSpeedMode != INVALID && downloadSpeedMode != [torrent speedMode: NO])
519            downloadSpeedMode = INVALID;
520       
521        if (downloadSpeedLimit != INVALID && downloadSpeedLimit != [torrent speedLimit: NO])
522            downloadSpeedLimit = INVALID;
523    }
524   
525    //set upload view
526    int index;
527    if (uploadSpeedMode == TR_SPEEDLIMIT_SINGLE)
528        index = OPTION_POPUP_LIMIT;
529    else if (uploadSpeedMode == TR_SPEEDLIMIT_UNLIMITED)
530        index = OPTION_POPUP_NO_LIMIT;
531    else if (uploadSpeedMode == TR_SPEEDLIMIT_GLOBAL)
532        index = OPTION_POPUP_GLOBAL;
533    else
534        index = -1;
535    [fUploadLimitPopUp selectItemAtIndex: index];
536    [fUploadLimitPopUp setEnabled: YES];
537   
538    [fUploadLimitLabel setHidden: uploadSpeedMode != TR_SPEEDLIMIT_SINGLE];
539    [fUploadLimitField setHidden: uploadSpeedMode != TR_SPEEDLIMIT_SINGLE];
540    if (uploadSpeedLimit != INVALID)
541        [fUploadLimitField setIntValue: uploadSpeedLimit];
542    else
543        [fUploadLimitField setStringValue: @""];
544   
545    //set download view
546    if (downloadSpeedMode == TR_SPEEDLIMIT_SINGLE)
547        index = OPTION_POPUP_LIMIT;
548    else if (downloadSpeedMode == TR_SPEEDLIMIT_UNLIMITED)
549        index = OPTION_POPUP_NO_LIMIT;
550    else if (downloadSpeedMode == TR_SPEEDLIMIT_GLOBAL)
551        index = OPTION_POPUP_GLOBAL;
552    else
553        index = -1;
554    [fDownloadLimitPopUp selectItemAtIndex: index];
555    [fDownloadLimitPopUp setEnabled: YES];
556   
557    [fDownloadLimitLabel setHidden: downloadSpeedMode != TR_SPEEDLIMIT_SINGLE];
558    [fDownloadLimitField setHidden: downloadSpeedMode != TR_SPEEDLIMIT_SINGLE];
559    if (downloadSpeedLimit != INVALID)
560        [fDownloadLimitField setIntValue: downloadSpeedLimit];
561    else
562        [fDownloadLimitField setStringValue: @""];
563   
564    //get ratio info
565    enumerator = [fTorrents objectEnumerator];
566    torrent = [enumerator nextObject]; //first torrent
567   
568    int checkRatio = [torrent ratioSetting];
569    float ratioLimit = [torrent ratioLimit];
570   
571    while ((torrent = [enumerator nextObject]) && (checkRatio != INVALID || checkRatio != INVALID))
572    {
573        if (checkRatio != INVALID && checkRatio != [torrent ratioSetting])
574            checkRatio = INVALID;
575       
576        if (ratioLimit != INVALID && ratioLimit != [torrent ratioLimit])
577            ratioLimit = INVALID;
578    }
579   
580    //set ratio view
581    if (checkRatio == NSOnState)
582        index = OPTION_POPUP_LIMIT;
583    else if (checkRatio == NSOffState)
584        index = OPTION_POPUP_NO_LIMIT;
585    else if (checkRatio == NSMixedState)
586        index = OPTION_POPUP_GLOBAL;
587    else
588        index = -1;
589    [fRatioPopUp selectItemAtIndex: index];
590    [fRatioPopUp setEnabled: YES];
591   
592    [fRatioLimitField setHidden: checkRatio != NSOnState];
593    if (ratioLimit != INVALID)
594        [fRatioLimitField setFloatValue: ratioLimit];
595    else
596        [fRatioLimitField setStringValue: @""];
597   
598    //get peer info
599    enumerator = [fTorrents objectEnumerator];
600    torrent = [enumerator nextObject]; //first torrent
601   
602    int maxPeers = [torrent maxPeerConnect];
603   
604    while ((torrent = [enumerator nextObject]))
605    {
606        if (maxPeers != [torrent maxPeerConnect])
607        {
608            maxPeers = INVALID;
609            break;
610        }
611    }
612   
613    //set peer view
614    [fPeersConnectField setEnabled: YES];
615    if (maxPeers != INVALID)
616        [fPeersConnectField setIntValue: maxPeers];
617    else
618        [fPeersConnectField setStringValue: @""];
619}
620
621- (NSRect) windowWillUseStandardFrame: (NSWindow *) window defaultFrame: (NSRect) defaultFrame
622{
623    NSRect windowRect = [window frame];
624    windowRect.size.width = [window minSize].width;
625    return windowRect;
626}
627
628- (void) setTab: (id) sender
629{
630    int oldTabTag = fCurrentTabTag;
631    fCurrentTabTag = [fTabMatrix selectedTag];
632    if (fCurrentTabTag == oldTabTag)
633        return;
634   
635    [self updateInfoStats];
636   
637    //take care of old view
638    float oldHeight = 0;
639    NSString * oldResizeSaveKey = nil;
640    if (oldTabTag != INVALID)
641    {
642        //deselect old tab item
643        [(InfoTabButtonCell *)[fTabMatrix cellWithTag: oldTabTag] setSelectedTab: NO];
644       
645        switch (oldTabTag)
646        {
647            case TAB_TRACKER_TAG:
648                oldResizeSaveKey = @"InspectorContentHeightTracker";
649                break;
650            case TAB_PEERS_TAG:
651                [fPeers release];
652                fPeers = nil;
653               
654                oldResizeSaveKey = @"InspectorContentHeightPeers";
655                break;
656            case TAB_FILES_TAG:
657                [[QuickLookController quickLook] updateQuickLook];
658               
659                oldResizeSaveKey = @"InspectorContentHeightFiles";
660                break;
661        }
662       
663        NSView * oldView = [self tabViewForTag: oldTabTag];
664        oldHeight = [oldView frame].size.height;
665        if (oldResizeSaveKey)
666            [[NSUserDefaults standardUserDefaults] setFloat: oldHeight forKey: oldResizeSaveKey];
667       
668        //remove old view
669        [oldView setHidden: YES];
670        [oldView removeFromSuperview];
671    }
672   
673    //set new tab item
674    NSView * view = [self tabViewForTag: fCurrentTabTag];
675   
676    NSString * resizeSaveKey = nil;
677    NSString * identifier, * title;
678    switch (fCurrentTabTag)
679    {
680        case TAB_INFO_TAG:
681            identifier = TAB_INFO_IDENT;
682            title = NSLocalizedString(@"General Info", "Inspector -> title");
683            break;
684        case TAB_ACTIVITY_TAG:
685            identifier = TAB_ACTIVITY_IDENT;
686            title = NSLocalizedString(@"Activity", "Inspector -> title");
687           
688            [fPiecesView updateView: YES];
689            break;
690        case TAB_TRACKER_TAG:
691            identifier = TAB_TRACKER_IDENT;
692            title = NSLocalizedString(@"Tracker", "Inspector -> title");
693            resizeSaveKey = @"InspectorContentHeightTracker";
694            break;
695        case TAB_PEERS_TAG:
696            identifier = TAB_PEERS_IDENT;
697            title = NSLocalizedString(@"Peers", "Inspector -> title");
698            resizeSaveKey = @"InspectorContentHeightPeers";
699            break;
700        case TAB_FILES_TAG:
701            identifier = TAB_FILES_IDENT;
702            title = NSLocalizedString(@"Files", "Inspector -> title");
703            resizeSaveKey = @"InspectorContentHeightFiles";
704            break;
705        case TAB_OPTIONS_TAG:
706            identifier = TAB_OPTIONS_IDENT;
707            title = NSLocalizedString(@"Options", "Inspector -> title");
708            break;
709        default:
710            return;
711    }
712   
713    [[NSUserDefaults standardUserDefaults] setObject: identifier forKey: @"InspectorSelected"];
714   
715    NSWindow * window = [self window];
716   
717    [window setTitle: [NSString stringWithFormat: @"%@ - %@", title, NSLocalizedString(@"Torrent Inspector", "Inspector -> title")]];
718   
719    //selected tab item
720    [(InfoTabButtonCell *)[fTabMatrix selectedCell] setSelectedTab: YES];
721   
722    NSRect windowRect = [window frame], viewRect = [view frame];
723   
724    if (resizeSaveKey)
725    {
726        float height = [[NSUserDefaults standardUserDefaults] floatForKey: resizeSaveKey];
727        if (height != 0)
728            viewRect.size.height = MAX(height, TAB_MIN_HEIGHT);
729    }
730   
731    float difference = (viewRect.size.height - oldHeight) * [window userSpaceScaleFactor];
732    windowRect.origin.y -= difference;
733    windowRect.size.height += difference;
734   
735    if (resizeSaveKey)
736    {
737        if (!oldResizeSaveKey)
738        {
739            [window setMinSize: NSMakeSize([window minSize].width, windowRect.size.height - viewRect.size.height + TAB_MIN_HEIGHT)];
740            [window setMaxSize: NSMakeSize(FLT_MAX, FLT_MAX)];
741        }
742    }
743    else
744    {
745        [window setMinSize: NSMakeSize([window minSize].width, windowRect.size.height)];
746        [window setMaxSize: NSMakeSize(FLT_MAX, windowRect.size.height)];
747    }
748   
749    viewRect.size.width = windowRect.size.width;
750    [view setFrame: viewRect];
751   
752    [window setFrame: windowRect display: YES animate: oldTabTag != INVALID];
753    [[window contentView] addSubview: view];
754    [view setHidden: NO];
755   
756    if (fCurrentTabTag == TAB_FILES_TAG)
757        [[QuickLookController quickLook] updateQuickLook];
758}
759
760- (void) setNextTab
761{
762    int tag = [fTabMatrix selectedTag]+1;
763    if (tag >= [fTabMatrix numberOfColumns])
764        tag = 0;
765   
766    [fTabMatrix selectCellWithTag: tag];
767    [self setTab: nil];
768}
769
770- (void) setPreviousTab
771{
772    int tag = [fTabMatrix selectedTag]-1;
773    if (tag < 0)
774        tag = [fTabMatrix numberOfColumns]-1;
775   
776    [fTabMatrix selectCellWithTag: tag];
777    [self setTab: nil];
778}
779
780- (int) numberOfRowsInTableView: (NSTableView *) tableView
781{
782    if (tableView == fPeerTable)
783        return fPeers ? [fPeers count] : 0;
784    else if (tableView == fWebSeedTable)
785        return fWebSeeds ? [fWebSeeds count] : 0;
786    else if (tableView == fTrackerTable)
787        return fTrackers ? [fTrackers count] : 0;
788    return 0;
789}
790
791- (id) tableView: (NSTableView *) tableView objectValueForTableColumn: (NSTableColumn *) column row: (int) row
792{
793    if (tableView == fPeerTable)
794    {
795        NSString * ident = [column identifier];
796        NSDictionary * peer = [fPeers objectAtIndex: row];
797       
798        if ([ident isEqualToString: @"Encryption"])
799            return [[peer objectForKey: @"Encryption"] boolValue] ? [NSImage imageNamed: @"Lock.png"] : nil;
800        else if ([ident isEqualToString: @"Client"])
801            return [peer objectForKey: @"Client"];
802        else if  ([ident isEqualToString: @"Progress"])
803            return [peer objectForKey: @"Progress"];
804        else if ([ident isEqualToString: @"UL To"])
805        {
806            NSNumber * rate;
807            return (rate = [peer objectForKey: @"UL To Rate"]) ? [NSString stringForSpeedAbbrev: [rate floatValue]] : @"";
808        }
809        else if ([ident isEqualToString: @"DL From"])
810        {
811            NSNumber * rate;
812            return (rate = [peer objectForKey: @"DL From Rate"]) ? [NSString stringForSpeedAbbrev: [rate floatValue]] : @"";
813        }
814        else
815            return [peer objectForKey: @"IP"];
816    }
817    else if (tableView == fWebSeedTable)
818    {
819        return [fWebSeeds objectAtIndex: row];
820    }
821    else if (tableView == fTrackerTable)
822    {
823        id item = [fTrackers objectAtIndex: row];
824        if ([item isKindOfClass: [NSNumber class]])
825        {
826            int tier = [item intValue];
827            if (tier == 0)
828                return NSLocalizedString(@"User-Added", "Inspector -> tracker table");
829            else
830                return [NSString stringWithFormat: NSLocalizedString(@"Tier %d", "Inspector -> tracker table"), tier];
831        }
832        else
833            return item;
834    }
835    return nil;
836}
837
838- (void) tableView: (NSTableView *) tableView didClickTableColumn: (NSTableColumn *) tableColumn
839{
840    if (tableView == fPeerTable)
841    {
842        if (fPeers)
843        {
844            NSArray * oldPeers = fPeers;
845            fPeers = [[fPeers sortedArrayUsingDescriptors: [self peerSortDescriptors]] retain];
846            [oldPeers release];
847            [tableView reloadData];
848        }
849    }
850}
851
852- (BOOL) tableView: (NSTableView *) tableView shouldSelectRow: (int) row
853{
854    return tableView == fTrackerTable;
855}
856
857- (void) tableViewSelectionDidChange: (NSNotification *) notification
858{
859    if ([notification object] == fTrackerTable)
860    {
861        int numSelected = [fTrackerTable numberOfSelectedRows];
862        [fTrackerAddRemoveControl setEnabled: numSelected > 0 forSegment: TRACKER_REMOVE_TAG];
863    }
864}
865
866- (BOOL) tableView: (NSTableView *) tableView isGroupRow: (NSInteger) row
867{
868    if (tableView == fTrackerTable)
869        return [[fTrackers objectAtIndex: row] isKindOfClass: [NSNumber class]];
870    return NO;
871}
872
873- (NSString *) tableView: (NSTableView *) tableView toolTipForCell: (NSCell *) cell rect: (NSRectPointer) rect
874                tableColumn: (NSTableColumn *) column row: (int) row mouseLocation: (NSPoint) mouseLocation
875{
876    if (tableView == fPeerTable)
877    {
878        NSDictionary * peer = [fPeers objectAtIndex: row];
879        NSMutableArray * components = [NSMutableArray arrayWithCapacity: 5];
880       
881        [components addObject: [NSString localizedStringWithFormat: NSLocalizedString(@"Progress: %.1f%%",
882            "Inspector -> Peers tab -> table row tooltip"), [[peer objectForKey: @"Progress"] floatValue] * 100.0]];
883       
884        if ([[peer objectForKey: @"Encryption"] boolValue])
885            [components addObject: NSLocalizedString(@"Encrypted Connection", "Inspector -> Peers tab -> table row tooltip")];
886       
887        NSString * portString;
888        int port;
889        if ((port = [[peer objectForKey: @"Port"] intValue]) > 0)
890            portString = [NSString stringWithFormat: @"%d", port];
891        else
892            portString = NSLocalizedString(@"N/A", "Inspector -> Peers tab -> table row tooltip");
893        [components addObject: [NSString stringWithFormat: @"%@: %@", NSLocalizedString(@"Port",
894            "Inspector -> Peers tab -> table row tooltip"), portString]];
895       
896        switch ([[peer objectForKey: @"From"] intValue])
897        {
898            case TR_PEER_FROM_TRACKER:
899                [components addObject: NSLocalizedString(@"From: tracker", "Inspector -> Peers tab -> table row tooltip")];
900                break;
901            case TR_PEER_FROM_INCOMING:
902                [components addObject: NSLocalizedString(@"From: incoming connection", "Inspector -> Peers tab -> table row tooltip")];
903                break;
904            case TR_PEER_FROM_CACHE:
905                [components addObject: NSLocalizedString(@"From: cache", "Inspector -> Peers tab -> table row tooltip")];
906                break;
907            case TR_PEER_FROM_PEX:
908                [components addObject: NSLocalizedString(@"From: peer exchange", "Inspector -> Peers tab -> table row tooltip")];
909                break;
910        }
911       
912        //determing status strings from flags
913        NSMutableArray * statusArray = [NSMutableArray arrayWithCapacity: 3];
914        NSString * flags = [peer objectForKey: @"Flags"];
915       
916        if ([flags rangeOfString: @"D"].location != NSNotFound)
917            [statusArray addObject: NSLocalizedString(@"Currently downloading (interested and not choked)",
918                "Inspector -> peer -> status")];
919        else if ([flags rangeOfString: @"d"].location != NSNotFound)
920            [statusArray addObject: NSLocalizedString(@"You want to download, but peer does not want to send (interested and choked)",
921                "Inspector -> peer -> status")];
922        else;
923       
924        if ([flags rangeOfString: @"U"].location != NSNotFound)
925            [statusArray addObject: NSLocalizedString(@"Currently uploading (interested and not choked)",
926                "Inspector -> peer -> status")];
927        else if ([flags rangeOfString: @"u"].location != NSNotFound)
928            [statusArray addObject: NSLocalizedString(@"Peer wants you to upload, but you do not want to (interested and choked)",
929                "Inspector -> peer -> status")];
930        else;
931       
932        if ([flags rangeOfString: @"K"].location != NSNotFound)
933            [statusArray addObject: NSLocalizedString(@"Peer is unchoking you, but you are not interested",
934                "Inspector -> peer -> status")];
935       
936        if ([flags rangeOfString: @"?"].location != NSNotFound)
937            [statusArray addObject: NSLocalizedString(@"You unchoked the peer, but the peer is not interested",
938                "Inspector -> peer -> status")];
939       
940        if ([statusArray count] > 0)
941            [components addObject: [@"\n" stringByAppendingString: [statusArray componentsJoinedByString: @"\n\n"]]];
942       
943        return [components componentsJoinedByString: @"\n"];
944    }
945    return nil;
946}
947
948- (void) tableView: (NSTableView *) tableView setObjectValue: (id) object forTableColumn: (NSTableColumn *) tableColumn
949    row: (NSInteger) row
950{
951    if (tableView != fTrackerTable)
952        return;
953   
954    [fTrackers replaceObjectAtIndex: row withObject: object];
955   
956    Torrent * torrent= [fTorrents objectAtIndex: 0];
957    if (![torrent updateAllTrackersForAdd: fTrackers])
958        NSBeep();
959   
960    //reset table with either new or old value
961    [fTrackers release];
962    fTrackers = [[torrent allTrackers: YES] retain];
963    [fTrackerTable deselectAll: self];
964   
965    [fTrackerTable setTrackers: fTrackers];
966    [fTrackerTable reloadData];
967}
968
969- (void) addRemoveTracker: (id) sender
970{
971    //don't allow add/remove when currently adding - it leads to weird results
972    if ([fTrackerTable editedRow] != -1)
973        return;
974   
975    if ([[sender cell] tagForSegment: [sender selectedSegment]] == TRACKER_REMOVE_TAG)
976        [self removeTrackers];
977    else
978        [self addTrackers];
979}
980
981- (BOOL) tableView: (NSTableView *) tableView shouldEditTableColumn: (NSTableColumn *) tableColumn row: (NSInteger) row
982{
983    if (tableView != fTrackerTable)
984        return NO;
985   
986    //only allow modification of custom-added trackers
987    if ([[fTrackers objectAtIndex: row] isKindOfClass: [NSNumber class]] || ![[fTorrents objectAtIndex: 0] hasAddedTrackers])
988        return NO;
989   
990    NSUInteger i;
991    for (i = row-1; ![[fTrackers objectAtIndex: i] isKindOfClass: [NSNumber class]]; i--);
992   
993    return [[fTrackers objectAtIndex: i] intValue] == 0;
994}
995
996- (BOOL) shouldQuickLookFileView
997{
998    return [[self window] isVisible] && fCurrentTabTag == TAB_FILES_TAG && [[fFileController outlineView] numberOfSelectedRows] > 0;
999}
1000
1001- (NSArray *) quickLookURLs
1002{
1003    FileOutlineView * fileOutlineView = [fFileController outlineView];
1004    Torrent * torrent = [fTorrents objectAtIndex: 0];
1005    NSString * folder = [torrent downloadFolder];
1006    NSIndexSet * indexes = [fileOutlineView selectedRowIndexes];
1007    NSMutableArray * urlArray = [NSMutableArray arrayWithCapacity: [indexes count]];
1008
1009    int i;
1010    for (i = [indexes firstIndex]; i != NSNotFound; i = [indexes indexGreaterThanIndex: i])
1011    {
1012        FileListNode * item = [fileOutlineView itemAtRow: i];
1013        if ([item isFolder] || [torrent fileProgress: [[item indexes] firstIndex]] == 1.0)
1014            [urlArray addObject: [NSURL fileURLWithPath: [folder stringByAppendingPathComponent: [item fullPath]]]];
1015    }
1016   
1017    return urlArray;
1018}
1019
1020- (BOOL) canQuickLook
1021{
1022    FileOutlineView * fileOutlineView = [fFileController outlineView];
1023    Torrent * torrent = [fTorrents objectAtIndex: 0];
1024    NSIndexSet * indexes = [fileOutlineView selectedRowIndexes];
1025
1026    int i;
1027    for (i = [indexes firstIndex]; i != NSNotFound; i = [indexes indexGreaterThanIndex: i])
1028    {
1029        FileListNode * item = [fileOutlineView itemAtRow: i];
1030        if ([item isFolder] || [torrent fileProgress: [[item indexes] firstIndex]] == 1.0)
1031            return YES;
1032    }
1033   
1034    return NO;
1035}
1036
1037- (NSRect) quickLookFrameWithURL: (NSURL *) url
1038{
1039    FileOutlineView * fileOutlineView = [fFileController outlineView];
1040   
1041    NSString * fullPath = [url path];
1042    NSString * folder = [[fTorrents objectAtIndex: 0] downloadFolder];
1043    NSRange visibleRows = [fileOutlineView rowsInRect: [fileOutlineView bounds]];
1044   
1045    int row;
1046    for (row = visibleRows.location; row < NSMaxRange(visibleRows); row++)
1047    {
1048        FileListNode * rowItem = [fileOutlineView itemAtRow: row];
1049        if ([[folder stringByAppendingPathComponent: [rowItem fullPath]] isEqualToString: fullPath])
1050        {
1051            NSRect frame = [fileOutlineView iconRectForRow: row];
1052            frame.origin = [fileOutlineView convertPoint: frame.origin toView: nil];
1053            frame.origin = [[self window] convertBaseToScreen: frame.origin];
1054            frame.origin.y -= frame.size.height;
1055            return frame;
1056        }
1057    }
1058   
1059    return NSZeroRect;
1060}
1061
1062- (void) setPiecesView: (id) sender
1063{
1064    [self setPiecesViewForAvailable: [sender selectedSegment] == PIECES_CONTROL_AVAILABLE];
1065}
1066
1067- (void) setPiecesViewForAvailable: (BOOL) available
1068{
1069    [fPiecesControl setSelected: available forSegment: PIECES_CONTROL_AVAILABLE];
1070    [fPiecesControl setSelected: !available forSegment: PIECES_CONTROL_PROGRESS];
1071   
1072    [[NSUserDefaults standardUserDefaults] setBool: available forKey: @"PiecesViewShowAvailability"];
1073    [fPiecesView updateView: YES];
1074}
1075
1076- (void) revealTorrentFile: (id) sender
1077{
1078    if ([fTorrents count] > 0)
1079        [[fTorrents objectAtIndex: 0] revealPublicTorrent];
1080}
1081
1082- (void) revealDataFile: (id) sender
1083{
1084    if ([fTorrents count] > 0)
1085        [[fTorrents objectAtIndex: 0] revealData];
1086}
1087
1088- (void) setSpeedMode: (id) sender
1089{
1090    BOOL upload = sender == fUploadLimitPopUp;
1091    int mode;
1092    switch ([sender indexOfSelectedItem])
1093    {
1094        case OPTION_POPUP_LIMIT:
1095            mode = TR_SPEEDLIMIT_SINGLE;
1096            break;
1097        case OPTION_POPUP_NO_LIMIT:
1098            mode = TR_SPEEDLIMIT_UNLIMITED;
1099            break;
1100        case OPTION_POPUP_GLOBAL:
1101            mode = TR_SPEEDLIMIT_GLOBAL;
1102            break;
1103        default:
1104            return;
1105    }
1106   
1107    Torrent * torrent;
1108    NSEnumerator * enumerator = [fTorrents objectEnumerator];
1109    while ((torrent = [enumerator nextObject]))
1110        [torrent setSpeedMode: mode upload: upload];
1111   
1112    NSTextField * field = upload ? fUploadLimitField : fDownloadLimitField;
1113   
1114    BOOL single = mode == TR_SPEEDLIMIT_SINGLE;
1115    [field setHidden: !single];
1116    if (single)
1117    {
1118        [field selectText: self];
1119        [[self window] makeKeyAndOrderFront:self];
1120    }
1121   
1122    NSTextField * label = upload ? fUploadLimitLabel : fDownloadLimitLabel;
1123    [label setHidden: !single];
1124}
1125
1126- (void) setSpeedLimit: (id) sender
1127{
1128    BOOL upload = sender == fUploadLimitField;
1129    int limit = [sender intValue];
1130   
1131    Torrent * torrent;
1132    NSEnumerator * enumerator = [fTorrents objectEnumerator];
1133   
1134    while ((torrent = [enumerator nextObject]))
1135        [torrent setSpeedLimit: limit upload: upload];
1136}
1137
1138- (void) setRatioSetting: (id) sender
1139{
1140    int setting;
1141    switch ([sender indexOfSelectedItem])
1142    {
1143        case OPTION_POPUP_LIMIT:
1144            setting = NSOnState;
1145            break;
1146        case OPTION_POPUP_NO_LIMIT:
1147            setting = NSOffState;
1148            break;
1149        case OPTION_POPUP_GLOBAL:
1150            setting = NSMixedState;
1151            break;
1152        default:
1153            return;
1154    }
1155   
1156    Torrent * torrent;
1157    NSEnumerator * enumerator = [fTorrents objectEnumerator];
1158    while ((torrent = [enumerator nextObject]))
1159        [torrent setRatioSetting: setting];
1160   
1161    BOOL single = setting == NSOnState;
1162    [fRatioLimitField setHidden: !single];
1163    if (single)
1164    {
1165        [fRatioLimitField selectText: self];
1166        [[self window] makeKeyAndOrderFront:self];
1167    }
1168   
1169    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateUI" object: nil];
1170}
1171
1172- (void) setRatioLimit: (id) sender
1173{
1174    float limit = [sender floatValue];
1175   
1176    Torrent * torrent;
1177    NSEnumerator * enumerator = [fTorrents objectEnumerator];
1178    while ((torrent = [enumerator nextObject]))
1179        [torrent setRatioLimit: limit];
1180}
1181
1182- (void) setPeersConnectLimit: (id) sender
1183{
1184    int limit = [sender intValue];
1185   
1186    Torrent * torrent;
1187    NSEnumerator * enumerator = [fTorrents objectEnumerator];
1188    while ((torrent = [enumerator nextObject]))
1189        [torrent setMaxPeerConnect: limit];
1190}
1191
1192
1193- (BOOL) control: (NSControl *) control textShouldBeginEditing: (NSText *) fieldEditor
1194{
1195    [fInitialString release];
1196    fInitialString = [[control stringValue] retain];
1197   
1198    return YES;
1199}
1200
1201- (BOOL) control: (NSControl *) control didFailToFormatString: (NSString *) string errorDescription: (NSString *) error
1202{
1203    NSBeep();
1204    if (fInitialString)
1205    {
1206        [control setStringValue: fInitialString];
1207        [fInitialString release];
1208        fInitialString = nil;
1209    }
1210    return NO;
1211}
1212
1213@end
1214
1215@implementation InfoWindowController (Private)
1216
1217- (void) updateInfoGeneral
1218{   
1219    if ([fTorrents count] != 1)
1220        return;
1221   
1222    Torrent * torrent = [fTorrents objectAtIndex: 0];
1223   
1224    [fTrackerField setStringValue: [torrent trackerAddressAnnounce]];
1225   
1226    NSString * location = [torrent dataLocation];
1227    [fDataLocationField setStringValue: [location stringByAbbreviatingWithTildeInPath]];
1228    [fDataLocationField setToolTip: location];
1229}
1230
1231- (void) updateInfoActivity
1232{
1233    int numberSelected = [fTorrents count];
1234    if (numberSelected == 0)
1235        return;
1236   
1237    uint64_t have = 0, haveVerified = 0, downloadedTotal = 0, uploadedTotal = 0, failedHash = 0;
1238    NSDate * lastActivity = nil;
1239    Torrent * torrent;
1240    NSEnumerator * enumerator = [fTorrents objectEnumerator];
1241    while ((torrent = [enumerator nextObject]))
1242    {
1243        have += [torrent haveTotal];
1244        haveVerified += [torrent haveVerified];
1245        downloadedTotal += [torrent downloadedTotal];
1246        uploadedTotal += [torrent uploadedTotal];
1247        failedHash += [torrent failedHash];
1248       
1249        NSDate * nextLastActivity;
1250        if ((nextLastActivity = [torrent dateActivity]))
1251            lastActivity = lastActivity ? [lastActivity laterDate: nextLastActivity] : nextLastActivity;
1252    }
1253   
1254    if (have == 0)
1255        [fHaveField setStringValue: [NSString stringForFileSize: 0]];
1256    else
1257    {
1258        NSString * verifiedString = [NSString stringWithFormat: NSLocalizedString(@"%@ verified", "Inspector -> Activity tab -> have"),
1259                                        [NSString stringForFileSize: haveVerified]];
1260        if (have == haveVerified)
1261            [fHaveField setStringValue: verifiedString];
1262        else
1263            [fHaveField setStringValue: [NSString stringWithFormat: @"%@ (%@)", [NSString stringForFileSize: have], verifiedString]];
1264    }
1265   
1266    [fDownloadedTotalField setStringValue: [NSString stringForFileSize: downloadedTotal]];
1267    [fUploadedTotalField setStringValue: [NSString stringForFileSize: uploadedTotal]];
1268    [fFailedHashField setStringValue: [NSString stringForFileSize: failedHash]];
1269   
1270    [fDateActivityField setObjectValue: lastActivity];
1271   
1272    if (numberSelected == 1)
1273    {
1274        torrent = [fTorrents objectAtIndex: 0];
1275       
1276        [fStateField setStringValue: [torrent stateString]];
1277       
1278        if ([torrent isFolder])
1279            [fProgressField setStringValue: [NSString localizedStringWithFormat: NSLocalizedString(@"%.2f%% (%.2f%% selected)",
1280                "Inspector -> Activity tab -> progress"), 100.0 * [torrent progress], 100.0 * [torrent progressDone]]];
1281        else
1282            [fProgressField setStringValue: [NSString localizedStringWithFormat: @"%.2f%%", 100.0 * [torrent progress]]];
1283           
1284        [fRatioField setStringValue: [NSString stringForRatio: [torrent ratio]]];
1285        [fSwarmSpeedField setStringValue: [torrent isActive] ? [NSString stringForSpeed: [torrent swarmSpeed]] : @""];
1286       
1287        NSString * errorMessage = [torrent errorMessage];
1288        if (![errorMessage isEqualToString: [fErrorMessageView string]])
1289        {
1290            [fErrorMessageView setString: errorMessage];
1291            [fErrorMessageView setSelectable: ![errorMessage isEqualToString: @""]];
1292        }
1293       
1294        [fDateCompletedField setObjectValue: [torrent dateCompleted]];
1295       
1296        [fPiecesView updateView: NO];
1297    }
1298}
1299
1300#warning reload table when necessary?
1301- (void) updateInfoTracker
1302{
1303    if ([fTorrents count] != 1)
1304        return;
1305    Torrent * torrent = [fTorrents objectAtIndex: 0];
1306   
1307    //announce fields
1308    NSString * announceAddress = [torrent trackerAddressAnnounce];
1309    [fAnnounceAddressField setStringValue: announceAddress];
1310    [fAnnounceAddressField setToolTip: announceAddress];
1311   
1312    [fAnnounceLastField setObjectValue: [torrent lastAnnounceTime]];
1313   
1314    NSString * announceResponse = [torrent announceResponse];
1315    [fAnnounceResponseField setStringValue: announceResponse];
1316    [fAnnounceResponseField setToolTip: announceResponse];
1317    [fAnnounceResponseField setSelectable: ![announceResponse isEqualToString: @""]];
1318   
1319    int announceNext = [torrent nextAnnounceTime];
1320    [fAnnounceNextField setStringValue: announceNext > 0 ? [NSString timeString: announceNext showSeconds: YES] : @""];
1321   
1322    //scrape fields
1323    NSString * scrapeAddress;
1324    if ((scrapeAddress = [torrent trackerAddressScrape]))
1325    {
1326        [fScrapeAddressField setStringValue: scrapeAddress];
1327        [fScrapeAddressField setToolTip: scrapeAddress];
1328    }
1329    else
1330    {
1331        [fScrapeAddressField setStringValue: @""];
1332        [fScrapeAddressField setToolTip: @""];
1333    }
1334   
1335    [fScrapeLastField setObjectValue: [torrent lastScrapeTime]];
1336   
1337    NSString * scrapeResponse = [torrent scrapeResponse];
1338    [fScrapeResponseField setStringValue: scrapeResponse];
1339    [fScrapeResponseField setToolTip: scrapeResponse];
1340    [fScrapeResponseField setSelectable: ![scrapeResponse isEqualToString: @""]];
1341   
1342    int scrapeNext = [torrent nextScrapeTime];
1343    [fScrapeNextField setStringValue: scrapeNext > 0 ? [NSString timeString: scrapeNext showSeconds: YES] : @""];
1344}
1345
1346- (void) updateInfoPeers
1347{
1348    if ([fTorrents count] != 1)
1349        return;
1350    Torrent * torrent = [fTorrents objectAtIndex: 0];
1351   
1352    int seeders = [torrent seeders], leechers = [torrent leechers], completed = [torrent completedFromTracker];
1353    [fSeedersField setStringValue: seeders >= 0 ? [NSString stringWithFormat: @"%d", seeders] : @""];
1354    [fLeechersField setStringValue: leechers >= 0 ? [NSString stringWithFormat: @"%d", leechers] : @""];
1355    [fCompletedFromTrackerField setStringValue: completed >= 0 ? [NSString stringWithFormat: @"%d", completed] : @""];
1356   
1357    BOOL active = [torrent isActive];
1358   
1359    if (active)
1360    {
1361        int total = [torrent totalPeersConnected];
1362        NSString * connected = [NSString stringWithFormat:
1363                                NSLocalizedString(@"%d Connected", "Inspector -> Peers tab -> peers"), total];
1364       
1365        if (total > 0)
1366        {
1367            NSMutableArray * components = [NSMutableArray arrayWithCapacity: 4];
1368            int count;
1369            if ((count = [torrent totalPeersTracker]) > 0)
1370                [components addObject: [NSString stringWithFormat:
1371                                        NSLocalizedString(@"%d tracker", "Inspector -> Peers tab -> peers"), count]];
1372            if ((count = [torrent totalPeersIncoming]) > 0)
1373                [components addObject: [NSString stringWithFormat:
1374                                        NSLocalizedString(@"%d incoming", "Inspector -> Peers tab -> peers"), count]];
1375            if ((count = [torrent totalPeersPex]) > 0)
1376                [components addObject: [NSString stringWithFormat:
1377                                        NSLocalizedString(@"%d PEX", "Inspector -> Peers tab -> peers"), count]];
1378            if ((count = [torrent totalPeersCache]) > 0)
1379                [components addObject: [NSString stringWithFormat:
1380                                        NSLocalizedString(@"%d cache", "Inspector -> Peers tab -> peers"), count]];
1381           
1382            connected = [connected stringByAppendingFormat: @": %@", [components componentsJoinedByString: @", "]];
1383        }
1384       
1385        [fConnectedPeersField setStringValue: connected];
1386       
1387        [fDownloadingFromField setIntValue: [torrent peersSendingToUs]];
1388        [fUploadingToField setIntValue: [torrent peersGettingFromUs]];
1389    }
1390    else
1391    {
1392        [fConnectedPeersField setStringValue: @""];
1393        [fDownloadingFromField setStringValue: @""];
1394        [fUploadingToField setStringValue: @""];
1395    }
1396   
1397    [fKnownField setIntValue: [torrent totalPeersKnown]];
1398   
1399    [fPeers release];
1400    fPeers = [[[torrent peers] sortedArrayUsingDescriptors: [self peerSortDescriptors]] retain];
1401   
1402    [fPeerTable reloadData];
1403}
1404
1405- (void) updateInfoFiles
1406{
1407    if ([fTorrents count] == 1)
1408        [fFileController reloadData];
1409}
1410
1411- (NSView *) tabViewForTag: (int) tag
1412{
1413    switch (tag)
1414    {
1415        case TAB_INFO_TAG:
1416            return fInfoView;
1417        case TAB_ACTIVITY_TAG:
1418            return fActivityView;
1419        case TAB_TRACKER_TAG:
1420            return fTrackerView;
1421        case TAB_PEERS_TAG:
1422            return fPeersView;
1423        case TAB_FILES_TAG:
1424            return fFilesView;
1425        case TAB_OPTIONS_TAG:
1426            return fOptionsView;
1427        default:
1428            return nil;
1429    }
1430}
1431
1432- (NSArray *) peerSortDescriptors
1433{
1434    NSMutableArray * descriptors = [NSMutableArray arrayWithCapacity: 2];
1435   
1436    NSArray * oldDescriptors = [fPeerTable sortDescriptors];
1437    BOOL useSecond = YES, asc = YES;
1438    if ([oldDescriptors count] > 0)
1439    {
1440        NSSortDescriptor * descriptor = [oldDescriptors objectAtIndex: 0];
1441        [descriptors addObject: descriptor];
1442       
1443        if ((useSecond = ![[descriptor key] isEqualToString: @"IP"]))
1444            asc = [descriptor ascending];
1445    }
1446   
1447    //sort by IP after primary sort
1448    if (useSecond)
1449    {
1450        NSSortDescriptor * secondDescriptor = [[NSSortDescriptor alloc] initWithKey: @"IP" ascending: asc
1451                                                                        selector: @selector(compareIP:)];
1452        [descriptors addObject: secondDescriptor];
1453        [secondDescriptor release];
1454    }
1455   
1456    return descriptors;
1457}
1458
1459- (void) addTrackers
1460{
1461    [[self window] makeKeyWindow];
1462   
1463    int i;
1464    if ([[fTorrents objectAtIndex: 0] hasAddedTrackers])
1465    {
1466        for (i = 1; i < [fTrackers count]; i++)
1467            if ([[fTrackers objectAtIndex: i] isKindOfClass: [NSNumber class]])
1468                break;
1469    }
1470    else
1471    {
1472        [fTrackers insertObject: [NSNumber numberWithInt: 0] atIndex: 0];
1473        i = 1;
1474    }
1475   
1476    [fTrackers insertObject: @"" atIndex: i];
1477    [fTrackerTable reloadData];
1478    [fTrackerTable selectRow: i byExtendingSelection: NO];
1479    [fTrackerTable editColumn: 0 row: i withEvent: nil select: YES];
1480}
1481
1482- (void) removeTrackers
1483{
1484    NSMutableIndexSet * indexes = [[[fTrackerTable selectedRowIndexes] mutableCopy] autorelease];
1485   
1486    //get all rows to remove and determine if any built-in trackers are being remove
1487    NSUInteger i = 0, numberBuiltIn = 0;
1488    while (i < [fTrackers count])
1489    {
1490        BOOL builtIn = i != 0 || [[fTrackers objectAtIndex: i] intValue] != 0;
1491       
1492        //if a group is selected, remove all trackers in the group
1493        if ([indexes containsIndex: i])
1494        {
1495            for (i = i+1; i < [fTrackers count] && ![[fTrackers objectAtIndex: i] isKindOfClass: [NSNumber class]]; i++)
1496            {
1497                [indexes addIndex: i];
1498                if (builtIn)
1499                    numberBuiltIn++;
1500            }
1501        }
1502        //remove empty groups
1503        else
1504        {
1505            BOOL allSelected = YES;
1506            NSUInteger j;
1507            for (j = i+1; j < [fTrackers count] && ![[fTrackers objectAtIndex: j] isKindOfClass: [NSNumber class]]; j++)
1508            {
1509                if (![indexes containsIndex: j])
1510                    allSelected = NO;
1511                else if (builtIn)
1512                    numberBuiltIn++;
1513                else;
1514            }
1515           
1516            if (allSelected)
1517                [indexes addIndex: i];
1518           
1519            i = j;
1520        }
1521    }
1522   
1523    if ([fTrackers count] == [indexes count])
1524    {
1525        NSBeep();
1526        return;
1527    }
1528   
1529    Torrent * torrent = [fTorrents objectAtIndex: 0];
1530   
1531    //determine if removing trackers built into the torrent
1532    if (numberBuiltIn > 0 && [[NSUserDefaults standardUserDefaults] boolForKey: @"WarningRemoveBuiltInTracker"])
1533    {
1534        NSAlert * alert = [[NSAlert alloc] init];
1535       
1536        if (numberBuiltIn > 1)
1537        {
1538            [alert setMessageText: [NSString stringWithFormat:
1539                                    NSLocalizedString(@"Are you sure you want to remove %d built-in trackers?",
1540                                    "Remove built-in tracker alert -> title"), numberBuiltIn]];
1541            [alert setInformativeText: NSLocalizedString(@"These tracker addresses are part of the torrent file."
1542                " Once removed, Transmission will no longer attempt to contact them.", "Remove built-in tracker alert -> message")];
1543        }
1544        else
1545        {
1546            [alert setMessageText: NSLocalizedString(@"Are you sure you want to remove a built-in tracker?",
1547                                    "Remove built-in tracker alert -> title")];
1548            [alert setInformativeText: NSLocalizedString(@"The tracker address is part of the torrent file."
1549                " Once removed, Transmission will no longer attempt to contact it.", "Remove built-in tracker alert -> message")];
1550        }
1551       
1552        [alert addButtonWithTitle: NSLocalizedString(@"Remove", "Remove built-in tracker alert -> button")];
1553        [alert addButtonWithTitle: NSLocalizedString(@"Cancel", "Remove built-in tracker alert -> button")];
1554       
1555        BOOL onLeopard = [NSApp isOnLeopardOrBetter];
1556        if (onLeopard)
1557            [alert setShowsSuppressionButton: YES];
1558        else
1559            [alert addButtonWithTitle: NSLocalizedString(@"Don't Alert Again", "Remove built-in tracker alert -> button")];
1560
1561        NSInteger result = [alert runModal];
1562        if ((onLeopard ? [[alert suppressionButton] state] == NSOnState : result == NSAlertThirdButtonReturn))
1563            [[NSUserDefaults standardUserDefaults] setBool: NO forKey: @"WarningRemoveBuiltInTracker"];
1564        [alert release];
1565       
1566        if (result != NSAlertFirstButtonReturn)
1567            return;
1568    }
1569   
1570    [fTrackers removeObjectsAtIndexes: indexes];
1571   
1572    [torrent updateAllTrackersForRemove: fTrackers];
1573    [fTrackerTable deselectAll: self];
1574   
1575    //reset table with either new or old value
1576    [fTrackers release];
1577    fTrackers = [[torrent allTrackers: YES] retain];
1578   
1579    [fTrackerTable setTrackers: fTrackers];
1580    [fTrackerTable reloadData];
1581}
1582
1583@end
Note: See TracBrowser for help on using the repository browser.