source: trunk/macosx/InfoWindowController.m @ 6047

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

avoid unnecessary memory allocation when updating the rpc access list

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