How do you detect a mouseDown event into a NSTextView?

I’m lost again, after hours of doc reading. I just can’t find a way to detect if a mouseDown event occurs in my TextView. Is this class not a descendant of NSResponder? I receive no notification-- my onMouseDown_ is never called. Why?

Should I use the textView:clickedOnCell:inRect:atIndex: method? Does not seen to work either…

Because it’s not onMouseDown but on mouseDown_(sender)

Hello DJ,

Sorry, mistyped on this post. Of course in my code it’s on mouseDown_(sender).

But it does not work. Nor the clickedOnCell_inRect_atIndex_ method. What about these “observers”?

I think your script has to be a subclass of NSTextView instead of NSObject, and in IB, you need to change the class of your text view to your script’s class.

Ric

Ok. It’s working. Now tell me WHY.

The hierarchy of all my application is not clear to me (and the doc does not show clearly the inheritance design).

Is it because NSObject (the basic class of the framework) does not have responder methods? So why is the class of the application delegate not set to NSApplication (a NSResponder)?

I have so much unanswered questions – maybe out of the scope of this topic, but, among others:

  • why are so many visual objects accompanied by a “cell” companion?
  • why is an essential visual object like NSButton not a descendant of NSControl? are “cells” a mean to fake multiple inheritance between visual entities and responsive entities?
  • is there a general, complete diagram of frameworks anywhere? AppKiDo helped but it’s does not work with XCode 4…

Nevertheless, thank you to you both to have solved my problem – I wish I could gain some independence soon… :confused:

Regards,
Bernard

well it’s much simpler as you think.

Go to documentation->NStextView
you’ll see inheritance chain NSText : NSView : NSResponder : NSObject

this means that:
NSTextView inherits from NSText
NSText inherits from NSView
NSView inherits from NSResponder
NSResponder inherits from NSObject

So all instance methods of NSObject, NSResponder, NSView and NSText can be used in the NSView.

If you tell an object to do something it will look if it contains the method, if not go to it’s parent and so on till the root is achieved and if it’s still not found you’ll get the error ‘selector not recognized’.

Similar in pure Applescript (hope this clarifies some things for you

script NSObject
	on alloc()
		copy me to x
		return x
	end alloc
end script

script NSResponder
	property parent : NSObject
	
	on mouseDown()
		display dialog "just a stupid handler"
	end mouseDown
end script

script NSView
	property parent : NSResponder
end script

script NSText
	property parent : NSView
end script

script NSTextView
	property parent : NSText
end script

set anTextView to NSTextView's alloc()
anTextView's mouseDown()

Bernard,

I think maybe the point you’re missing here is not about the inheritance chain, but the fact that the mouseDown message is sent to the view object over which you clicked – in your case that would be the text view, and I think the default response to a mouseDown event is just to pass it along to the next responder. So, if you want your object to handle the mouseDown event you have to subclass it, and that’s what you’re doing by making your script’s parent, NSTextView and making the text view an instance of your script class.

Ric

ps.

It is: NSButton → NSControl → NSView → NSResponder -->NSObject

Thank you for your answers, it’s now OK that Obj-C follows the standard inheritance scheme (no multiple inheritance).

So for the NSButton class [NSControl:NSView:NSResponder] the companion class NSButtonCell [NSActionCell:NSCell] is here to provide some methods which could not be reached by NSButton because of the “fork” diverging at NSObject level (NSResponder and NSCell). Is it correct?

Two more questions:

  1. the best class parent of the App delegate can be defined for each application (as long as it’s descending from NSResponder)?

  2. the method you gave me, Ric, is OK, I just to have to call the inherited method to get the standard behaviour:

    on mouseDown_(sender) -- intercepts the mouse down before calling inherited mouseDown
        -- do stuff here
        continue mouseDown_(sender)
    end mouseDown_

For me this was opportunity to make my first “inherited mouseDown_(sender)” call, it looks very Pascal-like :wink:

And for the question itself (the “do stuff” above) : how do you retrieve the position (relative to text, not relative to frame) where the mouse down occurs?

Thank you, everyone of you! It’s really a relief not to have to fight alone in the night… :slight_smile:

Bernard

Not really understand what you mean by that. Basically an NSButon is a sub class of NSControl that loads an NSButtonCell for you so you don’t have to do it yourself. In other words NSButton is a NSControl (with a few more methods and properties) that loads a NSButtonCell automatically. Almost every instance method of a NSButton will be passed to NSButtonCell. See a NSButton the same as an NSMatric or NSTableView only an NSButton covers the cell methods to avoid mistakes made by the programmer.

for example to change a button’s title

aButton’s setTitle_(“Click Me”)

is equivalent to

aButton’s cell()'s setTitle_(“Click Me”)

on mouseDown_(sender) -- intercepts the mouse down before calling inherited mouseDown
-- do stuff here
continue mouseDown_(sender)
end mouseDown_

I’m not sure what you mean by your comment here – the method has to be an inherited method, because NSTextView doesn’t have a mouseDown, so you’re not intercepting it before the inherited method is called. As far as I know (anybody out there please correct me if I’m wrong here) you don’t need to use the “continue mouseDown” line – the continue statement is used so that superclasses can do any work they need to do in addition to what your own code is doing, but mouseDown just gets passed along the responder chain until some object decides to implement it. So, no work is really done by mouseDown, it just reports where and what kind of mouse event occurred (usually, you use “theEvent” as the parameter name rather than “Sender” because what gets passed in is an NSEvent).

With this question, it makes me think that maybe you don’t even want to be using a mouseDown method. What is it that you want to do in the text view? Are you wanting to edit the text? When you click in a text view you get a blinking cursor that is at the insertion point – I think you might want to be working with that.

Ric

Hello Ric, hello DJ,

I have called continue mouseDown_(ok, theEvent) because if I don’t, the textView reacts no more to the mouse click – in fact it “reacts” as if it was not editable/not selectable.

To answer your question(s), my text view will contain some special characters, very simple “fields” that don’t request advanced knowledge of programming, for example “From here you can go to the castle [46] or to the guesthouse [117].” When the user later (in another, let’s say “use” mode) clicks inside the text, I want to intercept the mouse down, check if it is inside a “field” (one or more digits enclosed in brackets), decipher the “link ID” and then follow this “link” (i.e. bring up to the window the text corresponding to this link). I can only do this if I know where to start parsing my text.

Other words will be references to “objects” identified by name, so I have to determinate what word was clicked.

In fact, the order of actions is:

  1. Get the click in the text
  2. Parse the text to get the field / word boundaries
  3. (variant) simulate a second click at the same position to get word selection and extract this selection range.
  4. Test if the field / word is valid
  5. Follow the link or do something the object.

I don’t know if it makes sense to you but I hope it will help to understand what i’m trying to do.

Regards,

I’m not sure why this should be – I tested the code I posted above, and it worked fine without the continue.

I wonder if there would be a way to do what you want using hyperlinks? The nice thing about a hyperlink is that you can click anywhere on it and the whole thing is selected. There is some info in the docs about putting hyperlinks into text views. The thing I’m not sure about is whether you can use a hyperlink to go to another screen in your program rather than a file or website. I’ll see if I can find out.

Ric

After Edit: I see now what the continue statement is doing. I was just logging to see that I got to the mouseDown method, so that worked fine, but if you have some text in the text view and click somewhere, the cursor didn’t change position (because I had usurped the text view’s normal behavior for the mouseDown method). By adding the continue statement, the cursor position changes as it should.

I explored the idea of using hyperlinks that would call a method rather than opening a URL, and I found that it is indeed possible. The following program has a text view and a button. After typing some text into the text view, you can select some text and then press the button – that will turn that selected text into a link (colored red and using the pointing hand cursor). If you then click on the link it will call the method doSomething().

script HyperlinksAppDelegate
	property parent : class "NSObject"
	property tv : missing value --IBOutlet for the text view
	
	on applicationWillFinishLaunching_(aNotification)
		tv's setLinkTextAttributes_(missing value) --This deletes the default link attributes (blue underlined text)
	end applicationWillFinishLaunching_
	
	on textView_clickedOnLink_atIndex_(tv, linkValue, indx) --This is called when you click on a link
		if linkValue's isMemberOfClass_(current application's NSURL) as boolean then
			log "It was a URL"
			set linkValue to linkValue's absoluteString()
		end if
		performSelector_(linkValue)
		return 1
	end textView_clickedOnLink_atIndex_
	
	on doSomething()
		log "doSomething was called"
	end doSomething
	
	on buttonClick_(sender) --Connected to a button
		set selectorString to "doSomething"
		set theDict to current application's NSDictionary's dictionaryWithObjectsAndKeys_(selectorString, current application's NSLinkAttributeName, ¬
			current application's NSColor's redColor(), current application's NSForegroundColorAttributeName, ¬
			current application's NSCursor's pointingHandCursor, current application's NSCursorAttributeName, missing value)
		tv's textStorage()'s addAttributes_range_(theDict, tv's selectedRange)
		--log tv's textStorage()'s attributesAtIndex_effectiveRange_(tv's selectedRange()'s |location|, missing value)
	end buttonClick_
	
end script

The script must be the delegate for the text view since textView_clickedOnLink_atIndex_ is a text view delegate method. The test for whether the linkValue is a URL in the textView_clickedOnLink_atIndex_ method isn’t necessary for this little program, but I found that if I added this code to my program that reads and writes RTFDs, this test was necessary for correct operation when opening a saved file. The link still has “doSomething” as the value for the NSLinkAttributeName, but its class has been switched from NSString to NSURL. I’m not sure why it does that, but the class of the NSLinkAttributeName isn’t saved with the text.

Ric

I explored another interesting delegate method, but the app crashes :

Tell me why:

    on textViewDidChangeSelection_(aNotification)
        log aNotification -- correct : and I have my character range passed in the notification
    end

is OK, but :

   on textView_willChangeSelectionFromCharacterRange_toCharacterRange_(aTextView,oldRange,newRange)
        log "Changing range"
        return oldRange -- no modification
   end 

crashes?

I tried implementing this in ASOC, and I also get a crash (EXC_BAD_ACCESS), but the same thing in ObjectiveC works fine, so I wonder if this is a bug?

Ric

Did you subclass NSTextView in ASOC? or did you set the delegate properly?

I set my main script to NSTextView class and set my text view to myAppDelegate class, as I’ve been told.

DJ,

I tried this without any subclassing, I just made my script the delegate of the text view. The ASOC version crashes as soon as I click in the text view (with the EXC_BAD_ACCESS error message). The ObjectiveC version does not crash, and correctly logs the ranges as I type. This seems like a bug to me, unless I’m missing something here.

script TextViewTestAppDelegate
	property parent : class "NSObject"
	
	on textView_willChangeSelectionFromCharacterRange_toCharacterRange_(aTextView, oldRange, newRange)
		log "Changing range"
		return newRange
	end textView_willChangeSelectionFromCharacterRange_toCharacterRange_
	
end script
#import "TextViewTesterAppDelegate.h"

@implementation TextViewTesterAppDelegate

- (NSRange)textView:(NSTextView *)aTextView willChangeSelectionFromCharacterRange:(NSRange)oldRange toCharacterRange:(NSRange)newRange{
    NSLog(@"location: %d, size: %d", oldRange.location, oldRange.length);
    return newRange;
}
@end

Ric

Ric,

Seems like a bug indeed. Not sure where it comes from but when you use ranges instead of range it works all properly. I also tested it by subclassing it but also gives me the same bug (bad exec). So as an alternative you could use textView_willChangeSelectionFromCharacterRanges_toCharacterRanges_(aTextView, oldRange, newRange) instead

Yes it works. Exactly like textView_willChangeSelectionFromCharacterRange_toCharacterRange_ should do. Strange.

Thank you, DJ. I’ll extract each first NSRange of the arrays.

Regards