1/****************************************************************************** 2 * $Id: TrackerCell.m 13434 2012-08-13 00:52:04Z livings124 $ 3 * 4 * Copyright (c) 2009-2012 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 "TrackerNode.h" 27 28#import "transmission.h" // required by utils.h 29#import "utils.h" //tr_addressIsIP() 30 31#define PADDING_HORIZONAL 3.0 32#define PADDING_STATUS_HORIZONAL 3.0 33#define ICON_SIZE 16.0 34#define PADDING_BETWEEN_ICON_AND_NAME 4.0 35#define PADDING_ABOVE_ICON 1.0 36#define PADDING_ABOVE_NAME 1.0 37#define PADDING_BETWEEN_LINES 1.0 38#define PADDING_BETWEEN_LINES_ON_SAME_LINE 4.0 39#define COUNT_WIDTH 40.0 40 41@interface TrackerCell (Private) 42 43- (NSImage *) favIcon; 44- (void) loadTrackerIcon: (NSString *) baseAddress; 45 46- (NSRect) imageRectForBounds: (NSRect) bounds; 47- (NSRect) rectForNameWithString: (NSAttributedString *) string inBounds: (NSRect) bounds; 48- (NSRect) rectForCountWithString: (NSAttributedString *) string withAboveRect: (NSRect) aboveRect inBounds: (NSRect) bounds; 49- (NSRect) rectForCountLabelWithString: (NSAttributedString *) string withRightRect: (NSRect) rightRect inBounds: (NSRect) bounds; 50- (NSRect) rectForStatusWithString: (NSAttributedString *) string withAboveRect: (NSRect) aboveRect withRightRect: (NSRect) rightRect 51 inBounds: (NSRect) bounds; 52 53- (NSAttributedString *) attributedName; 54- (NSAttributedString *) attributedStatusWithString: (NSString *) statusString; 55- (NSAttributedString *) attributedCount: (NSInteger) count; 56 57@end 58 59@implementation TrackerCell 60 61//make the favicons accessible to all tracker cells 62NSCache * fTrackerIconCache; 63NSMutableSet * fTrackerIconLoading; 64 65+ (void) initialize 66{ 67 fTrackerIconCache = [[NSCache alloc] init]; 68 fTrackerIconLoading = [[NSMutableSet alloc] init]; 69} 70 71- (id) init 72{ 73 if ((self = [super init])) 74 { 75 NSMutableParagraphStyle * paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; 76 [paragraphStyle setLineBreakMode: NSLineBreakByTruncatingTail]; 77 78 fNameAttributes = [[NSMutableDictionary alloc] initWithObjectsAndKeys: 79 [NSFont messageFontOfSize: 12.0], NSFontAttributeName, 80 paragraphStyle, NSParagraphStyleAttributeName, nil]; 81 82 fStatusAttributes = [[NSMutableDictionary alloc] initWithObjectsAndKeys: 83 [NSFont messageFontOfSize: 9.0], NSFontAttributeName, 84 paragraphStyle, NSParagraphStyleAttributeName, nil]; 85 86 [paragraphStyle release]; 87 } 88 return self; 89} 90 91- (void) dealloc 92{ 93 [fNameAttributes release]; 94 [fStatusAttributes release]; 95 96 [super dealloc]; 97} 98 99- (id) copyWithZone: (NSZone *) zone 100{ 101 TrackerCell * copy = [super copyWithZone: zone]; 102 103 copy->fNameAttributes = [fNameAttributes retain]; 104 copy->fStatusAttributes = [fStatusAttributes retain]; 105 106 return copy; 107} 108 109- (void) drawWithFrame: (NSRect) cellFrame inView: (NSView *) controlView 110{ 111 //icon 112 [[self favIcon] drawInRect: [self imageRectForBounds: cellFrame] fromRect: NSZeroRect operation: NSCompositeSourceOver fraction: 1.0 respectFlipped: YES hints: nil]; 113 114 //set table colors 115 NSColor * nameColor, * statusColor; 116 if ([self backgroundStyle] == NSBackgroundStyleDark) 117 nameColor = statusColor = [NSColor whiteColor]; 118 else 119 { 120 nameColor = [NSColor controlTextColor]; 121 statusColor = [NSColor darkGrayColor]; 122 } 123 124 [fNameAttributes setObject: nameColor forKey: NSForegroundColorAttributeName]; 125 [fStatusAttributes setObject: statusColor forKey: NSForegroundColorAttributeName]; 126 127 TrackerNode * node = (TrackerNode *)[self objectValue]; 128 129 //name 130 NSAttributedString * nameString = [self attributedName]; 131 const NSRect nameRect = [self rectForNameWithString: nameString inBounds: cellFrame]; 132 [nameString drawInRect: nameRect]; 133 134 //count strings 135 NSAttributedString * seederString = [self attributedCount: [node totalSeeders]]; 136 const NSRect seederRect = [self rectForCountWithString: seederString withAboveRect: nameRect inBounds: cellFrame]; 137 [seederString drawInRect: seederRect]; 138 139 NSAttributedString * leecherString = [self attributedCount: [node totalLeechers]]; 140 const NSRect leecherRect = [self rectForCountWithString: leecherString withAboveRect: seederRect inBounds: cellFrame]; 141 [leecherString drawInRect: leecherRect]; 142 143 NSAttributedString * downloadedString = [self attributedCount: [node totalDownloaded]]; 144 const NSRect downloadedRect = [self rectForCountWithString: downloadedString withAboveRect: leecherRect inBounds: cellFrame]; 145 [downloadedString drawInRect: downloadedRect]; 146 147 //count label strings 148 NSString * seederLabelBaseString = [NSLocalizedString(@"Seeders", "tracker peer stat") stringByAppendingFormat: @": "]; 149 NSAttributedString * seederLabelString = [self attributedStatusWithString: seederLabelBaseString]; 150 const NSRect seederLabelRect = [self rectForCountLabelWithString: seederLabelString withRightRect: seederRect 151 inBounds: cellFrame]; 152 [seederLabelString drawInRect: seederLabelRect]; 153 154 NSString * leecherLabelBaseString = [NSLocalizedString(@"Leechers", "tracker peer stat") stringByAppendingFormat: @": "]; 155 NSAttributedString * leecherLabelString = [self attributedStatusWithString: leecherLabelBaseString]; 156 const NSRect leecherLabelRect = [self rectForCountLabelWithString: leecherLabelString withRightRect: leecherRect 157 inBounds: cellFrame]; 158 [leecherLabelString drawInRect: leecherLabelRect]; 159 160 NSString * downloadedLabelBaseString = [NSLocalizedString(@"Downloaded", "tracker peer stat") stringByAppendingFormat: @": "]; 161 NSAttributedString * downloadedLabelString = [self attributedStatusWithString: downloadedLabelBaseString]; 162 const NSRect downloadedLabelRect = [self rectForCountLabelWithString: downloadedLabelString withRightRect: downloadedRect 163 inBounds: cellFrame]; 164 [downloadedLabelString drawInRect: downloadedLabelRect]; 165 166 //status strings 167 NSAttributedString * lastAnnounceString = [self attributedStatusWithString: [node lastAnnounceStatusString]]; 168 const NSRect lastAnnounceRect = [self rectForStatusWithString: lastAnnounceString withAboveRect: nameRect 169 withRightRect: seederLabelRect inBounds: cellFrame]; 170 [lastAnnounceString drawInRect: lastAnnounceRect]; 171 172 NSAttributedString * nextAnnounceString = [self attributedStatusWithString: [node nextAnnounceStatusString]]; 173 const NSRect nextAnnounceRect = [self rectForStatusWithString: nextAnnounceString withAboveRect: lastAnnounceRect 174 withRightRect: leecherLabelRect inBounds: cellFrame]; 175 [nextAnnounceString drawInRect: nextAnnounceRect]; 176 177 NSAttributedString * lastScrapeString = [self attributedStatusWithString: [node lastScrapeStatusString]]; 178 const NSRect lastScrapeRect = [self rectForStatusWithString: lastScrapeString withAboveRect: nextAnnounceRect 179 withRightRect: downloadedLabelRect inBounds: cellFrame]; 180 [lastScrapeString drawInRect: lastScrapeRect]; 181} 182 183@end 184 185@implementation TrackerCell (Private) 186 187- (NSImage *) favIcon 188{ 189 id icon = nil; 190 NSURL * address = [NSURL URLWithString: [(TrackerNode *)[self objectValue] fullAnnounceAddress]]; 191 NSString * host; 192 if ((host = [address host])) 193 { 194 //don't try to parse ip address 195 const BOOL separable = !tr_addressIsIP([host UTF8String]); 196 197 NSArray * hostComponents = separable ? [host componentsSeparatedByString: @"."] : nil; 198 199 //let's try getting the tracker address without using any subdomains 200 NSString * baseAddress; 201 if (separable && [hostComponents count] > 1) 202 baseAddress = [NSString stringWithFormat: @"http://%@.%@", 203 [hostComponents objectAtIndex: [hostComponents count]-2], [hostComponents lastObject]]; 204 else 205 baseAddress = [NSString stringWithFormat: @"http://%@", host]; 206 207 icon = [fTrackerIconCache objectForKey: baseAddress]; 208 if (!icon && ![fTrackerIconLoading containsObject: baseAddress]) 209 { 210 [fTrackerIconLoading addObject: baseAddress]; 211 [NSThread detachNewThreadSelector: @selector(loadTrackerIcon:) toTarget: self withObject: baseAddress]; 212 } 213 } 214 215 return (icon && icon != [NSNull null]) ? icon : [NSImage imageNamed: @"FavIcon"]; 216} 217 218#warning better favicon detection 219- (void) loadTrackerIcon: (NSString *) baseAddress 220{ 221 @autoreleasepool 222 { 223 //try favicon.png 224 NSURL * favIconUrl = [NSURL URLWithString: [baseAddress stringByAppendingPathComponent: @"favicon.png"]]; 225 226 NSURLRequest * request = [NSURLRequest requestWithURL: favIconUrl cachePolicy: NSURLRequestUseProtocolCachePolicy 227 timeoutInterval: 30.0]; 228 NSData * iconData = [NSURLConnection sendSynchronousRequest: request returningResponse: NULL error: NULL]; 229 NSImage * icon = [[NSImage alloc] initWithData: iconData]; 230 231 //try favicon.ico 232 if (!icon) 233 { 234 favIconUrl = [NSURL URLWithString: [baseAddress stringByAppendingPathComponent: @"favicon.ico"]]; 235 236 request = [NSURLRequest requestWithURL: favIconUrl cachePolicy: NSURLRequestUseProtocolCachePolicy 237 timeoutInterval: 30.0]; 238 iconData = [NSURLConnection sendSynchronousRequest: request returningResponse: NULL error: NULL]; 239 icon = [[NSImage alloc] initWithData: iconData]; 240 } 241 242 if (icon) 243 { 244 [fTrackerIconCache setObject: icon forKey: baseAddress]; 245 [icon release]; 246 247 [[self controlView] setNeedsDisplay: YES]; 248 } 249 else 250 [fTrackerIconCache setObject: [NSNull null] forKey: baseAddress]; 251 252 [fTrackerIconLoading removeObject: baseAddress]; 253 } 254} 255 256- (NSRect) imageRectForBounds: (NSRect) bounds 257{ 258 return NSMakeRect(NSMinX(bounds) + PADDING_HORIZONAL, NSMinY(bounds) + PADDING_ABOVE_ICON, ICON_SIZE, ICON_SIZE); 259} 260 261- (NSRect) rectForNameWithString: (NSAttributedString *) string inBounds: (NSRect) bounds 262{ 263 NSRect result; 264 result.origin.x = NSMinX(bounds) + PADDING_HORIZONAL + ICON_SIZE + PADDING_BETWEEN_ICON_AND_NAME; 265 result.origin.y = NSMinY(bounds) + PADDING_ABOVE_NAME; 266 267 result.size.height = [string size].height; 268 result.size.width = NSMaxX(bounds) - NSMinX(result) - PADDING_HORIZONAL; 269 270 return result; 271} 272 273- (NSRect) rectForCountWithString: (NSAttributedString *) string withAboveRect: (NSRect) aboveRect inBounds: (NSRect) bounds 274{ 275 return NSMakeRect(NSMaxX(bounds) - PADDING_HORIZONAL - COUNT_WIDTH, 276 NSMaxY(aboveRect) + PADDING_BETWEEN_LINES, 277 COUNT_WIDTH, [string size].height); 278} 279 280- (NSRect) rectForCountLabelWithString: (NSAttributedString *) string withRightRect: (NSRect) rightRect inBounds: (NSRect) bounds 281{ 282 NSRect result = rightRect; 283 result.size.width = [string size].width; 284 result.origin.x -= NSWidth(result); 285 286 return result; 287} 288 289- (NSRect) rectForStatusWithString: (NSAttributedString *) string withAboveRect: (NSRect) aboveRect withRightRect: (NSRect) rightRect 290 inBounds: (NSRect) bounds 291{ 292 NSRect result; 293 result.origin.x = NSMinX(bounds) + PADDING_STATUS_HORIZONAL; 294 result.origin.y = NSMaxY(aboveRect) + PADDING_BETWEEN_LINES; 295 296 result.size.height = [string size].height; 297 result.size.width = NSMinX(rightRect) - PADDING_BETWEEN_LINES_ON_SAME_LINE - NSMinX(result); 298 299 return result; 300} 301 302- (NSAttributedString *) attributedName 303{ 304 NSString * name = [(TrackerNode *)[self objectValue] host]; 305 return [[[NSAttributedString alloc] initWithString: name attributes: fNameAttributes] autorelease]; 306} 307 308- (NSAttributedString *) attributedStatusWithString: (NSString *) statusString 309{ 310 return [[[NSAttributedString alloc] initWithString: statusString attributes: fStatusAttributes] autorelease]; 311} 312 313- (NSAttributedString *) attributedCount: (NSInteger) count 314{ 315 NSString * countString = count != -1 ? [NSString stringWithFormat: @"%ld", count] : NSLocalizedString(@"N/A", "tracker peer stat"); 316 return [[[NSAttributedString alloc] initWithString: countString attributes: fStatusAttributes] autorelease]; 317} 318 319@end 320