IBPalette Project: Drop NSActionCell proxy icon into NSTableColumn --> Weirdness
IBPalette Project: Drop NSActionCell proxy icon into NSTableColumn --> Weirdness
- Subject: IBPalette Project: Drop NSActionCell proxy icon into NSTableColumn --> Weirdness
- From: Jerry Krinock <email@hidden>
- Date: Sun, 3 Jun 2007 19:05:49 -0700
I'm building a widget, a subclass of NSActionCell which can be
dragged into an NSTableColumn. After debugging in my project, I'm
now incorporating it into an IBPalette.
Testing my palette in Interface Builder, when I drop my widget into
an NSTableColumn, my widget class gets the message -
attributedStringForObjectValue:withDefaultAttributes:, which is
defined in NSFormatter. That's obviously wrong, since I don't
declare an NSFormatter anywhere in my project, and indeed an Xcode
"Find in project" does not find NSFormatter anywhere. I've seen that
a working example project gets a -drawWithFrame:inView: instead.
(But in this working example, the widget is an NSView subclass, not
NSCell). Anyhow, so I implemented -drawWithFrame:inView:, but it
doesn't get invoked.
Just for fun, I implemented -
attributedStringForObjectValue:withDefaultAttributes:, returning an
attributed string "foobar", and now, when I drop my widget's proxy
icon into an NSTableColumn, it draws the string "foobar" in each
column. According to Apple HIG, after dropping a banana, you should
see a banana. I therefore expect to see my widget's proxy icon in
each row. This is what happens when you drop, for example an
NSButtonCell.
In my widget, I have sent -setType:NSImageCell, -setImage:
[aNonNilImage], and verified that my widget's returns NSImageCellType
when asked for its -type.
To make a long story short, I can't find any example palette project
that successfully drops a widget proxy icon into an NSTableColumn.
The "Switch Cell" in BusyPalette doesn't work. Maybe the clues I've
provided here are enough for some knowledgeable person to help me out?
Jerry Krinock
//********** SSRadioButtonCell.h ******************//
// Somewhat adapted from Buzz Andersen's Cocoalicious class,
SFHFRatingCell
#import <Cocoa/Cocoa.h>
@interface SSRadioButtonCell : NSActionCell <NSCopying, NSCoding> {
NSImage* _selectedImage ;
NSImage* _deselectedImage ;
NSMutableArray* _widths ;
NSRect _currentFrameInControlView ;
int _numberOfButtons ;
}
- (void)setSelectedImage:(NSImage*)newImage ;
- (void)setDeselectedImage:(NSImage*)newImage ;
- (NSImage*)selectedImage ;
- (NSImage*)deselectedImage ;
- (NSNumber *) calculateSelectionForPoint: (NSPoint) point inView:
(NSView *) controlView;
- (void)setNumberOfButtons:(int)x ;
- (int)numberOfButtons ;
- (void)setWidth:(float)width forSegment:(int)segment ;
@end
//********** SSRadioButtonCell.m ******************//
#import "SSRadioButtonCell.h"
#define VERTICAL_INSET 0.0
#define DEFAULT_BUTTON_SPACING 1.0
#define END_INSET 2.0
@implementation SSRadioButtonCell
// Implemented this NSFormatter method for debugging. Doesn't make
any sense, but I get
// this message when dropping an SSRadioButtonCell onto an
NSTableColumn in Interface Builder.
- (NSAttributedString*)attributedStringForObjectValue:(id)object
withDefaultAttributes:(NSDictionary*)attributes {
NSAttributedString* as = [[NSAttributedString alloc]
initWithString:@"SSRadioButtonCell Placeholder"] ;
NSLog(@"DebugLog: 448 returning attributed string for cell type %
i", [self type]) ;
return [as autorelease] ;
}
// Accessors
- (void)setObjectValue:(id <NSCopying>)object {
if (object) {
[super setObjectValue:object] ;
}
}
- (void)setSelectedImage:(NSImage*)newImage {
if (_selectedImage != newImage) {
[_selectedImage release];
_selectedImage = [newImage copy];
}
}
- (NSImage*)selectedImage {
return _selectedImage ;
}
- (void)setDeselectedImage:(NSImage*)newImage {
if (_deselectedImage != newImage) {
[_deselectedImage release];
_deselectedImage = [newImage copy];
}
}
- (NSImage*)deselectedImage {
return _deselectedImage ;
}
- (void)setWidths:(NSMutableArray*)newWidths {
if (_widths != newWidths) {
[_widths release];
_widths = [newWidths mutableCopy] ;
}
}
- (NSMutableArray*)widths {
return _widths ;
}
- (void)setWidth:(float)width forSegment:(int)segment {
NSMutableArray* widths = [self widths] ;
int widthsCount = [widths count] ;
if ((segment >= 0) && (segment < widthsCount)) {
float endInset = ((segment==0) || (segment==(widthsCount-1)))
? END_INSET : 0.0 ;
NSNumber* widthNumber = [NSNumber numberWithFloat:(width -
endInset)] ;
[widths replaceObjectAtIndex:segment withObject:widthNumber] ;
}
}
// NSCoder Protocol Conformance
// Probably NSCoder protocol conformance is needed for archiving by
Interface Builder.
- (id)initWithCoder:(NSCoder*)decoder {
self = [super initWithCoder:decoder];
_selectedImage = [[decoder decodeObjectForKey:@"selectedImage"]
retain];
_deselectedImage = [[decoder
decodeObjectForKey:@"deselectedImage"] retain];
return self;
}
- (void) encodeWithCoder:(NSCoder*)encoder {
[super encodeWithCoder:encoder];
[encoder encodeObject:[self selectedImage]
forKey:@"selectedImage"];
[encoder encodeObject:[self deselectedImage]
forKey:@"deselectedImage"];
return;
}
// NSCopying Protocol Conformance
// The documentation
// Control and Cell Programming Topics for Cocoa
// Subclassing NSCell
// makes the following stupid statement:
// "If the subclass contains instance variables that hold pointers to
objects,
// consider overriding copyWithZone: to duplicate the objects. The
default
// version copies only pointers to the objects."
// What the hell do them mean by "consider"? This is supposed to be
// technical documentation, not a poetry class. Well,
// it turns out that, if this cell is used in an NSTableColumn at least,
// copies seem to be made whenever the cell is clicked.
// Therefore, if you don't implement the following, you get frequent
crashes.
- (id) copyWithZone:(NSZone*)zone {
SSRadioButtonCell *cellCopy = [super copyWithZone:zone];
// For explanation of this see:
// Memory Management Programming Guide for Cocoa
// Implementing Object Copy
// Using NSCopyObject()
cellCopy->_selectedImage = nil;
[cellCopy setSelectedImage:[self selectedImage]];
cellCopy->_deselectedImage = nil;
[cellCopy setDeselectedImage:[self deselectedImage]];
cellCopy->_widths = nil ;
[cellCopy setWidths:[self widths] ] ;
cellCopy->_currentFrameInControlView = _currentFrameInControlView ;
cellCopy->_numberOfButtons = _numberOfButtons ;
return cellCopy;
}
// The Meat
- (id) init {
self = [super init] ;
if (self != nil) {
// Since -[NSImage imageNamed:] does not work in frameworks
for some reason,
// we have to dig for image resources with our bare hands...
NSBundle* bundle = [NSBundle bundleForClass:[self class]];
NSString* imagePath ;
NSImage* image ;
[self setType:NSImageCellType] ;
// The above does not "take" until you set the image
imagePath = [NSBundle
pathForResource:@"SSRadioButtonCellIcon" ofType:@"tiff"
inDirectory:[bundle bundlePath]];
image = [[NSImage alloc] initByReferencingFile:imagePath];
NSLog(@"DebugLog: 3838 setting image to %x %@", image, image) ;
[self setImage:image] ;
[image release] ;
imagePath = [NSBundle
pathForResource:@"RadioButtonSelectedSmall" ofType:@"png"
inDirectory:[bundle bundlePath]];
image = [[NSImage alloc] initByReferencingFile:imagePath];
[self setSelectedImage:image] ;
[image release] ;
imagePath = [NSBundle
pathForResource:@"RadioButtonDeselectedSmall" ofType:@"png"
inDirectory:[bundle bundlePath]];
image = [[NSImage alloc] initByReferencingFile:imagePath];
[self setDeselectedImage:image] ;
[image release] ;
[self setContinuous:YES] ;
[self setWidths:[NSMutableArray arrayWithCapacity:16]] ;
// Set to a harmless default value:
[self setObjectValue:[NSNumber numberWithInt:1]] ;
}
NSLog(@"DebugLog: 3333 after initting, type is %i", [self type]) ;
return self;
}
- (void) dealloc {
[_selectedImage release] ;
[_deselectedImage release] ;
[_widths release] ;
[super setObjectValue:nil] ;
[super dealloc] ;
}
- (id)defaultObjectValueAtIndex:(int)index {
return [[[SSRadioButtonCell alloc] init] autorelease] ;
}
- (id)stringForObjectValue:(id)object {
return [object description] ;
}
- (void)setNumberOfButtons:(int)x {
_numberOfButtons = x ;
NSMutableArray* widths = [self widths] ;
int currentWidthsCount = [widths count] ;
float defaultWidth = [[self selectedImage] size].width +
DEFAULT_BUTTON_SPACING ;
NSNumber* defaultWidthNumber = [NSNumber
numberWithFloat:defaultWidth] ;
int i ;
for (i=currentWidthsCount; i<x; i++) {
[widths addObject:defaultWidthNumber] ;
}
}
- (int)numberOfButtons {
return _numberOfButtons ;
}
// I implemented this to see if it would be invoked after dropping in
// Interface Builder, but it never gets invoked.
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
NSLog(@"DebugLog: 13079 drawWithFrame") ;
[controlView lockFocus];
NSImage* image = [self image] ;
[image setFlipped: [controlView isFlipped]];
[image drawInRect:cellFrame
fromRect:NSMakeRect(0,
0,
NSWidth(cellFrame),
NSHeight(cellFrame))
operation:NSCompositeSourceOver fraction:1.0] ;
[controlView unlockFocus];
}
// This method is invoked to draw the cell when it is instantiated and
// run in an actual application (i.e., not in Interface Builder).
- (void) drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)
controlView {
NSLog(@"DebugLog: 5950 drawInteriorWithFrame") ;
NSNumber *objectValue = [self objectValue];
if (objectValue) {
[controlView lockFocus];
int selectedIndex = [objectValue intValue] ;
int i ;
NSImage* selectedImage = [self selectedImage] ;
NSImage* deselectedImage = [self deselectedImage] ;
NSArray* widths = [self widths] ;
float x = NSMinX(cellFrame) ; // will increase in loop
float y = NSMinY(cellFrame) + VERTICAL_INSET ; // will
stay constant
float height = [selectedImage size].height ; // will
stay constant
for (i = 0; i < [self numberOfButtons]; i++) {
float width = [[widths objectAtIndex:i] floatValue] ;
NSRect rect = NSMakeRect(x, y, width, height) ;
if (NSIntersectsRect(rect, cellFrame)) {
NSRect intersectRect = NSIntersectionRect(rect,
cellFrame) ;
NSImage* image = (i == selectedIndex) ?
selectedImage : deselectedImage ;
float centeringOffset = (width - [image
size].width) / 2 ;
[image setFlipped: [controlView isFlipped]];
[image drawInRect:NSOffsetRect(intersectRect,
centeringOffset, 0.0)
fromRect:NSMakeRect(0,
0,
NSWidth(intersectRect),
NSHeight(intersectRect))
operation:NSCompositeSourceOver fraction:1.0] ;
}
x += width ;
}
[controlView unlockFocus];
}
}
- (BOOL) trackMouse:(NSEvent*)theEvent
inRect:(NSRect)cellFrame
ofView:(NSView*)controlView
untilMouseUp:(BOOL)untilMouseUp {
_currentFrameInControlView = [self drawingRectForBounds:cellFrame];
return [super trackMouse:theEvent
inRect:cellFrame
ofView:controlView
untilMouseUp:untilMouseUp] ;
}
- (BOOL) startTrackingAt:(NSPoint)startPoint
inView:(NSView*)controlView {
[super startTrackingAt:startPoint
inView:controlView] ;
return YES;
}
- (BOOL) continueTracking:(NSPoint)lastPoint
at:(NSPoint)currentPoint
inView:(NSView *)controlView {
if ([super continueTracking:lastPoint
at:currentPoint
inView: controlView]) {
NSNumber* newObjectValue = [self
calculateSelectionForPoint:currentPoint
inView:
controlView] ;
[self setObjectValue:newObjectValue] ;
}
return YES;
}
- (void) stopTracking:(NSPoint)
lastPoint at:(NSPoint)stopPoint
inView:(NSView*)controlView
mouseIsUp:(BOOL)flag {
NSNumber* newObjectValue = [self
calculateSelectionForPoint:stopPoint
inView:
controlView] ;
[self setObjectValue:newObjectValue];
[super stopTracking: lastPoint at: stopPoint inView: controlView
mouseIsUp: flag];
}
- (NSNumber*)calculateSelectionForPoint:(NSPoint)point
inView:(NSView*)controlView {
float zeroX = NSMinX([self
drawingRectForBounds:_currentFrameInControlView]) ;
float x = point.x - zeroX ;
NSArray* widths = [self widths] ;
int numberOfWidths = [widths count] ;
float end = 0 ;
int selectedIndex = 0 ;
int i ;
for (i=0; i<numberOfWidths; i++) {
end += [[widths objectAtIndex:i] floatValue] ;
if (x < end) {
selectedIndex = i ;
break ;
}
}
NSNumber* selection = [NSNumber numberWithInt:selectedIndex] ;
return selection ;
}
@end
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden