CocoaDev

Edit AllPages

Hello,

I know this has been amply discussed elsewhere and there are various solutions, but thought this might be useful to somebody else out there. I wanted Mail 2.0/iTunes-style gradient selections in the NSOutlineView in my app. Thanks to others who have posted here and elsewhere, this wasn’t too difficult. My implementation doesn’t use an external image, and allows you to specify whether the gradient is contiguous across multiple selected rows, whether to have a one pixel gap between selected rows, and whether - like in Mail - the outline view (or table view) should only ever show the disabled/unfocused grey gradient colour and never turn blue even when you click on a row.

Note that you will need NSBezierPath_AMShading.h and NSBezierPath_AMShading.m (a category of NSBezierPath that makes it easy to do gradient fills) by Andreas Mayer from http://www.harmless.de/cocoa.html (these files are contained in the AMRolloverButton example code).

This code was particularly helped by the information given by one of the Omni guys here: http://wilshipley.com/blog/2005/07/pimp-my-code-part-3-gradient.html

Here is my subclass of NSOutlineView:

Interface file:

#import <Cocoa/Cocoa.h>

@interface KBGradientOutlineView : NSOutlineView { NSIndexSet *draggedRows;

BOOL usesGradientSelection;
BOOL selectionGradientIsContiguous;
BOOL usesDisabledGradientSelectionOnly;
BOOL hasBreakBetweenGradientSelectedRows; }

/* Useful for delegate when deciding how to colour text */

// Gradient selection methods /* Sets whether the outline view should use gradient selection bars. */

/* Sets whether gradient selections should be contiguous across multiple rows. (iTunes and Mail don’t have this, but I think it looks better.) */

/* Sets whether the selection should always look disabled (grey), even when the outline view has the focus (like in Mail) */

/* Sets whether selected rows have a pixel gap between them that is the background colour rather than the selection colour */

Implementation file:

#import “KBGradientOutlineView.h” #import “NSBezierPath_AMShading.h” // For gradient highlighting

// We override (and may call) an undocumented private NSTableView method, // so we need to declare that here @interface NSObject (NSTableViewPrivateMethods)

@implementation KBGradientOutlineView

@end

A side effect of overriding these methods is that the text will remain black even when selected, which is annoying (I guess this is because drawRow:clipRect: checks that the selection colour is blue to determine whether or not to use white text). This can be fixed in the outline view’s delegate, as follows:

Finally, if you also want a nice blue background like in Mail or iTunes, the color is:

[NSColor colorWithDeviceRed:(231.0/255.0) green:(237.0/255.0) blue:(248.0/255.0) alpha:1.0]

Some grabs:

http://www.rumramruf.com/ScrivenerBeta/ScrivenerGradient1.jpg

http://www.rumramruf.com/ScrivenerBeta/ScrivenerGradient2.jpg

http://www.rumramruf.com/ScrivenerBeta/ScrivenerGradient3.jpg

http://www.rumramruf.com/ScrivenerBeta/ScrivenerGradient4.jpg

http://www.rumramruf.com/ScrivenerBeta/ScrivenerGradient5.jpg

Hope that is useful to someone, KB


Very cool!

Another (simple, featureless) solution that just came to me..

Not nearly as cool or flexible but it seemed to work well enough in my tests.

Thanks… I just realised that my original code didn’t take into account drag and drop, so that if you dragged a selected row, its text would be drawn in white in the drag image. I’ve updated it to account for that. KB


I added a couple optimization-oriented comments. :)


Thanks. The reason those parts call [self setNeedsDisplay:YES] rather than [self setNeedsDisplayInRect:[self rectOfRow:row]] is that they need to force display of more than just the row being selected when the gradient is contiguous - all selected rows have to be redrawn… I’ve added comments to the code to make that clearer. I suppose it could iterate through the selected rows and make a union rect, but with the extra iteration I doubt that would optimise much. :) I have optimised it very slightly, though, so that it only redraws everything if there is a multiple selection (EDIT: and changed it back again, because that caused some minor drawing glitches when going from multiple selection to single). KB


I should’ve known that already. smacking forehead Thanks for the clarification.


Updated 15/12/05: Just got rid of the NSIntersectionRect(highlightRect,clipRect) stuff, as this causes drawing problems when scrolling because the gradient can get drawn in the wrong place. It means drawing more than strictly necessary, but that’s not too heavy. KB


Instead of using a delegate, you can do the following (it doesn’t break bindings; it just alters the cell before display):