0

I am trying to make a table view cell that shows ratings for songs in a playlist. I have successfully created the cell so that it shows the current number of stars, and also an indication of how a new setting will be when you hover your mouse cursor over a cell to give a new rating.

The problem is that while mouseEnter, mouseExit and mouseMove works like a charm, I get no messages for mouseDown, which is required to actually change the value of the cell.

I have searched all over the Internet, but I can't find any solution to how to solve this problem anywhere. I have spent so many hours trying to sort this. I hope anyone have any answer or hint what I can do. Thank you.

The full code for the current implementation is as follows:

#import "FavouriteCellView.h"

@implementation FavouriteCellView {
    NSTrackingArea *_trackingArea;
    int _starsRated; //The current rating value
    BOOL _hovering; //YES if the mouse is currently hovering over this cell
    int _starsHovering; //The number of stars hovering, if the mouse is hovering over this cell
}

- (void)awakeFromNib {
    [super awakeFromNib];
    _starsRated = 1;
    _hovering = NO;
    _starsHovering = 0;
    [self createTrackingArea];
}

- (void)createTrackingArea
{
    _trackingArea = [[NSTrackingArea alloc] initWithRect:self.bounds options:NSTrackingMouseEnteredAndExited |NSTrackingActiveInActiveApp | NSTrackingMouseMoved owner:self userInfo:nil];
    [self addTrackingArea:_trackingArea];
}

- (void)updateTrackingAreas{
    [self removeTrackingArea:_trackingArea];
    _trackingArea = nil;
    [self createTrackingArea];
}

- (void)drawRect:(NSRect)dirtyRect {
    [super drawRect:dirtyRect];

//    CGFloat middleX = [self bounds].size.width / 2.0f;
    CGFloat middleY = [self bounds].size.height / 2.0f;

    CGFloat starDivs = [self bounds].size.width / 5.0f;;

    NSColor *starSelectedColor = [NSColor colorWithDeviceRed:0.8f green:0.0f blue:0.4f alpha:1.0f];
    NSColor *starUnselectedColor = [NSColor colorWithDeviceRed:0.5f green:0.5f blue:0.5f alpha:1.0f];
    NSColor *starHoverColor = [NSColor colorWithDeviceRed:1.0f green:0.843f blue:0.0f alpha:1.0f];
    NSColor *starHoverColorSelected = [NSColor colorWithDeviceRed:0.9f green:0.843f blue:0.6f alpha:1.0f];

    for (int i = 0; i < 5; i++) {

        NSColor *useColor = [NSColor redColor];

        if (_hovering && (i <= _starsHovering)) {
            if (i <= _starsRated) {
                useColor = starHoverColorSelected;
            } else {
                useColor = starHoverColor;
            }
        } else if (i <= _starsRated) {
            useColor = starSelectedColor;
        } else {
            useColor = starUnselectedColor;
        }

        [self star:NSMakePoint((starDivs / 2.0f) + starDivs * i, middleY) color:useColor];
    }
}

-(void)star:(NSPoint)center color:(NSColor *)color {
    [color set];

    CGFloat t = (2.0f * M_PI) / 10.0f;

    NSBezierPath *path = [[NSBezierPath alloc] init];

    CGFloat radii1 = 12.0f;
    CGFloat radii2 = 4.0f;

    CGFloat rot = M_PI / 2.0f;

    BOOL first = YES;
    for (int i = 0; i < 10; i++) {
        CGFloat pointX = cos(t * i + rot) * radii1 + center.x;
        CGFloat pointY = sin(t * i + rot) * radii1 + center.y;

        CGFloat tempRadii = radii1;
        radii1 = radii2;
        radii2 = tempRadii;

        if (first) {
            first = NO;
            [path moveToPoint:NSMakePoint(pointX, pointY)];
        }
        else {
            [path lineToPoint:NSMakePoint(pointX, pointY)];
        }
    }

    [path closePath];
    [path fill];

/*
    [[NSColor blackColor] set];
    [path setLineWidth:0.25f];
    [path stroke];
*/
}

-(NSView *)hitTest:(NSPoint)aPoint {
    //THIS GETS CALLED
    return self;
}

-(BOOL)validateProposedFirstResponder:(NSResponder *)responder forEvent:(NSEvent *)event {
    printf("$");  //DOES NOT GET CALLED
    return YES;
}

-(BOOL)acceptsFirstResponder {
    printf("!");  //DOES NOT GET CALLED
    return YES;
}

-(BOOL)acceptsFirstMouse:(NSEvent *)theEvent {
    printf("8");  //DOES NOT GET CALLED
    return YES;
}

-(void)mouseDown:(NSEvent *)theEvent {
    printf("o");  //DOES NOT GET CALLED
    _starsRated = _starsHovering;
}

-(void)mouseUp:(NSEvent *)theEvent {
    printf("O");  //DOES NOT GET CALLED
}

-(void)mouseEntered:(NSEvent *)theEvent {
    //DOES GET CALLED
    _hovering = YES;
    [self setNeedsDisplay:YES];
}

-(void)mouseExited:(NSEvent *)theEvent {
    //DOES GET CALLED
    _hovering = NO;
    [self setNeedsDisplay:YES];
}

-(void)mouseMoved:(NSEvent *)theEvent {
    //DOES GET CALLED
    NSPoint mouseLocation = [[self window] mouseLocationOutsideOfEventStream];
    mouseLocation = [self convertPoint: mouseLocation
                          fromView: nil];

    int newStarsHoveringValue = mouseLocation.x / ([self bounds].size.width / 5.0f);

    if (newStarsHoveringValue != _starsHovering) {
        _starsHovering = newStarsHoveringValue;
        [self setNeedsDisplay:YES];
    }
}

@end
RoyGal
  • 176
  • 7
  • 1
    Perhaps you're not aware of [`NSLevelIndicator`](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSLevelIndicator_Class/), which is a built-in control for this sort of [star-ranking interface](https://developer.apple.com/library/mac/documentation/UserExperience/Conceptual/OSXHIGuidelines/ControlsIndicators.html#//apple_ref/doc/uid/20000957-CH50-SW8). Just put one of those in a stock table cell view and you should be done. – Ken Thomases Aug 02 '15 at 13:38
  • Oh, you are right that I am not aware of that. I will have a look into that and replace my implementation with that if it does what I want to achieve. Wonderful! Thank you! As for the question, I am still curious how I could solve this even though I probably have a solution now, for educational reasons. EDIT: Yes, it looks perfect! Thank you. – RoyGal Aug 02 '15 at 14:30

1 Answers1

0

It was a bit fiddly, but I managed to create a solution that works. I subclassed NSTableView, then overrode mouseDown with the following code:

-(void)mouseDown:(NSEvent *)theEvent {
    NSPoint globalLocation = [theEvent locationInWindow];
    NSPoint localLocation = [self convertPoint:globalLocation fromView:nil];
    NSInteger clickedRow = [self rowAtPoint:localLocation];

    if (clickedRow != -1) {
        NSInteger clickedColumn = [self columnAtPoint:localLocation];
        if (clickedColumn != -1) {
            if (clickedColumn == 3) {
                FavouriteCellView *fv = [self viewAtColumn:clickedColumn row:clickedRow makeIfNecessary:NO];
                if (fv != nil) {
                    [fv mouseDown:theEvent];
                }
                return;
            }
        }
    }

    [super mouseDown:theEvent];
}

Now it works exactly like I wanted.

RoyGal
  • 176
  • 7