Undo Manager is nil --- Why? - macos

Why is [self undoManager] zero in a child window, in a doc-based app?
Should it not refer to the undo manager of its parent window? In the parent window, I get an actual address for the undo manager!

The undoManageris not a member of NSWindowController.
This is just a NSDocument "feature".
An excerpt from the NSDocument docs :
... A document manages its window’s edited status and is set up to perform undo and redo operations. ....
Section "Subclassing NSDocument":
.... Subclasses are also responsible for the creation of the window controllers that manage document windows and for the implementation of undo and redo. ....
The code you've written won't work on other strongly typed languages because you would send a message to an object that doesn't exist. I'm pretty sure you should have a compiler warning here.
Hope this helps,
best,
Flo

Flo's answer was a good starting point. Some time later, it turns out that the responder chain is somehow acting up (or, it may be me :-) ).
The child window, controlled by NSWindowController, should automatically (??) have a document property so that [self document] returns the document associated with this window. It's easy to pull the Undo manager from that.
However, in my application (and in a small testing app, too) this document is not set. When I set it manually from within the document ([newWindow setDocument:self]), everything works: registering the undo/redo actions, the menu bar, etc.

Related

Make "Close All" (cmd+option+W) apply only to one type of document windows

I have an application that manages different types of NSDocument subclasses (along with matching NSWindow subclasses).
For instance, it's possible that the app has one window of type A open, and two windows of type B.
Now, if a window of type B is active, and the user chooses "Close All" or hits cmd+option+W, all my app's windows are sent the close message.
But I only want all of the active window type's windows closed instead, i.e. only the two type B, not the type A window. How do I accomplish this?
I currently have no explicit menu entry for "Close All". Instead, macOS provides that automagically. If there perhaps a way to intercept a "closeAll" message? Can't find one, though.
AppKit will add the Close All menu item if there isn't one. Add an alternate menu item item with key equivalent cmd+option+W below the Close menu and connect it to your own action method.
You might succeed with overriding your document's canClose(withDelegate:,shouldClose:,contextInfo:) to return whether a document should be closed.
If this doesn't behave the way you want, you can create a subclass of NSDocumentController (if you don't have one already). Details on how to do that vary, but usually you have main (menu) XIB or main Storyboard, which has a "Document Controller" object: set its class to your custom class.
Then override closeAllDocuments(withDelegate:,didCloseAllSelector:,contextInfo:) and implement your custom logic.
Note that you should detect whether your app is about to quit and then really do close all you documents (unless you really want to prevent the app quit, e.g. because a document is dirty).
After some digging I figured out where the auto-generated "Close All" menu item sends its action to: To the closeAll: selector of the application target:
Thus my solution is to subclass NSApplication and implement the handler there, which then simply closes all windows that are of the same type (this assumes that I use specific subclasses for my different types of windows):
- (IBAction)closeAll:(id)sender {
Class windowClass = self.keyWindow.class;
for (NSWindow *w in self.windows) {
if (windowClass == w.class) {
[w performClose:sender];
}
}
}
Caution: If you adopt this pattern be aware that:
The closeAll: selector is not documented nor mentioned in the header files, meaning that Apple might feel free to change in a future SDK, though I find that unlikely. It will probably not break anything if that happens, but instead your custom handler won't be called any more.
The code simply tells all windows to close, ignoring the fact that one might reject to be closed, e.g. by user interaction. In that case you may want to stop the loop instead of continuing to close more windows (though I know of no easy way to accomplish that).

Why does setting initialFirstResponder have no effect?

I have a simple form (NSWindow) with 3 text fields. NSWindow's initialFirstResponder is 'pointing' to the first field (NSTextField). All three text fields are circularly linked to each other via nextKeyView.
Problem that I have is that when I start the application from Xcode it'll focus on the text field that was last active (in focus) when the application closed.
So for example, if I name text fields A, B and C and initialFirstResponder is set to A. Now if I start the application, focus on B, and close the application, next time I start it, the focus will be on B.
Why is that and how would I fix this?
(Sorry if this is a trivial question, these are my first steps in cocoa...)
EDIT:
This is on OS X Lion 10.7.1, Xcode 4.1.
EDIT 2:
I found a way to "fix" this... In the main window (or any window for that matter) XIB/NIB file, click on "Attributes Inspector", then uncheck "Restorable" box. Now the application will not store the last position and so the initialFirstResponder seeing will be respected and followed accordingly.
Welcome to Cocoa! :) I suspect this is happening as part of the new user interface preservation features in OS X Lion. (In fact, I just created a simple app with 3 text fields, and I see this behavior too.) Because windows automatically restore themselves, you will see a lot of this behavior happening automatically even if you didn't implement it. This is probably desirable — most applications will work this way, and the user will come to expect it.
However, if you really want to disable it, you can probably do so by subclassing NSWindow or perhaps NSTextField and overriding -encodeRestorableStateWithCoder:. But, I definitely recommend you leave the default behavior alone.
Edit with a little further information: the app state seems to be stored in ~/Library/Saved Application State/com.yourapp.savedState. There you can see a plist file with information about the windows. The other files don't seem easily readable, but they probably contain information about which field is first responder, etc.
Despite this thread being almost 10 years old I'll gonna add an answer. Just about one month after the answer from jbandes OS X 10.7 Lion was introduced.
Following a quote from NSWindowRestoration.h
#interface NSWindow (NSUserInterfaceRestoration)
/* Determines whether the window should be restored on relaunch. By default, windows with NSTitledWindowMask set in the styleMask are restorable, and windows without it set are not.
*/
#property (getter=isRestorable) BOOL restorable API_AVAILABLE(macos(10.7));

NSDocument Subclass not closed by NSWindowController?

Okay, I'm fairly new to Cocoa and Objective-C, and to OOP in general.
As background, I'm working on an extensible editor that stores the user's documents in a package. This of course required some "fun" to get around some issues with NSFileWrapper (i.e. a somewhat sneaky writing and loading process to avoid making NSFileWrappers for every single document within the bundle). The solution I arrived at was to essentially treat my NSDocument subclass as just a shell -- use it to make the folder for the bundle, and then pass off writing the actual content of the document to other methods.
Unfortunately, at some point I seem to have completely screwed the pooch. I don't know how this happened, but closing the document window no longer releases the document. The document object doesn't seem to receive a "close" message -- or any related messages -- even though the window closes successfully.
The end result is that if I start my app, create a new document, save it, then close it, and try to reopen it, the document window never appears. With some creative subclassing and NSLogging, I managed to figure out that the document object was still in memory, and still attached to the NSDocumentController instance, and so trying to open the document never got past the NSDocumentController's "hmm, currently have that one open" check.
I did have an NSWindowController and NSDocumentController instance, but I've purged them from my project completely. I've overridden nearly every method for NSDocument trying to find out where the issue is. So far as I know, my Interface Builder bindings are all correct -- "Close" in the main menu is attached to performClose: of the First Responder, etc, and I've tried with fresh unsullied MainMenu and Document xibs as well.
I thought that it might be something strange with my bundle writing code, so I basically deleted it all and started from scratch, but that didn't seem to work. I took out my -init method overrides, and that didn't help either. I don't have the source of any simple document apps here, so I didn't try the next logical step (to substitute known-working code for mine in the readFromUrl and writeToUrl methods).
I've had this problem for about sixteen hours of uninterrupted troubleshooting now, and needless to say, I'm at the end of my rope. If I can't figure it out, I guess I'm going to try the project from scratch with a lot more code and intensity based around the bundle-document mess.
Hard to tell without code but I would suggest sending:
closeAllDocumentsWithDelegate:didCloseAllSelector:contextInfo:
... to the document controller and then looking at the controller as it is passed to the delegate to see how its state changes.
If the controller closes the document when you send the explicit message then your problem is with the binding to the window.

Trouble with data not saving with bindings and shared NSUserDefaults in IB

I'm having a bit of a strange issue that I can't quite figure out. I'm somewhat of a n00b to Interface Builder. What I am trying to do seems like it should be simple, but it's not working for some reason.
In interface builder I have a preferences window with a simple NSTextField. I have set the value binding to the Shared User Defaults Controller with the controller key "values" and model key "test". I build/run my app and open the preferences window, type some random value into said text field, close the window. Command-Q the app. Then in a shell i do a "defaults read com.xxx.yyy" for my app and the key and value are nowhere to be found. That being said, it seems like the next time I fire up the app and change the value it works but only if I switch focus off of the NSTextField before closing the window.
In the documentation for NSUserDefaults it says that the shared controller saves values immediately, am I missing something stupid here?
Thanks for any help.
I'm answering this a long time after it was asked in case others find it useful.
It sounds like you need to set "Continuously Updates Values" for the text field you've bound. Otherwise, the value is only sent and, accordingly, the preferences only updated when something happens to 'finalise' the edit. That's usually triggered by pressing Return and probably also happens when you switch focus away from the window (though I just tested this in one of my own applications and it didn't seem to commit the edit).

What's a good way to bind from a shared utility window and the frontmost document window?

I have an application which allows for multiple NSDocuments to be open. In this application is a single utility window that contains some functionality that I want to apply to the frontmost document.
I am trying to use bindings here, so the trick is how to cleanly bind the user interface of the utility window to the frontmost document. The goal is that then switching the frontmost document window will update the view in the utility window; controls that are bound to properties of the frontmost document's model would be updated appropriately when state changes in the document's model, etc.
For sending actions from such a window, it's easy to just use first responder; the document object can intercept actions via the responder chain. But I want more than this, and of course you can't bind to the first responder.
A few ideas that I have:
put an object controller in my nib for the shared window. When a document window changes frontmost status, change the content of that binding. A disadvantage of this is that if I were to have another kind of utility window, I'd have to remember to hook up the bindings from the document window to that utility window too!
Make an accessor in the application delegate that gets the frontmost document window by traversing the window list. My utility window would just bind through the application delegate's method. A disadvantage here is that it's not KVO compliant
Have a getter and setter in the application delegate to determine (and perhaps set to be KVO-compliant? would that make sense?) the frontmost document. Perhaps use window notifications set an ivar to the appropriate value when a window loses main status. Update: I'm using this for now, and it actually seems pretty clean. I set the value from the windowDidBecomeMain notification of my doc window and clear it (if it's the current value) in windowWillClose. Unless there is any major objection, this is probably the approach I'll use.
One idea was to bind to mainWindow.windowController.document ... this comes close, except that when my shared window becomes main, then this binding goes away. So really I need to find the frontmost document window's controller (and of the right class).
None of these seem quite right. Is there a better way to do this that I'm missing?
I’ve always bound through Shared Application, mainWindow.document, which works fine. if you have windows w/o documents, you may want to add a mainYourKindOfWindow key that is implemented by watching mainWindow and updating the value based on some filter criteria.
Leopard's TextEdit does this for its inspector. Check it out in file:///Developer/Examples/AppKit/TextEdit.
put an object controller in my nib for the shared window. When a document window changes frontmost status, change the content of that binding.
That makes the most sense to me. You'd change the content to the document instance ([NSDocumentController currentDocument]).
A disadvantage of this is that if I were to have another kind of utility window, I'd have to remember to hook up the bindings from the document window to that utility window too!
Huh? I don't understand this.
Leopard's TextEdit does this for its inspector. Check it out in >file:///Developer/Examples/AppKit/TextEdit.
In TextEdit, inspector values are bound via an intermediate object controller. The controller content object is bound to the shared application mainWindow.
You may bind the content to mainWindow.firstResponder and uncheck "Raises for not applicable keys".
Use the key window, not the main window. KVO might not be supported for NSApplication's keyWindow property, but you can still use NSNotifications if it doesn't work. The reason for this is that NSDocumentController's currentDocument uses the keyWindow, so it better represents the built in functionality. Also, panels can be set to avoid becoming key window.

Resources