I have a custom NSTextField and I'd like to detect double clicks by the user in the text field. My goal: I want to be able to double click on a parenthesis in an expression, such as "(2+2) = 4" and have it select everything inside the matching parentheses. Thought I could do this with...
- (void)textView:(NSTextView *)textView doubleClickedOnCell:(id <NSTextAttachmentCell>)cell inRect:(NSRect)cellFrame atIndex:(NSUInteger)charIndex;
but it never gets called in my custom NSTextField.
Then I thought I could override -mouseDown, but that isn't getting called either. I'm stumped. Any suggestions for what should be an easy function to implement.
Thanks!
Philip
The answer from Ken Thomases here is correct in its analysis of the issue regarding the field editor and how to replace it, but the solution it then recommends – replacing the
NSTextStorage
of the field editor – is not the correct solution, according to Apple. In their doc they specifically recommend that for delimiter-balancing theselectionRangeForProposedRange:granularity:
method should be used. Once you have a custom field editor going, as per Ken's answer, you should therefore use the solution forNSTextView
here, applied to a customNSTextView
subclass that you use for your field editor.In case it is of interest, using
NSTextStorage
'sdoubleClickAtIndex:
method for delimiter-balancing is probably the wrong solution for several trivial reasons: (1) because Apple says so, (2) because subclassingNSTextStorage
is complicated and error-prone, and (3) becauseNSTextView
provides a method specifically intended for the purpose of doing things like delimiter-balancing. But it is also wrong for a non-trivial reason: (4) thatdoubleClickAtIndex:
is documented as "Returns the range of characters that form a word (or other linguistic unit) surrounding the given index, taking language characteristics into account". SodoubleClickAtIndex:
is really about how the linguistic units of the text (i.e. words) are defined, and redefining those in some way to make delimiter-balancing work would probably break other aspects of word-level text processing. For example, I would guess that it would be pretty tricky to make double-click-drag (dragging out a selection word by word) work properly if you have overriddendoubleClickAtIndex:
to do delimiter balancing. Cocoa may usedoubleClickAtIndex:
for other aspects of word-finding too, and may add more uses of it in the future. Since a delimiter-balanced section of text is not a "word", who knows what weirdness might result.A text field does not handling editing, as such. When a text field has focus, a text view is added to the window, overlapping the area of the text field. This is called the "field editor" and it is responsible for handling editing.
It seems the most likely place for you to change the behavior of a double-click is in the text storage object used by that text view.
NSTextStorage
inherits fromNSMutableAttributedString
which inherits fromNSAttributedString
which has a-doubleClickAtIndex:
method. That method returns the range of the text that should be selected by a double-click at a particular index.So, you'll want to implement a subclass of
NSTextStorage
that overrides that method and returns a different result in some circumstances.NSTextStorage
is a semi-abstract base class of a class cluster. Subclassing it requires a bit more than usual. You have to implement the primitive methods ofNSAttributedString
andNSMutableAttributedString
. See the docs about it.There are a few places to customize the field editor by replacing its text storage object with an instance of your class:
NSTextFieldCell
. Set your text field to use this as its cell. In your subclass, override-fieldEditorForView:
. In your override, instantiate anNSTextView
. Obtain itslayoutManager
and call-replaceTextStorage:
on that, passing it an instance of your custom text storage class. (This is easier than putting together the hierarchy of objects that is involved with text editing, although you could do that yourself.) Set thefieldEditor
property of the text view to true and return it.-windowWillReturnFieldEditor:toObject:
. Create, configure, and return anNSTextView
using your custom text storage, as above.