fill the void

Posted
10 November 2008 @ 10pm

Tagged
development

5 Comments

Cocoa Tutorial: Source List Badges, Part 2

2010-01-25 Update: Perspx has a far better implementation of source list badges as part of his PXSourceList project.

So close but not quite. My original post on source list badges contained code that produced almost the desired result. First off, Ken Orr pointed out that the correct font is Helvetica. That fixed the font, but look at the colors in the screenshots.

Clearly, the final image just looks wrong. The badge’s text color is bright blue, rather than subtle blue. I couldn’t figure out how to identify a defocused NSTableView last time, but tonight I finally found the correct call at CocoaDev. It utilizes NSResponder to see whether the NSTableView is in focus or not. So that enabled me to fix the color issues. Check out the actual implementation below and feel free to use it wherever.

// Subclass of NSTextFieldCell
@implementation BDBadgeCell

// Initialize badge variables, based on Apple Mail.
static int BADGE_BUFFER_LEFT = 4;
static int BADGE_BUFFER_SIDE = 3;
static int BADGE_BUFFER_TOP = 3;
static int BADGE_BUFFER_LEFT_SMALL = 2;
static int BADGE_CIRCLE_BUFFER_RIGHT = 5;
static int BADGE_TEXT_HEIGHT = 14;
static int BADGE_X_RADIUS = 7;
static int BADGE_Y_RADIUS = 8;
static int BADGE_TEXT_MINI = 8;
static int BADGE_TEXT_SMALL = 20;
static int ICON_WIDTH = 16;
static int ICON_HEIGHT_OFFSET = 2;

- (void)setBadgeCount:(int)newBadgeCount
{
    badgeCount = newBadgeCount;
}

- (void)setIcon:(NSImage *)newIcon
{
	[newIcon retain];
	[icon release];
	icon = newIcon;
	[icon setFlipped:YES];
}

- (void)drawInteriorWithFrame:(NSRect)aRect inView:(NSView *)controlView
{
	// Set up badge string and size.
	NSString *badge = [NSString stringWithFormat:@"%d", badgeCount];
	NSSize badgeNumSize = [badge sizeWithAttributes:nil];
	NSFont *badgeFont = [NSFont fontWithName:@"Helvetica-Bold" size:11];

	// Calculate the badge's coordinates.
	int badgeWidth = badgeNumSize.width + BADGE_BUFFER_SIDE * 2;
	if (badgeNumSize.width < BADGE_TEXT_MINI)
	{
		// The text is too short. Decrease the badge's size.
		badgeWidth = BADGE_TEXT_SMALL;
	}
	int badgeX = aRect.origin.x + aRect.size.width - BADGE_CIRCLE_BUFFER_RIGHT - badgeWidth;
	int badgeNumX = badgeX + BADGE_BUFFER_LEFT;
	int badgeY = aRect.origin.y + BADGE_BUFFER_TOP;
	if (badgeNumSize.width < BADGE_TEXT_MINI)
	{
		badgeNumX += BADGE_BUFFER_LEFT_SMALL;;
	}
	NSRect badgeRect = NSMakeRect(badgeX, badgeY, badgeWidth, BADGE_TEXT_HEIGHT);

	// Draw the badge and number.
	NSBezierPath *badgePath = [NSBezierPath bezierPathWithRoundedRect:badgeRect xRadius:BADGE_X_RADIUS yRadius:BADGE_Y_RADIUS];
	BOOL isWindowFront = [[NSApp mainWindow] isVisible];
	BOOL isViewInFocus = [[[[self controlView] window] firstResponder] isEqual:[self controlView]];
	BOOL isCellHighlighted = [self isHighlighted];

	if (isWindowFront &amp;&amp; isViewInFocus &amp;&amp; isCellHighlighted)
	{
		[[NSColor whiteColor] set];
		[badgePath fill];
		NSDictionary *dict = [[NSMutableDictionary alloc] init];
		[dict setValue:badgeFont forKey:NSFontAttributeName];
		[dict setValue:[NSColor alternateSelectedControlColor] forKey:NSForegroundColorAttributeName];
		[badge drawAtPoint:NSMakePoint(badgeNumX,badgeY) withAttributes:dict];
	}
	else if (isWindowFront &amp;&amp; isViewInFocus &amp;&amp; !isCellHighlighted)
	{
		[[NSColor colorWithCalibratedRed:.53 green:.60 blue:.74 alpha:1.0] set];
		[badgePath fill];
		NSDictionary *dict = [[NSMutableDictionary alloc] init];
		[dict setValue:badgeFont forKey:NSFontAttributeName];
		[dict setValue:[NSColor whiteColor] forKey:NSForegroundColorAttributeName];
		[badge drawAtPoint:NSMakePoint(badgeNumX,badgeY) withAttributes:dict];
	}
	else if (isWindowFront &amp;&amp; isCellHighlighted)
	{
		[[NSColor whiteColor] set];
		[badgePath fill];
		NSDictionary *dict = [[NSMutableDictionary alloc] init];
		[dict setValue:badgeFont forKey:NSFontAttributeName];
		[dict setValue:[NSColor colorWithCalibratedRed:.51 green:.58 blue:.72 alpha:1.0] forKey:NSForegroundColorAttributeName];
		[badge drawAtPoint:NSMakePoint(badgeNumX,badgeY) withAttributes:dict];
	}
	else if (!isWindowFront &amp;&amp; isCellHighlighted)
	{
		[[NSColor whiteColor] set];
		[badgePath fill];
		NSDictionary *dict = [[NSMutableDictionary alloc] init];
		[dict setValue:badgeFont forKey:NSFontAttributeName];
		[dict setValue:[NSColor disabledControlTextColor] forKey:NSForegroundColorAttributeName];
		[badge drawAtPoint:NSMakePoint(badgeNumX,badgeY) withAttributes:dict];
	}
	else
	{
		[[NSColor disabledControlTextColor] set];
		[badgePath fill];
		NSDictionary *dict = [[NSMutableDictionary alloc] init];
		[dict setValue:badgeFont forKey:NSFontAttributeName];
		[dict setValue:[NSColor whiteColor] forKey:NSForegroundColorAttributeName];
		[badge drawAtPoint:NSMakePoint(badgeNumX,badgeY) withAttributes:dict];
	}

	// Draw icon.
	NSRect iconRect = aRect;
	iconRect.origin.y = ICON_HEIGHT_OFFSET;
	iconRect.size.height = ICON_WIDTH;
	iconRect.size.width = ICON_WIDTH;
	[icon drawInRect:iconRect fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0];

	// Draw text.
	NSRect labelRect = aRect;
	labelRect.origin.x += ICON_WIDTH;
	labelRect.size.width -= badgeWidth;
	[super drawInteriorWithFrame:labelRect inView:controlView];
}

@end

5 Comments

Posted by
fill the void – RssBucket on Google Code
23 November 2008 @ 10pm

[...] posted updated methods for doing source list badges and link arrows in NSTableViews, so I figured I should update the RssBucket code to reflect those. [...]


Posted by
links for 2009-12-23 | manicwave.com
23 December 2009 @ 5am

[...] fill the void – Cocoa Tutorial: Source List Badges, Part 2 (tags: nstextcell cocoa badge customdrawing) [...]


Posted by
Alex Rozanski
6 January 2010 @ 7am

Great post, however I have a couple of suggestions:

1. For the highlighted colour value, use `keyboardFocusIndicatorColor` instead of `alternateSelectedControlColor`. If the user has the Graphite appearance set in the User Preferences, the highlighted row background colour will be a dark-grey instead of blue, and the badge text colour will not match.

By using `keyboardFocusIndicatorColor`, it will return a blue colour when the Blue style is set in the user Preferences, and a dark grey when Graphite is set, which will match the row background.

2. If the cell for a row is being edited, the Source List is no longer the window’s first responder, so badge text will not be presented in the dark blue colour even if it is selected. To rectify this you can check -[NSTableView editedRow] to see if the current row is being edited – if it is and it is also selected you can give the badge text the dark blue colour too.


Posted by
Decoder
22 June 2010 @ 5am

Hi,

It’s a very good post..

One thing to tell you that – when I tried this updated code… it is working properly when row is not selected.. once it is selected it is hiding badge icon and text over it.

Your previous code is working fine in both cases…


Posted by
bdunagan
19 August 2010 @ 8pm

@Decoder Yep, I’d ignore my implementation and look at Perspx’s PXSourceList project on GitHub.


Leave a Comment