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

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

/* -------------------------------------------------------------------
#import <appkit/Responder.h>
#define Notify(title, msg) NXRunAlertPanel(title, msg, "OK", NULL, NULL)
#define	NotifyArg(title, msg, arg)\
 NXRunAlertPanel(title, msg, "OK", NULL, NULL, arg)

	RDR, Inc. 

	Objective-C source file for the class Document

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

	Responsible:			Approved:
	RDR:Ernest Prabhakar		

	Date:				Rev:
	1991-Jun-28			0.9

                               Document                               

	1. Introduction
 * This class is used to keep track of a document. 
 *
 * Its contents and window instance variables keep track of the contents of the
 * document as well as its window and sub Views. The name and directory
 * specify where the document is to be saved. haveSavedDocument keeps track
 * of whether a disk file is associated with the document yet (i.e. if it has
 * ever been saved). 
 *
 * The Document class's responsibilities: 
 *
 * 1. Manage the window which displays the document's contents. This includes
 * ensuring that if the window contains an unsaved document and the user
 * tries to close it, the user gets an opportunity to save his changes. 
 * 2. Saving the document to a disk file. 

	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/publicWraps.h>
#import <appkit/nextstd.h>
#import <objc/hashtable.h>	/* for NXCopyStringBuffer() */

#import <mach.h>
#import <zone.h>
#import <string.h>
#import <libc.h>

/* ------------------------  Classes used  ------------------------ */
#import "AppWithDoc.h"
#import "List-Ordered.h"

#import <objc/List.h>
#import <appkit/Text.h>
#import <appkit/Window.h>
#import <appkit/ScrollView.h>
#import <appkit/PrintInfo.h>
#import <appkit/TextField.h>
#import <appkit/SavePanel.h>
#import <appkit/Listener.h>
#import <appkit/Speaker.h>
#import <appkit/Pasteboard.h>

/* --------------------  Auxiliary Functions  --------------------- */
#define SCROLLVIEW_BORDER NX_NOBORDER
#define WINDOW_MASK (NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK|NX_FLAGSCHANGEDMASK)

static NXRect *calcFrame(id printInfo, NXRect *viewRect)
/*
 * Calculates the size of the page the user has chosen minus its margins.
 */
{
    float lm, rm, bm, tm;
    const NXRect *paperRect;

    viewRect->origin.x = viewRect->origin.y = 0.0;
    paperRect = [printInfo paperRect];
    [printInfo getMarginLeft:&lm right:&rm top:&tm bottom:&bm];
    viewRect->size = paperRect->size;
    viewRect->size.width -= lm + rm;
    viewRect->size.height -= tm + bm;

    return viewRect;
}

static void getContentSizeForView(id view, NXSize * contentSize)
/*
 * Calculates the size of the window's contentView by accounting for the
 * existence of the ScrollView around the GraphicView.  No scrollers are
 * assumed since we are interested in the minimum size need to enclose the
 * entire view and, if the entire view is visible, we don't need scroll bars! 
 */
{
    NXRect      viewFrame;
    [view getFrame:&viewFrame];
    [ScrollView getFrameSize:contentSize
     forContentSize:&viewFrame.size
     horizScroller:NO vertScroller:NO
     borderType:SCROLLVIEW_BORDER];
}

static id createWindowFor(id view, NXRect* windowContentRect)
/*
 * Creates a window for the specified view. If windowContentRect is not NULL,
 * then it is used as the contentView of the newly created window. 
 */
{
    NXSize      screenSize;
    id          scrollView, window;
    NXRect      defaultWindowContentRect;
    if (!windowContentRect) {
	windowContentRect = &defaultWindowContentRect;
	getContentSizeForView(view, &windowContentRect->size);
	[NXApp getScreenSize:&screenSize];
	if (windowContentRect->size.width > screenSize.width / 2.0) {
	    windowContentRect->size.width = floor(screenSize.width / 2.0);
	}
	if (windowContentRect->size.height > screenSize.height - 60.0) {
	    windowContentRect->size.height = screenSize.height - 60.0;
	}
	windowContentRect->origin.x = screenSize.width - 85.0 -
	    windowContentRect->size.width;
	windowContentRect->origin.y = floor((screenSize.height -
				     windowContentRect->size.height) / 2.0);
    }
    window = [[Window alloc] initContent:windowContentRect
	      style:NX_TITLEDSTYLE
	      backing:NX_BUFFERED
	      buttonMask:NX_ALLBUTTONS
	      defer:YES];

    scrollView = [[ScrollView alloc] initFrame:windowContentRect];
    [scrollView setVertScrollerRequired:NO];
    [scrollView setHorizScrollerRequired:NO];
    [scrollView setBorderType:SCROLLVIEW_BORDER];
    [scrollView setDocView:view];
    [window setContentView:scrollView];
    [window addToEventMask:WINDOW_MASK];
    [window makeFirstResponder:view];

    return window;
}

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

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

#define ClassKind(Obj,Class) [Obj isKindOf:[Class class]]

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

static const char DEFAULT_TITLE[] = "Untitled%d.%s";

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

static id zoneList = nil;
static id contentsClass = nil;
static char *extension = NULL;

/*====================================================================
                   Implementation of class Document                   
====================================================================*/
#import "Document.h"

@implementation Document : Responder
{
    BOOL haveSavedDocument;	/* is associated with a disk file */
    char *name;			/* the name of the document */
    char *directory;		/* the directory it is in */
    id window;			/* the window it is in */
    id contents;		/* the document's contents */
    id view;			/* main or current view */
    id printInfo;		/* Print Margins etc. */
    id listener;		/* Listen for Workspace Icons */
}

/*====================================================================
                          App Initialization                          
====================================================================*/

/*--------------------------------------------------------------------
|+setContentsClass:newClass| 	    Set the class to use for contents.
Returns self.
                                          Ernest Prabhakar/1991-Jul-02
--------------------------------------------------------------------*/
+setContentsClass:newClass
{
    contentsClass = newClass;
    return self;
}

/*--------------------------------------------------------------------
|+setExtension:(const char *)newExtension| 
Returns self.
                                          Ernest Prabhakar/1991-Jul-02
--------------------------------------------------------------------*/
+setExtension:(const char *)newExtension
{
    if (extension) NX_FREE(extension);
    extension = NXCopyStringBuffer(newExtension);
    return self;
}

/*--------------------------------------------------------------------
|+(const char *)extension| 
This should be overridden to set the default view.
Returns the extension.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
+(const char *)extension
{
    if (!contentsClass) contentsClass = [View class];
    if (extension) {
		return extension;
    } else {
		return [contentsClass name];
    }
}
 
/*====================================================================
                         Zone Control Methods                         
====================================================================*/

/*--------------------------------------------------------------------
|+(NXZone *)newZone| 					Create a zone.
Returns self.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
+(NXZone *)newZone
{
    if (!zoneList || ![zoneList count]) {
	    return NXCreateZone(vm_page_size, vm_page_size, YES);
    } else {
	    return (NXZone *)[zoneList removeLastObject];
    }
}

/*--------------------------------------------------------------------
|+(void)reuseZone:(NXZone *)aZone| 		      Reuse this zone.
Returns self.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
+(void)reuseZone:(NXZone *)aZone
{
    if (!zoneList) zoneList = [CZAlloc(List) init];
    [zoneList addObject:(id)aZone];
    NXNameZone(aZone, "Unused");
}

/*--------------------------------------------------------------------
|+allocFromZone:(NXZone *)aZone| 	     Not implemented; use new.
Returns self.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
+allocFromZone:(NXZone *)aZone
{
    return [self notImplemented:@selector(allocFromZone:)];
}

/*--------------------------------------------------------------------
|+alloc|				      Not implemented; use new.
Returns self.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
+alloc
{
    return [self notImplemented:@selector(alloc)];
}


/*====================================================================
                         Initialize and Free                          
====================================================================*/

/*--------------------------------------------------------------------
|+new|					  Create a new View document
Can change the default document
Returns self.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
+new
{
    id newCon;
    if (!contentsClass) contentsClass = [View class];
/* This if-else should not be necessary */
    if (ClassKind(contentsClass,View)) {
	newCon = [CZAlloc(contentsClass) initFrame:NULL];
    } else {
	newCon = [CZAlloc(contentsClass) init];
    }
    return [self newFromContents:newCon];
}

/*--------------------------------------------------------------------
|+newFromContents:newContent| 
Create from the contents as 'untitled'
Returns self.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
+newFromContents:newContent
{
	return [self newFromContents:newContent withName:NULL];
}

/*--------------------------------------------------------------------
|+newFromContents:newContent withName:(const char *)newName| 
Creates a new document, with contents and a window
as the given name.  Everything else forward here.
Returns self.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
+newFromContents:newContent withName:(const char *)newName
{
    NXZone *zone;
    zone = [self newZone];
    self = [super allocFromZone:zone];
#ifdef DEBUG
    printf("New document built around a %s.\n",[[newContent class] name]);
#endif
    [self setContents:newContent];
    [self getWindow];
    [self setName:newName];
    [window setDocEdited:YES];
    return self;
}

/*--------------------------------------------------------------------
|+newFromFile:(const char *)file| 
 * Opens an existing document from the specified file. 
Returns self.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
+newFromFile:(const char *)file
{
    id newContent;
    if (newContent = [NXApp readFromFile:file]) {
	self = [self newFromContents:newContent withName:file];
	[window setDocEdited:NO];
	haveSavedDocument = YES;
	return self;
    } else {
	NotifyArg("Open", "File %s not found.", file);
	return nil;
    }
}


/*--------------------------------------------------------------------
|-free| 				   Free all subsisting objects 
Returns self.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-free
{
#ifdef DEBUG
    printf("Freeing %s.\n", [[contents class] name]);
#endif
    NX_FREE(name);
    NX_FREE(directory);
    [[self class] reuseZone:[self zone]];
    [self freeContents];
    [window free];
    return [super free];
}

/*====================================================================
                        Set Contents and View                         
====================================================================*/

/*--------------------------------------------------------------------
|-freeContents| 				      Free everything.
Returns self.
                                          Ernest Prabhakar/1991-Sep-03
--------------------------------------------------------------------*/
-freeContents
{
    if (ClassKind(contents,List)) {
	[contents freeObjects];
    } else {
	[contents free];
    }
    if (view) {
	if (view != contents) [view free];
	[printInfo free];
	view = printInfo = nil;
	}
    return contents = nil;
}

/*--------------------------------------------------------------------
|-setContents:anObject| 	    Set the Contents from this Object.
If a view, or a composite view/printInfo, set that as well.
Returns the Object.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-setContents:anObject
{
    if (!anObject) return nil;
    contents = nil;
    
    if (ClassKind(anObject,View)) {
	contents = view = anObject;
	return view;
    }
    
    if (ClassKind(anObject,List)) {
	int n = [anObject count];
	while (n--) {
	    id temp = [anObject objectAt:n];
	    if (ClassKind(temp,PrintInfo)) {
		printInfo = temp;
	    } else if (ClassKind(temp,View)) {
		view = temp;
	    } else {
		if (view && printInfo) contents = temp;
	    }
	}
    }

    if (!contents) contents = anObject;
    return contents;
}

/*--------------------------------------------------------------------
|-getContents| 			       Returns the contents.
If there is a view and printInfo, those are included in a list.
That list is static, and should not be freed.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-getContents
{
    static id tempList = nil;
    if (view) {
	if (!tempList) {
	    tempList = [ZAlloc(List) initCount:2];
	} else {
	    [tempList empty];
	}
	[tempList addObject:printInfo];
	[tempList addObject:contents];
	if (view != contents) [tempList addObject:view];
	return tempList;
	}
    return contents;
}


/*====================================================================
                   Generate windows; set delegates                    
====================================================================*/

/*--------------------------------------------------------------------
|-getWindow| 				Generic get Window routine.
By default, creates a scrollview if there is a view.
Override to create a specific type of window (such as from a nib).

Returns self.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-getWindow
{
    if (view)
	    [self getWindowForView:view];
    else
	    [self getWindow:window];
    return self;
}

/*--------------------------------------------------------------------
|-getWindowFromNib:(const char *)nibFile| 	     Loads a nib file.
Returns self.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-getWindowFromNib:(const char *)nibFile
{
    return [self getWindow:[NXApp loadNibSection:nibFile owner:self]];
}

/*--------------------------------------------------------------------
|-getWindowForView:aView| 
Returns self.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-getWindowForView:aView
{
    NXRect frame;
    if (!printInfo) {
	    printInfo = [ZAlloc(PrintInfo) init];
	    [printInfo setMarginLeft:36.0 right:36.0 top:36.0 bottom:36.0];
    }
    [aView getFrame:&frame];
    if (!NX_WIDTH(&frame) || !NX_HEIGHT(&frame)) {
	    calcFrame(printInfo, &frame);
	    [view sizeTo:frame.size.width :frame.size.height];
    }

    [aView setClipping:NO];
    [self getWindow:createWindowFor(aView, NULL)];
    [self registerWindow];
    [self resetScrollers];
    return self;
}

-getWindow:aWindow
{
    if (aWindow) {
	[self registerForServicesMenu];
	window=[NXApp locateWindow:aWindow];
	[window setDelegate:self];
	[window makeKeyAndOrderFront:self];
    }
    return self;
}

/*====================================================================
                       Register Window with WM                        
====================================================================*/

/*--------------------------------------------------------------------
|-registerWindow| 
 Registers the document window with the Workspace Manager so that when the
 user picks up an icon in the Workspace and drags it over our document window
 and lets go, iconEntered:... and iconReleasedAt::ok: messages will be
 sent to the Document from the Workspace Manager.  Allows the user to
 drag PostScript and TIFF files into the document.
Return self.
                                          Ernest Prabhakar/1991-Jun-01
--------------------------------------------------------------------*/
-registerWindow
{
	unsigned int windowNum;
	id speaker = [NXApp appSpeaker];

	listener = [ZAlloc(Listener) init];
	[listener setDelegate:self];
	[listener usePrivatePort];
	[listener addPort];
	NXConvertWinNumToGlobal([window windowNum], &windowNum);
	[speaker setSendPort:NXPortFromName(NX_WORKSPACEREQUEST,
					    NULL)];
	[speaker registerWindow:windowNum toPort:[listener listenPort]];
	return self;
}

/*--------------------------------------------------------------------
|-unregisterWindow| 	If listener exists, deregister window.
Returns self.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-unregisterWindow
{
	unsigned int windowNum;
	id speaker = [NXApp appSpeaker];
	if (listener) {
		[speaker setSendPort:NXPortFromName(NX_WORKSPACEREQUEST, 
						    NULL)];
		NXConvertWinNumToGlobal([window windowNum], &windowNum);
		[speaker unregisterWindow:windowNum];
		[listener free];
		listener = nil;
	}
	return self;
}

/*====================================================================
                    Services menu support methods                     
====================================================================*/

/*--------------------------------------------------------------------
|-registerForServicesMenu| 		       Services menu registrar
Returns self.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-registerForServicesMenu
{
    static BOOL registered = NO;
    const char *validSendTypes[2];

    if (!registered) {
	    registered = YES;
	    validSendTypes[0] = NXFilenamePboardType;
	    validSendTypes[1] = NULL;
	    [NXApp registerServicesMenuSendTypes:validSendTypes
	 andReturnTypes:NULL];
    }

    return self;
}

/*--------------------------------------------------------------------
|-validRequestorForSendType:(NXAtom)sendType andReturnType:(NXAtom)returnType| 
 * Services menu support.
 * We are a valid requestor if the send type is filename
 * and there is no return data from the request.
Returns self.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-validRequestorForSendType:(NXAtom)sendType andReturnType:(NXAtom)returnType
{
    return (haveSavedDocument && sendType == NXFilenamePboardType &&
	    (!returnType || !*returnType)) ? self : nil;
}

/*--------------------------------------------------------------------
|-writeSelectionToPasteboard:pboard types:(NXAtom *)types| 
 * Services menu support.
 * Here we are asked by the Services menu mechanism to supply
 * the filename (which we said we were a valid requestor for
 * in the above method).
Returns self if document saved.
If not, skips and returns nil.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-writeSelectionToPasteboard:pboard types:(NXAtom *)types
{
    int save;

    if (haveSavedDocument) {
	while (types && *types) 
		if (*types == NXFilenamePboardType) break; 
		else types++;
	if (types && *types) {
	    if ([self needsSaving]) {
		save = NXRunAlertPanel("Service", 
 "Do you wish to save this document before your request is serviced?",
				       "Save", "Don't Save", NULL);
		if (save == NX_ALERTDEFAULT) [self save];
	    }
	    [pboard declareTypes:&NXFilenamePboardType num:1 owner:self];
	    [pboard writeType:NXFilenamePboardType data:[self filename] 
		length:strlen([self filename])+1];
	    return self;
	}
    }

    return nil;
}

/*====================================================================
                           State Variables                            
====================================================================*/

/*--------------------------------------------------------------------
|-window| 
 * Returns the Window associated with this document. 
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-window
{
    return window;
}

/*--------------------------------------------------------------------
|-contents| 
Returns the Contents associated with this document. 
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-contents
{
    return contents;
}

/*--------------------------------------------------------------------
|-view| 
 * Returns the document view 
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-view
{
    return view;
}

/*--------------------------------------------------------------------
|-printInfo| 
 * Returns the document printInfo (if none, use NXApp's)
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-printInfo
{
    if (printInfo)
	return printInfo;
    else
	return [NXApp printInfo];
}

/*--------------------------------------------------------------------
|-changeLayout:sender| 
 * Puts up a PageLayout panel and allows the user to pick a different
 * size paper to work on.  After she does so, the view is resized to the
 * new paper size.
 * Since the PrintInfo is effectively part of the document, we dirty
 * the view (by performing the dirty method).
Returns self.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-changeLayout:sender
{
    NXRect frame;

    if ([[NXApp pageLayout] runModal] == NX_OKTAG) {
	calcFrame([self printInfo], &frame);
	[view sizeTo:frame.size.width :frame.size.height];
	[self resetScrollers];
	[view update];
	[window setDocEdited:YES];
    }

    return self;
}

/*====================================================================
                            Action Methods                            
====================================================================*/

/*--------------------------------------------------------------------
|-find:sender| 
 * Find a string.  Use a panel to set parameters 
 * Only valid if contents implements 'findSTR:'
 * Works even better if it implements 'findSTR:dir:'
Returns the Object found.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-find:sender
{
    static id findField = nil;
    id found;
    
    [self sync];
    if (![contents respondsTo:@selector(findSTR:)])
	return [self notImplemented:@selector(find:)];

    if (!findField) {
	findField = NXGetNamedObject("FindField",[sender superview]);
    }
 
    if ([contents respondsTo:@selector(findSTR:dir:)]) {
	found = [contents findSTR:[findField stringValue] dir:[sender tag]];
    } else {
	found = [contents findSTR:[findField stringValue]];
    }
    
    if (found) {
	return found;
    } else {
	NotifyArg("Find", "Can't find any more %s.", [findField stringValue]);
	return nil;
    }
}

/*--------------------------------------------------------------------
|-save:sender| 
 * Saves the file.  If this document has never been saved to disk, then a
 * SavePanel is put up to ask the user what file name she wishes to use to
 * save the document. 
Returns self.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-save:sender
{
    id savepanel;
    if (!haveSavedDocument) {
	savepanel = [NXApp saveAsPanel];
	if ([savepanel runModalForDirectory:directory file:name]) {
	    [self setName:[savepanel filename]];
	} else {
	    return nil;
	}
    }
    return [self save];
}

/*--------------------------------------------------------------------
|-saveAs:sender| 			 * Save under a different name 
Returns self.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-saveAs:sender
{
    BOOL dirty = [window isDocEdited];
    if (!dirty) [window setDocEdited:YES];
    haveSavedDocument = NO;
    if (![self save:sender]) {
	if (!dirty) [window setDocEdited:NO];
	haveSavedDocument = YES;
    }
    return self;
}

/*--------------------------------------------------------------------
|-saveTypes:sender| 	Save file to any format, analogous to 'save:'
   If this document has never been saved to disk, then a
 * SavePanel is put up to ask the user what file name she wishes to use to
 * save the document. 
Returns self.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-saveTypes:sender
{
    id savepanel;
    if (!view || view != contents) return [self save:sender];

    savepanel = [NXApp saveToPanel];
    if ([savepanel runModalForDirectory:directory file:name]) {
	[self setName:[savepanel filename]];
	return [self save];
    }
    return self;
}

/*--------------------------------------------------------------------
|-saveTo:sender| 			      Save to a different name 
but keep editing the original document.
Will also save to different types, if a view.
Returns self.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-saveTo:sender
{
    char *tempName = NULL;
    id savepanel;

    if (haveSavedDocument) {
	tempName = NXCopyStringBuffer([self filename]);
    }
    savepanel = [NXApp saveToPanel];
    if ([savepanel runModalForDirectory:directory file:name]) {
	[self setName:[savepanel filename]];
	[window setDocEdited:YES];
	[self save];
	[self setName:tempName];
	[window setDocEdited:YES];
    }
    if (tempName) NX_FREE(tempName);
    return self;
}

/*--------------------------------------------------------------------
|-revertToSaved:sender| 
 * Revert the document back to what is on the disk. 
Returns self.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-revertToSaved:sender
{
    id newContents;
    if (!haveSavedDocument || ![window isDocEdited] || (NXRunAlertPanel
    	("Revert", "%s was edited.  Are you sure you want to undo changes?",
		"Revert", "Cancel", NULL, name) != NX_ALERTDEFAULT))
			return self;

    [self sync];

    if (newContents = [NXApp readFromFile:[self filename]]) {
	[self freeContents];
	[self setContents:newContents];
	[view update];
	[window setDocEdited:NO];
    } else {
	NotifyArg("Revert", "I/O error. Can't find file %s.", [self filename]);
    }

    return self;
}

/*====================================================================
                      Name and Save the Document                      
====================================================================*/

/*--------------------------------------------------------------------
|-(const char *)filename| 
   If directory is NULL,
 * then the Home is used. If name is NULL, then the default title is used. 
Returns the full pathname..
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-(const char *)filename
{
    static char filenamebuf[MAXPATHLEN+1];

    if (!directory && !name) {
	[self setName:NULL andDirectory:NULL];
    }
    if (directory) {
	strcpy(filenamebuf, directory);
	strcat(filenamebuf, "/");
    } else {
	filenamebuf[0] = '\0';
    }
    if (name) {
	strcat(filenamebuf, name);
    }

    return filenamebuf;
}

/*--------------------------------------------------------------------
|-(const char *)directory| 
Returns the directory.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-(const char *)directory
{
    return directory;
}

/*--------------------------------------------------------------------
|-(const char *)name| 
Returns the (not pathname) name.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-(const char *)name
{
    return name;
}

/*--------------------------------------------------------------------
|-setName:(const char *)newName andDirectory:(const char *)newDirectory| 
 * Updates the name and directory of the document. newName or newDirectory
 * can be NULL, in which case the name or directory will not be changed
 * (unless one is currently not set, in which case a default name will be
 * used). 
Returns self.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
#define ISnewName (newName && *newName)
#define ISnewDir (newDirectory && (*newDirectory == '/'))

-setName:(const char *)newName andDirectory:(const char *)newDirectory
{
    char oldName[MAXPATHLEN + 1];
    char newNameBuf[MAXPATHLEN + 1];
    static int uniqueCount = 1;

    if (directory && name)
	strcpy(oldName, [self filename]);
    else
	*oldName = '\0';

    if (ISnewName || !name) {
	if (!ISnewName) {
	    sprintf(newNameBuf, DEFAULT_TITLE,uniqueCount++,[NXApp extension]);
	    newName = newNameBuf;
	}
	NX_FREE(name);
	name = NXCopyStringBufferFromZone(newName, [self zone]);
    }

    if (ISnewDir || !directory) {
	if (!ISnewDir) newDirectory = [NXApp currentDirectory];
	NX_FREE(directory);
	directory = NXCopyStringBufferFromZone(newDirectory, [self zone]);
    }

    [window setTitleAsFilename:[self filename]];
    NXNameZone([self zone], [self filename]);

    return self;
}

/*--------------------------------------------------------------------
|-(BOOL)setName:(const char *)file| 
 * If file is a full path name, then both the name and directory of the
 * document is updated appropriately, otherwise, only the name is changed. 
Returns YES if changed, otherwise no..
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-(BOOL)setName:(const char *)file
{
    char *lastComponent;
    char path[MAXPATHLEN+1];

    if (file) {
	strcpy(path, file);
	lastComponent = strrchr(path, '/');
	if (lastComponent) {
	    *lastComponent++ = '\0';
	    [self setName:lastComponent andDirectory:path];
	    return YES;
	}
    }
    [self setName:file andDirectory:NULL];
    return NO;
}

/*--------------------------------------------------------------------
|-sync| Make sure the contents are in a stable state for operating on.
End editing.
Returns self.
                                          Ernest Prabhakar/1991-Oct-18
--------------------------------------------------------------------*/
-sync
{
    [window makeFirstResponder:window];
    [window endEditingFor:nil];
    return self;
}


/*--------------------------------------------------------------------
|-save| 			Save the 'Contents' as a single object.
Writes out the document (i.e, the Contents, possibly view and printInfo). 
See the Contents's write: method for more details on how it is archived. 
Returns self.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-save
{
    id container = [self getContents];
    [self sync];
    if ([self needsSaving]) {
	if ([NXApp write:container toFile:[self filename]]) {
	    haveSavedDocument = YES;
	    [window setDocEdited:NO];
	} else {
	    NotifyArg("Save", "Can't create file %s.", [self filename]);
	}
    }
    return self;
}

/*--------------------------------------------------------------------
|-(BOOL)needsSaving| 
Returns self.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-(BOOL)needsSaving
{
    return ([window isDocEdited] && (haveSavedDocument || contents));
}

/*====================================================================
                       Window delegate methods                        
====================================================================*/

/*--------------------------------------------------------------------
|-windowWillClose:sender| 
References 'windowWillClose:action:'
Returns self.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-windowWillClose:sender
{
    return [self windowWillClose:sender action:"Close"];
}

/*--------------------------------------------------------------------
|-windowWillClose:sender action:(const char *)action| 
 * If the Window has been edited, then this asks the user if she
 * wants to save the changes before closing the window.  When the window
 * is closed, the Document itself must be freed.  This is accomplished
 * via Application's delayedFree: mechanism.  Unfortunately, by the time
 * delayedFree: frees the Document, the window and view instance variables
 * will already have automatically been freed by virtue of the window's being
 * closed.  Thus, those instance variables must be set to nil to avoid their
 * being freed twice.
 *
 * Returning nil from this method informs the caller that the window should
 * NOT be closed.  Anything else implies it should be closed.
Returns self, or nil to prevent closing.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-windowWillClose:sender action:(const char *)action
{
    int save;
    if ([self needsSaving]) {
	save = NXRunAlertPanel(action, "%s has changes. Save them?", "Save",
			       "Don't Save", "Cancel", name);
	if (save != NX_ALERTDEFAULT && save != NX_ALERTALTERNATE) {
	    return nil;
	} else {
	    [sender endEditingFor:self];/* terminate any editing */
	    [self sync];
	    if (save == NX_ALERTDEFAULT) {
		[self save:nil];
	    }
	}
    }

    [self unregisterWindow];
    window = nil;
    if (contents == view) contents = nil;
    view = nil;
    if (printInfo) [NXApp setPrintInfo:nil];
    [NXApp delayedFree:self];

    return self;
}

/*--------------------------------------------------------------------
|-windowDidBecomeMain:sender| 
 * Switch the Application's PrintInfo to the document's when the document
 * window becomes the main window.
Returns self.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-windowDidBecomeMain:sender
{
    [NXApp setPrintInfo:[self printInfo]];
    return self;
}

/*--------------------------------------------------------------------
|-windowWillResize:sender toSize:(NXSize *) size| 
 If the contents is the view, then
  Constrains the size of the window to never be larger than the GraphicView
  inside it (including the ScrollView around it). 
Returns self.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-windowWillResize:sender toSize:(NXSize *) size
{
    NXRect      frameRect, contentRect;

    if (contents != view) return nil;

    getContentSizeForView(view, &contentRect.size);
    [[window class] getFrameRect:&frameRect forContentRect:&contentRect
   	style:[window style]];

    if (size->width > NX_WIDTH(&frameRect))
	size->width = NX_WIDTH(&frameRect);
    if (size->height > NX_HEIGHT(&frameRect))
	size->height = NX_HEIGHT(&frameRect);
    return self;
}

/*--------------------------------------------------------------------
|-windowWillMiniaturize:sender toMiniwindow:counterpart| 
Returns self.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-windowWillMiniaturize:sender toMiniwindow:counterpart
{
    char *dot;
    char title[MAXPATHLEN+1];

    strcpy(title, [self name]);
    dot = strrchr(title, '.');
    if (dot && !strcmp((dot+1), [NXApp extension])) *dot = '\0';
    [counterpart setTitle:title];
    return self;
}

/*--------------------------------------------------------------------
|-windowDidResize:sender| 
 * Ensures that if the view is bigger than the window's content view, scroll
 * bars are added to the ScrollView. 
Returns self.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-windowDidResize:sender
{
    return [self resetScrollers];
}

/*--------------------------------------------------------------------
|-resetScrollers| 
 * Checks to see if the new window size requires a vertical or horizontal
 * scroll bar.  Note that if a scroll bar is required, the window may have to
 * be resized to accomodate it.  Called whenever the user resizes the window 
Returns self.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-resetScrollers
{
    id          scrollView;
    NXSize      scrollViewSize, contentSize;
    BOOL        resizeHoriz = NO, resizeVert = NO;
    NXRect      contentRect, windowFrame, viewFrame;

    if (contents != view) return nil;

    if (window) {
	[window getFrame:&windowFrame];
	[[window class] getContentRect:&contentRect
	 forFrameRect:&windowFrame
	 style:[window style]];
	scrollView = [window contentView];
	[scrollView setHorizScrollerRequired:YES];
	[scrollView setVertScrollerRequired:YES];
	getContentSizeForView(view, &contentSize);
	if (NX_WIDTH(&contentRect) >= contentSize.width ||
	    NX_HEIGHT(&contentRect) >= contentSize.height) {
	    if (NX_WIDTH(&contentRect) >= contentSize.width) {
		[scrollView setHorizScrollerRequired:NO];
		resizeHoriz = YES;
	    }
	    if (NX_HEIGHT(&contentRect) >= contentSize.height) {
		[scrollView setVertScrollerRequired:NO];
		resizeVert = YES;
	    }
	    [view getFrame:&viewFrame];
	    [[scrollView class] getFrameSize:&scrollViewSize
	     forContentSize:&viewFrame.size
	     horizScroller:!resizeHoriz
	     vertScroller:!resizeVert
	     borderType:[scrollView borderType]];
	    if (!resizeHoriz)
		scrollViewSize.width = NX_WIDTH(&contentRect);
	    if (!resizeVert)
		scrollViewSize.height = NX_HEIGHT(&contentRect);
	    [window sizeWindow:scrollViewSize.width:scrollViewSize.height];
	}
    }
    return self;
}

/*====================================================================
                         Validate menu command                        
====================================================================*/

/*--------------------------------------------------------------------
|-(BOOL)validateCommand:menuCell| 
 * Validates whether a menu command that DrawDocument responds to
 * is valid at the current time.
Returns self.
                                          Ernest Prabhakar/1991-Jun-28
--------------------------------------------------------------------*/
-(BOOL)validateCommand:menuCell
{
    SEL action = [menuCell action];

    if (action == @selector(save:)) {
	return [window isDocEdited];
    } else if (action == @selector(revertToSaved:)) {
	return ([window isDocEdited] && haveSavedDocument);
    } else if ((action == @selector(saveAs:)) ||
	       (action == @selector(saveTo:))) {
	return (haveSavedDocument);
    } else if (action == @selector(alignSelLeft:) ||
	       action == @selector(alignSelRight:) ||
	       action == @selector(alignSelCenter:) ||
	       action == @selector(checkSpelling:) ||
	       action == @selector(showGuessPanel:)) {
	return [[window getFieldEditor:NO for:NXApp] superview] ? YES : NO;
    } else if (action == @selector(saveTypes:)) {
	return(view && view == contents) ? 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.