Question

I'm trying to figure out how to limit my NSDocument based application to one open document at a time. It is quickly becoming a mess.

Has anyone been able to do this in a straightforward & reliable way?

////EDIT//// I would like to be able to prompt the user to save an existing open document and close it before creating/opening a new document.

////EDIT 2 I'm now trying to just return an error with an appropriate message if any documents are opening -- however, the error message is not displaying my NSLocalizedKeyDescription. This is in my NSDocumentController subclass.

-(id)openUntitledDocumentAndDisplay:(BOOL)displayDocument error:(NSError **)outError{


if([self.documents count]){

    NSMutableDictionary* dict = [NSMutableDictionary dictionaryWithObject:@"Only one document can be open at a time. Please close your document." forKey:NSLocalizedDescriptionKey];

    *outError = [NSError errorWithDomain:@"Error" code:192 userInfo:dict];

    return nil;
}
return     [super openUntitledDocumentAndDisplay:displayDocument error:outError];
}
Was it helpful?

Solution

It won't be an easy solution, since it's a pretty complex class, but I would suggest that you subclass NSDocumentController and register your own which disables opening beyond a certain number of documents. This will allow you to prevent things like opening files by dropping them on the application's icon in the dock or opening in the finder, both of which bypass the Open menu item.

You will still need to override the GUI/menu activation code to prevent Open... from being available when you have a document open already, but that's just to make sure you don't confuse the user.

Your document controller needs to be created before any other document controllers, but that's easy to do by placing a DocumentController instance in your MainMenu.xib and making sure the class is set to your subclass. (This will cause it to call -sharedDocumentController, which will create an instance of yours.)

In your document controller, then, you will need to override:

- makeDocumentForURL:withContentsOfURL:ofType:error:
- makeUntitledDocumentOfType:error:
- makeDocumentWithContentsOfURL:ofType:error:

to check and see if a document is already open and return nil, setting the error pointer to a newly created error that shows an appropriate message (NSLocalizedDescriptionKey).

That should take care of cases of drag-and-drop, applescript,etc.

EDIT As for your additional request of the close/save prompt on an opening event, that's a nastier problem. You could:

  1. Save off the information (basically the arguments for the make requests)
  2. Send the -closeAllDocumentsWithDelegate:didCloseAllSelector:contextInfo: with self as a delegate and a newly-created routine as the selector
  3. When you receive the selector, then either clear out the saved arguments, or re-execute the commands with the arguments you saved.

Note that step 2 and 3 might need to be done on delay with performSelector

I haven't tried this myself (the rest I've done before), but it seems like it should work.

OTHER TIPS

Here's the solution I ended up with. All of this is in a NSDocumentController subclass.

- (NSInteger)runModalOpenPanel:(NSOpenPanel *)openPanel forTypes:(NSArray *)extensions{

    [openPanel setAllowsMultipleSelection:NO];

    return [super runModalOpenPanel:openPanel forTypes:extensions];
}

-(NSUInteger)maximumRecentDocumentCount{
    return 0;
}


-(void)newDocument:(id)sender{

    if ([self.documents count]) {

        [super closeAllDocumentsWithDelegate:self
                         didCloseAllSelector:@selector(newDocument:didCloseAll:contextInfo:) contextInfo:(void*)sender];

    }
    else{
        [super newDocument:sender];
    }
}

- (void)newDocument:(NSDocumentController *)docController  didCloseAll: (BOOL)didCloseAll contextInfo:(void *)contextInfo{

    if([self.documents count])return;

    else [super newDocument:(__bridge id)contextInfo];

}

-(void)openDocument:(id)sender{

    if ([self.documents count]) {

        [super closeAllDocumentsWithDelegate:self
                         didCloseAllSelector:@selector(openDocument:didCloseAll:contextInfo:) contextInfo:(void*)sender];

    }
    else{
        [super openDocument:sender];
    }



}

- (void)openDocument:(NSDocumentController *)docController  didCloseAll: (BOOL)didCloseAll contextInfo:(void *)contextInfo{

    if([self.documents count])return;
    else [super openDocument:(__bridge id)contextInfo];

}

Also, I unfortunately needed to remove the "Open Recent" option from the Main Menu. I haven't figured out how to get around that situation.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top