Parmanoir

Debugging with Activity Monitor

Quirky workaround of the week !

To draw a custom header in a table view, derive from NSTableHeaderCell and write your drawing code in highlight:withFrame:inView:. This works fine until you disable column selection : highlight:withFrame:inView: is never called, although the header cell obviously looks highlighted. To understand why, we can use … Activity Monitor ! As strange as it sounds, double click your process listing to get a sample button. Instead of launching Shark and getting a trace from everything, we can sample for one moment while clicking around to highlight those table header cells. Behold :

// Column selection enabled
  1 -[NSTableHeaderView drawRect:]
	1 -[NSTableHeaderView _drawColumnHeaderWithIndexes:]
	  1 -[NSTableHeaderView _drawHeaderOfColumn:]
		1 -[NSTableHeaderCell highlight:withFrame:inView:]
		  1 -[NSTableHeaderCell _drawThemeContents:highlighted:inView:]
			1 -[NSTableHeaderCell drawInteriorWithFrame:inView:]

// Column selection disabled
	3 -[NSTableHeaderView drawRect:]
	  3 -[NSTableHeaderView _drawColumnHeaderWithIndexes:]
		3 -[NSTableHeaderView _drawHeaderOfColumn:]
		  3 -[NSTableHeaderView _drawHeaderCell:withFrame:withStateFromColumn:]
			3 -[NSTableHeaderCell drawWithFrame:inView:]
			  3 -[NSTableHeaderCell _drawThemeContents:highlighted:inView:]
				3 -[NSTableHeaderCell _drawBezelWithFrame:highlighted:inView:]

Et voilà ! When column selection is disabled, highlight:withFrame:inView: is never called. _drawBezelWithFrame:highlighted:inView: is called instead. Hey, there's even some code using that very method ! Using it to draw the highlighted cell does work.

This is not foolproof as sampling just peeks at calls from time to time, not ALL calls : you might need a few runs to catch the cell redrawing. Still, Activity Monitor provides an easy way to target sampling : click on what you want sampled while the sample is being taken. It also gives quasi realtime results without needing to start Shark or wait for application termination.

How Core Image Color Tracking works

CIColorTracking tracks a color blob from an image and overlays another image on top of it. It does that with a Core Image filter. How ?

Image:Core Image Color Tracking.png

To overlay an image on top of blob matching a given color, we need a location : the center of the blob.

If we have a set of 3D points, eg all vertices from a model, we add positions and divide by point count :

  • add all coordinates (x, y) into one point
  • divide by point count
  • there's the center !

To work from an image, we could go about the same way : go over all pixels, find the matching ones, average them down. We can't do that in Core Image as it is limited to kernel functions : no explicit loops, no temporary variables — only a kernel taking inputs (images, colors, numbers) and outputting an image.

The trick that CIColorTracking uses to average matching points is to go over ALL pixels of the image, repeatedly halving its size until it's 1x1. (This is done with a custom filter written in ObjC). It adds pixel coordinates and divides by area :

  • compute a mask from target color : white indicates target color, black is empty
  • convert to a coordinate mask : Red and Green store pixels' position (range is 0..1), Blue stores coverage (copied from the original mask)
  • average that mask down to a 1x1 image
  • divide the average position by the average coverage, this gives the location

SAMPLE QTZ Color Tracking.qtz You'll need to compile CIColorTracking first.

I guess this method will be obsolete in Snow Leopard, where OpenCL will hopefully simplify this down to one function.

Custom NSThemeFrame4

EDIT to completely change the window appearance, check out Matt Gallagher's Drawing a custom window

After seeing Safari 4 drawing its tab bar in a custom way, I wondered how to do that. Each window has a frame view (the superview of contentView) that draws the window, replacing that view's drawRect: with our own will let us draw it ourselves !

Image:Custom NSThemeFrame drawing.png

First, replace the window's frame drawRect: with our own :

	// Get window's frame view class
	id class = [[[window contentView] superview] class];

	// Add our drawRect: to the frame class
	Method m0 = class_getInstanceMethod([self class], @selector(drawRect:));
	class_addMethod(class, @selector(drawRectOriginal:), method_getImplementation(m0), method_getTypeEncoding(m0));
	
	// Exchange methods
	Method m1 = class_getInstanceMethod(class, @selector(drawRect:));
	Method m2 = class_getInstanceMethod(class, @selector(drawRectOriginal:));
	method_exchangeImplementations(m1, m2);

Cocoa will then call our method each time the window frame needs to be drawn. We can draw an entirely custom frame or draw over the existing one. (The attached sample does the latter)

A standard Cocoa window has rounded corners that need to be accounted for by building a clipping path to clip our custom drawing to the window shape :

  • Get the rounded corner radius with roundedCornerRadius
  • Using this radius, build a rounded corner path that describes the window
  • Intersect that path with the current rect
  • Draw our stuff
- (void)drawRect:(NSRect)rect
{
	// Call original drawing method
	[self drawRectOriginal:rect];

	//
	// Build clipping path : intersection of frame clip (bezier path with rounded corners) and rect argument
	//
	NSRect windowRect = [[self window] frame];
	windowRect.origin = NSMakePoint(0, 0);

	float cornerRadius = [self roundedCornerRadius];
	[[NSBezierPath bezierPathWithRoundedRect:windowRect xRadius:cornerRadius yRadius:cornerRadius] addClip];
	[[NSBezierPath bezierPathWithRect:rect] addClip];

	// Any custom drawing goes here
	...
}
Sample Code Image:iconZip.png Custom frame drawing.zip draws an NSImageNameActionTemplate image and a custom color (in kCGBlendModeColorDodge) over the existing frame

Which framework is running ?

When debugging JSCocoa I build JavascriptCore from the WebKit nightly source. This allows gdb to break right into the JavascriptCore source, but this can also be a problem as the nightly JavascriptCore may be less stable than the one in /System.

Sometimes I forget JSCocoa is running from a nightly JavascriptCore and wonder about this new strange crash :) — so it's time for a console warning :

	Dl_info info;
	// Grab a JavascriptCore symbol with dlsym
	// Get its framework path with dladdr
	dladdr(dlsym(RTLD_DEFAULT, "JSClassCreate"), &info);
	
	BOOL runningFromSystemLibrary = [[NSString stringWithUTF8String:info.dli_fname] hasPrefix:@"/System"];
	// Warn if not running from /System
	if (!runningFromSystemLibrary)	NSLog(@"***Running a nightly JavascriptCore***");

CoreUI can paint pretty big4

If you've been using the Safari 4 beta, you've discovered the full page zoom : instead of changing the font size, Safari now zooms everything on the page, from images to buttons. This produces ultra sharp text, dreadfully pixelated images, and … ultra sharp controls !

The controls are drawn by CoreUI, a private framework new to Leopard. To draw controls at any size, CoreUI uses xml "recipes" to mix BIG pictures and PDFs. We can't access CoreUI directly, but as WebKit is open source, we can peek to see how Safari does it !

Image:CoreUI Embiggened.png

Here's the Safari recipe :

  • change the scale of the graphics context
  • scale back the drawing rect by the same amount
  • ask an NSButtonCell to paint itself

… Et voilà !

// We're in a custom view, derived right from NSView
- (void)drawRect:(NSRect)rect
{
	// Allocate a button cell
	id cell = [NSButtonCell new];
	[cell setButtonType:NSMomentaryPushInButton];
	[cell setBezelStyle:NSRoundedBezelStyle];

	// Compute scale - scale the button to fit width
	float scale = (rect.size.width / [cell cellSize].width);

	// Apply scale to the graphics context
	CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];;
	CGContextScaleCTM(context, scale, scale);

	// Scale down drawing rect
	rect.size.width /= scale;
	rect.size.height /= scale;
	
	// Draw rect
	[cell drawWithFrame:rect inView:self];

	// We're done !	
	[cell release];
}

Sample Code Image:iconZip.pngEmbiggened paints a big button. Click to cycle through button styles.

Localization with functions

Localization is very tricky as different languages have very different rules. NSLocalizedString and plural form shows how we need a special case for Russian :
	// To get this result ...
	- 1 fail skopirovalsya.
	- 2 faila skopirovalis'.
	- 5 failov skopirovalos'.

	// ... we need this code.
	if (x == 1) str = NSLocalizedString("KEY_1",...);
	else if (x == 2) str = NSLocalizedString("KEY_2",...);
	else str = NSLocalizedString("KEY_3",...);

Going this way injects grammar specific code for each language into ObjC code that just needs to display information. If we need to support a new language, we may need to change our ObjC code ! That's BAD.

Let's try it in JSCocoa. We'll define strings and functions on the Javascript side …

	// Javascript side : register localizations.
	// Register raw strings
	localizedStrings['Hello World'] = 'Hallo Welt'
	// Register javascript functions that will be called back with arguments
	localizedStrings['BookCount'] = function (count)
			{
				if (count == 0)	return 'Keine Bücher gefunden !'
				if (count == 1)	return 'Ein Buch'
				return count + ' Bücher'
			}

… and use them on the ObjC side :

	// Call JSLocalizedString with an identifier and optional parameters
	id string = JSLocalizedString(@"BookCount", [NSNumber numberWithInt:bookCount], nil);
	[label setStringValue:string];
There ! However many languages we support, we'll always have the same ObjC code : the language-specific logic will be where it belongs, in a localized file. Try out JSLocalizedString in the latest JSCocoa. (Get it from GitHub)

Did you forget to nest alloc and init?

Google's light on the subject, so here's a post

No I didn't, but thanks for the warning ! Calling NSLog(@"%@", [NSString alloc]); will trigger that message. It does indeed come from the description message called on a non fully initialized class.


Follow me on Twitter
Planet Cocoa
Cocoa.fr

2009 04 15Debugging with Activity Monitor
2009 03 25How Core Image Color Tracking works
2009 03 154Custom NSThemeFrame
2009 03 10Which framework is running ?
2009 03 074CoreUI can paint pretty big
2009 02 18Localization with functions
2009 01 30Did you forget to nest alloc and init?
2009 01 16JSCocoa on the iPhone
2009 01 11Mixing WebView and JavascriptCore
2009 01 09Badge overflow
2009 01 09Find your Garbage Collection leaks with Instruments
2008 12 19384 percent dynamic
2008 12 15Class pairs and super
2008 12 113Redirecting NSLog to a file
2008 12 10Whoops ! super is not runtime
2008 12 09Don't overload release or retainCount in JSCocoa
2008 12 05Sidestepping JavascriptCore's JSEvaluateScript
2008 12 031Multiple processes rendering to one window
2008 12 02When does autorelease release ?
2008 12 02Process Sandboxes : an easy path to security ?
Image:rss.png
Image:rss.png

Powered by MediaWiki

Hi ! I'm learning Cocoa to (hopefully !) become an indie developer.

I've written software all my professional life, in C++, PHP, Javascript. I've designed websites and web interfaces. My last venture went into flames as clients were happy but didn't like paying very much.

I've had little luck in the B2B world, I'm hoping for a better future writing Mac applications.