source: trunk/macosx/TrackerCell.m @ 9214

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

right-align the peer counts in the tracker cells

File size: 13.6 KB
Line 
1/******************************************************************************
2 * $Id$
3 *
4 * Copyright (c) 2009 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#import "TrackerCell.h"
26#import "NSApplicationAdditions.h"
27#import "TrackerNode.h"
28
29#define PADDING_HORIZONAL 3.0
30#define PADDING_STATUS_HORIZONAL 3.0
31#define ICON_SIZE 14.0
32#define PADDING_BETWEEN_ICON_AND_NAME 4.0
33#define PADDING_ABOVE_ICON 1.0
34#define PADDING_ABOVE_NAME 2.0
35#define PADDING_BETWEEN_LINES 1.0
36#define PADDING_BETWEEN_LINES_ON_SAME_LINE 3.0
37#define COUNT_WIDTH 40.0
38
39@interface TrackerCell (Private)
40
41- (NSImage *) favIcon;
42- (void) loadTrackerIcon: (NSString *) baseAddress;
43
44- (NSRect) imageRectForBounds: (NSRect) bounds;
45- (NSRect) rectForNameWithString: (NSAttributedString *) string inBounds: (NSRect) bounds;
46- (NSRect) rectForCountWithString: (NSAttributedString *) string withAboveRect: (NSRect) aboveRect inBounds: (NSRect) bounds;
47- (NSRect) rectForCountLabelWithString: (NSAttributedString *) string withRightRect: (NSRect) rightRect inBounds: (NSRect) bounds;
48- (NSRect) rectForStatusWithString: (NSAttributedString *) string withAboveRect: (NSRect) aboveRect withRightRect: (NSRect) rightRect
49            inBounds: (NSRect) bounds;
50
51- (NSAttributedString *) attributedNameWithColor: (NSColor *) color;
52- (NSAttributedString *) attributedStatusWithString: (NSString *) statusString color: (NSColor *) color;
53
54@end
55
56@implementation TrackerCell
57
58//make the favicons accessible to all tracker cells
59#warning make NSCache when 10.6-only
60NSMutableDictionary * fTrackerIconCache;
61NSMutableSet * fTrackerIconLoading;
62
63+ (void) initialize
64{
65    fTrackerIconCache = [[NSMutableDictionary alloc] init];
66    fTrackerIconLoading = [[NSMutableSet alloc] init];
67}
68
69- (id) init
70{
71    if ((self = [super init]))
72    {
73        NSMutableParagraphStyle * paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
74        [paragraphStyle setLineBreakMode: NSLineBreakByTruncatingTail];
75       
76        fNameAttributes = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
77                            [NSFont messageFontOfSize: 11.0], NSFontAttributeName,
78                            paragraphStyle, NSParagraphStyleAttributeName, nil];
79       
80        fStatusAttributes = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
81                                [NSFont messageFontOfSize: 9.0], NSFontAttributeName,
82                                paragraphStyle, NSParagraphStyleAttributeName, nil];
83       
84        [paragraphStyle release];
85    }
86    return self;
87}
88
89- (void) dealloc
90{
91    [fNameAttributes release];
92    [fStatusAttributes release];
93   
94    [super dealloc];
95}
96
97- (id) copyWithZone: (NSZone *) zone
98{
99    TrackerCell * copy = [super copyWithZone: zone];
100   
101    copy->fNameAttributes = [fNameAttributes retain];
102    copy->fStatusAttributes = [fStatusAttributes retain];
103   
104    return copy;
105}
106
107- (void) drawWithFrame: (NSRect) cellFrame inView: (NSView *) controlView
108{
109    //icon
110    if ([NSApp isOnSnowLeopardOrBetter])
111        [[self favIcon] drawInRect: [self imageRectForBounds: cellFrame] fromRect: NSZeroRect operation: NSCompositeSourceOver
112                        fraction: 1.0 respectFlipped: YES hints: nil];
113    else
114    {
115        NSImage * icon = [self favIcon];
116        [icon setFlipped: YES];
117        [icon drawInRect: [self imageRectForBounds: cellFrame] fromRect: NSZeroRect operation: NSCompositeSourceOver fraction: 1.0];
118    }
119
120   
121    NSColor * nameColor, * statusColor;
122    if ([self backgroundStyle] == NSBackgroundStyleDark)
123        nameColor = statusColor = [NSColor whiteColor];
124    else
125    {
126        nameColor = [NSColor controlTextColor];
127        statusColor = [NSColor darkGrayColor];
128    }
129   
130    TrackerNode * node = (TrackerNode *)[self objectValue];
131   
132    //name
133    NSAttributedString * nameString = [self attributedNameWithColor: nameColor];
134    const NSRect nameRect = [self rectForNameWithString: nameString inBounds: cellFrame];
135    [nameString drawInRect: nameRect];
136   
137    //count strings
138    NSString * seederBaseString = [node totalSeeders] != -1 ? [NSString stringWithFormat: @"%d", [node totalSeeders]]
139                                                            : NSLocalizedString(@"N/A", "tracker peer stat");
140    NSAttributedString * seederString = [self attributedStatusWithString: seederBaseString color: statusColor];
141    const NSRect seederRect = [self rectForCountWithString: seederString withAboveRect: nameRect inBounds: cellFrame];
142    [seederString drawInRect: seederRect];
143   
144    NSString * leecherBaseString = [node totalLeechers] != -1 ? [NSString stringWithFormat: @"%d", [node totalLeechers]]
145                                                                : NSLocalizedString(@"N/A", "tracker peer stat");
146    NSAttributedString * leecherString = [self attributedStatusWithString: leecherBaseString color: statusColor];
147    const NSRect leecherRect = [self rectForCountWithString: leecherString withAboveRect: seederRect inBounds: cellFrame];
148    [leecherString drawInRect: leecherRect];
149   
150    NSString * downloadedBaseString = [node totalDownloaded] != -1 ? [NSString stringWithFormat: @"%d", [node totalDownloaded]]
151                                                                    : NSLocalizedString(@"N/A", "tracker peer stat");
152    NSAttributedString * downloadedString = [self attributedStatusWithString: downloadedBaseString color: statusColor];
153    const NSRect downloadedRect = [self rectForCountWithString: downloadedString withAboveRect: leecherRect inBounds: cellFrame];
154    [downloadedString drawInRect: downloadedRect];
155   
156    //count label strings
157    NSString * seederLabelBaseString = [NSLocalizedString(@"Seeders", "tracker peer stat") stringByAppendingFormat: @": "];
158    NSAttributedString * seederLabelString = [self attributedStatusWithString: seederLabelBaseString color: statusColor];
159    const NSRect seederLabelRect = [self rectForCountLabelWithString: seederLabelString withRightRect: seederRect
160                                        inBounds: cellFrame];
161    [seederLabelString drawInRect: seederLabelRect];
162   
163    NSString * leecherLabelBaseString = [NSLocalizedString(@"Leechers", "tracker peer stat") stringByAppendingFormat: @": "];
164    NSAttributedString * leecherLabelString = [self attributedStatusWithString: leecherLabelBaseString color: statusColor];
165    const NSRect leecherLabelRect = [self rectForCountLabelWithString: leecherLabelString withRightRect: leecherRect
166                                        inBounds: cellFrame];
167    [leecherLabelString drawInRect: leecherLabelRect];
168   
169    NSString * downloadedLabelBaseString = [NSLocalizedString(@"Downloaded", "tracker peer stat") stringByAppendingFormat: @": "];
170    NSAttributedString * downloadedLabelString = [self attributedStatusWithString: downloadedLabelBaseString color: statusColor];
171    const NSRect downloadedLabelRect = [self rectForCountLabelWithString: downloadedLabelString withRightRect: downloadedRect
172                                        inBounds: cellFrame];
173    [downloadedLabelString drawInRect: downloadedLabelRect];
174   
175    //status strings
176    NSAttributedString * lastAnnounceString = [self attributedStatusWithString: [node lastAnnounceStatusString] color: statusColor];
177    const NSRect lastAnnounceRect = [self rectForStatusWithString: lastAnnounceString withAboveRect: nameRect
178                                        withRightRect: seederLabelRect inBounds: cellFrame];
179    [lastAnnounceString drawInRect: lastAnnounceRect];
180   
181    NSAttributedString * nextAnnounceString = [self attributedStatusWithString: [node nextAnnounceStatusString] color: statusColor];
182    const NSRect nextAnnounceRect = [self rectForStatusWithString: nextAnnounceString withAboveRect: lastAnnounceRect
183                                        withRightRect: leecherLabelRect inBounds: cellFrame];
184    [nextAnnounceString drawInRect: nextAnnounceRect];
185   
186    NSAttributedString * lastScrapeString = [self attributedStatusWithString: [node lastScrapeStatusString] color: statusColor];
187    const NSRect lastScrapeRect = [self rectForStatusWithString: lastScrapeString withAboveRect: nextAnnounceRect
188                                    withRightRect: downloadedLabelRect inBounds: cellFrame];
189    [lastScrapeString drawInRect: lastScrapeRect];
190}
191
192@end
193
194@implementation TrackerCell (Private)
195
196- (NSImage *) favIcon
197{
198    NSURL * address = [NSURL URLWithString: [(TrackerNode *)[self objectValue] host]];
199    NSArray * hostComponents = [[address host] componentsSeparatedByString: @"."];
200   
201    //let's try getting the tracker address without using any subdomains
202    NSString * baseAddress;
203    if ([hostComponents count] > 1)
204        baseAddress = [NSString stringWithFormat: @"http://%@.%@",
205                        [hostComponents objectAtIndex: [hostComponents count] - 2], [hostComponents lastObject]];
206    else
207        baseAddress = [NSString stringWithFormat: @"http://%@", [hostComponents lastObject]];
208   
209    id icon = [fTrackerIconCache objectForKey: baseAddress];
210    if (!icon && ![fTrackerIconLoading containsObject: baseAddress])
211    {
212        [fTrackerIconLoading addObject: baseAddress];
213        [NSThread detachNewThreadSelector: @selector(loadTrackerIcon:) toTarget: self withObject: baseAddress];
214    }
215   
216    return (icon && icon != [NSNull null]) ? icon : [NSImage imageNamed: @"FavIcon.png"];
217}
218
219- (void) loadTrackerIcon: (NSString *) baseAddress
220{
221    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
222   
223    NSURL * favIconUrl = [NSURL URLWithString: [baseAddress stringByAppendingPathComponent: @"favicon.ico"]];
224   
225    NSURLRequest * request = [NSURLRequest requestWithURL: favIconUrl cachePolicy: NSURLRequestUseProtocolCachePolicy
226                                timeoutInterval: 30.0];
227    NSData * iconData = [NSURLConnection sendSynchronousRequest: request returningResponse: NULL error: NULL];
228    NSImage * icon = [[NSImage alloc] initWithData: iconData];
229   
230    if (icon)
231    {
232        [fTrackerIconCache setObject: icon forKey: baseAddress];
233        [icon release];
234    }
235    else
236        [fTrackerIconCache setObject: [NSNull null] forKey: baseAddress];
237   
238    [fTrackerIconLoading removeObject: baseAddress];
239
240    [pool drain];
241}
242
243- (NSRect) imageRectForBounds: (NSRect) bounds
244{
245    NSRect result = bounds;
246    result.origin.x += PADDING_HORIZONAL;
247    result.origin.y += PADDING_ABOVE_ICON;
248    result.size = NSMakeSize(ICON_SIZE, ICON_SIZE);
249   
250    return result;
251}
252
253- (NSRect) rectForNameWithString: (NSAttributedString *) string inBounds: (NSRect) bounds
254{
255    const NSSize nameSize = [string size];
256   
257    NSRect result = bounds;
258    result.origin.x += PADDING_HORIZONAL + ICON_SIZE + PADDING_BETWEEN_ICON_AND_NAME;
259    result.origin.y += PADDING_ABOVE_NAME;
260       
261    result.size = nameSize;
262    result.size.width = MIN(result.size.width, NSMaxX(bounds) - NSMinX(result));
263   
264    return result;
265}
266
267- (NSRect) rectForCountWithString: (NSAttributedString *) string withAboveRect: (NSRect) aboveRect inBounds: (NSRect) bounds
268{
269    return NSMakeRect(NSMaxX(bounds) - PADDING_HORIZONAL - COUNT_WIDTH,
270                        NSMaxY(aboveRect) + PADDING_BETWEEN_LINES,
271                        COUNT_WIDTH, [string size].height);
272}
273
274- (NSRect) rectForCountLabelWithString: (NSAttributedString *) string withRightRect: (NSRect) rightRect inBounds: (NSRect) bounds
275{
276    NSRect result = rightRect;
277    result.size.width = [string size].width;
278    result.origin.x -= result.size.width;
279   
280    return result;
281}
282
283- (NSRect) rectForStatusWithString: (NSAttributedString *) string withAboveRect: (NSRect) aboveRect withRightRect: (NSRect) rightRect
284            inBounds: (NSRect) bounds
285{
286    const NSSize statusSize = [string size];
287   
288    NSRect result = bounds;
289    result.origin.x += PADDING_STATUS_HORIZONAL;
290    result.origin.y = NSMaxY(aboveRect) + PADDING_BETWEEN_LINES;
291   
292    result.size = statusSize;
293    result.size.width = MIN(result.size.width, (NSMinX(rightRect) - PADDING_BETWEEN_LINES_ON_SAME_LINE) - NSMinX(result));
294   
295    return result;
296}
297
298- (NSAttributedString *) attributedNameWithColor: (NSColor *) color
299{
300    if (color)
301        [fNameAttributes setObject: color forKey: NSForegroundColorAttributeName];
302       
303    NSString * name = [(TrackerNode *)[self objectValue] host];
304    return [[[NSAttributedString alloc] initWithString: name attributes: fNameAttributes] autorelease];
305}
306
307- (NSAttributedString *) attributedStatusWithString: (NSString *) statusString color: (NSColor *) color
308{
309    if (color)
310        [fStatusAttributes setObject: color forKey: NSForegroundColorAttributeName];
311   
312    return [[[NSAttributedString alloc] initWithString: statusString attributes: fStatusAttributes] autorelease];
313}
314
315@end
Note: See TracBrowser for help on using the repository browser.