//
//	MiscDocManager.m -- class to manage multiple documents
//		Written by Mike Ferris Copyright (c) 1993, 1995 by Mike Ferris.
//		Modified for use in the MiscKit by Don Yacktman from MODocManager.
//				Version 1.0.  All rights reserved.
//		This notice may not be removed from this source code.
//
//	This object is included in the MiscKit by permission from the author
//	and its use is governed by the MiscKit license, found in the file
//	"LICENSE.rtf" in the MiscKit distribution.  Please refer to that file
//	for a list of all applicable permissions and restrictions.
//	

#import <misckit/misckit.h>
#import <objc/objc-runtime.h>

#define CLASS_VERSION				0
#define CLASS_NAME					"MiscDocManager"

#define BUNDLE_TYPE					"bundle"

#define MISCKIT_BUNDLE				"MiscKitBundle"
#define QUITPANEL_NIBNAME			"MiscDocQuitPanel.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 REVERTALERTPANEL_TITLE		NXLocalizedStringFromTableInBundle("MiscDocAlertPanels", MiscKitBundle, "RevertTitle", "Revert", Title of the Revert alert panel used by MiscDocManager)
#define REVERTALERTPANEL_TEXT		NXLocalizedStringFromTableInBundle("MiscDocAlertPanels", MiscKitBundle, "RevertText", "Do you really want to revert to %s version?  This cannot be undone.", Text of the Revert alert panel used by MiscDocManager)
#define REVERTALERTPANEL_REVERT		NXLocalizedStringFromTableInBundle("MiscDocAlertPanels", MiscKitBundle, "RevertRevertButton", "Revert", Title of the NX_ALERTDEFAULT button in the Revert alert panel used by MiscDocManager)
#define REVERTALERTPANEL_CANCEL		NXLocalizedStringFromTableInBundle("MiscDocAlertPanels", MiscKitBundle, "RevertCancelButton", "Cancel", Title of the NX_ALERTALTERNATE button in the Revert alert panel used by MiscDocManager)
#define REVERTALERTPANEL_BLANK		NXLocalizedStringFromTableInBundle("MiscDocAlertPanels", MiscKitBundle, "RevertSubText1", "blank", Text for indicating reverting to blank version)
#define REVERTALERTPANEL_PREVSAVED	NXLocalizedStringFromTableInBundle("MiscDocAlertPanels", MiscKitBundle, "RevertSubText2", "previously saved", Text for indicating reverting to previously saved version)

#define NEW_MENU_TITLE			NXLocalizedStringFromTableInBundle("MiscDocMenu", MiscKitBundle, "NewMenuTitle", "New", Title of the New menu used by MiscDocManager)
#define OPEN_MENU_TITLE			NXLocalizedStringFromTableInBundle("MiscDocMenu", MiscKitBundle, "OpenMenuTitle", "Open", Title of the Open menu used by MiscDocManager)


@interface MiscDocManager(Private)

- Misc_resetOpenMenus;

@end

static id MiscKitBundle = nil;

@implementation MiscDocManager

+ initialize
// Set the version.  Load classes.  Load the MiscKit bundle.
{
	if (self == [MiscDocManager class])  {
		[self setVersion:CLASS_VERSION];
	}
	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 Misc_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 Misc_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++)  {
			MiscDocType *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)])  {
		MiscString *path = [[MiscString allocFromZone:[self zone]] 
					initString:[openPanel filename]];
		MiscString *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(REVERTALERTPANEL_TITLE, REVERTALERTPANEL_TEXT,
				     REVERTALERTPANEL_REVERT, REVERTALERTPANEL_CANCEL, NULL, 
				     ([currentDoc isUntitled] ?
				     REVERTALERTPANEL_BLANK:REVERTALERTPANEL_PREVSAVED))
				== 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:(MiscDocument *)aDocument
// Add a new document to our list.  DocController calls this automatically.
{
	[docList addObject:aDocument];
	
	return self;
}

- removeDocument:(MiscDocument *)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;
}

- (MiscDocument *)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:(MiscDocument *)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 Misc_resetOpenMenus];
	return self;
}

- removeDocumentClass:(Class)docControllerClass
// Remove the class from the list of classes we manage.  
// Rebuilds the Document menu.
{
	[docClassList removeObject:docControllerClass];
	[self Misc_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];
}

- appDidInit:sender
{
	if ([NXApp defaultBoolValue:"NXAutoLaunch"] &&
			[NXApp defaultBoolValue:"HideOnAutoLaunch"]) {
		// Note:  this will cause trouble if you bring up *any*
		// windows when launching!  The way around this is to
		// subclass this object and catch this method first before
		// passing execution to this code...and suppress ordering
		// up any windows until the app is unhid.  You'll need to
		// basically delay loading those windows until later--when
		// the app is unhidden.  The easiest way to to have a one
		// shot method that will bring up the windows with a static
		// var telling if it's done it's thing yet or not and call
		// it from where ever applicable--with it returning and
		// doing nothing if it has already done its work.  This also
		// means that any app windows should be loaded from another
		// .nib file than the main .nib, should not be visible upon
		// launch, and should probably not be deferred.
		[NXApp hide:sender];
	}
	return self;
}

- 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 (MiscKitBundle)  {
				char pathBuff[MAXPATHLEN+1];
				if (![MiscKitBundle getPath:pathBuff 
							forResource:QUITPANEL_NIBNAME ofType:NULL])  {
					NXLogError("%s: failed to find nib file %s in "
								"MiscKit 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;
}

- Misc_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, open;
	List *openList= NULL;
    
	// See if the nib is loaded and we do menus
	if (!docMenu)  {
		return nil;
	}
    
	// Count needed open submenu entries
	c = [docClassList count];
	open = 0;
	for (i=0; i<c; i++) {
		openList = [[docClassList objectAt:i] getOpenTypesList: openList];
		open += [openList count]>0;
		[openList empty];
	}

	// Handle openMenu
	if (open > 1)  {
		// We should have submenus for openMenu.
		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 a submenu for openMenu.
		itemList = [docMenu itemList];
		// take out the open submenu item if its there
		if ([itemList getRow:&row andCol:&col ofCell:openSubmenuCell]) {
			[itemList removeRowAt:row andFree:NO];
		}
		[docMenu sizeToFit];
    }

	// Handle newMenu
	if ([docClassList count] > 1)  {
		// We should have submenus for 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];
		}
	} else {
		// We should not have a submenu for newMenu.
		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];
		[docMenu sizeToFit];
	}
	[openList free];
	return(self);
}


@end
