1/****************************************************************************** 2 * $Id: NSStringAdditions.m 13510 2012-09-22 16:09:52Z livings124 $ 3 * 4 * Copyright (c) 2005-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 "NSApplicationAdditions.h" 26#import "NSStringAdditions.h" 27 28#import "transmission.h" 29#import "utils.h" 30 31@interface NSString (Private) 32 33+ (NSString *) stringForFileSizeLion: (uint64_t) size showUnitUnless: (NSString *) notAllowedUnit unitsUsed: (NSString **) unitUsed; 34 35+ (NSString *) stringForSpeed: (CGFloat) speed kb: (NSString *) kb mb: (NSString *) mb gb: (NSString *) gb; 36 37@end 38 39@implementation NSString (NSStringAdditions) 40 41+ (NSString *) ellipsis 42{ 43 return [NSString stringWithUTF8String: "\xE2\x80\xA6"]; 44} 45 46- (NSString *) stringByAppendingEllipsis 47{ 48 return [self stringByAppendingString: [NSString ellipsis]]; 49} 50 51#warning use localizedStringWithFormat: directly when 10.8-only 52+ (NSString *) formattedUInteger: (NSUInteger) value 53{ 54 if ([NSApp isOnMountainLionOrBetter]) 55 return [NSString localizedStringWithFormat: @"%lu", value]; 56 else 57 { 58 static NSNumberFormatter * numberFormatter; 59 static dispatch_once_t onceToken; 60 dispatch_once(&onceToken, ^{ 61 numberFormatter = [[NSNumberFormatter alloc] init]; 62 [numberFormatter setNumberStyle: NSNumberFormatterDecimalStyle]; 63 [numberFormatter setMaximumFractionDigits: 0]; 64 }); 65 66 return [numberFormatter stringFromNumber: [NSNumber numberWithUnsignedInteger: value]]; 67 } 68} 69 70#warning should we take long long instead? 71+ (NSString *) stringForFileSize: (uint64_t) size 72{ 73 if ([NSApp isOnMountainLionOrBetter]) 74 return [NSByteCountFormatterMtLion stringFromByteCount: size countStyle: NSByteCountFormatterCountStyleFile]; 75 else 76 return [self stringForFileSizeLion: size showUnitUnless: nil unitsUsed: nil]; 77} 78 79#warning should we take long long instead? 80+ (NSString *) stringForFilePartialSize: (uint64_t) partialSize fullSize: (uint64_t) fullSize 81{ 82 NSString * partialString, * fullString; 83 if ([NSApp isOnMountainLionOrBetter]) 84 { 85 NSByteCountFormatter * fileSizeFormatter = [[NSByteCountFormatterMtLion alloc] init]; 86 87 fullString = [fileSizeFormatter stringFromByteCount: fullSize]; 88 89 //figure out the magniture of the two, since we can't rely on comparing the units because of localization and pluralization issues (for example, "1 byte of 2 bytes") 90 BOOL partialUnitsSame; 91 if (partialSize == 0) 92 partialUnitsSame = YES; //we want to just show "0" when we have no partial data, so always set to the same units 93 else 94 { 95 const unsigned int magnitudePartial = log(partialSize)/log(1000); 96 const unsigned int magnitudeFull = fullSize < 1000 ? 0 : log(fullSize)/log(1000); //we have to catch 0 with a special case, so might as well avoid the math for all of magnitude 0 97 partialUnitsSame = magnitudePartial == magnitudeFull; 98 } 99 100 [fileSizeFormatter setIncludesUnit: !partialUnitsSame]; 101 partialString = [fileSizeFormatter stringFromByteCount: partialSize]; 102 103 [fileSizeFormatter release]; 104 } 105 else 106 { 107 NSString * units; 108 fullString = [self stringForFileSizeLion: fullSize showUnitUnless: nil unitsUsed: &units]; 109 partialString = [self stringForFileSizeLion: partialSize showUnitUnless: units unitsUsed: nil]; 110 } 111 112 return [NSString stringWithFormat: NSLocalizedString(@"%@ of %@", "file size string"), partialString, fullString]; 113} 114 115+ (NSString *) stringForSpeed: (CGFloat) speed 116{ 117 return [self stringForSpeed: speed 118 kb: NSLocalizedString(@"KB/s", "Transfer speed (kilobytes per second)") 119 mb: NSLocalizedString(@"MB/s", "Transfer speed (megabytes per second)") 120 gb: NSLocalizedString(@"GB/s", "Transfer speed (gigabytes per second)")]; 121} 122 123+ (NSString *) stringForSpeedAbbrev: (CGFloat) speed 124{ 125 return [self stringForSpeed: speed kb: @"K" mb: @"M" gb: @"G"]; 126} 127 128+ (NSString *) stringForRatio: (CGFloat) ratio 129{ 130 //N/A is different than libtransmission's 131 if ((int)ratio == TR_RATIO_NA) 132 return NSLocalizedString(@"N/A", "No Ratio"); 133 else if ((int)ratio == TR_RATIO_INF) 134 return [NSString stringWithUTF8String: "\xE2\x88\x9E"]; 135 else 136 { 137 if (ratio < 10.0) 138 return [NSString localizedStringWithFormat: @"%.2f", tr_truncd(ratio, 2)]; 139 else if (ratio < 100.0) 140 return [NSString localizedStringWithFormat: @"%.1f", tr_truncd(ratio, 1)]; 141 else 142 return [NSString localizedStringWithFormat: @"%.0f", tr_truncd(ratio, 0)]; 143 } 144} 145 146+ (NSString *) percentString: (CGFloat) progress longDecimals: (BOOL) longDecimals 147{ 148 if (progress >= 1.0) 149 return [NSString localizedStringWithFormat: @"%d%%", 100]; 150 else if (longDecimals) 151 return [NSString localizedStringWithFormat: @"%.2f%%", tr_truncd(progress * 100.0, 2)]; 152 else 153 return [NSString localizedStringWithFormat: @"%.1f%%", tr_truncd(progress * 100.0, 1)]; 154} 155 156+ (NSString *) timeString: (uint64_t) seconds showSeconds: (BOOL) showSeconds 157{ 158 return [NSString timeString: seconds showSeconds: showSeconds maxFields: NSUIntegerMax]; 159} 160 161+ (NSString *) timeString: (uint64_t) seconds showSeconds: (BOOL) showSeconds maxFields: (NSUInteger) max 162{ 163 NSParameterAssert(max > 0); 164 165 NSMutableArray * timeArray = [NSMutableArray arrayWithCapacity: MIN(max, 5)]; 166 NSUInteger remaining = seconds; //causes problems for some users when it's a uint64_t 167 168 if (seconds >= 31557600) //official amount of seconds in one year 169 { 170 const NSUInteger years = remaining / 31557600; 171 if (years == 1) 172 [timeArray addObject: NSLocalizedString(@"1 year", "time string")]; 173 else 174 [timeArray addObject: [NSString stringWithFormat: NSLocalizedString(@"%u years", "time string"), years]]; 175 remaining %= 31557600; 176 --max; 177 } 178 if (max > 0 && seconds >= (24 * 60 * 60)) 179 { 180 const NSUInteger days = remaining / (24 * 60 * 60); 181 if (days == 1) 182 [timeArray addObject: NSLocalizedString(@"1 day", "time string")]; 183 else 184 [timeArray addObject: [NSString stringWithFormat: NSLocalizedString(@"%u days", "time string"), days]]; 185 remaining %= (24 * 60 * 60); 186 --max; 187 } 188 if (max > 0 && seconds >= (60 * 60)) 189 { 190 [timeArray addObject: [NSString stringWithFormat: NSLocalizedString(@"%u hr", "time string"), remaining / (60 * 60)]]; 191 remaining %= (60 * 60); 192 --max; 193 } 194 if (max > 0 && (!showSeconds || seconds >= 60)) 195 { 196 [timeArray addObject: [NSString stringWithFormat: NSLocalizedString(@"%u min", "time string"), remaining / 60]]; 197 remaining %= 60; 198 --max; 199 } 200 if (max > 0 && showSeconds) 201 [timeArray addObject: [NSString stringWithFormat: NSLocalizedString(@"%u sec", "time string"), remaining]]; 202 203 return [timeArray componentsJoinedByString: @" "]; 204} 205 206- (NSComparisonResult) compareNumeric: (NSString *) string 207{ 208 const NSStringCompareOptions comparisonOptions = NSNumericSearch | NSForcedOrderingSearch; 209 return [self compare: string options: comparisonOptions range: NSMakeRange(0, [self length]) locale: [NSLocale currentLocale]]; 210} 211 212- (NSArray *) betterComponentsSeparatedByCharactersInSet: (NSCharacterSet *) separator 213{ 214 NSMutableArray * components = [NSMutableArray array]; 215 216 NSUInteger i = 0; 217 while (i < [self length]) 218 { 219 const NSRange range = [self rangeOfCharacterFromSet: separator options: 0 range: NSMakeRange(i, [self length]-i)]; 220 221 if (range.location == NSNotFound) 222 { 223 [components addObject: [self substringFromIndex: i]]; 224 break; 225 } 226 else if (range.location != i) 227 { 228 const NSUInteger length = range.location - i; 229 [components addObject: [self substringWithRange: NSMakeRange(i, length)]]; 230 231 i += length; 232 } 233 234 i += range.length; 235 } 236 237 return components; 238} 239 240@end 241 242@implementation NSString (Private) 243 244+ (NSString *) stringForFileSizeLion: (uint64_t) size showUnitUnless: (NSString *) notAllowedUnit unitsUsed: (NSString **) unitUsed 245{ 246 double convertedSize; 247 NSString * unit; 248 NSUInteger decimals; 249 if (size < pow(1000, 2)) 250 { 251 convertedSize = size / 1000.0; 252 unit = NSLocalizedString(@"KB", "File size - kilobytes"); 253 decimals = convertedSize >= 10.0 ? 0 : 1; 254 } 255 else if (size < pow(1000, 3)) 256 { 257 convertedSize = size / powf(1000.0, 2); 258 unit = NSLocalizedString(@"MB", "File size - megabytes"); 259 decimals = 1; 260 } 261 else if (size < pow(1000, 4)) 262 { 263 convertedSize = size / powf(1000.0, 3); 264 unit = NSLocalizedString(@"GB", "File size - gigabytes"); 265 decimals = 2; 266 } 267 else 268 { 269 convertedSize = size / powf(1000.0, 4); 270 unit = NSLocalizedString(@"TB", "File size - terabytes"); 271 decimals = 2; 272 } 273 274 //match Finder's behavior 275 NSNumberFormatter * numberFormatter = [[NSNumberFormatter alloc] init]; 276 [numberFormatter setNumberStyle: NSNumberFormatterDecimalStyle]; 277 [numberFormatter setMinimumFractionDigits: 0]; 278 [numberFormatter setMaximumFractionDigits: decimals]; 279 280 NSString * fileSizeString = [numberFormatter stringFromNumber: [NSNumber numberWithFloat: convertedSize]]; 281 [numberFormatter release]; 282 283 if (!notAllowedUnit || ![unit isEqualToString: notAllowedUnit]) 284 fileSizeString = [fileSizeString stringByAppendingFormat: @" %@", unit]; 285 286 if (unitUsed) 287 *unitUsed = unit; 288 289 return fileSizeString; 290} 291 292+ (NSString *) stringForSpeed: (CGFloat) speed kb: (NSString *) kb mb: (NSString *) mb gb: (NSString *) gb 293{ 294 if (speed <= 999.95) //0.0 KB/s to 999.9 KB/s 295 return [NSString localizedStringWithFormat: @"%.1f %@", speed, kb]; 296 297 speed /= 1000.0; 298 299 if (speed <= 99.995) //1.00 MB/s to 99.99 MB/s 300 return [NSString localizedStringWithFormat: @"%.2f %@", speed, mb]; 301 else if (speed <= 999.95) //100.0 MB/s to 999.9 MB/s 302 return [NSString localizedStringWithFormat: @"%.1f %@", speed, mb]; 303 else //insane speeds 304 return [NSString localizedStringWithFormat: @"%.2f %@", (speed / 1000.0), gb]; 305} 306 307@end 308