Can't change the mouse cursor of a NSTextField
Asked Answered
E

5

6

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?

Epicycloid answered 22/8, 2012 at 18:49 Comment(4)
Did you solve the problem? I am having a similar issue.Pam
@Pam Nope. Are you trying something different?Epicycloid
Just managed to solove this by overriding the mouseMoved method in its superview.Pam
@Pam Why don't post it as an answer? I'll give it a try.Epicycloid
O
8

Clickable links with NSTextField

I got this working after subclassing NSTextField as mentioned :

- (void)resetCursorRects {
    [self addCursorRect:[self bounds] cursor:[NSCursor pointingHandCursor]];
}
Orme answered 4/9, 2013 at 11:3 Comment(0)
N
2

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
Nazarius answered 6/2, 2014 at 16:3 Comment(0)
P
1

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.

Pam answered 6/4, 2013 at 16:50 Comment(0)
R
1

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
    }
}
Rhythmical answered 4/11, 2015 at 11:34 Comment(0)
U
0

This works for me:

class TextField: NSTextField {
    override func becomeFirstResponder() -> Bool {
        addTrackingAreaIfNeeded()
        return super.becomeFirstResponder()
    }
    override func mouseMoved(with event: NSEvent) {
        super.mouseMoved(with: event)
        NSCursor.pointingHand.set()
    }
    private func addTrackingAreaIfNeeded() {
        if trackingAreas.isEmpty {
            let area = NSTrackingArea(rect: bounds, options: [.mouseEnteredAndExited, .activeAlways], owner: self, userInfo: nil)
            addTrackingArea(area)
        }
    }
}
Unfurl answered 24/11, 2017 at 23:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.