Luke Sneeringer
Luke Sneeringer

Reputation: 9428

How do I use a subclass of NSDocumentController in Xcode 4?

I am currently in the process of trying to teach myself Cocoa development. Toward this end, I purchased a mostly-excellent book, Cocoa Recipes for Mac OS X: Vermont Recipes, which walks through creating a sample application. It's pretty good, but it's written against XCode 3.2 rather than XCode 4. So far, I've been able to bridge this myself, but I've run up against an issue where I can't figure out how to follow the instruction.

Essentially, the book goes through a sample case of subclassing NSDocumentController so that the application can handle two (eventually maybe an arbitrary number) different types of documents, and so it opens the appropriate window for each type. So, I've created a custom subclass of NSDocumentController (which the book calls VRDocumentController), and now I need to make it such that an instance of this controller loads relatively early in the application launch process. Basically, the class is a singleton, and so I have to instantiate mine before the application instantiates the standard class, and this has to be done early in the process. Fair enough.

The book cites the Apple documentation for subclassing NSDocumentController, which states that there are two ways to attack the problem: to instantiate the class in your MainMenu.xib file or to instantiate one in your -applicationWillFinishLaunching: delegate method. The Apple documentation doesm't give clear instruction on how to do either of these (more on that in a moment) and the book covers only the first version, which I think is probably my preferred method.

My problem: I cannot for the life of me pull this off in XCode 4. The instructions the book provides for XCode 3.2 are no longer accurate, because Interface Builder has been shuffled into XCode itself now and the new version of the "classes tab" no longer shows my project's classes. I found this question on Stack Overflow asking a similar question, so I tried following the accepted answer there. However, when I open the Identity Inspector and try to type VRDocumentController, it just beeps at me and doesn't take it. None of the other controller classes I've written seem to be acceptable inputs either.

I'd also be happy to go the other route; instantiating a copy in the -applicationWillFinishLaunching method. But, I have no earthly idea in which class that method actually belongs, or what its return type is. I've done a non-trivial amount of searching for that, too, with no luck.

Upvotes: 16

Views: 8083

Answers (9)

Aleksandr Medvedev
Aleksandr Medvedev

Reputation: 8968

Had to spent a couple of hours figuring it out in nowadays Xcode (14.1) interface builder and here are my findings:

  1. In the Object Library of interface builder find Object: enter image description here

  2. Drag-n-Drop it under your Application Scene:

enter image description here

  1. Specify your custom class under Class field of the newly created object's Identity Inspector:

enter image description here

And you are done

Upvotes: 0

Moose
Moose

Reputation: 2737

Just create an object in the main storyboard, set it's class as your NSDocumentController subclass, and it will be used as the application shared document controller.

enter image description here

Upvotes: 4

feca
feca

Reputation: 1169

You can subclass easily in Swift 3:

class AppDelegate: NSObject {
    let docController = DocController()
}

class DocController: NSDocumentController {
}

Upvotes: 5

Leslie Godwin
Leslie Godwin

Reputation: 2661

Easier method of getting the Custom Document Controller created in time is to implement the + (void) load method:

This is literally all you need to do it implement a custom document controller.

@implementation AppDocumentController

+ (void) load
{
    [AppDocumentController new];
}

// ... your overriding goes here

@end

This is cool because there's no separation of the codez. Not 100% sure if the controller is created too early, but works well for me.

Upvotes: 9

Joel
Joel

Reputation: 2295

Here's a solution:

// In MyDocumentController.h
@interface MyDocumentController : NSDocumentController
@end

// In MyDocumentController.m
@implementation MyDocumentController
  // ... your custom code here
@end

// In MyAppDelegate.h
@interface AppDelegate : NSObject <NSApplicationDelegate>
@property (nonatomic, strong) IBOutlet MyDocumentController *myController;
@end

Now, go into MainMenu.xib and add a custom object to your nib. Be sure to use the inspector on this object, and in the third pane of the inspector, set the custom class to MyDocumentController.

Now wire this object into your outlet by ctrl-clicking on your new object in the left list of things in the nib and drag (while still ctrl-clicking) to App Delegate. Release and it should flash and show myController. Click that and you're all set.

Now you can test that you're getting your custom controller with the following code:

// In MyAppDelegate.m
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    NSLog(@"sharedController is %@", [NSDocumentController sharedController]);
}

This should print something like

<MyDocumentController: 0x600000044710>

You're done!

The key is to wire in your custom MyDocumentController in MainMenu.xib. If you try to just initialize it in applicationDidFinishLaunching, it's often too late and AppKit has already created and set sharedDocumentController (of which there can only be one).

Also, the outlet needs to be strong not weak because your custom controller is a top-level object in the nib not referenced by anything else in the nib.

This works for me on multiple versions of OS X and Xcode.

Please mark this as the correct answer if it works for you! :-)

Upvotes: 2

user557219
user557219

Reputation:

In your application delegate:

// LukeAppDelegate.h
#import "LukeAppDelegate.h"
#import "VRDocumentController"

- (void)applicationWillFinishLaunching:(NSNotification *)notification {
    VRDocumentController *dc = [[VRDocumentController alloc] init];
}

This will make sure that an instance of VRDocumentController is created and registered as the shared document controller, preventing Cocoa from using the default NSDocumentController.

As to why you haven’t been able to use a custom object in your nib file, make sure that that you select Object (blue cube) instead of Object Controller (blue cube inside a green sphere) when dragging a new object into the nib file.


Edit: If you’re targeting an OS X version that supports restoration, -applicationWillFinishLaunching: may be too late to register a custom document controller. If the application delegate is placed inside MainMenu.xib, it should be instantiated by the nib loading process before any documents are restored, hence you can move the NSDocumentController subclass initialisation to the application delegate’s init method:

// LukeAppDelegate.h
#import "LukeAppDelegate.h"
#import "VRDocumentController"

- (id)init {
    self = [super init];
    VRDocumentController *dc = [[VRDocumentController alloc] init];
    return self;
}

Upvotes: 16

zakdances
zakdances

Reputation: 23685

The answer marked correct (@Bavarious'answer) fails to work on document-based apps because documents often load before any applicationWillFinishLaunching: is called. After reading the helpful clues in @Graham Perks answer, I tried a few different approaches and this seems to work reliably:

@implementation AppDelegate

- (id)init
{
    self = [super init];
    if (self) {
        MyDocumentController *dc = [[MyDocumentController alloc] init];
        if (dc) {};
    }
    return self;
}

Note Obviously (if not already created) you'll need to create an AppDelegate yourself first and link it in your MainMenu.XIB.

Upvotes: 7

Graham Perks
Graham Perks

Reputation: 23390

If - as can sometimes happen - your NSDocumentController subclass seems to randomly NOT be returned as the sharedDocumentController, add a breakpoint on -[NSDocumentController init] and see on the stack what bit of your code is (probably accidentally) creating the regular NSDocumentController. The first controller created is always returned from sharedDocumentController.

This can happen when something in your XIB file gets init'd and (even indirectly) invokes sharedDocumentController. The order of object creation during XIB file loading isn't deterministic, so sometimes your NSDocumentController subclass will be created first, other times another offending object gets init'd first and subsequently your subclass gets ignored.

You'll have to move the other object's creation to a later point (e.g. applicationDidFinishLaunching:). Or have the object created at the same time, but delay whatever initialization its doing.

(Just ran into this with an inspector panel screwing things up, thought it worth noting here!)

Upvotes: 1

Keith Knauber
Keith Knauber

Reputation: 802

// This looked like the best thread to post this.
// This is incomplete skeleton example of NSDocument/NSUndoManager usage.
// Use at your own risk
// Ultimately, would love if Apple reviewed/corrected this and added correct sample code to their docs.
// I put pseudo code in implementation specific spots:
//    "... implementation specific ..."
//    "... implement something inherently dangerous... loading your data from a file"
//    "... implement something inherently dangerous... commit changes to a file ..."
//    "... in my implementation, I prompt user with save/quit options when edit window is closed ..."
//

// Apple's documentation states that NSDocumentController should *rarely* be subclassed, 
// but Apple fails to provide sample code to accomplish what you need.
// After trying to subclass NSDocumentController, I decided to rip all that code out.
// What I really needed, in my case, was this:
//    @interface NSObject(NSApplicationDelegate)
//    - (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename
//
// My requirements in a nutshell:
// NSUndoManager, only allow 1 document open at a time (for now), multiple editing windows for 1 document, 
// automatically save to temporary file, ability to quit and relaunch without saving,
// Custom "open", "save", "save as", "revert to saved", etc... MyDocumentController
// subclass below provides a simple way to bypass much of the NSDocumentController
// file dialogs, but still use other features of NSDocument (like track unsaved changes).
// Override only "NSDocumentController documentForURL".

/* your NSDocument subclass MUST be defined in your Info.plist.
    If you don't, Lion will complain with a NSLog message that looks like this:
  -[NSDocumentController openDocumentWithContentsOfURL:display:completionHandler:] failed during state restoration. Here's the error:
   Error Domain=NSCocoaErrorDomain Code=256 "The document “blah.myfiletype” could not be opened.  cannot open files in the “blah Document” format." 
In this example, files of type .myfiletype are associated with my NSDocument subclass
Personally, I hate having to put a class name in my Info.plist, because its not maintainable!
Note to Apple developers: if you are going to force us to do this, then pleasssse make
sure that an XCode search "In Project" for "MyDocument" finds the entry in the plist file!
Oh wow, I just answered my own question: Apple! please make "All candidate files" the
default option for search!

MyDocument:
<key>CFBundleDocumentTypes</key>
<array>
    <dict>
        <key>CFBundleTypeName</key>
        <string>My Funky App File</string>
        <key>NSDocumentClass</key>
        <string>MyDocument</string>
        <key>CFBundleTypeExtensions</key>
        <array>
            <string>myfiletype</string>
        </array>
        <key>CFBundleTypeIconFile</key>
        <string>My_File_Icon.icns</string>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
    </dict>
</array>
*/
#define kMyFileTypeExtension @"myfiletype"

@interface MyDocument : NSDocument
{
}
- (void) registerForUndoGrouping;
- (NSString *)filePath; // convenience function
- (void) setFilePath: (NSString *)filePath; // convenience function

+ (BOOL) openMyDocument: (NSString *)filename; // class method
@end

extern int gWantsToQuit; // global indicator that it's time to stop drawing/updating

// track my startup state so I can control order of initialization, and so I don't
// waste time drawing/updating before data is available.
enum // MyLaunchStatus
{
   kFinishedPreWaking       = 0x01, // step 1: applicationWillFinishLaunching called
   kFinishedOpenFile        = 0x02, // step 2: (optionally) application:openFile: called (important for detecting double-click file to launch app)
   kFinishedWaking          = 0x04, // step 3: NSApp run loop ready to run. applicationDidFinishLaunching
   kFinishedPreLaunchCheck  = 0x08, // step 4: error recovery check passed
   kFinishedLoadingData     = 0x10, // step 5: data loaded
   kFinishedAndReadyToDraw  = 0x20, // step 6: run loop ready for drawing

};

typedef NSUInteger MyLaunchStatus;

#pragma mark -
@interface MyAppController : NSResponder <MidiProtocol, NSOpenSavePanelDelegate, NSTextFieldDelegate, NSWindowDelegate>
MyDocument *toDocument;
@end



#pragma mark -
@implementation MyDocument

- (id)init
{
   if ( !(self = [super init]) ) return self;
   return self;
}

- (void) registerForUndoGrouping
{


   [[NSNotificationCenter defaultCenter] addObserver:self
                                            selector:@selector(beginUndoGroup:) 
                                                name:NSUndoManagerDidOpenUndoGroupNotification 
                                              object:nil]; 
}

- (void)canCloseDocumentWithDelegate:(id)delegate shouldCloseSelector:(SEL)shouldCloseSelector contextInfo:(void *)contextInfo
{
   if ( [[MyAppController instance] windowShouldClose: delegate] )
      if ( [delegate respondsToSelector:@selector(close)] )
         [delegate close];
   //[delegate performSelector:shouldCloseSelector];
   //if ( [delegate isKindOfClass:[NSWindow class]] )
   //   [delegate performClose:self]; // :self];
   return; // handled by [[MyAppController instance] windowShouldClose:(id)sender
}

- (void)shouldCloseWindowController:(NSWindowController *)windowController delegate:(id)delegate shouldCloseSelector:(SEL)shouldCloseSelector contextInfo:(void *)contextInfo
{
   if ( [[MyAppController instance] windowShouldClose: [windowController window]] )
      if ( [[windowController window] respondsToSelector:@selector(close)] )
         [[windowController window] close];
}

- (void) beginUndoGroup: (NSNotification *)iNotification
{
   NSUndoManager *undoMgr = [self undoManager];
   if ( [undoMgr groupingLevel] == 1 )
   {
      // do your custom stuff here
   }
}

// convenience functions:
- (NSString *)filePath { return [[self fileURL] path]; }
- (void) setFilePath: (NSString *)filePath 
{ 
   if ( [filePath length] )
      [self setFileURL:[NSURL fileURLWithPath: filePath]];
   else
      [self setFileURL:nil];
}

- (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)anItem
{
   if ( [self isDocumentEdited] && [anItem action] == @selector(revertDocumentToSaved:) )
      return YES;
   BOOL retVal = [super validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)anItem];
   return retVal;
}

- (IBAction)revertDocumentToSaved:(id)sender
{
   NSInteger retVal = NSRunAlertPanel(@"Revert To Saved?", [NSString stringWithFormat: @"Revert to Saved File %@?", [self filePath]], @"Revert to Saved", @"Cancel", NULL);
   if ( retVal == NSAlertDefaultReturn )
      [[MyAppController instance] myOpenFile:[self filePath]];
}

+ (BOOL) openMyDocument: (NSString *)filename
{
   if ( ![[filename pathExtension] isEqualToString: kMyAppConsoleFileExtension] )
      return NO;

   // If the user started up the application by double-clicking a file, the delegate receives the application:openFile: message FIRST
   MyLaunchStatus launchStatus = [[MyAppController instance] isFinishedLaunching];
   BOOL userDoubleClickedToLaunchApp = !( launchStatus & kFinishedPreLaunchCheck );

   MyDocument *currDoc = [[MyAppController instance] document];
   NSString *currPath = [currDoc filePath];
   NSInteger retVal;
   NSLog( @"open file %@ currPath %@ launchStatus %d", filename, currPath, launchStatus );
   if ( userDoubleClickedFileToLaunchApp )
   {
      // user double-clicked a file to start MyApp
      currPath = [[NSUserDefaults standardUserDefaults] objectForKey:@"LastSaveFile"];
      if ( [currPath isEqualToString: filename] )
      {
         sWasAlreadyOpen = YES;
         if ( [[[NSUserDefaults standardUserDefaults] objectForKey:@"isDocumentEdited"] boolValue] == YES )
         {
            retVal = NSRunAlertPanel(@"Open File", @"Revert to Saved?", @"Revert to Saved", @"Keep Changes", @"Quit", NULL);
            if ( retVal == NSAlertDefaultReturn )
            {
               [[MyAppController instance] myOpenFile:filename];
            }
            else if ( retVal == NSAlertOtherReturn )
               exit(0);
         }

         // proceed with normal startup
         if ( currDoc )
            return YES;
         else
            return NO;
      }
   }

   if ( !(launchStatus & kFinishedPreLaunchCheck ) ) // not done launching
      return YES; // startup in whatever state we were before

   if ( [currPath isEqualToString: filename] )
   {
      sWasAlreadyOpen = YES;
      NSLog( @"is edited %d currDoc %@", [currDoc isDocumentEdited], currDoc );
      if ( [currDoc isDocumentEdited] )
         [currDoc revertDocumentToSaved:self]; // will prompt
      else // document is already open, so do what Apple's standard action is... 
         [currDoc showWindows];
   }
   else 
   {
      if ( [currDoc isDocumentEdited] )
         retVal = NSRunAlertPanel(@"Open File", [NSString stringWithFormat: @"The current file has unsaved changes.  Discard unsaved changes and switch to file '%@'?", filename], @"Discard unsaved changes and switch to file", @"Keep Current", NULL);
      else
         retVal = NSRunAlertPanel(@"Switch to File", [NSString stringWithFormat: @"Switch to File '%@'?\n\nCurrent file '%@'", filename, currfilePath ? currfilePath : @"Untitled"], @"Switch", @"Keep Current", NULL);
      if ( retVal == NSAlertDefaultReturn )
         [[MyAppController instance] myOpenFile:filename];
   }

   // user cancelled
   if ( currDoc )
      return YES;
   else
      return NO;
}


// Note: readFromURL is here for completeness, but it should never be called,
// because we override NSDocumentController documentForURL below.
- (BOOL)readFromURL:(NSURL *)absoluteURL ofType:(NSString *)typeName error:(NSError **)outError
{
   if ( outError )
      *outError = nil;
   if ( ![typeName isEqualToString: kMyFileTypeExtension ] ) // 
      return NO;
   return YES;
}

// Note: writeToURL is here for completeness, but it should never be called,
// because we override NSDocumentController documentForURL below.
- (BOOL)writeToURL:(NSURL *)absoluteURL ofType:(NSString *)typeName error:(NSError **)outError
{
   if ( outError )
      *outError = nil;
   return YES;
}
@end


// kpk migrating slowly toward NSDocument framework 
// (currently most functionality is in MyAppController)
// Must bypass default NSDocumentController behavior to allow only 1 document
// and keep MyAppController responsible for read, write, dialogs, etc.
@implementation MyDocumentController

// this should be the only override needed to bypass NSDocument dialogs, readFromURL,
// and writeToURL calls.
// Note: To keep Lion happy, MainInfo.plist and Info.plist must define "MyDocument" for key "NSDocumentClass"
- (id)documentForURL:(NSURL *)absoluteURL
{
   MyDocument *currDoc = [[MyAppController instance] document];
   if ( [[currDoc filePath] isEqualToString: [absoluteURL path]] )
      return currDoc;
   else
      return nil;
}

@end

#pragma mark -
@implementation MyAppController
static MyAppController *sInstance;

+ (MyAppController *)instance
{
   return sInstance; // singleton... why is this not in all Apple's sample code?
}


// called by main.mm before MyAppController (or NSApp for that matter) is created.
// need to init some global variables here.
+ (void) beforeAwakeFromNib
{
   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

   ... implementation specific ...

   // disable fancy stuff that slows launch down
   [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:NO] forKey: @"NSAutomaticWindowAnimationsEnabled"];
   [pool release];
}


- (void) awakeFromNib
{
   NSLog(@"MyAppController awake\n");
   sInstance = self;

   [toWindow setNextResponder:self];
   [NSApp setDelegate:self];


   [self addWindowToDocument:toWindow];
}

- (MyDocument *)document 
{ 
   if ( !toDocument )
   {
      toDocument = [[MyDocument alloc] init];
   }
   return toDocument; 
}

- (NSUndoManager *)undoManager
{
   // !!! WARNING: there are multiple NSUndoManager's in this App

   // Note: when an editable text field is in focus, 
   // NSTextField will create 
   // a separate undo manager for editing text while that field is in focus.
   // This means that hitting undo/redo while editing a text field will not go beyond the scope of that field.

   // This will return the global undo manager if the keyWindow was registered
   // via [self addWindowToDocument:];
   // Windows which are NOT part of the document (such as preferences, popups, etc.), will
   // return their own undoManager, and undo will do nothing while those windows are in front.
   // You can't undo preferences window changes, so we don't want to surprise the user.
   NSUndoManager *undomgr =  [[NSApp keyWindow] undoManager];
   if ( undomgr )
   {
      static bool sFirstTime = true;
      if ( sFirstTime )
      {
         sFirstTime = false;
         [undomgr setLevelsOfUndo:1000]; // set some sane limit
         [[NSNotificationCenter defaultCenter] addObserver:self
                                                  selector:@selector(beginUndo:) 
                                                      name:NSUndoManagerWillUndoChangeNotification 
                                                    object:nil]; 
         [[NSNotificationCenter defaultCenter] addObserver:self
                                                  selector:@selector(beginUndo:) 
                                                      name:NSUndoManagerWillRedoChangeNotification 
                                                    object:nil];  

         [toDocument registerForUndoGrouping];
//         [[NSNotificationCenter defaultCenter] addObserver:self
//                                                  selector:@selector(endUndo:) 
//                                                      name:NSUndoManagerDidUndoChangeNotification 
//                                                    object:nil];       
//         [[NSNotificationCenter defaultCenter] addObserver:self
//                                                  selector:@selector(endUndo:) 
//                                                      name:NSUndoManagerDidRedoChangeNotification 
//                                                    object:nil];  
      }

   }
   return undomgr;
}

- (void) showStatusText: (id)iStatusText
{
  ... implementation specific ...
}

- (void) beginUndo:(id)sender
{
  // implementation specific stuff here
     NSUndoManager *undomgr =  [[NSApp keyWindow] undoManager];

   if ( [sender object] == undomgr )
   {
      if ( [undomgr isUndoing] )
         [self showStatusText: [NSString stringWithFormat:@"Undo %@", [undomgr undoActionName]]];
      else if ( [undomgr isRedoing] )
         [self showStatusText: [NSString stringWithFormat:@"Redo %@", [undomgr redoActionName]]];
   }
}

// Add a window (with a window controller) to our document, so that the window
// uses the document's NSUndoManager.  In the future, we may want to use other features of NSDocument.
- (void)addWindowToDocument:(NSWindow *)iWindow
{
   NSString *autosaveName = [iWindow frameAutosaveName]; // preserve for "mainWindow", others.
   NSWindowController *winController = [iWindow windowController];
   if ( !winController )
      winController = [[NSWindowController alloc] initWithWindow:iWindow];

   // create document if needed, and add window to document.
   [[self document] addWindowController: winController];
   if ( autosaveName )
      [iWindow setFrameAutosaveName:autosaveName]; // restore original for "mainWindow", others.
   [winController setNextResponder:self]; // keep last hotkey destination... see keyDown:

}

- (void) myOpenFile:(NSString*)path
{
  // this is just a skeleton of what I do to track unsaved changes between relaunches

   [toDocument setFilePath:path];

  ... implementation specific ...
      [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:NO] forKey:@"isDocumentEdited"];
      [toDocument updateChangeCount:NSChangeCleared];
      [[NSUserDefaults standardUserDefaults] setObject:[toDocument filePath] forKey:@"LastSaveFile"];

      BOOL success = [[NSUserDefaults standardUserDefaults] synchronize]; // bootstrap
      // kpk very important... resetStandardUserDefaults forces the immutable
      // tree returned by dictionaryWithContentsOfFile to be mutable once re-read.
      // Apple: "Synchronizes any changes made to the shared user defaults object and releases it from memory.
      //         A subsequent invocation of standardUserDefaults creates a new shared user defaults object with the standard search list."
      [NSUserDefaults resetStandardUserDefaults];

      NSString *name = [[NSUserDefaults standardUserDefaults] objectForKey:@"LastSaveFile"]; 
}

- (void) mySaveData:(NSString*)path
{
  // this is just a skeleton of what I do to track unsaved changes between relaunches
     @try 
   {
  ... implement something inherently dangerous... commit changes to a file ...
         if ( !errorStr )
         {
            if ( [toDocument isDocumentEdited] )
            {
               // UInt64 theTimeNow = VMPGlue::GetMilliS();
               [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:NO] forKey:@"isDocumentEdited"];
               [[NSUserDefaults standardUserDefaults] synchronize]; // bootstrap
               // DLog( @"synchronize success %d ms", (int)(VMPGlue::GetMilliS() - theTimeNow) );
            }
            // tbd consider MyDocument saveToURL:ofType:forSaveOperation:error:
            [toDocument updateChangeCount:NSChangeCleared];
         }
   @catch (...)
   {
      ... run critical alert ...
   }
}

- (void) finishLoadingData
{
   @try 
   {


     if ( dataexists )
     {
          ... implement something inherently dangerous... loading your data from a file


      [toDocument setFilePath: [[NSUserDefaults standardUserDefaults] objectForKey:@"LastSaveFile"]];
      NSNumber *num = [[NSUserDefaults standardUserDefaults] objectForKey:@"isDocumentEdited"];
      if ( [num boolValue] == YES )
         [toDocument updateChangeCount:NSChangeDone];
     }
     else
     {
        [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:NO] forKey:@"isDocumentEdited"];
        [toDocument updateChangeCount:NSChangeCleared];
     }

     sFinishedLaunching |= kFinishedLoadingData;
   }
   @catch (...)
   {
      // !!! will not return !!!
      ... run critical alert ...
      // !!! will not return !!!
   }
}
#pragma mark NSApplication delegate

// Apple: Sent directly by theApplication to the delegate. The method should open the file filename, 
//returning YES if the file is successfully opened, and NO otherwise. 
//If the user started up the application by double-clicking a file, the delegate receives the application:openFile: message before receiving applicationDidFinishLaunching:. 
//(applicationWillFinishLaunching: is sent before application:openFile:.)
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename
{
   BOOL didOpen = [MyDocument openMyDocument: filename];
   sFinishedLaunching |= kFinishedOpenFile;
   return didOpen;
}

// NSApplication notification
- (void) applicationDidFinishLaunching:(NSNotification*)note
{
   // kpk note: currentEvent is often nil at this point! [[NSApp currentEvent] modifierFlags]
   CGEventFlags modifierFlags = CGEventSourceFlagsState(kCGEventSourceStateHIDSystemState);

   sFinishedLaunching |= kFinishedWaking;
   if ( modifierFlags & (kCGEventFlagMaskShift | kCGEventFlagMaskCommand) )
   {
      ... implementation specific ... alert: @"Shift or Command key held down at startup.\nWhat would you like to do?" 
                           title: @"Startup Options"
                     canContinue: @"Continue" ];
   }
   sFinishedLaunching |= kFinishedPreLaunchCheck;
   [self finishLoadingData];
   sFinishedLaunching |= kFinishedAndReadyToDraw;   
}

- (BOOL)windowShouldClose:(id)sender
{
   if ( [sender isKindOfClass: [NSWindow class]] && sender != toWindow )
      return YES; // allow non-document-edit windows to close normally

   ... in my implementation, I prompt user with save/quit options when edit window is closed ...
   return NO;
}

- (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication*)sender
{
   if ( !gWantsToQuit && [toDocument isDocumentEdited] )
   {
      if ( ![self windowShouldClose:self] )
         return NSTerminateCancel;

   }
   return NSTerminateNow;
}

- (void) applicationWillTerminate:(NSNotification *)notification
{
   if ( gWantsToQuit )
   {
      ... implementation specific ... dont save potentially wonky data if relaunch is required
   }
   else
   {  
      [self saveData: [toDocument filePath]];
   }
}
@end

Upvotes: -2

Related Questions