Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can't change the mouse cursor of a NSTextField

I'm trying to change the mouse cursor of a NSTextField in a window sheet, loaded from a NIB.

Following the documentation, I have subclassed NSTextField and implemented resetCursorRects.

- (void) resetCursorRects {
    [self addCursorRect:[self bounds] cursor:[NSCursor pointingHandCursor]];
}

This is never called. Not even after adding the following in the NSWindowViewController:

- (void) windowDidLoad {
    [self.window invalidateCursorRectsForView:self.linkTextField];
}

I also tried with a tracking area by adding the following in the NSTextField subclass:

- (void) awakeFromNib {
    NSTrackingArea* trackingArea = [[NSTrackingArea alloc] initWithRect:self.bounds
                                                 options:(NSTrackingCursorUpdate | NSTrackingActiveAlways)
                                                   owner:self
                                                userInfo:nil];
    [self addTrackingArea:trackingArea];
}

- (void)cursorUpdate:(NSEvent *)theEvent {
    [[NSCursor pointingHandCursor] set];
}

Didn't work either. What am I doing wrong?

like image 513
hpique Avatar asked Aug 22 '12 18:08

hpique


4 Answers

Clickable links with NSTextField

I got this working after subclassing NSTextField as mentioned :

- (void)resetCursorRects {
    [self addCursorRect:[self bounds] cursor:[NSCursor pointingHandCursor]];
}
like image 79
Girish Kolari Avatar answered Nov 13 '22 00:11

Girish Kolari


NSTextView seems to be handling the cursor through its -(void)mouseMoved:(NSEvent*)theEvent method. Also, when the NSTextView becomes first responder, some private code in the RunLoop seems to force the cursor to the IBeamCursor without giving us a choice. Here is a subclass that works around those limitations :

@interface MyTextView : NSTextView {
    NSTrackingArea* area;
    BOOL mouseInside;
}

@property(nonatomic, retain) NSTrackingArea* area;

@end


@implementation MyTextView

@synthesize area;

- (void)setArea:(NSTrackingArea *)newArea
{
    [newArea retain];
    if (area) {
        [self removeTrackingArea:area];
        [area release];
    }
    if (newArea) {
        [self addTrackingArea:newArea];
    }
    area = newArea;
}

- (BOOL)becomeFirstResponder
{
    NSRect rect = <insert the tracking rect where you want to have a special cursor>;
    self.area = [[[NSTrackingArea alloc] initWithRect:rect options:NSTrackingMouseEnteredAndExited | NSTrackingActiveWhenFirstResponder owner:self userInfo:nil] autorelease];
    NSEvent* ev = [NSApp currentEvent];
    if (NSPointInRect([self convertPoint:ev.locationInWindow fromView:nil], self.bounds)) {
        mouseInside = YES;
        // This is a workaround for the private call that resets the IBeamCursor
        [[NSCursor arrowCursor] performSelector:@selector(set) withObject:nil afterDelay:0];
    }
}

- (void)mouseEntered:(NSEvent *)theEvent
{
    [super mouseEntered:theEvent];
    [[NSCursor arrowCursor] set];
    mouseInside = YES;
}

- (void)mouseExited:(NSEvent *)theEvent
{
    [super mouseExited:theEvent];
    mouseInside = NO;
}

- (void)mouseMoved:(NSEvent *)theEvent
{
    si (!mouseInside) {
        // We only forward the mouseMoved event when the mouse is outside the zone for which we control the cursor
        [super mouseMoved:theEvent];
    }
}

- (oneway void)dealloc
{
    [area release];
    [super dealloc];
}

@end
like image 21
Dalzhim Avatar answered Nov 13 '22 00:11

Dalzhim


I had the same issue, the cursorUpdate method was called each time the cursor entered the tracking area, but the cursor was set back somewhere else, probably its superview.

I managed to solve it by overriding the mouseMoved method.

// In the textfield subclass:
- (void)mouseEntered:(NSEvent *)theEvent {
    [super mouseEntered:theEvent];
    self.isMouseIn = YES;
}

- (void)mouseExited:(NSEvent *)theEvent {
    [super mouseExited:theEvent];
    self.isMouseIn = NO;
}


//In the superview of the textfield:
- (void)mouseMoved:(NSEvent *)theEvent {
    if (self.hoverButton.isMouseIn) {
        [[NSCursor pointingHandCursor] set];
    }
    [super mouseMoved:theEvent];
}

I overrided the mouseMoved method in my windowController class, but overriding the superview should work.

like image 42
Jensen Avatar answered Nov 13 '22 00:11

Jensen


I wrote a Swift version 2.0 of the answers above and made it available as a GitHub sample project found here.

And even though it's set for NSTextField, the concepts should work with anything that subclasses from NSResponder (which is where mouseEntered and becomeFirstResponder come from).

Here's the guts of the code I wrote:

import Cocoa

class CCTextField: NSTextField {

    var myColorCursor : NSCursor?

    var mouseIn : Bool = false

    var trackingArea : NSTrackingArea?

    override func awakeFromNib()
    {
        myColorCursor = NSCursor.init(image: NSImage(named:"heart")!, hotSpot: NSMakePoint(0.0, 0.0))
    }

    override func resetCursorRects() {
        if let colorCursor = myColorCursor {
            self.addCursorRect(self.bounds, cursor: colorCursor)
        }
    }

    override func mouseEntered(theEvent: NSEvent) {
        super.mouseEntered(theEvent)
        self.mouseIn = true
    }

    override func mouseExited(theEvent: NSEvent) {
        super.mouseExited(theEvent)
        self.mouseIn = false
    }

    override func mouseMoved(theEvent: NSEvent) {
        if self.mouseIn {
            myColorCursor?.set()
        }
        super.mouseMoved(theEvent)
    }

    func setArea(areaToSet: NSTrackingArea?)
    {
        if let formerArea = trackingArea {
            self.removeTrackingArea(formerArea)
        }

        if let newArea = areaToSet {
            self.addTrackingArea(newArea)
        }
        trackingArea = areaToSet
    }

    override func becomeFirstResponder() -> Bool {
        let rect = self.bounds
        let trackingArea = NSTrackingArea.init(rect: rect, options: [NSTrackingAreaOptions.MouseEnteredAndExited, NSTrackingAreaOptions.ActiveAlways], owner: self, userInfo: nil)

        // keep track of where the mouse is within our text field
        self.setArea(trackingArea)

        if let ev = NSApp.currentEvent {
            if NSPointInRect(self.convertPoint(ev.locationInWindow, fromView: nil), self.bounds) {
                self.mouseIn = true
                myColorCursor?.set()
            }
        }
        return true
    }
}
like image 45
Michael Dautermann Avatar answered Nov 13 '22 02:11

Michael Dautermann