views:

216

answers:

2

OK I'm really stumped on this one. I want to make a checkbox with a NSTextFieldCell combined together. It's important that the checkbox goes ON if the mouse hits the box, NOT the text. I've accomplished this, more or less, but the issue is receiving the mouse event because I click one checkbox in a row, but ALL of them turn to NSOnState. I will show what I've done and my various failed attempts in order to get this to work.

So this is how I've done it so far:

header:

@interface MyCheckboxCellToo : NSTextFieldCell {
   NSButtonCell *_checkboxCell;
}

implementation:

- (NSUInteger)hitTestForEvent:(NSEvent *)event inRect:(NSRect)cellFrame ofView:(NSView *)controlView {
    NSPoint point = [controlView convertPoint:[event locationInWindow] fromView:nil];
    NSLog(@"%@", NSStringFromPoint(point));

    NSRect checkFrame;
    NSDivideRect(cellFrame, &checkFrame, &cellFrame, cellFrame.size.height/*3 + [[_checkboxCell image] size].width*/, NSMinXEdge);

    if (NSMouseInRect(point, checkFrame, [controlView isFlipped])) {
        // the checkbox, or the small region around it, was hit. so let's flip the state
        NSCellStateValue checkState = ([_checkboxCell state] == NSOnState) ? NSOffState:NSOnState;
        [self setState:checkState];
        [_checkboxCell setState:checkState];
        [controlView setNeedsDisplay:YES];
        return NSCellHitTrackableArea;
    }        
    return [super hitTestForEvent:event inRect:cellFrame ofView:controlView];    
}

I know I probably shouldn't be doing:

   [self setState:checkState];
   [_checkboxCell setState:checkState];
   [controlView setNeedsDisplay:YES];

in there... because the result is that EVERY checkbox in every goes to NSOnState. Is this because cells are re-used? How come the ImageAndTextCell can have different images in the same tableview? How do I handle the mouse event?

I have tried:

- (BOOL)trackMouse:(NSEvent *)theEvent inRect:(NSRect)cellFrame
ofView:(NSView *)controlView untilMouseUp:(BOOL)untilMouseUp {
   NSLog(@"%s %@", _cmd, theEvent);
   return [_checkboxCell trackMouse:theEvent inRect:cellFrame
ofView:controlView untilMouseUp:untilMouseUp];
//    return YES;
//    return [super trackMouse:theEvent inRect:cellFrame
ofView:controlView untilMouseUp:untilMouseUp];
}

- (BOOL)startTrackingAt:(NSPoint)startPoint inView:(NSView *)controlView {
   NSLog(@"%s %@", _cmd, NSStringFromPoint(startPoint));
   return [super startTrackingAt:startPoint inView:controlView];
}

- (BOOL)continueTracking:(NSPoint)lastPoint at:(NSPoint)currentPoint
inView:(NSView *)controlView {
   NSLog(@"%s %@", _cmd, NSStringFromPoint(currentPoint));
   return [super continueTracking:lastPoint at:currentPoint
inView:controlView];
}

- (void)stopTracking:(NSPoint)lastPoint at:(NSPoint)stopPoint
inView:(NSView *)controlView mouseIsUp:(BOOL)flag {
   NSLog(@"%s %d %@", _cmd, flag, NSStringFromPoint(stopPoint));
}

trackMouse: ... DOES gets called

but

startTrackingAt:..., continueTracking:..., and stopTracking:.... DO NOT get called when I click on the checkbox "hit area"

in trackMouse:... I have tried

return [_checkboxCell trackMouse:theEvent inRect:cellFrame ofView:controlView untilMouseUp:untilMouseUp];

and

return [super trackMouse:theEvent inRect:cellFrame ofView:controlView untilMouseUp:untilMouseUp];

and neither seems to result in the mouse event being handled by the checkbox.

How do I get that single checkbox to go NSOnState? I know I'm pretty close but after a lot of doc reading and google searching I haven't been successful at solving this.

suggestions and comments welcome..


OK here is a bit more to show creation and destruction of the object..

- (id)init {
    if ((self = [super init])) {
        _checkboxCell = [[NSButtonCell alloc] init];
        [_checkboxCell setButtonType:NSSwitchButton];
        [_checkboxCell setTitle:@""];
        [_checkboxCell setTarget:self];
        [_checkboxCell setImagePosition:NSImageLeft];
        [_checkboxCell setControlSize:NSRegularControlSize];

    }
    return self;
}

- copyWithZone:(NSZone *)zone {
    MyCheckboxCellToo *cell = (MyCheckboxCellToo *)[super copyWithZone:zone];
    cell->_checkboxCell = [_checkboxCell copyWithZone:zone];
    return cell;
}

- (void)dealloc {
    [_checkboxCell release];
    [super dealloc];
}
A: 

I can't directly answer the handling-events-in-cells part of the question, but I can answer these parts:

I know I probably shouldn't be doing:

[self setState:checkState];
[_checkboxCell setState:checkState];
[controlView setNeedsDisplay:YES];

in there... because the result is that EVERY checkbox in every goes to NSOnState. Is this because cells are re-used?

They're reused in a different sense from how UITableView reuses UITableViewCells.

(I noticed that you tagged the question “NSTableViewCell”. No such class exists in AppKit.)

How come the ImageAndTextCell can have different images in the same tableview?

The answer to both questions is the same.

While UITableView uses one cell for each row, and keeps on hand as many cells as there are rows on the screen, NSTableView uses a single cell for every column.

Each column exists as an NSTableColumn object, and each NSTableColumn has exactly one cell*. The table view sets the cell's object value to whatever value you provided for that column-row intersection, and then tells the cell to draw at the appropriate place within its bounds. It'll do the same thing, with that same cell, for that column for every row.

(There are exceptions here, since Apple added the ability to use a different cell for specific rows, and since they added group rows, which have no columns and use a different cell. The fundamental mechanism is the same, though.)

I'm sorry that I can't help you all the way to a solution (aside from my just-make-them-separate-columns suggestion), but I hope this at least helps you part of the way.

*It has a header cell, too, but that doesn't count here. The “exactly one cell” I'm talking about is its data cell.

Peter Hosey
A: 

Ok this is what appeared to fix my issue, more or less.

in hitForTestEvent: ...

instead of

    if (NSMouseInRect(point, checkFrame, [controlView isFlipped])) {
    // the checkbox, or the small region around it, was hit. so let's flip the state
    NSCellStateValue checkState = ([_checkboxCell state] == NSOnState) ? NSOffState:NSOnState;
    [self setState:checkState];
    [_checkboxCell setState:checkState];
    [controlView setNeedsDisplay:YES];
    return NSCellHitTrackableArea;
}        

it should be:

    if (NSMouseInRect(point, checkFrame, [controlView isFlipped])) {
    [_checkboxCell setState:![_checkboxCell state]];
    return NSCellHitTrackableArea;
}        

I still have some issues where if I click the checkbox hit zone in a certain spot it doesn't register but more or less this works.