// // CTBadge.m // CTWidgets // // Created by Chad Weider on 2/14/07. // Copyright (c) 2007 Chad Weider. // Some rights reserved: // // Version: 1.5 #import "CTBadge.h" const float CTLargeBadgeSize = 46.; const float CTSmallBadgeSize = 23.; const float CTLargeLabelSize = 24.; const float CTSmallLabelSize = 11.; static NSString *stringForValue(unsigned value) { if(value < 100000) return [NSString stringWithFormat:@"%u", value]; else //give infinity return [NSString stringWithUTF8String:"\xe2\x88\x9e"]; } @interface CTBadge (Private) - (NSImage *)badgeMaskOfSize:(float)size length:(unsigned)length; //return a badge with height of to fit characters - (NSAttributedString *)labelForString:(NSString *)string size:(unsigned)size; //returns appropriately attributed label string (not autoreleased) - (CTGradient *)badgeGradient; //gradient used to fill badge mask @end @implementation CTBadge - (id)init { self = [super init]; if (self != nil) { badgeColor = nil; labelColor = nil; [self setBadgeColor:[NSColor redColor]]; [self setLabelColor:[NSColor whiteColor]]; } return self; } - (void)dealloc { if(badgeColor != nil) [badgeColor release]; if(labelColor != nil) [labelColor release]; [super dealloc]; } + (CTBadge *)systemBadge { id newInstance = [[[self class] alloc] init]; return [newInstance autorelease]; } + (CTBadge *)badgeWithColor:(NSColor *)badgeColor labelColor:(NSColor *)labelColor; { id newInstance = [[[self class] alloc] init]; [newInstance setBadgeColor:badgeColor]; [newInstance setLabelColor:labelColor]; return [newInstance autorelease]; } #pragma mark - #pragma mark Appearance - (void)setBadgeColor:(NSColor *)theColor; { if(badgeColor != nil) [badgeColor release]; badgeColor = theColor; [badgeColor retain]; } - (void)setLabelColor:(NSColor *)theColor; { if(labelColor != nil) [labelColor release]; labelColor = theColor; [labelColor retain]; } - (NSColor *)badgeColor { return badgeColor; } - (NSColor *)labelColor { return labelColor; } #pragma mark - #pragma mark Drawing - (NSImage *)smallBadgeForValue:(unsigned)value //does drawing in it's own special way { return [self badgeOfSize:CTSmallBadgeSize forString:stringForValue(value)]; } - (NSImage *)smallBadgeForString:(NSString *)string { return [self badgeOfSize:CTSmallBadgeSize forString:string]; } - (NSImage *)largeBadgeForValue:(unsigned)value { return [self badgeOfSize:CTLargeBadgeSize forString:stringForValue(value)]; } - (NSImage *)largeBadgeForString:(NSString *)string { return [self badgeOfSize:CTLargeBadgeSize forString:string]; } - (NSImage *)badgeOfSize:(float)size forValue:(unsigned)value { return [self badgeOfSize:(float)size forString:stringForValue(value)]; } - (NSImage *)badgeOfSize:(float)size forString:(NSString *)string { float scaleFactor = 1; if(size <= 0) [NSException raise:NSInvalidArgumentException format:@"%@ %@: size (%f) must be positive", [self class], NSStringFromSelector(_cmd), size]; else if(size <= CTSmallBadgeSize) scaleFactor = size/CTSmallBadgeSize; else scaleFactor = size/CTLargeBadgeSize; //Label stuff ----------------------------------------------- NSAttributedString *label; NSSize labelSize; if(size <= CTSmallBadgeSize) label = [self labelForString:string size:CTSmallLabelSize*scaleFactor]; else label = [self labelForString:string size:CTLargeLabelSize*scaleFactor]; labelSize = [label size]; //Badge stuff ----------------------------------------------- NSImage *badgeImage; //this the image with the gradient fill NSImage *badgeMask ; //we nock out this mask from the gradient CTGradient *badgeGradient = [self badgeGradient]; float shadowOpacity, shadowOffset, shadowBlurRadius; int angle; if(size <= CTSmallBadgeSize) { shadowOpacity = .6; shadowOffset = floorf(1*scaleFactor); shadowBlurRadius = ceilf(1*scaleFactor); } else { shadowOpacity = .8; shadowOffset = ceilf(1*scaleFactor); shadowBlurRadius = ceilf(2*scaleFactor); } if ([label length] <= 3) //Badges have different gradient angles angle = -45; else angle = -30; badgeMask = [self badgeMaskOfSize:size length:[label length]]; NSSize badgeSize = [badgeMask size]; NSPoint origin = NSMakePoint(shadowBlurRadius, shadowBlurRadius+shadowOffset); badgeImage = [[NSImage alloc] initWithSize:NSMakeSize(badgeSize.width + 2*shadowBlurRadius, //sometimes it needs more badgeSize.height + 2*shadowBlurRadius - shadowOffset + (size <= CTSmallBadgeSize))]; //space when small [badgeImage lockFocus]; [badgeGradient fillRect:NSMakeRect(origin.x, origin.y, floorf(badgeSize.width), floorf(badgeSize.height)) angle:angle]; //apply the gradient [badgeMask compositeToPoint:origin operation: NSCompositeDestinationAtop]; //knock out the badge area [label drawInRect:NSMakeRect(origin.x+floorf((badgeSize.width-labelSize.width)/2), origin.y+floorf((badgeSize.height-labelSize.height)/2), badgeSize.width, labelSize.height)]; //draw label in center [badgeImage unlockFocus]; //Final stuff ----------------------------------------------- NSImage *image = [[NSImage alloc] initWithSize:[badgeImage size]]; [image lockFocus]; [NSGraphicsContext saveGraphicsState]; NSShadow *theShadow = [[NSShadow alloc] init]; [theShadow setShadowOffset: NSMakeSize(0,-shadowOffset)]; [theShadow setShadowBlurRadius:shadowBlurRadius]; [theShadow setShadowColor:[[NSColor blackColor] colorWithAlphaComponent:shadowOpacity]]; [theShadow set]; [theShadow release]; [badgeImage compositeToPoint:NSZeroPoint operation:NSCompositeSourceOver]; [NSGraphicsContext restoreGraphicsState]; [image unlockFocus]; [label release]; [badgeImage release]; return [image autorelease]; } - (NSImage *)badgeOverlayImageForString:(NSString *)string insetX:(float)dx y:(float)dy; { NSImage *badgeImage = [self largeBadgeForString:string]; NSImage *overlayImage = [[NSImage alloc] initWithSize:NSMakeSize(128,128)]; //draw large icon in the upper right corner of the overlay image [overlayImage lockFocus]; NSSize badgeSize = [badgeImage size]; [badgeImage compositeToPoint:NSMakePoint(128-dx-badgeSize.width,128-dy-badgeSize.height) operation:NSCompositeSourceOver]; [overlayImage unlockFocus]; return [overlayImage autorelease]; } - (void)badgeApplicationDockIconWithString:(NSString *)string insetX:(float)dx y:(float)dy; { NSImage *appIcon = [NSImage imageNamed:@"NSApplicationIcon"]; NSImage *badgeOverlay = [self badgeOverlayImageForString:string insetX:dx y:dy]; //Put the appIcon underneath the badgeOverlay [badgeOverlay lockFocus]; [appIcon compositeToPoint:NSZeroPoint operation:NSCompositeDestinationOver]; [badgeOverlay unlockFocus]; [NSApp setApplicationIconImage:badgeOverlay]; } - (NSImage *)badgeOverlayImageForValue:(unsigned)value insetX:(float)dx y:(float)dy { return [self badgeOverlayImageForString:stringForValue(value) insetX:dx y:dy]; } - (void)badgeApplicationDockIconWithValue:(unsigned)value insetX:(float)dx y:(float)dy { [self badgeApplicationDockIconWithString:stringForValue(value) insetX:dx y:dy]; } #pragma mark - #pragma mark Misc. - (CTGradient *)badgeGradient { CTGradient *aGradient = [CTGradient gradientWithBeginningColor:[self badgeColor] endingColor:[[self badgeColor] shadowWithLevel:(1./3.)]]; aGradient = [aGradient addColorStop:[self badgeColor] atPosition:(1./3.)]; return aGradient; } - (NSAttributedString *)labelForString:(NSString *)label size:(unsigned)size { //set Attributes to use on String --------------------------- NSFont *labelFont; if(size <= CTSmallLabelSize) labelFont = [NSFont boldSystemFontOfSize:size]; else labelFont = [NSFont fontWithName:@"Helvetica-Bold" size:size]; NSMutableParagraphStyle *pStyle = [[NSMutableParagraphStyle alloc] init];[pStyle setAlignment:NSCenterTextAlignment]; NSDictionary *attributes = [[NSDictionary alloc] initWithObjectsAndKeys:[self labelColor], NSForegroundColorAttributeName, labelFont , NSFontAttributeName , nil]; [pStyle release]; //Label stuff if([label length] >= 6) //replace with summarized string - ellipses at end and a zero-width space to trick us into using the 5-wide badge label = [NSString stringWithFormat:@"%@%@", [label substringToIndex:3], [NSString stringWithUTF8String:"\xe2\x80\xa6\xe2\x80\x8b"]]; NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:label attributes:attributes]; [attributes release]; return attributedString; } - (NSImage *)badgeMaskOfSize:(float)size length:(unsigned)length; { NSImage *badgeMask; if(length <=2) badgeMask = [NSImage imageNamed:@"CTBadge_1.pdf"]; else if(length <=3) badgeMask = [NSImage imageNamed:@"CTBadge_3.pdf"]; else if(length <=4) badgeMask = [NSImage imageNamed:@"CTBadge_4.pdf"]; else badgeMask = [NSImage imageNamed:@"CTBadge_5.pdf"]; if(size > 0 && size != [badgeMask size].height) { [badgeMask setName:nil]; [badgeMask setScalesWhenResized:YES]; [badgeMask setSize:NSMakeSize([badgeMask size].width*(size/[badgeMask size].height), size)]; } return badgeMask; } @end