Written on July 30th, 2005.

Warning: Even though this article was written three years ago, I considered it to be relevant enough to move it to my new blog. The article itself has been changed slightly, mostly to fix broken URLs. Also, it’s probably good to know that Mac OS 10.5 has native source lists built-in!

Ever wondered how to turn a NSOutlineView into a source list? I’ve been struggling with it for some time, until Brent Simmons (creator of NetNewsWire) pushed me in the right direction. There’s a lot of information on how to do this floating around on the web, but it’s carefully hidden or obscured.

The reason why I’m writing this blog post is to clear things up. Keywords for this blog posts are: NSOutlineView, gradient, source list. The techniques explained here probably also work for NSTableView; your mileage may vary.

Start by creating a NSOutlineView subclass. You’ll need to add two gradient images to your project: a blue one (active) and a grey one (inactive); I used the ones provided by Matt Gemmell’s iTableView (it’s on the Source Code page).

The method that needs to be overridden in order to draw a gradient is -(void)highlightSelectionInClipRect:(NSRect)clipRect.

The first thing I do in this method is checking whether a row is selected. This may be superfluous, but I still consider it good practice. Here’s the code:

int selectedRow = [self selectedRow];
if(selectedRow == -1)
    return;

Secondly, I lock the focus. Always do this when drawing in a view.

[self lockFocus];

Next up is the gradient. Depending on whether the view is active or not, a different gradient should be used. A view is “active” when its window is the main window, the key window, and the windows’ first responder is the outline view.

NSImage *gradient;
if(([[self window] firstResponder] == self) &&
    [[self window] isMainWindow] &&
    [[self window] isKeyWindow])
{
    gradient = [NSImage imageNamed:@"highlight_blue.tiff"];
}
else
{
    gradient = [NSImage imageNamed:@"highlight_grey.tiff"];
}

The gradient is one pixel wide (at least the one I use). We’ll want the gradient to be scaled horizontally if we want the entire row to have the gradient background, and not just the first pixel. This is how we do it:

[gradient setScalesWhenResized:YES];

We’ll need to flip the gradient upside down so its get drawn properly.

[gradient setFlipped:YES];

Next, get the rectangle corresponding to the selected row. This is where the gradient needs to be drawn in.

NSRect drawingRect = [self rectOfRow:selectedRow];

We need the size of the gradient, and the rectangle from which the image shall be drawn.

NSRect imageRect;
imageRect.origin = NSZeroPoint;
imageRect.size = [gradient size];

Finally, we draw the gradient. It is drawn in the rectangle corresponding to the selected row (see up). The image is scaled automatically. Also note that we don’t draw if the rectangle the gradient is drawn into has zero height or zero width.

if(drawingRect.size.width != 0 && drawingRect.size.height != 0)
{
    [gradient drawInRect:drawingRect
                fromRect:imageRect
               operation:NSCompositeSourceOver
                fraction:1.];
}

We locked the focus before, don’t forget to unlock it.

[self unlockFocus];

This won’t work yet. You’ll need to override -(id)_highlightColorForCell:(NSCell *)cell to return nil so the view doesn’t attempt to draw a solid background color on top of our gradient.

There’s one more thing. A selected row’s text color stays black while it should be white. To fix this, we need a NSTextFieldCell subclass which overrides -(void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView and draws the text in white.

This method starts by checking whether it is highlighted (selected) or not. If it is not, it simply lets the drawing be done by super.

if(![self isHighlighted])
{
    [super drawInteriorWithFrame:frame inView:view];
    return;
}

Next, we draw the text in white. I must admit that I mercilessly stole this code from Matt Gemmel’s iTableView.

NSRect inset = frame;
inset.origin.x += 2;
NSMutableDictionary *attributes =
    [NSMutableDictionary dictionaryWithDictionary:
        [[self attributedStringValue] attributesAtIndex:0
                                         effectiveRange:NULL
        ]
    ];
[attributes setValue:[NSColor whiteColor] forKey:@"NSColor"];
[[self stringValue] drawInRect:inset withAttributes:attributes];

The last thing we should do is make sure our source list uses this NSTextFieldCell subclass. This belongs in the source list outline view’s -awakeFromNib method:

[[self outlineTableColumn]
    setDataCell:[[[MySourceListTextCell alloc] init] autorelease]
];

That’s it. Enjoy!

Before I leave, I’d like to thank Brent Simmons, Matt Gemmell and, more importantly, all contributors of the NSTableViewInFinderWindows article on CocoaDev! Without this article I wouldn’t have been able to give my outline view that cool gradient.