Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Subclassing NSSlider: Need a workaround for missing mouse up events (Cocoa OSX)

I am trying to subclass NSSlider to create a control called a jog dial. Basically what I need is a slider which always starts at the middle and when it is moved to the left or right it will send notifications every so often (determined by an attribute one can set) informing its container of its current value, then when you let you go of the knob, it will return to the middle. I was hoping to implement the functionality to return the slider to the middle and stop sending notifications in the mouseUp event of the slider but it seems that for some reason apple disables the MouseUp event after a mouseDown event on the slider and handles all the slider functionality at a lower level. Is there anyway I can get the mouseUp event back? If not can anyone suggest a reasonable workaround?

like image 685
Mike2012 Avatar asked Oct 12 '10 23:10

Mike2012


4 Answers

Whenever you notice that a superclass's implementation of mouseDragged: or mouseUp: is not getting called, it's most likely because the class's implementation of mouseDown: enters a tracking loop. This is certainly true of many NSControl subclasses including NSSlider.

A better way to detect a mouse up is to subclass the cell and override the appropriate tracking method. In this case, you probably want - (void)stopTracking:(NSPoint)lastPoint at:(NSPoint)stopPoint inView:(NSView *)controlView mouseIsUp:(BOOL)flag, but the startTracking: and continueTracking: variants might prove useful as well for what you're trying to do.

like image 95
kperryua Avatar answered Nov 15 '22 16:11

kperryua


There is a trick that I use (but didn't invent) for such situations. First, in IB, designate the slider as "continuous", so that you'll get action messages as the slider is moved. Then, in the action method, do this:

[NSObject cancelPreviousPerformRequestsWithTarget: self
  selector: @selector(finishTrack) object: nil ];
[self performSelector: @selector(finishTrack) withObject: nil
  afterDelay: 0.0];

After the mouse is released, the finishTrack method will be called.

like image 23
JWWalker Avatar answered Nov 15 '22 16:11

JWWalker


This works for me (and is easier than subclassing NSSlider):

- (IBAction)sizeSliderValueChanged:(id)sender {
    NSEvent *event = [[NSApplication sharedApplication] currentEvent];
    BOOL startingDrag = event.type == NSLeftMouseDown;
    BOOL endingDrag = event.type == NSLeftMouseUp;
    BOOL dragging = event.type == NSLeftMouseDragged;

    NSAssert(startingDrag || endingDrag || dragging, @"unexpected event type caused slider change: %@", event);

    if (startingDrag) {
        NSLog(@"slider value started changing");
        // do whatever needs to be done when the slider starts changing
    }

    // do whatever needs to be done for "uncommitted" changes
    NSLog(@"slider value: %f", [sender doubleValue]);

    if (endingDrag) {
        NSLog(@"slider value stopped changing");
        // do whatever needs to be done when the slider stops changing
    }
}
like image 31
marcprux Avatar answered Nov 15 '22 15:11

marcprux


It seems that Kperryua's idea would provide the cleanest solution so I will mark that as the accepted answer, but I ended using a bit of a hack that worked for my specific situation so I thought I might share that as well.

The app that I am making needs to be cross platform so I am using Cocotron (an open source project that implements much of the Cocoa API on Windows) to achieve this goal and cocotron does not support the stopTracking:... method mentioned above.

Luckily, while playing around with a little test program we made it was discovered that if you override the the mouseDown method of the NSSlider and call the super of that method, it does not return until the mouse button is released, so you can just put whatever code should be in mouseUp inside the mouseDown method after the call to super. This is a bit of a dirty hack but it seems to be the only solution that will work in our case so we are going to have to go with it.

like image 3
Mike2012 Avatar answered Nov 15 '22 17:11

Mike2012