Capturing undo/redo key events on NSTableView - cocoa

I need to capture undo/redo key commands in an NSTableView and forward that down to a managed object context's undo manager. I have tried overriding -keyDown, but that's hard to navigate. I really need an internationalized solution to this problem that doesn't revolve around checking for the "z" key with the command key modifier mask.
Is there a way I can set up my tableview to look for the standard "undo" key binding? Any ideas?

Implement windowWillReturnUndoManager in the delegate of the window containing your NSTableView. Then return your object context's undo manager there. Then your table view will be able to receive those events.
[NSWindowDelegate windowWillReturnUndoManager:]

You generally use NSUndoManager in Cocoa apps for handling Undo/Redo events. For more information, refer to http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSUndoManager_Class/Reference/Reference.html

Related

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.

Trying to see the delegate of an NSTextView of an NSCell

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.

Dragging from NSBrowser and use bindings?

I've implemented a browser that shares NSTreeController with NSOutlineView so I can easily switch between them and keep the states in sync. Implementing drag support for NSOutlineView is simple, but it appears that NSBrowser cannot support dragging while using bindings. Has anyone been able to support dragging from NSBrowser while also using bindings?
I solved this by creating custom subclasses of NSBrowser, NSMatrix, and NSBrowserCell. I had to force NSBrowser to use NSRadioModeMatrix mode (single cell) by setting this mode in NSBrowser subclass -mouseDown method because it was constantly being reset.
In the NSBrowserCell subclass, I implemented trackMouse:inRect:ofView:untilMouseUp: by simply having superclass do it's thing, but returned NO so tracking would occur, which allowed mouseDragged: to be called in my NSMatrix subclass. In mouseDragged: I then did all the stuff to get the mouse location, construct a mouse image, and used [self dragImage:at:offset:pasteboard:source:slideBack:].
I'm not sure if this is the best approach, but it worked. It only works when there is a single selection allowed in NSBrowser.

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.

How to get notifications of NSView isHidden changes?

I am building a Cocoa desktop application. I want to know when a NSView's isHidden status has changed. So far using target/action doesn't help, and I can't find anything in NSNotification for this task. I would like to avoid overriding the setHidden method, because then I'll have to override all the NSView derived class that I am using.
UPDATE: I ended up using KVO. The path for "isHidden" is "hidden", probably because the setter is "setHidden".
You could use Key-Value Observing to observe the isHidden property of the NSView(s). When you receive a change notification from one of these views, you can check if it or one of its superviews is hidden with -isHiddenOrHasHiddenAncestor.
A word of warning: getting Key-Value Observing right is slightly tricky. I would highly recommend reading this post by Michael Ash, or using the -[NSObject gtm_addObserver:forKeyPath:selector:userInfo:options] method from the NSObject+KeyValueObserving category from the Google Toolbox for Mac.
More generally, one can override viewWillMoveToWindow: or the other related methods in NSView to tell when a view will actually be showing (i.e. it's window is in the window display list AND the view is not hidden). Thus the dependency on KVO for the 'hidden' key used above is removed, which only works if setIsHidden has been called on that view. In the override, 'window' (or [self window]) will indicate whether the view is being put into a visible view hierarchy (window is non-nil) or being taken out of it (window is nil).
I use it for example to start/stop a timer to update a control from online data periodically - when I only want to update while the control is visible.
Could you override the setter method for the hidden property so that it will trigger some custom notification within your application?

Resources