• Open Menu Close Menu
  • Apple
  • Shopping Bag
  • Apple
  • Mac
  • iPad
  • iPhone
  • Watch
  • TV
  • Music
  • Support
  • Search apple.com
  • Shopping Bag

Lists

Open Menu Close Menu
  • Terms and Conditions
  • Lists hosted on this site
  • Email the Postmaster
  • Tips for posting to public mailing lists
IBPalette Project: Drop NSActionCell proxy icon into NSTableColumn --> Weirdness
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

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


  • Prev by Date: Re: RS: RTF to unicode conversion
  • Next by Date: NSURLDownload notifies on wrong thread?
  • Previous by thread: Re: How does NSArrayController bind to data model from IB?
  • Next by thread: NSURLDownload notifies on wrong thread?
  • Index(es):
    • Date
    • Thread