source: trunk/macosx/InfoWindowController.m @ 6262

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

cleanup for the removal of the ip sort function

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