source: trunk/macosx/InfoWindowController.m @ 6088

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

"web seeds", not "web seeders"

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