ftp.nice.ch/pub/next/developer/resources/classes/MOKit.1.0.0.s.tar.gz#/MOKit_1.0.0/Source/MODocManager.m

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

// MODocManager.m
//
// by Mike Ferris
// Part of MOKit
// Copyright 1993, all rights reserved.

// ABOUT MOKit
// 
// MOKit is a collection of useful and general objects.  Permission is 
// granted by the author to use MOKit in your own programs in any way 
// you see fit.  All other rights to the kit are reserved by the author 
// including the right to sell these objects as part of a LIBRARY or as 
// SOURCE CODE.  In plain English, I wish to retain rights to these 
// objects as objects, but allow the use of the objects as pieces in a 
// fully functional program.  NO WARRANTY is expressed or implied.  The author 
// will under no circumstances be held responsible for ANY consequences to 
// you from the use of these objects.  Since you don't have to pay for 
// them, and full source is provided, I think this is perfectly fair.

#import "MOKit/MODocManager.h"
#import "MOKit/MODocController.h"
#import "MOKit/MODocType.h"
#import "MOKit/MOString.h"
#import "MOKit/MOPathString.h"
#import <objc/objc-runtime.h>

#define CLASS_VERSION						0
#define CLASS_NAME							"MODocManager"

#define MOSTRING_CLASS_NAME					"MOString"
#define MOPATHSTRING_CLASS_NAME				"MOPathString"
#define MODOCTYPE_CLASS_NAME				"MODocType"

#define BUNDLE_TYPE							"bundle"

#define MOKIT_BUNDLE						"MOKitBundle"
#define QUITPANEL_NIBNAME					"MODocQuitPanel.nib"
#define QUITPANEL_REVIEW		0
#define QUITPANEL_SAVEALL		1
#define QUITPANEL_QUIT			2
#define QUITPANEL_CANCEL		3

#define MAX_FRAME_CYCLE				10
#define FRAME_ADJUST				25.0
#define FRAME_START_LOC_X			150.0
#define FRAME_START_LOC_Y			30.0  // top of screen to top of window

#define NEW_MENU_TITLE				"New"
#define OPEN_MENU_TITLE				"Open"


@interface MODocManager(Private)

+ (Class)MO_loadClassBundle:(const char *)className;
+ (BOOL)MO_loadMOKitBundle;

- MO_resetOpenMenus;

@end


@implementation MODocManager

static Class MOStringClass;
static Class MOPathStringClass;
static Class MODocTypeClass;

static NXBundle *MOKitBundle;

+ (Class)MO_loadClassBundle:(const char *)className
// Finds the bundle of the same name as the class, grabs it and loads the
// class from it and returns the named class.
{
	char pathBuff[MAXPATHLEN+1];
	id classBundle = nil;
	Class class = nil;
	
	// Load the bundle
	if ((class = objc_lookUpClass(className)) == nil)  {
		// class is not already loaded... load it.
		
		// Look for the bundle in the main bundle first, 
		// else try in this class's bundle.
		if (![[NXBundle mainBundle] getPath:pathBuff forResource:className 
					ofType:BUNDLE_TYPE])  {
			if (![[NXBundle bundleForClass:[self class]] getPath:pathBuff 
						forResource:className ofType:BUNDLE_TYPE])  {
				NXLogError("[%s loadClassBundle] failed to "
						"find %s class bundle.", [self name], className);
				return nil;
			}
		}
		classBundle = [[NXBundle allocFromZone:[self zone]] 
					initForDirectory:pathBuff];
		if (!classBundle)  {
			NXLogError("[%s loadClassBundle] failed to "
						"create bundle for class %s.", [self name], className);
			return nil;
		}
		if ((class = [classBundle classNamed:className]) == nil)  {
			NXLogError("[%s loadClassBundle] failed to "
						"load %s class from bundle.", [self name], className);
			return nil;
		}
	}
	
	return class;
}

+ (BOOL)MO_loadMOKitBundle;
// Locates and loads (hopefully) the MOKit bundle which contains a nib we need.
{
	char pathBuff[MAXPATHLEN+1];
	
	// Look for the bundle in the main bundle first, 
	// else try in this class's bundle.
	if (![[NXBundle mainBundle] getPath:pathBuff forResource:MOKIT_BUNDLE 
				ofType:BUNDLE_TYPE])  {
		if (![[NXBundle bundleForClass:[self class]] getPath:pathBuff 
					forResource:MOKIT_BUNDLE ofType:BUNDLE_TYPE])  {
			NXLogError("[%s MO_loadMOKitBundle] failed to "
					"find MOKit bundle.", [self name]);
			return NO;
		}
	}
	MOKitBundle = [[NXBundle allocFromZone:[self zone]] 
				initForDirectory:pathBuff];
	if (!MOKitBundle)  {
		NXLogError("[%s MO_loadMOKitBundle] failed to "
					"create bundle for MOKit bundle.", [self name]);
		return NO;
	}
	
	return YES;

}

+ initialize
// Set the version.  Load classes.  Load the MOKit bundle.
{
	if (self == [MODocManager class])  {
		[self setVersion:CLASS_VERSION];

		// Load all the classes we need
		MOStringClass = [self MO_loadClassBundle:MOSTRING_CLASS_NAME];
		MOPathStringClass = [self MO_loadClassBundle:MOPATHSTRING_CLASS_NAME];
		MODocTypeClass = [self MO_loadClassBundle:MODOCTYPE_CLASS_NAME];
		
		// Find the MOKit bundle which contains MOKit's nibs and 
		// other non-code resources.
		[self MO_loadMOKitBundle];
	}
	return self;
}

- init
// Call the DI.
{
	return [self initDocumentClass:nil];
}

- initDocumentClass:(Class)docControllerClass;
// Deesignated initializer.
// Init the manager with defaults.  If docControllerClass is non-nil it is
// added as the first of potentially several document controller classes this
// manager will manage.
{
	const NXScreen *screen = NULL;
	
	[super init];
	
	docList = [[List allocFromZone:[self zone]] init];
	currentDoc = nil;
	docClassList = [[List allocFromZone:[self zone]] init];
	if (docControllerClass)  [docClassList addObject:docControllerClass];
	
	untitledCount = 0;
	frameCycle = 0;
	
	screen = [NXApp mainScreen];
	
	windowStartingLocation.x = FRAME_START_LOC_X;
	windowStartingLocation.y = screen->screenBounds.size.height - 
				FRAME_START_LOC_Y;
	
	return self;
}

- free
// free stuff we allocated.
{
	[[docList freeObjects] free];
	[docClassList empty];
	[self MO_resetOpenMenus];
	if (newSubmenuCell) [newSubmenuCell free];
	if (newMenu) [newMenu free];
	if (openSubmenuCell) [openSubmenuCell free];
	if (openMenu) [openMenu free];
	[docClassList free];
	
	return [super free];
}

- awakeFromNib
// Set the updateActions for the menu items we'll manage.
{
	if (docMenu)  {
		[NXApp setAutoupdate:YES];
		[docMenu setDelegate:self];
		[openCell setUpdateAction:@selector(menuUpdate:) forMenu:docMenu];
		[newCell setUpdateAction:@selector(menuUpdate:) forMenu:docMenu];
		[saveCell setUpdateAction:@selector(menuUpdate:) forMenu:docMenu];
		[saveAsCell setUpdateAction:@selector(menuUpdate:) forMenu:docMenu];
		[saveToCell setUpdateAction:@selector(menuUpdate:) forMenu:docMenu];
		[saveAllCell setUpdateAction:@selector(menuUpdate:) forMenu:docMenu];
		[revertCell setUpdateAction:@selector(menuUpdate:) forMenu:docMenu];
		[closeCell setUpdateAction:@selector(menuUpdate:) forMenu:docMenu];
	}
	[self MO_resetOpenMenus];
	return self;
}


- new:sender
// create a new untitled doc.  This is only used when we have one 
// document class
{
	id newDoc;
	if ([docClassList count] != 1)  {
		NXBeep();
		return nil;
	}
	newDoc = [[[docClassList objectAt:0] allocFromZone:[self zone]] 
				initFromFile:NULL manager:self];
	if (!newDoc)  {
		return nil;
	}
	
	return self;
}

- newFromDocumentClass:sender
// Create a new untitled doc from the class that's at the same position 
// in our doc class list as the selectedRow of the sender.  This is only 
// used when there is more than one doc class.
{
	id newDoc = [[[docClassList objectAt:[sender selectedTag]] 
				allocFromZone:[self zone]] initFromFile:NULL manager:self];
	if (!newDoc)  {
		return nil;
	}
	return self;
}

static BOOL isExtensionInList(const char *ext, List *list, int *pos)
// Return YES and the position of the type in the list if we find a match.
// If there is no match, try matching a NULL extension.  Returns YES only if
// the extension is actually in the list.  Returns NO if not.  pos is set to 
// the position of the entry if the match is made.  If no match is made, but 
// there is a null-extension type in the list, it's position is put in pos. 
// Otherwise pos is set to -1.
{
	int i, c = [list count];
	for (i=0; i<c; i++)  {
		const char *curExt = [[list objectAt:i] typeExtension];
		// They match if they are both empty or if they match
		if ((!ext || !*ext) && (!curExt || !*curExt))  {
			if (pos)  *pos = i;
			return YES;
		}
		if (ext && curExt && (strcmp(ext, curExt)) == 0)  {
			if (pos)  *pos = i;
			return YES;
		}
	}
	if (ext && *ext)  {
		if (!isExtensionInList(NULL, list, pos))  {
			if (pos)  *pos = -1;
		}
	}  else  {
		if (pos)  *pos = -1;
	}
	return NO;
}

- open:sender
// Allow the user to open a document from disk.  We must handle all 
// doc extensions supported by all doc controller classes.  If two 
// classes support reading the same extension, the first class which 
// supports that extension is used to open files with that extension.
{
	id openPanel = [OpenPanel new];

	int currentClass, docClassCount, currentType, typeCount;
	List *openTypes = nil;

	// We'll fill this list with each open type supported by each doc 
	// controller class.  No duplicates, though.
	List *allTypes = [[List allocFromZone:[self zone]] init];

	const char **types = NULL;
	const char *aType;
	BOOL useTypes = YES;
	
	// First, fill allTypes and classForTypes with info for all the extensions
	// we need to handle.
	docClassCount = [docClassList count];
	if (docClassCount == 0)  {
		[allTypes free];
		return nil;
	}
	for (currentClass = 0; currentClass < docClassCount; currentClass++)  {
		openTypes = [[docClassList objectAt:currentClass] 
					getOpenTypesList:nil];
		typeCount = [openTypes count];
		for (currentType = 0; currentType < typeCount; currentType++)  {
			MODocType *type = [openTypes objectAt:currentType];
			if (!isExtensionInList([type typeExtension], allTypes, 
						NULL))  {
				[allTypes addObject:type];
			}
		}
		[openTypes free];
	}
	
	
	// Now make a nasty array like the open panel wants.
	typeCount = [allTypes count];
	if (typeCount == 0)  {
		[allTypes free];
		return nil;
	}
	NX_MALLOC(types, char *, typeCount+1);
	for (currentType=0; currentType<typeCount; currentType++)  {
		aType = [[allTypes objectAt:currentType] typeExtension];
		useTypes = (useTypes && aType && *aType);
		types[currentType] = (aType?aType:"");
	}
	types[typeCount] = NULL;
	
	// Now run the damn thing.
	[openPanel allowMultipleFiles:NO];
	[openPanel chooseDirectories:NO];
	[openPanel setAccessoryView:nil];
	[openPanel setDelegate:self];
	[openPanel setTreatsFilePackagesAsDirectories:NO];
	if ([openPanel runModalForTypes:(useTypes?types:NULL)])  {
		MOPathString *path = [[MOPathStringClass allocFromZone:[self zone]] 
					initPath:[openPanel filename]];
		MOPathString *extension = [path fileExtension];
		// Now we need to open the damn thing with the right class.
		int index = -1;
		
		isExtensionInList([extension stringValue], allTypes, &index);
		if (index < 0)  {
			// we shouldn't have been allowed to open the doc if we can't find
			// a type for it... why are we here
			[allTypes free];
			if (types)  NX_FREE(types);
			[path free];
			[extension free];
			return nil;
		}

		[self openDocument:[path stringValue] 
					withClass:[[allTypes objectAt:index] controllerClass]];
		[extension free];
		[path free];
	}
	
	[allTypes free];
	if (types)  NX_FREE(types);
	return self;
}

- openFromDocumentClass:sender
// This runs the open panel for the types which are supported by the 
// document class at the same position in our doc class list as the 
// selectedRow of the sender.  This is used when there is more than 
// one doc class.
{
	id openPanel = [OpenPanel new];
	id openTypes = [[docClassList objectAt:[sender selectedTag]] 
				getOpenTypesList:nil];
	int i, c;
	const char **types = NULL;
	const char * aType;
	BOOL useTypes = YES;
	
	c = [openTypes count];
	if (c == 0)  {
		[openTypes free];
		return nil;
	}
	NX_MALLOC(types, char *, c+1);
	for (i=0; i<c; i++)  {
		aType = [[openTypes objectAt:i] typeExtension];
		useTypes = (useTypes && aType && *aType);
		types[i] = (aType?aType:"");
	}
	types[i] = NULL;
	
	[openPanel allowMultipleFiles:NO];
	[openPanel chooseDirectories:NO];
	[openPanel setAccessoryView:nil];
	[openPanel setDelegate:self];
	[openPanel setTreatsFilePackagesAsDirectories:NO];
	if ([openPanel runModalForTypes:(useTypes?types:NULL)])  {
		[self openDocument:[openPanel filename] 
					withClass:[docClassList objectAt:[sender selectedTag]]];
	}
	
	NX_FREE(types);
	[openTypes free];
	return self;
}

- openDocument:(const char *)path withClass:(Class)docClass;
// Make a new documentof the given class and initialize it from the given file.
{
	id newDoc;
	
	newDoc = [[docClass allocFromZone:[self zone]] 
				initFromFile:path manager:self];
	
	return self;
}

- save:sender
// Tell the current doc to save.
{
	if (currentDoc)  {
		[currentDoc save:self];
	}
	return self;
}

- saveAs:sender
// Tell the current doc to save as.
{
	if (currentDoc)  {
		[currentDoc saveAs:self];
	}
	return self;
}

- saveTo:sender
// Tell the current doc to save to.
{
	if (currentDoc)  {
		[currentDoc saveTo:self];
	}
	return self;
}

- saveAll:sender
// Save all the open docs.
{
	int i, c = [docList count];
	id temp;
	
	for (i=0; i<c; i++)  {
		temp = [docList objectAt:i];
		[temp saveIfNeeded];
	}
	return self;
}

- revert:sender
// Tell the current doc to revert.
{
	if (currentDoc)  {
		if (NXRunAlertPanel("Revert", "Do you really want to revert to "
					"%s version?  This cannot be undone.", 
					"Revert", "Cancel", NULL, 
					([currentDoc isUntitled]?"blank":"previously saved")) == 
					NX_ALERTDEFAULT)  {
			[currentDoc revert:self];
		}
	}
	return self;
}

- close:sender
// Tell the current doc to close.
{
	if (currentDoc)  {
		[currentDoc close:self];
	}
	return self;
}

- print:sender
// Tell the current doc to print.
{
	if (currentDoc)  {
		[currentDoc print:self];
	}
	return self;
}

- getNextWindowLocation:(NXPoint *)pt
// Get the location for the next document window.
{
	pt->x = windowStartingLocation.x + (FRAME_ADJUST * frameCycle);
	pt->y = windowStartingLocation.y - (FRAME_ADJUST * frameCycle);
	frameCycle++;
	if (frameCycle>=MAX_FRAME_CYCLE)  frameCycle = 0;
	
	return self;
}

- (int)nextUntitledNum
// Get the number for the next untitled doc.
{
	untitledCount++;
	return untitledCount;
}

- setWindowStartingLocation:(const NXPoint *)pt
// Set the starting window location from which the staggering of doc 
// windows will begin.  This should be set before any windows are 
// created for best results.
{
	windowStartingLocation.x = pt->x;
	windowStartingLocation.y = pt->y;
	return self;
}

- addDocument:(MODocController *)aDocument
// Add a new document to our list.  DocController calls this automatically.
{
	[docList addObject:aDocument];
	
	return self;
}

- removeDocument:(MODocController *)aDocument
// Remove a document to our list.  (DocController calls this when the
// document is closing.)
{
	[docList removeObject:aDocument];
	if (currentDoc == aDocument)  {
		currentDoc = nil;
	}
	
	return self;
}

- (MODocController *)findDocumentForWindow:aWindow
// Find the controller that controls the given window (if any).
{
	int i, c = [docList count];
	id temp;
	
	for (i=0; i<c; i++)  {
		temp = [docList objectAt:i];
		if ([temp window] == aWindow)  {
			return temp;
		}
	}
	return nil;
}

- (List *)documentList
// Return the List object we use to store our docs.
{
	return docList;
}

- makeDocumentsPerform:(SEL)aMethod with:anArg
// Distribute a message to the documents.
{
	[docList makeObjectsPerform:aMethod with:anArg];
	
	return self;
}

- (BOOL)areDocumentsDirty
// Returns YES if at least one open document is dirty.
{
	int i, c = [docList count];
	
	for (i=0; i<c; i++)  {
		if ([[docList objectAt:i] isDirty])  {
			return YES;
		}
	}
	return NO;
}

- setCurrentDocument:(MODocController *)aDocument
// Sets the current doc.  Called from DocController's window delegate
// methods.
{
	currentDoc = aDocument;
	return self;
}

- currentDocument
// Returns the current doc's controller.
{
	return currentDoc;
}

- addDocumentClass:(Class)docControllerClass
// Add the class to the list of classes we manage.  Rebuilds the Document menu.
{
	[docClassList addObject:docControllerClass];
	[self MO_resetOpenMenus];
	return self;
}

- removeDocumentClass:(Class)docControllerClass
// Remove the class from the list of classes we manage.  
// Rebuilds the Document menu.
{
	[docClassList removeObject:docControllerClass];
	[self MO_resetOpenMenus];
	return self;
}

- (List *)documentClassList
// Return the list of classes we manage.
{
	return docClassList;
}

- (int)docClassCount
// Returns the number of classes that this manager is managing.
{
	return [docClassList count];
}

- appWillTerminate:sender
// If there are unsaved docs, put up a panel asking if the user wants to
// Review Unsaved, Save All, Quit Anyway, or Cancel.
{
	int ret;
	int i, c;
	
	if ([self areDocumentsDirty])  {
		if (!quitPanel)  {
			if (MOKitBundle)  {
				char pathBuff[MAXPATHLEN+1];
				if (![MOKitBundle getPath:pathBuff 
							forResource:QUITPANEL_NIBNAME ofType:NULL])  {
					NXLogError("%s: failed to find nib file %s in "
								"MOKit bundle.", 
								[[self class] name], QUITPANEL_NIBNAME);
					return nil;
				}
				if (![NXApp loadNibFile:pathBuff owner:self])  {
					NXLogError("%s: failed to load nib file %s.", 
							[[self class] name], pathBuff);
					return nil;
				}
			}  else  {
				if (![NXApp loadNibSection:QUITPANEL_NIBNAME owner:self])  {
					NXLogError("%s: failed to load nib file %s.", 
							[[self class] name], QUITPANEL_NIBNAME);
					return nil;
				}
			}
		}
		ret = [NXApp runModalFor:quitPanel];
		[quitPanel orderOut:self];
		
		if (ret == QUITPANEL_REVIEW)  {
			c = [docList count];
			for (i=c-1;i>=0;i--)  {
				[[[docList objectAt:i] window] makeKeyAndOrderFront:self];
				if ([[docList objectAt:i] windowWillClose:[[docList 
							objectAt:i] window]] == nil)  {
					return nil;
				}
				[[[docList objectAt:i] window] orderOut:self];
			}
			return self;
		}  else if (ret == QUITPANEL_SAVEALL)  {
			[self saveAll:self];
			return self;
		}  else if (ret == QUITPANEL_QUIT)  {
			return self;
		}  else if (ret == QUITPANEL_CANCEL)  {
			return nil;
		}
	}
	return self;  // nil to abort termination
}

- quitPanelStopModalAction:sender
// This is the action for the buttons in the quit panel.
{
	[NXApp stopModal:[sender selectedTag]];
	return self;
}

#define QUITPANEL_OK_BUTTON_ROW		1
#define QUITPANEL_OK_BUTTON_COL		2

- windowDidBecomeKey:sender
// For the quit panel... make sure that the return icon is only there
// if the window is key.
{
	if (sender == quitPanel)  {
		[[buttonMatrix cellAt:QUITPANEL_OK_BUTTON_ROW:QUITPANEL_OK_BUTTON_COL] 
					setIconPosition:NX_ICONRIGHT];
		[buttonMatrix setIcon:"NXreturnSign" 
					at:QUITPANEL_OK_BUTTON_ROW:QUITPANEL_OK_BUTTON_COL];
	}
	return self;
}

- windowDidResignKey:sender
// For the quit panel... make sure that the return icon is only there
// if the window is key.
{
	if (sender == quitPanel)  {
		[[buttonMatrix cellAt:QUITPANEL_OK_BUTTON_ROW:QUITPANEL_OK_BUTTON_COL] 
					setIconPosition:NX_TITLEONLY];
		[buttonMatrix setIcon:NULL 
					at:QUITPANEL_OK_BUTTON_ROW:QUITPANEL_OK_BUTTON_COL];
	}
	return self;
}

- (BOOL)menuUpdate:menuCell
// This is the updateAction for all the Document menu stuff.
{
	BOOL enabled = [menuCell isEnabled];
	BOOL docsDirty;
	if ((menuCell == openCell) || (menuCell == newCell))  {
		if (([docClassList count] == 0) && (enabled))  {
			[menuCell setEnabled:NO];
			return YES;
		}  else if (!enabled)  {
			[menuCell setEnabled:YES];
			return YES;
		}
	}  else if ((menuCell == saveCell) || (menuCell == saveAsCell) || 
				(menuCell == saveToCell) || (menuCell == closeCell))  {
		if ((currentDoc == nil) && (enabled))  {
			[menuCell setEnabled:NO];
			return YES;
		}  else if ((currentDoc != nil) && (!enabled))  {
			[menuCell setEnabled:YES];
			return YES;
		}
	}  else if (menuCell == saveAllCell)  {
		docsDirty = [self areDocumentsDirty];
		if ((docsDirty) && (!enabled))  {
			[menuCell setEnabled:YES];
			return YES;
		}  else if ((!docsDirty) && (enabled))  {
			[menuCell setEnabled:NO];
			return YES;
		}
	}  else if (menuCell == revertCell)  {
		if ((currentDoc == nil) && (enabled))  {
			[menuCell setEnabled:NO];
			return YES;
		}  else if (currentDoc != nil)  {
			if (([currentDoc isDirty]) && (!enabled))  {
				[menuCell setEnabled:YES];
				return YES;
			}  else if ((![currentDoc isDirty]) && (enabled))  {
				[menuCell setEnabled:NO];
				return YES;
			}
		}
	}
	return NO;
}

- awake
// Initialize stuff we don't archive.
{
	quitPanel = nil;
	buttonMatrix = nil;
	newMenu = nil;
	newSubmenuCell = nil;
	openMenu = nil;
	openSubmenuCell = nil;
	return self;
}

- read:(NXTypedStream *)strm
// This method is probably not useful.  But here it is.
{
	int classVersion;
	const NXScreen *screen = NULL;

	[super read:strm];
	
	classVersion = NXTypedStreamClassVersion(strm, CLASS_NAME);
	
	switch (classVersion)  {
		case 0:		// First version.
			docList = NXReadObject(strm);
			currentDoc = NXReadObject(strm);
			docClassList = NXReadObject(strm);
			NXReadType(strm, "i", &untitledCount);
			NXReadType(strm, "i", &frameCycle);
			NXReadPoint(strm, &windowStartingLocation);
			
			docMenu = NXReadObject(strm);
			openCell = NXReadObject(strm);
			newCell = NXReadObject(strm);
			saveCell = NXReadObject(strm);
			saveAsCell = NXReadObject(strm);
			saveToCell = NXReadObject(strm);
			saveAllCell = NXReadObject(strm);
			revertCell = NXReadObject(strm);
			closeCell = NXReadObject(strm);
			break;
		default:
			NXLogError("[%s read:] class version %d cannot read "
						"instances archived with version %d", 
						CLASS_NAME, CLASS_VERSION, classVersion);
			docList = [[List allocFromZone:[self zone]] init];
			currentDoc = nil;
			docClassList = [[List allocFromZone:[self zone]] init];
			untitledCount = 0;
			frameCycle = 0;
			screen = [NXApp mainScreen];
			windowStartingLocation.x = FRAME_START_LOC_X;
			windowStartingLocation.y = screen->screenBounds.size.height - 
						FRAME_START_LOC_Y;
			docMenu = nil;
			openCell = nil;
			newCell = nil;
			saveCell = nil;
			saveAsCell = nil;
			saveToCell = nil;
			saveAllCell = nil;
			revertCell = nil;
			closeCell = nil;
			
			break;
	}
	return self;
}

- write:(NXTypedStream *)strm
// This method is probably not useful.  But here it is.
{
	[super write:strm];
	NXWriteObject(strm, docList);
	NXWriteObject(strm, currentDoc);
	NXWriteObject(strm, docClassList);
	NXWriteType(strm, "i", &untitledCount);
	NXWriteType(strm, "i", &frameCycle);
	NXWritePoint(strm, &windowStartingLocation);
	
	NXWriteObjectReference(strm, docMenu);
	NXWriteObjectReference(strm, openCell);
	NXWriteObjectReference(strm, newCell);
	NXWriteObjectReference(strm, saveCell);
	NXWriteObjectReference(strm, saveAsCell);
	NXWriteObjectReference(strm, saveToCell);
	NXWriteObjectReference(strm, saveAllCell);
	NXWriteObjectReference(strm, revertCell);
	NXWriteObjectReference(strm, closeCell);
	return self;
}

- MO_resetOpenMenus
// Keeps the document menu in step with changes in the manager's class list.
// This is the method that makes sure the Open and New stuff is correct for
// the document classes being managed.
{
	id itemList;
	int i, c;
	const char *name;
	id cell;
	int row, col;
	
	// See if the nib is loaded and we do menus
	if (!docMenu)  {
		return nil;
	}
	
	if ([docClassList count] > 1)  {
		// We should have submenus for new and open.
		
		// First do the newMenu
		if (!newMenu)  {
			newMenu = [[Menu allocFromZone:[Menu menuZone]] 
						initTitle:NEW_MENU_TITLE];
		}
		itemList = [newMenu itemList];
		[itemList renewRows:0 cols:1];
		c = [docClassList count];
		for (i=0; i<c; i++)  {
			name = [[[docClassList objectAt:i] controllerName] stringValue];
			[itemList addRow];
			cell = [itemList cellAt:i:0];
			[cell setTitle:name];
			[cell setTag:i];
			[cell setTarget:self];
			[cell setAction:@selector(newFromDocumentClass:)];
		}
		[newMenu sizeToFit];
		
		// Now make a newSubmenuItem
		if (!newSubmenuCell)  {
			newSubmenuCell = [[MenuCell allocFromZone:[Menu menuZone]] 
						initTextCell:NEW_MENU_TITLE];
		}
		
		// Put the submenu item in the doc menu in place of the new item
		itemList = [docMenu itemList];
		if (![itemList getRow:&row andCol:&col ofCell:newSubmenuCell])  {
			[itemList getRow:&row andCol:&col ofCell:newCell];
			[itemList putCell:newSubmenuCell at:row :col];
			[docMenu setSubmenu:newMenu forItem:newSubmenuCell];
			[docMenu sizeToFit];
		}
		
		// Now do the open menu
		if (!openMenu)  {
			openMenu = [[Menu allocFromZone:[Menu menuZone]] 
						initTitle:OPEN_MENU_TITLE];
		}
		itemList = [openMenu itemList];
		[itemList renewRows:0 cols:1];
		c = [docClassList count];
		for (i=0; i<c; i++)  {
			name = [[[docClassList objectAt:i] controllerName] stringValue];
			[itemList addRow];
			cell = [itemList cellAt:i:0];
			[cell setTitle:name];
			[cell setTag:i];
			[cell setTarget:self];
			[cell setAction:@selector(openFromDocumentClass:)];
		}
		[openMenu sizeToFit];
		
		// Now make an openSubmenuItem
		if (!openSubmenuCell)  {
			openSubmenuCell = [[MenuCell allocFromZone:[Menu menuZone]] 
						initTextCell:OPEN_MENU_TITLE];
		}
		
		// Put the submenu item in the doc menu below the open item
		itemList = [docMenu itemList];
		[itemList getRow:&row andCol:&col ofCell:openCell];
		if ([itemList cellAt:row+1:col] != openSubmenuCell)  {
			[itemList insertRowAt:row+1];
			[[itemList putCell:openSubmenuCell at:row+1 :col] free];
			[docMenu setSubmenu:openMenu forItem:openSubmenuCell];
			[docMenu sizeToFit];
		}
	}  else  {
		// We should not have submenus.
		
		itemList = [docMenu itemList];
		
		// take out the new submenu item if its there and put back the new item
		if ([itemList getRow:&row andCol:&col ofCell:newSubmenuCell])  {
			[itemList putCell:newCell at:row :col];
		}
		// take out the open submenu item if its there
		if ([itemList getRow:&row andCol:&col ofCell:openSubmenuCell])  {
			[itemList removeRowAt:row andFree:NO];
		}
		[docMenu sizeToFit];
		
	}
	return self;
}


@end

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