Edit sub-cells in a NSActionCell subclass

Discussion in 'Mac Programming' started by misee, Aug 6, 2010.

  1. misee macrumors member

    Joined:
    Jul 4, 2010
    #1
    I have a custom cell for a NSTableView. My cell is a subclass of NSActionCell and contains three sub-cells, each a NSTextFieldCell. Two are at the top of my cell, placed next to each other and contain only one line of text, the third is below them and takes up the rest of the cells space (there can be multiple lines). Now I have two things I can't get to work:
    1. All of the sub-cells should be editable, however I couldn't find a way to do so.
    2. I would like to be able to adjust the row height, so I somehow need to figure out the height that the third cell needs to display it's text.

    I would be glad if you could point me to the right direction to accomplish these things.

    Here's the code for my custom cell:
    Code:
    //
    //  SESubtitleCell.h
    //  Custom Table Cell
    //
    
    #import <Cocoa/Cocoa.h>
    
    
    @interface SESubtitleCell : NSActionCell
    {
    @private
    	NSTextFieldCell *startTimeCell;
    	NSTextFieldCell *endTimeCell;
    	NSTextFieldCell *subtitleTextCell;
    }
    
    @property (retain) NSString *startTime;
    @property (retain) NSString *endTime;
    @property (retain) NSString *subtitleText;
    
    @end
    
    Code:
    //
    //  SESubtitleCell.m
    //  Custom Table Cell
    //
    
    #import "SESubtitleCell.h"
    
    static const CGFloat _SESubtitleCellSubtitleTextLeftInset = 4.0;
    static const CGFloat _SESubtitleCellSubtitleTextTopInset = 5.0;
    static const CGFloat _SESubtitleCellTimeTopInset = 3.0;
    static const CGFloat _SESubtitleCellTimeHeight = 15.0;
    static const CGFloat _SESubtitleCellTimeWidth = 80.0;
    static const CGFloat _SESubtitleCellStartTimeInset = 4.0;
    static const CGFloat _SESubtitleCellEndTimeInset = 7.0;
    
    
    @interface SESubtitleCell (private)
    
    - (NSRect)_subtitleFrameForInteriorFrame:(NSRect)frame;
    - (NSRect)_startTimeFrameForInteriorFrame:(NSRect)frame;
    - (NSRect)_endTimeFrameForInteriorFrame:(NSRect)frame;
    
    - (void)_ensureAllCellsCreated;
    - (void)_ensureStartTimeCellCreated;
    - (void)_ensureEndTimeCellCreated;
    - (void)_ensureSubtitleTextCellCreated;
    
    @end
    
    
    
    @implementation SESubtitleCell
    
    - (id)copyWithZone:(NSZone *)zone
    {
    	SESubtitleCell *result = [super copyWithZone:zone];
    	if (result == nil)
    		return nil;
    	
    	result->startTimeCell = [startTimeCell copyWithZone:zone];
    	result->endTimeCell = [endTimeCell copyWithZone:zone];
    	result->subtitleTextCell = [subtitleTextCell copyWithZone:zone];
    	
    	return result;
    }
    
    - (void)dealloc
    {
    	[startTimeCell release];
    	[endTimeCell release];
    	
    	[super dealloc];
    }
    
    // MARK: -
    // MARK: Getters and Setters
    
    @dynamic startTime;
    @dynamic endTime;
    @dynamic subtitleText;
    
    - (NSString *)startTime
    {
    	return [startTimeCell title];
    }
    
    - (void)setStartTime:(NSString *)newStartTime
    {
    	[self _ensureStartTimeCellCreated];
    	
    	[startTimeCell setTitle:newStartTime];
    }
    
    - (NSString *)endTime
    {
    	return [endTimeCell title];
    }
    
    - (void)setEndTime:(NSString *)newEndTime
    {
    	[self _ensureEndTimeCellCreated];
    	
    	[endTimeCell setTitle:newEndTime];
    }
    
    - (NSString *)subtitleText
    {
    	return [subtitleTextCell title];
    }
    
    - (void)setSubtitleText:(NSString *)newSubtitleText
    {
    	[self _ensureSubtitleTextCellCreated];
    	
    	[subtitleTextCell setTitle:newSubtitleText];
    }
    
    // MARK: -
    // MARK: Override some setters that must be set for the sub-cells too
    
    - (void)setControlView:(NSView *)controlView
    {
    	[super setControlView:controlView];
    	
    	[self _ensureAllCellsCreated];
    	
    	[startTimeCell setControlView:controlView];
    	[endTimeCell setControlView:controlView];
    	[subtitleTextCell setControlView:controlView];
    }
    
    - (void)setBackgroundStyle:(NSBackgroundStyle)style
    {
    	[super setBackgroundStyle:style];
    	
    	[self _ensureAllCellsCreated];
    	
    	[startTimeCell setBackgroundStyle:style];
    	[endTimeCell setBackgroundStyle:style];
    	[subtitleTextCell setBackgroundStyle:style];
    }
    
    // MARK: -
    // MARK: Calculate the frames for drawing
    
    - (NSRect)_subtitleFrameForInteriorFrame:(NSRect)frame
    {
    	NSRect result = frame;
    	
    	result.origin.x = _SESubtitleCellSubtitleTextLeftInset;
    	result.size.width = NSMaxX(frame) - NSMinX(result);
    	
    	result.origin.y = NSMaxY([self _startTimeFrameForInteriorFrame:frame]) + _SESubtitleCellSubtitleTextTopInset;
    	result.size.height = NSMaxY(frame) - NSMinY(result);
    	
    	return result;
    }
    
    - (NSRect)_startTimeFrameForInteriorFrame:(NSRect)frame
    {
    	NSRect result = frame;
    	
    	result.origin.x = _SESubtitleCellStartTimeInset;
    	result.size.width = _SESubtitleCellTimeWidth;
    	
    	result.origin.y = NSMinY(frame) + _SESubtitleCellTimeTopInset;
    	result.size.height = _SESubtitleCellTimeHeight;
    	
    	return result;
    }
    
    - (NSRect)_endTimeFrameForInteriorFrame:(NSRect)frame
    {
    	NSRect starTimeFrame = [self _startTimeFrameForInteriorFrame:frame];
    	NSRect result = starTimeFrame;
    	
    	result.origin.x = NSMaxX(starTimeFrame) + _SESubtitleCellEndTimeInset;
    	
    	return result;
    }
    
    // MARK: Draw
    
    - (void)drawInteriorWithFrame:(NSRect)frame inView:(NSView *)controlView
    {
    	[self _ensureAllCellsCreated];
    	
    	NSRect startTimeFrame = [self _startTimeFrameForInteriorFrame:frame];
    	[startTimeCell drawWithFrame:startTimeFrame inView:controlView];
    	
    	NSRect endTimeFrame = [self _endTimeFrameForInteriorFrame:frame];
    	[endTimeCell drawWithFrame:endTimeFrame inView:controlView];
    	
    	NSRect titleFrame = [self _subtitleFrameForInteriorFrame:frame];
    	[subtitleTextCell drawInteriorWithFrame:titleFrame inView:controlView];
    }
    
    // MARK: -
    // MARK: Hit Testing
    
    - (NSUInteger)hitTestForEvent:(NSEvent *)event inRect:(NSRect)frame ofView:(NSView *)controlView
    {
    	[self _ensureAllCellsCreated];
    	
    	NSPoint point = [controlView convertPoint:[event locationInWindow] fromView:nil];
    	
    	NSRect startTimeFrame = [self _startTimeFrameForInteriorFrame:frame];
    	if (NSPointInRect(point, startTimeFrame))
    		return [startTimeCell hitTestForEvent:event inRect:startTimeFrame ofView:controlView];
    	
    	NSRect endTimeFrame = [self _endTimeFrameForInteriorFrame:frame];
    	if (NSPointInRect(point, endTimeFrame))
    		return [endTimeCell hitTestForEvent:event inRect:endTimeFrame ofView:controlView];
    	
    	NSRect subtitleFrame = [self _subtitleFrameForInteriorFrame:frame];
    	if (NSPointInRect(point, subtitleFrame))
    		return [subtitleTextCell hitTestForEvent:event inRect:subtitleFrame ofView:controlView];
    	
    	return NSCellHitNone;
    }
    
    // MARK: -
    // MARK: Set Object Value
    
    - (void)setObjectValue:(id <NSCopying>)obj
    {
    	[super setObjectValue:obj];
    	
    	[self _ensureAllCellsCreated];
    	
    	if (obj) {
    		[startTimeCell bind:@"title" toObject:[self objectValue] withKeyPath:@"startTime" options:nil];
    		[endTimeCell bind:@"title" toObject:[self objectValue] withKeyPath:@"endTime" options:nil];
    		[subtitleTextCell bind:@"title" toObject:[self objectValue] withKeyPath:@"subtitle" options:nil];
    	}
    }
    
    // MARK: -
    // MARK: Create the sub-cells
    
    - (void)_ensureAllCellsCreated
    {
    	[self _ensureStartTimeCellCreated];
    	[self _ensureEndTimeCellCreated];
    	[self _ensureSubtitleTextCellCreated];
    }
    
    - (void)_ensureStartTimeCellCreated
    {
    	if (startTimeCell == nil) {
    		startTimeCell = [[NSTextFieldCell alloc] init];
    		[startTimeCell setControlView:self.controlView];
    		[startTimeCell setBackgroundStyle:self.backgroundStyle];
    		[startTimeCell setTextColor:[NSColor grayColor]];
    	}
    }
    
    - (void)_ensureEndTimeCellCreated
    {
    	if (endTimeCell == nil) {
    		endTimeCell = [[NSTextFieldCell alloc] init];
    		[endTimeCell setControlView:self.controlView];
    		[endTimeCell setBackgroundStyle:self.backgroundStyle];
    		[endTimeCell setTextColor:[NSColor grayColor]];
    	}
    }
    
    - (void)_ensureSubtitleTextCellCreated
    {
    	if (subtitleTextCell == nil) {
    		subtitleTextCell = [[NSTextFieldCell alloc] init];
    		[subtitleTextCell setControlView:self.controlView];
    		[subtitleTextCell setBackgroundStyle:self.backgroundStyle];
    	}
    }
    
    @end
    
     
  2. kainjow Moderator emeritus

    kainjow

    Joined:
    Jun 15, 2000
    #2
    Look at the NSCell docs under the Editing and Selecting Text section. There are several methods you'd need to override in your base cell and forward to your subcells.
     
  3. misee thread starter macrumors member

    Joined:
    Jul 4, 2010
    #3
    Thanks for the response. Unfortunately, I'm still not quite there yet... I over rid the editWithFrame:inView:editor:delegate:event:, but it is never called. Same goes for the selectWithFrame:inView:editor:delegate:start:length: method. The hit test returns 3, which I believe means it's an editable content area (so it would be the correct value I guess). You wouldn't happen to know of any sample code that does something similar to what I'm trying to get an idea? I've looked at AnimatedTableView, but they use an NSTextFieldCell. I'm using an array controller to set my table contents, so that wouldn't really work for me except I abandon the bindings and go for delegation.

    EDIT:
    I got most of the stuff to work now. I had to make my cell a subclass of NSTextFieldCell. I encountered some problems, but I guess I will be able to solve these by moving away from NSArrayController for populating my table and use a data source and delegate to set the actual represented object.
     

Share This Page