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?
Clickable links with NSTextField
I got this working after subclassing NSTextField
as mentioned :
- (void)resetCursorRects {
[self addCursorRect:[self bounds] cursor:[NSCursor pointingHandCursor]];
}
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
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.
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
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With