Trying to see the delegate of an NSTextView of an NSCell - macos

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.

Related

Manage Undo across multiple NSTextFields in the same window

While Undo for multiple NSTextViews in a window work persistently, the same is not the case for NSTextField controls by default.
I like to make Undo behave the same for NSTextField as it works with NSTextView.
I have so far found out that NSTextField uses its own private NSUndoManager, which does enable and handle the Undo/Redo menus autmatically, but without using the window's shared undoManager, and without marking the connected document dirty ("edited"): If I provide a private NSUndoManager instance to an NSTextField, it'll behave the same.
So I've tried, by overwriting undoManagerForTextView:, to have the NSTextFields in the window use the window's shared undoManager. That will indeed lead to the effect that the text fields share the same undo stack, and I can still issue an Undo after editing and then leaving a specific text field.
However, the problem is that it'll then crash, because it appears that the Undo gets applied to the wrong (i.e. the currently active) text field and not to the one the undo action was created from.
I've provided a simple example here: https://github.com/tempelmann/Undo_shared_NSTextFields
It's a freshly created document based app, with two text fields and two text views in the main window. To enable the shared undo manager for the text fields, I've created a subclass ("CustomTextField") of NSTextField that overrides the following function to provide the shared undo manager:
-(NSUndoManager *)undoManagerForTextView:(NSTextView *)view { // NSTextViewDelegate
return view.window.undoManager;
}
And the text fields in the storyboard are set to this subclass instead of NSTextField.
When run, it looks like this:
Now, if you edit the first and the second text view, and then use Undo twice, it'll undo the changes in each text view just fine.
But if you try the same with the two text fields, e.g. by replacing the longer preset texts in the fields with something shorter (such as "1" and "2"), you'll find that Undo replaces the text in the same active text field twice:
How do I make Undo in the text fields behave correctly, i.e. the same as the text views do?

NSTextField edit coalescing

I am writing a Cocoa app which contains several NSTextFields. I am trying to propagate changes to the text in the text field to my model layer, but since I am registering an Undo action when the model is updated, this makes it a bit more complicated than simply listening for controlTextDidChange:.
If I hook onto the delegate method controlTextDidChange:, then it gets called once for every key pressed. If I update the model then, the model will treat each separate keypress as a separate Undo event. This is not desirable. What I want is to be notified when the default "Undo Typing" action is registered with the Undo manager, so I can update the model then. The NSTextField registers one (and only one) such event, when I pause typing for a moment. Unfortunately, I have no idea when this occurs.
I have tried listening for controlTextDidEndEditing:, and updating the model in that delegate method, but then I have to explicitly tab out of the text field before the model will update. Since I archive my model object directly in my NSDocument subclass, doing this will result in data loss for the user if they save the document after changing the contents of the text field, but don't tab out of the text field first, because the model would not have been updated in time. Therefore, this technique will not work.
I have also tried hooking onto the text field's field editor (during control:textShouldBeginEditing:) and listening for textDidEndEditing:, but then I find that this simply doesn't fire, so the model won't be updated, causing data loss as described in the previous paragraph. Can anyone help me figure this out? Thanks!
By default, Apple NSTextField leaves dangling edits as you mentioned.
Because of this problem, amongst others, I subclassed NSTextField and NSApplication to handle text field editing the way it should be handled: Pressing 'Enter', clicking outside the field, and other actions should end editing and produce the Undo entry.
Also, I highly recommend GCUndoManager replacement for NSUndoManager.

Custom accessory view for NSSavePanel of NSDocument

Consider a Cocoa NSDocument that supports two document types (e.g. BMP and JPEG). While one type has no save options (e.g. BMP), the other does (e.g. compression level for JPEG).
How do you implement this?
I understand that you should override prepareSavePanel: and set the accessory view of the given NSSavePanel. However, doing this replaces the default accessory view with the document type pop-up.
Is it necessary to recreate the document type pop-up if when using a custom accessory view?
If yes, how can pass the selected document type and the additional save options to the NSDocument write methods?
If no, how can I show the additional save options (e.g. compression level) only if the corresponding document (e.g. JPEG) type has been selected? Is there a delegate method for document type changes in the NSSavePanel?
I'm pretty certain that yes, if you want a custom accessory view like this, you have to provide the entire thing. It's a shame — but there's no harm in filing a radar!
It might be possible to provide your own accessory view in -prepareSavePanel: and then override -fileTypeFromLastRunSavePanel to return the type selected. However, NSDocument's docs make no promises that it'll respect that, annoyingly. Try it and see!
if that doesn't work, it looks like you need to provide your own implementation of -runModalSavePanelForSaveOperation:delegate:didSaveSelector:contextInfo:. Follow Apple's description of the method:
The default implementation of this method first makes sure that any editor registered using Cocoa Bindings' NSEditorRegistration informal protocol has committed its changes, then creates a save panel, adds a standard "file format" accessory view if there is more than one file type for the user to choose from and [self shouldRunSavePanelWithAccessoryView] returns YES, sets various attributes of the panel, invokes [self prepareSavePanel:theSavePanel] to provide an opportunity for customization, then presents the panel. If the user OKs the panel -saveToURL:ofType:forSaveOperation:delegate:didSaveSelector:contextInfo: is invoked.
The docs for -writableTypesForSaveOperation: do note:
You can invoke this method when creating a custom save panel accessory view to easily present the same set of types that NSDocument would in its standard file format popup menu.

How to customize cursor in NSSearchField?

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.

NSTextFinder action on NSTextView

I'm trying to capture all the NSTextFinderClient calls on my custom NSTextView subclass.
The show action is called on my -(void)performTextFinderAction:(id)sender override, but for find next, find previous, etc. it's not called.
Any ideas?
Thanks!
Edit:
If you create a new project and drag an NSTextView from interface builder, command-g and command-shift-g (find next and find previous) don't work when the find bar is first responder.
Why is this?
I need a custom subclass of NSTextView to respond to the find bar for every event.
I searched in the Apple's TextEdit source code because with TextEdit, the standard search bar within the Text View works fine for command-G (and other shortcuts) even the search field is the first responder.
I found the solution.
Go to your nib for the main menu, and select the "Find" (and related) menu items. They should be bound to the default action called "performFindPanelAction:." Now unbind them and bind to "performTextFinderAction:" of the First Responder instead.
You may not find that action in the First Responder's action list. So you need to add it by yourself in the First Responder's attributes inspector pane.
This was meant by the document below saying
Before OS X v10.7, the default action for these menu items was performFindPanelAction:. Whenever possible which you should update your implementation to use this new action.
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSResponder_Class/#//apple_ref/occ/instm/NSResponder/performTextFinderAction:
The find bar communicates privately with the client's NSTextFinder instead of calling NSResponder's -performTextFinderAction:. This is necessary to allow find to work when something besides the client has key focus.
What are you trying to accomplish?

Resources