ftp.nice.ch/pub/next/developer/objc/appkit/BasicApp.99.99.s.tar.gz#/Flash/BasicApp.subproj/AppWithDoc.m

This is AppWithDoc.m in view mode; [Download] [Up]

#import "AppWithDoc.h"
/* -------------------------------------------------------------------
#import <appkit/Application.h>
	RDR, Inc. 

	Objective-C source file for the class AppWithDoc

	COPYRIGHT (C), 1991, RDR, Inc.
	ALL RIGHTS RESERVED.

	Responsible:			Approved:
	RDR:Ernest Prabhakar		

	Date:				Rev:
	1991-Jun-27			0.9

                              AppWithDoc                              

	1. Introduction

 * This class is used primarily to handle the opening of new documents and
 * other application-wide activity.  It listens for requests from the
 * Workspace Manager to open a Document file as well as target/action
 * messages from the New and Open... menu items. 


	2. Revision History

	___	The starting point.	Ernest Prabhakar/1990-Jul-03
		Upgraded to 2.0		Ernest Prabhakar/1991-Feb-15
		New Release		ENP/1991-Jun-27

	3. Source Code

------------------------------------------------------------------- */

/* ------------------------- Import files ------------------------- */
#import <appkit/nextstd.h>
#import <objc/hashtable.h>
#import <appkit/defaults.h>
#import <sys/param.h>
#import <ldsyms.h>
#import <sys/loader.h>
#import <libc.h>
#import <string.h>
#import <mach.h>

#import <sys/time.h>
#import <sys/resource.h>

/* ------------------------  Classes used  ------------------------ */
#import "Document.h"
#import "PageMargins.h"

#import <objc/List.h>
#import <appkit/Font.h>
#import <appkit/Form.h>
#import <appkit/Box.h>
#import <appkit/Menu.h>
#import <appkit/MenuCell.h>
#import <appkit/OpenPanel.h>
#import <appkit/Pasteboard.h>

/* --------------------  Auxiliary Functions  --------------------- */

static void initMenu(id menu)
/*
 * Sets the updateAction for every menu item which sends to the
 * First Responder (i.e. their target is nil).  When autoupdate is on,
 * every event will be followed by an update of each of the menu items
 * which is visible.  This keep all unavailable menu items dimmed out
 * so that the user knows what options are available at any given time.
 * Returns the activate menu if is found in this menu.
 */ 
{
    int count;
    id matrix, cell;
    id matrixTarget, cellTarget;

    matrix = [menu itemList];
    matrixTarget = [matrix target];

    count = [matrix cellCount];
    while (count--) {
	cell = [matrix cellAt:count :0];
	cellTarget = [cell target];
	if (!matrixTarget && !cellTarget) {
	    [cell setUpdateAction:@selector(menuItemUpdate:) forMenu:menu];
	} else if ([cell hasSubmenu]) {
	    initMenu(cellTarget);
	}
    }
}

static id   documentInWindow(id window)
/*
 * Checks to see if the passed window's delegate is a Document. If it is, it
 * returns that document, otherwise it returns nil. 
 */
{
    id          document = [window delegate];
    return [document isKindOf:[Document class]] ? document : nil;
}

static id   findDocument(const char *name)
/*
 * Searches the window list looking for a Document with the specified name.
 * Returns the window containing the document if found. If name == NULL then
 * the first document found is returned. 
 */
{
    int count;
    id document, window, windowList;

    windowList = [NXApp windowList];
    count = [windowList count];
    while (count--) {
	window = [windowList objectAt:count];
	document = documentInWindow(window);
	if (document && (!name || !strcmp([document filename], name))) {
	    return window;
	}
    }

    return nil;
}

static id openFile(const char *directory, const char *name, id newClass, 
		   BOOL display)
/*
 * Opens a file with the given name in the specified directory. If we already
 * have that file open, it is ordered front. Returns the document if
 * successful, nil otherwise. 
 */
{
    id          window;
   char buffer[MAXPATHLEN+1], path[MAXPATHLEN+1];

    if (name && *name) {
	if (!directory) {
	    directory = ".";
	} else if (*directory != '/') {
	    strcpy(buffer, "./");
	    strcat(buffer, directory);
	    directory = buffer;
	}
	if (!chdir(directory) && getwd(path)) {
	    strcat(path, "/");
	    strcat(path, name);
	    window = findDocument(path);
	    if (window) {
		if (display)  [window makeKeyAndOrderFront:window];
		return [window delegate];
	    } else {
		return [newClass newFromFile:path];
	    }
	} else {
	    NXRunAlertPanel("Open", "Invalid path: %s",
			    "OK", NULL, NULL, directory);
	}
    }
    return nil;
}

/* --------------------------  Defines  --------------------------- */

#define ZAlloc(CL) [CL allocFromZone:[self zone]]

/* -------------------------  Constants  -------------------------- */

static const char VersionFormat[] =  "Version %.2f";
static const char BasicNib[] = "BasicAddons";

/* ----------------------  Class variables  ----------------------- */

/*====================================================================
                  Implementation of class AppWithDoc                  
====================================================================*/

@implementation AppWithDoc : Application
{
    char *  extension;	/* file extension for documents */
    id      docClass;		/* Class of Document to use */
    float   version;		/* The number to put there */
    id      inspectorPanel;	/* the shared inspector panel */
    id      findPanel;		/* the shared inspector panel */
    id 	    marginsAddon;	/* Margins for Page Layout */
    id 	    saveToAddon;	/* Save to different Types */
    
    BOOL haveOpenedDocument;	/* whether we have opened a document */
}

/*====================================================================
                      Factory Methods - Creation                      
====================================================================*/

/*--------------------------------------------------------------------
|+new| 			 	   Default Document class, version 0
Returns self.
                                          Ernest Prabhakar/1991-Jun-27
--------------------------------------------------------------------*/
+new
{
    self = [super new];
    docClass = [Document class];
    return [self setAutoupdate:YES];
}

/*--------------------------------------------------------------------
|-setDocClass:newDoc| 			Set the Default Document to use
Returns self.
                                          Ernest Prabhakar/1991-Jun-27
--------------------------------------------------------------------*/
-setDocClass:newDoc
{
    docClass = newDoc;
    extension = NXCopyStringBuffer([newDoc extension]);
    return self;
}

/*--------------------------------------------------------------------
|-docClass| 			Return the current Default Document
Returns the default Doc
 		Ernest Prabhakar/1991-Jun-27
--------------------------------------------------------------------*/
-docClass
{
    return docClass;
}

/*--------------------------------------------------------------------
|-setVersion:(int)newVersion| 
Returns self.
                                          Ernest Prabhakar/1991-Oct-12
--------------------------------------------------------------------*/
-setVersion:(int)newVersion
{
    if (newVersion) {
	version = newVersion / 100.0;
	[[self class] setVersion:newVersion];
    } else {
	version = [[self class] version] / 100.0;
    }
	
    return self;
}

/*====================================================================
                      Application-Wide Services                       
====================================================================*/

/*--------------------------------------------------------------------
|-readFromFile:(const char *)filename| 
Returns the object read.
                                          Ernest Prabhakar/1991-Jun-27
--------------------------------------------------------------------*/
-readFromFile:(const char *)filename
{
    id anObject = nil;
    NXTypedStream *tstream;
    if (! (tstream=NXOpenTypedStreamForFile(filename, NX_READONLY)) ) {
	NotifyArg("Read","Error opening file %s", filename);
	return nil;
    }
#ifdef DEBUG 
	anObject = NXReadObject(tstream);
#else
    NX_DURING
	anObject = NXReadObject(tstream);
    NX_HANDLER
	NotifyArg("Read","Error reading file %s", filename);
    NX_ENDHANDLER
#endif
    NXCloseTypedStream(tstream);

    return anObject;
}

/*--------------------------------------------------------------------
|-write:anObject toFile:(const char *)filename| 
Writes the (root) Object to the file, if valid.
Return anObject if succesful, else nil;
                                          Ernest Prabhakar/1991-Jun-27
--------------------------------------------------------------------*/
-write:anObject toFile:(const char *)filename
{
    NXTypedStream *tstream;
    if (! (tstream=NXOpenTypedStreamForFile(filename, NX_WRITEONLY)) ) {
	NotifyArg("Write", "Error opening file %s", filename);
	return nil;
    }
    NX_DURING
	NXWriteRootObject(tstream, anObject);
    NX_HANDLER
	NotifyArg("Write","Error writing file %s", filename);
    	anObject = nil;
    NX_ENDHANDLER
    NXCloseTypedStream(tstream);
    return anObject;
}

/*--------------------------------------------------------------------
|-locateWindow:aWindow| 
Places the window using the Default Database values and shifting.
Returns the window.
                                          Ernest Prabhakar/1991-Jun-27
--------------------------------------------------------------------*/
-locateWindow:aWindow
{
    static NXCoord x, dx, y, dy;
    if (!x) {
	x = atof(NXGetDefaultValue(appName, "WinLocX"));
	dx = atof(NXGetDefaultValue(appName, "WinLocDx"));
	y = atof(NXGetDefaultValue(appName, "WinLocY"));
	dy = atof(NXGetDefaultValue(appName, "WinLocDy"));
    }
    [aWindow moveTo:x :y];
    x += dx;
    y += dy;
    return aWindow;
}

/*--------------------------------------------------------------------
|-defaultFont| 
Returns the NXFont font object.
                                          Ernest Prabhakar/1991-Jun-27
--------------------------------------------------------------------*/
-defaultFont
{
    return [self defaultFont:"NXFont"];
}


/*--------------------------------------------------------------------
|-defaultFont:(const char *)fontName| 
Returns the named default Font.
                                          Ernest Prabhakar/1991-Jun-27
--------------------------------------------------------------------*/
-defaultFont:(const char *)fontName
{
	static char defaultName[] = "NXFont";
	static char *sizeName;
	id font;

	if (!fontName) fontName = defaultName;
	NX_MALLOC(sizeName, char, strlen(fontName)+5);
	strcpy(sizeName, fontName);
	strcat(sizeName,"Size");
	font = [Font newFont:NXGetDefaultValue(appName, fontName)
			size:atof(NXGetDefaultValue(appName, sizeName))];
	if (!font) NotifyArg("Defaults", "Font %s not found.", fontName);
	NX_FREE(sizeName);
	return font;
}

/*--------------------------------------------------------------------
|-loadZonedNibSection:(const char *)nibName| 
Loads a Nib and Creates a zone for it.
(Don't include the '.nib' extension, though.
Returns the nib.
                                          Ernest Prabhakar/1991-Jun-27
--------------------------------------------------------------------*/
-loadZonedNibSection:(const char *)nibName
{
	static char name[MAXPATHLEN];
	id newNib;
	NXZone *zone = NXCreateChildZone([self zone],
					 vm_page_size, vm_page_size, YES);
	sprintf(name,"%s.nib",nibName);
	NXNameZone(zone, nibName);
	newNib = [self loadNibSection:name owner:self withNames:YES 
		  fromZone:zone];
	if (!newNib) {
	    NotifyArg("LoadNib", "Can't load nib named %s", nibName);
	} else {
	    NXMergeZone(zone);
	}
	return newNib;
}

/*====================================================================
                     Application-wide shared info                     
====================================================================*/

/*--------------------------------------------------------------------
|-currentDocument| 
Returns the current document. (the main window's delegate) 
                                          Ernest Prabhakar/1991-Jun-27
--------------------------------------------------------------------*/
-currentDocument
{
    return documentInWindow(mainWindow);
}

/*--------------------------------------------------------------------
|-(const char *)currentDirectory| 
Return the Directory of the Current Document.
                                          Ernest Prabhakar/1991-Jun-27
--------------------------------------------------------------------*/
-(const char *)currentDirectory
{
    const char *curDir = [[self currentDocument] directory];
    if (!curDir || !*curDir) curDir = [[OpenPanel new] directory];
    return curDir;
}

/*--------------------------------------------------------------------
|-(const char *)extension| 
Returns the extension used for open/save panels.
                                          Ernest Prabhakar/1991-Jun-27
--------------------------------------------------------------------*/
-(const char *)extension
{
    return extension;
}

/*--------------------------------------------------------------------
|-saveAsPanel| 
Returns a regular SavePanel with extension of the required file type. 
                                          Ernest Prabhakar/1991-Jun-27
--------------------------------------------------------------------*/
-saveAsPanel
{
    id savepanel = [SavePanel new];
    [savepanel setAccessoryView:nil];
    [savepanel setRequiredFileType:extension];
    return savepanel;
}

/*--------------------------------------------------------------------
|-saveToPanel| 
Returns a regular SavePanel with extension of the required file type. 
                                          Ernest Prabhakar/1991-Jun-27
--------------------------------------------------------------------*/
-saveToPanel
{
    id savepanel = [SavePanel new];
    static id matrix = nil;
    if (!saveToAddon) {
	[self loadZonedNibSection:BasicNib];
	matrix = NXGetNamedObject("Matrix", saveToAddon);
	[matrix setAction:@selector(changeSaveType:)];
    }
    [savepanel setAccessoryView:saveToAddon];
    [matrix selectCellAt:0 :0];
    [savepanel setRequiredFileType:extension];
    return savepanel;
}

/*--------------------------------------------------------------------
|-findPanel:sender| 			    Order Front the Find Panel
It will use the panel in BasicNib, unless you've set
'findPanel' to something else.
Expect the text to be named "FindField" and the buttons
"Next" and "Previous" to have tags '0' and '1' and call 'find:'.
Returns said panel, ordered front.
                                          Ernest Prabhakar/1991-Jun-27
--------------------------------------------------------------------*/
-findPanel:sender
{
    if (!findPanel) {
	[self loadZonedNibSection:BasicNib];
    }
	    
    return [findPanel makeKeyAndOrderFront:sender];
}

/*--------------------------------------------------------------------
|-inspectorPanel:sender| Order Front the Inspector Panel
Presumable this outlet is set in some NIB file somewhere.
Returns said panel, ordered front.
                                          Ernest Prabhakar/1991-Jun-27
--------------------------------------------------------------------*/
-inspectorPanel:sender
{
    return [inspectorPanel makeKeyAndOrderFront:sender];
}


/*--------------------------------------------------------------------
|-pageLayout| 
Called by the Active Document.
Returns and runs the application-wide PageLayout (with margins) panel.
                                          Ernest Prabhakar/1991-Jun-27
--------------------------------------------------------------------*/
-pageLayout
{
    static id newpl = nil;
    if (!newpl) {
#ifdef DEBUG
	printf("New Page Margins\n");
#endif
	newpl = [PageMargins new];
	if (!marginsAddon) [self loadZonedNibSection:BasicNib];
	[newpl setAccessoryView:marginsAddon];
	[newpl setSideForm:NXGetNamedObject("Sides", marginsAddon)];
	[newpl setTopBotForm:NXGetNamedObject("Top", marginsAddon)];
    }
	
    return newpl;
}

/*====================================================================
                            Action Methods                            
====================================================================*/
/*--------------------------------------------------------------------
|-info:sender| 				Brings up the information panel. 
Returns the InfoPanel (taken from a separate nib).
                                          Ernest Prabhakar/1991-Jun-27
--------------------------------------------------------------------*/
-info:sender
{
    static id infoPanel;		/* the Info... panel */
    if (!infoPanel) {
	char buf[20];
	id versionField;	/* version field in info panel */
	infoPanel = [self loadZonedNibSection:"InfoPanel"];
	versionField = NXGetNamedObject("VersionNumber", infoPanel);
	if (versionField) {
	    sprintf(buf, VersionFormat, version);
	    [versionField setStringValue:buf];
	}
   }

    return [infoPanel orderFront:self];
}

/*--------------------------------------------------------------------
|-new:sender| 		    Called by pressing New in the Window menu.
Creates a default Document.
Returns self.
                                          Ernest Prabhakar/1991-Jun-27
--------------------------------------------------------------------*/
-new:sender
{
    [docClass new];
    return self;
}

/*--------------------------------------------------------------------
|-open:sender| 		 Called by pressing Open... in the Window menu. 
Generates an Open Panel for the default extension and Document
Return self.
                                          Ernest Prabhakar/1991-Jun-04
--------------------------------------------------------------------*/
-open:sender
{
    const char *const docType[] = {extension, NULL};
    return [self openTypes:docType using:docClass];
}

/*--------------------------------------------------------------------
|-openTypes:(const char * const *)docTypes using:aClass;| 
Generate an open panel for the given types.
Open the specified documents using those files.
Return self.
                                          Ernest Prabhakar/1991-Jun-27
--------------------------------------------------------------------*/
-openTypes:(const char * const *)docTypes using:aClass;
{
    const char *directory;
    const char*  const* files;
    id openpanel = [[OpenPanel new] allowMultipleFiles:YES];
        
    directory = [[self currentDocument] directory];
    if (directory && (*directory == '/')) 	
	    [openpanel setDirectory:directory];
    if ([openpanel runModalForTypes:docTypes]) {
	    files = [openpanel filenames];
	    directory = [openpanel directory];
	    while (files && *files) {
		    openFile(directory, *files, aClass, YES);
		    files++;
	    }
    }
    return self;
}

/*--------------------------------------------------------------------
|-saveAll:sender| 			      Saves all the documents.
Return self.
                                          Ernest Prabhakar/1991-Jun-27
--------------------------------------------------------------------*/
-saveAll:sender
{
    int count;
    id window;

    count = [windowList count];
    while (count--) {
	window = [windowList objectAt:count];
	[documentInWindow(window) save:self];
    }

    return self;
}

/*--------------------------------------------------------------------
|-print:sender| 		    Print the Current Document's view.
Return self.
                                          Ernest Prabhakar/1991-Jun-27
--------------------------------------------------------------------*/
-print:sender
{
    return [[[self currentDocument] view] printPSCode:sender];
}

/*--------------------------------------------------------------------
|-terminate:sender| 
 * Overridden to be sure all documents get an opportunity to be saved
 * before exiting the program.
Returns nil.
                                          Ernest Prabhakar/1991-Jun-27
--------------------------------------------------------------------*/
-terminate:sender
{
    int count, choice;
    id window, document;

    count = [windowList count];
    while (count--) {
	window = [windowList objectAt:count];
 	document = [window delegate];
	if ([document isKindOf:[Document class]])
	    if ([document needsSaving]) {
		choice = NXRunAlertPanel("Quit", 
					 "You have unsaved documents.", 
					 "Review Unsaved", 
					 "Quit Anyway",
					 "Cancel");
	    if (choice == NX_ALERTOTHER)  {
		return self;
	    } else if (choice == NX_ALERTDEFAULT) {
		count = [windowList count];
		while (count--) {
		    window = [windowList objectAt:count];
		    document = [window delegate];
		    if ([document windowWillClose:window action:"Quit"]) {
			[window close];
		    } else {
			return self;
		    }
		}
	    }
		break;
	    }
    }

    [super terminate:sender];

    return nil;
}

/*====================================================================
                           Delegate Methods                           
====================================================================*/

/*
 * Application object delegate methods. Since we don't have an application
 * delegate, messages that would normally be sent there are sent to the
 * Application object itself instead. 
 */

/*--------------------------------------------------------------------
|-appDidInit:sender| 
 * Check for files to open specified on the command line. Initialize the
 * menus and get a handle on the activate menu. Add all opened documents to
 * the activate menu. If there are no open documents, then open a blank one. 
Returns self.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-appDidInit:sender
{
    int         i;
    char        buffer[MAXPATHLEN + 1];
    char        type[MAXPATHLEN + 1];
    char       *directory, *name, *ext;
    if (NXArgc > 1) {
	for (i = 1; i < NXArgc; i++) {
	    strcpy(buffer, NXArgv[i]);
	    strcpy(type, ".");
	    strcat(type, extension);
	    ext = strrchr(buffer, '.');
	    if (!ext || strcmp(ext, type))  strcat(buffer, type);
	    name = strrchr(buffer, '/');
	    if (name) {
		*name++ = '\0';
		directory = buffer;
	    } else {
		name = buffer;
		directory = NULL;
	    }
	    haveOpenedDocument = openFile(directory, name, docClass, YES)
		 || haveOpenedDocument;
	}
    }

    if (!haveOpenedDocument) [self new:self];

    if (NXGetDefaultValue(appName, "Quit")) {
	[self activateSelf:YES];
	NXPing();
	[self terminate:self];
    }

    initMenu([NXApp mainMenu]);

    return self;
}

/*--------------------------------------------------------------------
|-(int)appOpenFile:(const char *)path type:(const char *)type| 
 * This method is performed whenever a user double-clicks on an icon in the
 * Workspace Manager representing the document. 
Returns whether or not it opened a file.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-(int)appOpenFile:(const char *)path type:(const char *)type
{
    char *name;
    char directory[MAXPATHLEN+1];

    if (type && !strcmp(type,extension)) {
	strcpy(directory, path);
	name = strrchr(directory, '/');
	if (name) {
	    *name++ = '\0';
	    if (openFile(directory, name, docClass, YES)) {
		haveOpenedDocument = YES;
		return YES;
	    }
	}
    }

    return NO;
}


/*--------------------------------------------------------------------
|-(BOOL) appAcceptsAnotherFile:sender| 
 * We accept any number of appOpenFile:type: messages. 
Returns YES to indicate acceptance.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-(BOOL) appAcceptsAnotherFile:sender
{
    return YES;
}

/*====================================================================
                    Listener/Speaker remote methods                   
====================================================================*/

/*--------------------------------------------------------------------
|-(int)msgVersion:(char* *)aString ok:(int *)flag| 
Puts "Version x.xx" in the string.
Returns 0.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-(int)msgVersion:(char* *)aString ok:(int *)flag
{
    char        buf[6];
    sprintf(buf, VersionFormat, version);
    *aString = NXCopyStringBuffer(buf);
    *flag = YES;

    return 0;
}

/*--------------------------------------------------------------------
|-(int)msgDirectory:(const char **)totalPath ok:(int *)flag| 
Puts the directory (and whether it has one).
Returns 0.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-(int)msgDirectory:(const char **)totalPath ok:(int *)flag
{
    *totalPath = [self currentDirectory];
    if (*totalPath) {
	*flag = YES;
    } else {
	*totalPath = "";
	*flag = NO;
    }
    return 0;
}

/*--------------------------------------------------------------------
|-(int)msgFile:(const char **)totalPath ok:(int *)flag| 
Return current filename, if any.
Returns 0.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-(int)msgFile:(const char **)totalPath ok:(int *)flag
{
    const char *file;
    file = [[self currentDocument] filename];
    if (file) {
	*totalPath = file;
	*flag = YES;
    } else {
	*totalPath = "";
	*flag = NO;
    }

    return 0;
}

/*--------------------------------------------------------------------
|-(int)msgPrint:(const char *)fullPath ok:(int *)flag| 
Print the document.
Returns 0.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-(int)msgPrint:(const char *)fullPath ok:(int *)flag
{
    BOOL close;
    BOOL InMsgPrint;
    id document = nil;
    char *directory, *name;
    char path[MAXPATHLEN+1];
    char buffer[MAXPATHLEN+1];

    InMsgPrint = YES;
    strcpy(buffer, fullPath);
    name = strrchr(buffer, '/');
    if (name) {
	*name++ = '\0';
	directory = buffer;
    } else {
	name = buffer;
	directory = NULL;
    }
    if (!chdir(directory) && getwd(path)) {
	strcat(path, "/");
	strcat(path, name);
	document = [findDocument(path) delegate];
    }
    if (document) {
	close = NO;
    } else {
	document = openFile(directory, name, docClass, NO);
	if (document) haveOpenedDocument = YES;
	close = YES;
    }
    if (document && ![document contents]) {
	[NXApp setPrintInfo:[document printInfo]];
	[[document view] printPSCode:self];
	if (close) [[[document view] window] close];
	*flag = YES;
    } else {
	*flag = NO;
    }
    InMsgPrint = NO;

    return 0;
}

/*--------------------------------------------------------------------
|-(int)msgQuit:(int *)flag| 				Quit message.
Returns 0.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-(int)msgQuit:(int *)flag
{
    *flag = ([self terminate:self] ? NO : YES);
    return 0;
}

/*====================================================================
                       Automatic update methods                       
====================================================================*/


/*--------------------------------------------------------------------
|-(BOOL)menuItemUpdate:menuCell| 
 * Method called by all menu items which send their actions to the
 * First Responder.  First, if the object which would respond were the
 * action sent down the responder chain also responds to the message
 * validateCommand:, then it is sent validateCommand: to determine
 * whether that command is valid now, otherwise, if there is a responder
 * to the message, then it is assumed that the item is valid.
 * The method returns YES if the cell has changed its appearance (so that
 * the caller (a Menu) knows to redraw it).
Returns self.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-(BOOL)menuItemUpdate:menuCell
{
    SEL action;
    id responder, target;
    BOOL enable;

    target = [menuCell target];
    enable = [menuCell isEnabled];

    if (!target) {
	action = [menuCell action];
	responder = [self calcTargetForAction:action];
	if ([responder respondsTo:@selector(validateCommand:)]) {
	    enable = [responder validateCommand:menuCell];
	} else {
	    enable = responder ? YES : NO;
	}
    }

    if ([menuCell isEnabled] != enable) {
	[menuCell setEnabled:enable];
	return YES;
    } else {
	return NO;
    }
}

/*--------------------------------------------------------------------
|-(BOOL)validateCommand:menuCell| 
Returns NO if no documents to 'saveAll:', otherwise YES for everything.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-(BOOL)validateCommand:menuCell
{
    SEL action = [menuCell action];

    if (action == @selector(saveAll:)) {
	return findDocument(NULL) ? YES : NO;
    } else if (action == @selector(findPanel:)) {
	return [[self currentDocument] respondsTo:@selector(find:)] ? YES : NO;
    }

    return YES;
}

@end

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.