How to get notification whenever a character is inputted into NSTextField? - cocoa

I'm a little rusty on my Cocoa, so bear with me on terminology and such.
I want to write something that is essentially a reverse spell checker. As in, if a word is spell correctly, then for random words it changes it to a misspelled version. Harmless prank to play on someone.
So then, my main hitch is that I have no idea how to do this (major problem, I know). I like the way that textedit performs on-the-fly spellchecking, but I'd like to incorporate that sort of behavior into the generic behavior of the NSTextField. Is there some way for an application to be notified whenever a character is input into an NSTextField?
EDIT: My aim is to make this system-wide, as in any NSTextField in the system would get this behavior as a matter of inheritance. I'm open to some serious hacking here.

To answer your question: attach a delegate to the NSTextField control, and implement
- (void)controlTextDidChange:(NSNotification *)aNotification
Note that NSTextField uses the shared field editor to edit text, which is a NSText object. You might be able to manipulate that to do what you want.

I like the idea! This should be fairly easy to do. First you have to decide if you want to use NSTextField or NSTextView. TextEdit.app uses NSTextView which is appropriate for more extensive word processing-type tasks. NSTextField is more for smaller, minimally-formatted chunks of text. There's lots of ways to tackle this, but I'll give you a couple.
For NSTextField, set your controller object to be the delegate for the text field and override the controlTextDidChange: method. Whenever the user types a character into the text field, you'll get this message. You can then modify the field's string to introduce the misspelled word.
For NSTextView, you can activate spell checking and use the text view's delegate method textView:didCheckTextInRange:types:options:results:orthography:wordCount:. You should be able to modify the results of the spell check.

Related

What is the difference between UITextInputDelegate.selectionDidChange and UITextViewDelegate.textViewDidChangeSelection

Two delegate methods are very similar.
I want to know what's their differences.
When the first will be called, when will another.
UITextInputDelegate.selectionDidChange
and
UITextViewDelegate.textViewDidChangeSelection
The difference is that UITextInputDelegate.selectionDidChange tells the view which conforms UITextInput that the text selection has changed.
While UITextViewDelegate.textViewDidChangeSelection tells UITextView that the text selection has changed.
In reality UITextView conforms the UITextInput, so it should not make any difference which method you will be using. But if you implement UITextInputDelegate to your textview, it will give you more flexibility to alter selection process e.g selectionWillChange, selectionDidChange. If you do not this flexibility, stick to UITextViewDelegate.textViewDidChangeSelection :)

how to detect double Click in NSTextField

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
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 from NSMutableAttributedString which inherits from NSAttributedString 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 of NSAttributedString and NSMutableAttributedString. 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:
You could implement a custom subclass of NSTextFieldCell. Set your text field to use this as its cell. In your subclass, override -fieldEditorForView:. In your override, instantiate an NSTextView. Obtain its layoutManager 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 the fieldEditor property of the text view to true and return it.
In your window delegate, implement -windowWillReturnFieldEditor:toObject:. Create, configure, and return an NSTextView using your custom text storage, as above.
it is simple just use this class to detect double tap
final class doubleClickableTextField : NSTextField {
override func mouseDown(with event: NSEvent) {
super.mouseDown(with: event)
if (event.clickCount == 2){
// do the work here
self.isEditable = true
}
}
}
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 the selectionRangeForProposedRange:granularity: method should be used. Once you have a custom field editor going, as per Ken's answer, you should therefore use the solution for NSTextView here, applied to a custom NSTextView subclass that you use for your field editor.
In case it is of interest, using NSTextStorage's doubleClickAtIndex: method for delimiter-balancing is probably the wrong solution for several trivial reasons: (1) because Apple says so, (2) because subclassing NSTextStorage is complicated and error-prone, and (3) because NSTextView 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) that doubleClickAtIndex: 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". So doubleClickAtIndex: 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 overridden doubleClickAtIndex: to do delimiter balancing. Cocoa may use doubleClickAtIndex: 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.

How to tell when user selects text in NSTextView

I have an NSTextView and want to be able to tell when the user highlights (selects) a portion of the text, so that I can make changes to it. The NSTextViewDelegate protocol doesn't seem to have a method for this. Is there a way to capture this event?
Are you sure - (void)textViewDidChangeSelection:(NSNotification *)aNotification doesn't do the trick? It's part of NSTextViewDelegate (reference). If it doesn't work the way you want, what specifically are you looking for?

Get index of NSTextField completion

I have an NSTextField UI element where the user can type into the text field and I want to drop down a list of completions beneath the text field as a "live search".
I was hoping to use the native text completions infrastructure, but when the user chooses the appropriate completion, I don't want to merely put the text into the NSTextField. The user is actually choosing one of many custom objects in an NSArray by searching on string properties of the object. When they choose, I need to know which object they chose.
Is there a way to know the index of the completion that was chosen (so that I can get the object from that index in my array)?
Or do I need to forget about using the native text completions and just populate and display a dropdown under the text field?
Could you use an NSComboBox in this situation? And perhaps subclass NSComboBoxCell to override
- (NSString *)completedString:(NSString *)substring
You could also observe changes in the NSComboBox delegate protocol to detect changes to the selected item
In the end I used an NSTokenField because of some UI appearance things that NSTokenField added for me. But I think that the extra trick I came up with (below) might also work with an NSTextField. Sorry this is kind of convoluted.
In a nutshell what I did was to generate an NSMutableDictionary (an iVar) where the keys are the full completions for the partial string in the NSTokenField and the objects are the custom objects that the completion strings represent. In other words, as I am generating the custom completion strings and putting them into an NSArray to be returned from the NSTokenFieldDelegate method tokenField:completionsForSubstring:indexOfToken:indexOfSelectedItem:, I am at the same time stuffing each of those completions and the object they represent into an NSMutableDictionary with the completion as key and the object as value.
When the user "tokenizes" the completion (by hitting Return or Tab -- i modified the tokenizing characterSet so that's all that will tokenize), the NSTokenFieldDelegate method tokenField:representedObjectForEditingString: is called. Inside there, I am able to get my object from the NSMutableDictionary by using the editingString parameter as the key: [dict objectForKey:editingString]
I think it might be possible with some wrangling in the controlTextDidChange: NSTextFieldDelegate method to do the same thing with completions on an NSTextField instead of an NSTokenField using the dictionary trick, but in order to do that, I think that you would have to have the full completion in the NSTextField, grab its stringValue and then use that as the key. In my case, I did not want the whole completion in the text field, so NSTokenField's tokenizing worked better for me.

Getting accented characters in a custom NSView

In Mac OSX text views, it's possible to enter accented characters with a sequence of key presses (e.g. option-e e to get e-acute). Is there a way to access this functionality in a custom NSView. In my case I have class derived from NSOpenGLView. I've implemented a responder for keyDown: so I can get the unicode characters that come from a single press, but with the sequences I just get events for the individual presses. I hoped that interpretKeyEvents: from NSResponder would help but it doesn't seem to.
I could implement it myself by copying what NSTextView does but I imagine it will be tricky, especially if people use a different keyboard setup to mine.
Calling interpretKeyEvents: is the right way to go, but you need to also implement other methods that will notify you when marked text (which I always called 'in progress text') or text to be inserted arrives in your view.
Your NSView must implement NSTextInputClient. See the Cocoa reference here. A search for some of the methods in that protocol found this chunk of code on github. which looks like a very good starting point.
For what you're asking for, it will be sufficient to test with English and at least one other Western language (French is a good one). But longer term, you'll also want to test with at least one of the Kotoeri and Hangul layouts. The code I linked to above, however, looks like it will handle the vast majority of text you can throw at it.
Hhhmm.. that sounds pretty tricky.
NSTextField does some pretty similar things as it does not have its own editing facilities but rather delegates them to the parent window's "field editor" which is an instance of an invisible NSTextView. You also get a lot of the NSTextView behavior without a window in NSText.
It might be worth checking how NSTextFields delegate to the field editor and see whether you can hook yourself into the field editor in the same way rather than doing your keyDown: events.
Another possibility could be to create an invisible NSTextView and delegate each key press to it and then display the NSTextStorage associated with it in your own view.
It sounds like you are in for a bit of pain, but that's what I would start by exploring.
Of course, I'm no Cocoa Text Subsystem wizard..
I hope this helps.

Resources