2

I'm writing a Cocoa app using the document architecture. Whenever an untitled document is created in this app, the user should be shown a window that lets them pick a template and prompts for other information. Only one of these windows should show up at a time, and preferably it should be possible to interact with the rest of the application while the template chooser is visible. (This is how Pages behaves.)

I've got most of this working by overriding -[NSDocumentController openUntitledDocumentAndDisplay:error:]:

- (id)openUntitledDocumentAndDisplay:(BOOL)displayDocument 
                               error:(NSError *__autoreleasing *)outError {
    TsDocument * doc = [self makeUntitledDocumentOfType:self.defaultType 
                                                  error:outError];
    if(!doc) {
        return nil;
    }

    TsNewWindowController * newController = [TsNewWindowController new];
    newController.document = doc;

    if([NSApp runModalForWindow:newController.window] == NSRunAbortedResponse) {
        if(outError) {
            *outError = [NSError errorWithDomain:NSCocoaErrorDomain 
                                            code:NSUserCancelledError 
                                       userInfo:nil];
        }
        return nil;
    }

    [self addDocument:doc];

    if(displayDocument) {
        [doc makeWindowControllers];
        [doc showWindows];
    }

    return doc;
}

However, as you can see, the window is displayed modally, blocking access to the rest of the app. Is there a simple way to achieve what I want without making the template picker modal?


To explain a couple things more clearly:

  1. I do of course know that -runModalForWindow: will run the window modally—it's right there in the name! I'm looking for another way to display the window that will still block -openUntitledDocumentAndDisplay:error:, or that doesn't require me to block the method at all.

  2. I don't believe I can simply create the document, show the newController's window, and call the document's makeWindowControllers and showWindows later because, if the app quits, Restore will not show the template chooser—it shows the normal editing interface.

Becca Royal-Gordon
  • 17,541
  • 7
  • 56
  • 91

2 Answers2

4

You do need to create and use a NSWindowController, but you need to do so before openUntitledDocument…:: is called.

In the unreleased Adium Xtras Creator, I tapped in at several points:

  • In the application's delegate, in applicationOpenUntitledFile:, I show the template-chooser window and return YES.
  • In the document controller, in removeDocument:, I pass the message on to super, then check whether there are still any documents open. If not, I show the template-chooser window.
  • In the document controller, in addDocument:, I hide the template-chooser window, then pass to super.

Thus:

  • If the user tries to create a new document (of no particular type) by any means, the template-chooser will be shown instead.
  • If the user creates a new document (of an explicit type) by any means, the template-chooser will be hidden. (The application I did this in had its “New” menu item set up as a submenu containing specific types.)
  • If the user opens any document by any means, the template-chooser will be hidden.
  • If the user closes the last open document, the template-chooser will be shown.
  • If the user or another application tries to reopen the application by any means, the template-chooser will be shown.
Peter Hosey
  • 95,783
  • 15
  • 211
  • 370
1

You are calling runModalForWindow: so of course it's running the window as a modal window.

Why not just show the window instead? Use an NSWindowController and call showWindow: to display the template window. In your window controller, implement actions that react to the user's selection and then create the appropriate document (or cancel).

I don't think you need to actually create a document in openUntitledDocumentAndDisplay:error:.

Rob Keniger
  • 45,830
  • 6
  • 101
  • 134
  • I do, of course, realize that `-runModalForWindow:` will run the window modally—I've expanded the original post to reply in more detail on that point. Can I really return from `-openUntitledDocumentAndDisplay:error:` without returning an open document? Won't my caller try to display the error if I do? – Becca Royal-Gordon Feb 20 '12 at 22:26
  • What happens when you try it? – Rob Keniger Feb 21 '12 at 10:56
  • Returning `nil` from `openUntitledDocumentAndDisplay:error:` causes an exception to be thrown; a `nil` `outError`regrettably does not help. – Graham Miln Jul 24 '13 at 09:31