This seems it should be easy enough, but could anyone give me pointers on how to do this? Seems I should be subclassing NSTextView and using drawInsertionPointInRect:color:turnedOn: but how would I do this? I don't really want to do major customization maybe just a touch thicker or a touch shorter, but the question is where?
Thanks,
rc
This isn't really straight forward, since a NSSearchField is a subclass of NSTextField and not NSTextView. However, each NSTextField uses a proxy NSTextView to do the drawing, and this proxy NSTextView (called the field editor), is maintained by the current window. So, what you want to do is to create your custom NSTextView subclass, instantiate it somewhere in your window controller (or whatever you use as your windows delegate) and then create the following method: windowWillReturnFieldEditor:toObject:.
In the method you check if the toObject is your search field (or just any search field, in case you want to override it for every search field in the window), and then return your custom NSTextView, otherwise return nilfor the default field editor with the default behaviour.
Related
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.
I am having a devil of a time trying to figure out how to get the address of the Text Field Editor (NSTextView) of an NSCell—NSFormCell and NSTextFieldCell in particular? NSCell does not have a property to access it. I did figure out the editor is not allocated until one is actually editing the field.
I want to set the delegate so I can capture keystrokes for auto-completion.
By default, there's a single field editor for each window. Even if a control or cell uses a custom field editor, it's still vended by the window. You would call -[NSWindow fieldEditor:forObject:] to obtain the field editor for a given control.
However, the delegate of the field editor is always set to the control on whose behalf it is working. Setting the delegate to something else is likely to break things. So, you would typically use a custom subclass of the control and implement your delegate methods there.
Finally, controlling completions is normally done using -textView:completions:forPartialWordRange:indexOfSelectedItem: in the text view delegate, not by capturing keystrokes.
I have a custom NSTextView implementation that automatically adjusts the font size so that the text fills the entire view.
I overwrote didChangeText to call my font size adjustment method. Works great when the user is editing text, but didChangeText (and the delegate method textDidChange:) are not called when the text view contents are set via bindings.
The font adjustment code needs to run whenever the text is set/changes, not only when it's changed by the user.
How can I detect all changes to the text in an NSTextView, even via bindings?
Note: If there's a better way to have the text fill the entire text view other than increasing the font size, let me know.
It would be better to set the font attributes into the NSAttributedString that is bound to the text view's "attributedString". In the textDidChange: delegate method, you can just recreate the NSAttributedString with the correct font attributes.
The NSTextView method didChangeText is not called when a binding updates the text (as opposed to the text view updating the model).
didChangeText is the source of the binding update. If you override it and don't call super, the binding is broken. didChangeText calls the delegate method textDidChange.
Unfortunately, didChangeText is also called rather late in the NSTextView update process - after the layout and storage delegate calls.
I found that the NSTextStorageDelegate method "didProcessEditing" was the best way to catch changes to the bound string. Although you have to be careful what changes you can make back to the textview at this point - some calls crashed.
I answered my own similar question more fully here:
NSTextView textDidChange not called through binding
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.
What's the minimum implementation needed to make a custom NSView with an editable text area? I assume NSTextFieldCell can be used for this. I've succeeded in drawing the cell in the view (which is straightforward), but making it editable seems to require a more complicated coordination between the view and the cell. Is there sample code available somewhere?
Update. I should have made clear that my longer-term goal is to have many more editable text areas on the same view. AFAIU it is better to use cells in that case as they are more light-weight than full-blown views. My updated question is: What's the minimum implementation needed to make a custom NSView with an editable text area using an appropriate NSCell?
What's the minimum implementation needed to make a custom NSView with an editable text area?
Make an NSView.
Put an NSTextField in it.
Remember, NSViews (custom or otherwise) can contain other NSViews, and an NSTextField is a kind of NSView.
If you don't want code outside the custom view class to know about the text field, and it probably shouldn't, the custom view can create the text field and add it to itself as a private implementation detail. To do this, simply don't expose the text field in the custom view class's #interface (aside from the instance variable declaration, which is unavoidable).
The custom view should, of course, not draw wherever it put its text field. It could draw there, but the text field would cover it.
I assume NSTextFieldCell can be used for this.
Yes, if you don't mind reimplementing NSTextField. Adding an NSTextField as a subview of your view is much easier.
If you want to make a grid of text fields (with a dynamic number of them, perhaps), use an NSMatrix of NSTextFieldCells. You can, of course, add the NSMatrix as a subview of your custom view.
If you want to edit a text cell just call editWithFrame:inView:editor:delegate:event: on the cell object. This method requires the NSEvent that started the editing, so you can only call this from an event handler. There also is selectWithFrame:inView:editor:delegate:start:length: which sets up the field editor with an selection. You can use this if you need to start the editing from outside of an event handler.
After the user is done editing you need to call endEditing: on your cell.