ftp.nice.ch/pub/next/developer/objc/appkit/Briefcase.2.0.N.bs.tar.gz#/Version2.0/MultApp.m

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

#import "MultApp.h"
#import "MultDoc.h"
//#import "MultDrop.h"
#import "PrefDelegate.h"
#import "PageMargin.h"

#import "Utility.h"

#define	PAGE_LAYOUT		LocalString("Page_Layout")
#define	INFO_PANEL		LocalString("Info_Panel")
#define HELP_PANEL		LocalString("Help_Panel")
#define PREF_PANEL		LocalString("Preferences_Panel")

#import <appkit/Menu.h>
#import <appkit/MenuCell.h>

#import <appkit/Listener.h>
#import <appkit/Matrix.h>
#import <appkit/OpenPanel.h>
#import <appkit/publicWraps.h>
#import <appkit/Speaker.h>
#import <mach/mach_init.h>
#import <objc/List.h>


@implementation MultApp:Object

// Canon Information Systems is not responsible for anything anyone does with
// this code, nor are they responsible for the correctness of this code.
// Basically, this has very little to do with the company I work for, and you
// can't blame them.

// This file is best read in a window as wide as this comment, and with tab
// settings of 4 spaces and indent setting of 4 spaces (at least, that's what
// I use).

// You are welcome to do as you would with this file under the following
// conditions.
// First, I accept no blame for anything that goes wrong no matter how you
// use it, no matter how catastrophic, not even if it stems from a bug in my
// code.  Second, please keep my notices on it when/if you distribute it.					 // Third, if you discover any bugs or have any comments, PLEASE TELL ME!
// Code won't get better without people picking it apart and giving the writer
// feedback.  Fourth, if you modify it, please keep a notice that your version
// is based on mine in the source files (and keep the notice that mine is based
// on four other pieces of code :<).  Thanks, and have fun.
// - Subrata Sircar, ssircar@canon.com

/* This class is intended as an Application Delegate.  As such it */
/* is supposed to provide the document-management functionality   */
/* that a multiple-document, drag-and-drop application should be  */
/* able to do.  This includes keeping track of the current thing, */
/* managing the shared panels, updating the defaults and menus,   */
/* and controlling communication with the Workspace Manager.	  */

/* The paradigm used is that the application delegate relays new messages */
/* to the factory Document class, and every other doc message goes to     */
/* the first responder.  The delegate opens nib sections containing       */
/* the info and preferences panel (the prefs panel should have a delegate */
/* to update it) and lets the helpObject worry about itself.  Otherwise   */
/* it acts on delegate messages.										  */

/* Source code stolen from Draw, Acceptor, and WhatsUpDoc.  Thanks! 	  */
/* Source code also taken from Ernest Prabhakar's BasicApp example, which */
/* includes more methods than mine does (for things like services, etc.)  */
/* His examples are well worth checking out, especially if you need more  */
/* functionality than this class can provide.							  */
/* - Subrata Sircar			ssircar@canon.com	Canon Information Systems */
 
/* Version 0.9b		Apr-19-92		First Public Release	*/
/* Version 0.95b	Jun-10-92		Minor File path fixes	*/
/* Version 1.00b	Aug-10-92		Cleaned up #imports		*/
/* Version 1.1b		Jan-10-93		Modified for 3.0		*/

/* Preprocessor macros */

#define DocClass		[[[self class] docClass] class]
#define DropClass		[[[self class] dropClass] class]
#define DocExtension	[DocClass extension]
#define theVersion		((float)([[self class] version])/100.0)

/* Factory (Class) Variables */
/* These are declared as static internal because I felt that all members	*/
/* of this class would use the same values for these variables.  Subclasses	*/
/* can only manipulate these variables through the provided class methods. 	*/
/* BE VERY CAREFUL about overriding methods which depends on the class		*/
/* variables without calling the super method - it might cause problems.	*/

static id      		docClass;				// Class of Document to use
static id      		dropClass;				// Class of DropView to use
static const char	*theAppName;			// Cache the appName for later use
static const char	*noValue;				// Cache the local no and yes
static const char	*yesValue;

static const char 	*VersionFormat;			// Version Format String
static const int 	myVersion 		= 110;	// Version multiplied by 100
BOOL 				InMsgPrint 		= NO;

/* Factory (Class) Methods */

+ initialize
/* Class variable initialization.  DO NOT call from subclasses. */
{
    if (self == [MultApp class]) {
		[self setVersion:myVersion];
		[self setDocClass:[MultDoc class]];
//		[self setDropClass:[MultDrop class]];
		VersionFormat = LocalString("Version %.2f");
		theAppName = NXCopyStringBufferFromZone([NXApp appName],MyZone);
		noValue = LocalString("NO");
		yesValue = LocalString("YES");
	}
	return self;
}

+ setDocClass:newDoc
/* Sets the document class and the file extensions to be used.  Requires	*/
/* the document class to implement a class method to return the extension	*/
/* and the file types.														*/
{
    docClass = newDoc;
    return self;
}

+ docClass
{
    return docClass;
}

+ setDropClass:newDropView
{
    dropClass = newDropView;
    return self;
}

+ dropClass
{
    return dropClass;
}

/* Private C functions used to implement methods in this class. */

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

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

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

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

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

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

    return nil;
}

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

    if (name && *name) {
		if (!directory) directory = ".";
		else if (*directory != '/') {
			strcpy(buffer, "./");
			strcat(buffer, directory);
			directory = buffer;
			getwd(temp);
			if (!chdir(directory) && getwd(path)) chdir(temp);
			else {
				Notify(LocalString("Open:  path invalid"), directory);
				return nil;
			}
		} else strcpy(path,directory);
		strcat(path, "/");
		strcat(path, name);
		window = findDocument(path);
		if (window) {
			if (display) [window makeKeyAndOrderFront:window];
			return [window delegate];
	    } else return [docClass newFromFile:path];
    }

    return nil;
}


/* Public Methods */

- openFile:(const char *)path file:(const char *)type flag:(BOOL)flag
{
	return openFile(path,type,flag);
}

- currentDocument
{
    return documentInWindow([NXApp mainWindow]);
}

- (const char *)currentDirectory
/* Returns the current document's directory */
{
    const char *retval = [[self currentDocument] directory];
    if (!retval || !*retval) retval = defaultDir;
    return retval;
}

- setDefaultDir:(const char *)dir
{
	sprintf(defaultDir,"%s",dir);
    return self;
}

- (const char *)launchDirectory
{
    return launchDir;
}


/* Default support methods */
// These functions manage the app's behavior when it quits with unsaved documents

- (BOOL)saveAll
{
    return saveAll;
}

- setSaveAll:(BOOL)value
{
    saveAll = value;
	return self;
}

- (BOOL)dumpAll
{
    return dumpAll;
}

- setDumpAll:(BOOL)value
{
    dumpAll = value;
	return self;
}


/* Shared Panels */

- info:sender
/*
 * Returns the information Panel if it hasn't already been loaded.
 */
{
	id versionField;	/* version field in info panel */
	char buf[20];
    if (!infoPanel) {
		NXZone *zone = NXCreateChildZone(MyZone, vm_page_size, vm_page_size, YES);
		NXNameZone(zone,"infoPanel");
		if (LoadLocalNib(LocalString("Info.nib"),self,YES,zone)) {
			versionField = NXGetNamedObject("VersionNumber", infoPanel);
			if (versionField) {
				sprintf(buf, VersionFormat,theVersion);
				[versionField setStringValue:buf];
			}
			[infoPanel setFrameUsingName:INFO_PANEL];
		} else {
			char temp[MAXPATHLEN+1];
			sprintf(temp,"%s %s\n",LocalString("Could not load requested resource"),LocalString("Info.nib")); 
			Notify(LocalString("Resource Error"),temp);
		}
		NXMergeZone(zone);
    }
	[infoPanel orderFront:self];
    return infoPanel;
}

- pref:sender
/*
 * The preferences panel is a separate nib module and only loaded on demand.
 * When loaded, the object that manages the panel is asked to update it.
 */
{
    if (!prefPanel) {
		NXZone *zone = NXCreateChildZone(MyZone, vm_page_size, vm_page_size, YES);
		if (LoadLocalNib(LocalString("Pref.nib"),self,NO,zone)) {
			[prefPanel setFrameUsingName:PREF_PANEL];
		} else {
			char temp[MAXPATHLEN+1];
			sprintf(temp,"%s %s\n",LocalString("Could not load requested resource"),LocalString("Pref.nib")); 
			Notify(LocalString("Resource Error"),temp);
		}
		NXMergeZone(zone);
    }
	[[prefPanel delegate] load:self];
	[prefPanel makeKeyAndOrderFront:self];
    return prefPanel;
}

- saveAsPanel:sender
/*
 * Gets us a SavePanel with an accessory
 */
{
    id savepanel = [SavePanel new];
	[savepanel setAccessoryView:nil];
	[savepanel setRequiredFileType:DocExtension];
    return savepanel;
}

- pageLayout:sender
/*
 * Returns the application-wide PageLayout panel, with margins.
 */
{
    return pageMargin;
}

- stringSet:sender
/*
 * Returns the application-wide string table, if it exists.
 */
{
    if (stringSet) return stringSet;
	else return nil;
}


/* Target/Action Methods */

- new:sender
/*
 * Called by pressing New in the Document menu.
 */
{
    [[docClass class] new];
    return self;
}

- open:sender
/*
 * Called by pressing Open... in the Document menu.
 */
{
    const char *directory;
    const char *const *files;
    const char *const myType[2] = {DocExtension, NULL};
    id openpanel = [[OpenPanel new] allowMultipleFiles:YES];

	directory = [self currentDirectory];
    if (directory && (*directory == '/')) [openpanel setDirectory:directory];
    if ([openpanel runModalForTypes:myType]) {
		files = [openpanel filenames];
		directory = [openpanel directory];
		while (files && *files) {
			haveOpenedDocument = openFile(directory, *files, YES) || haveOpenedDocument;
			files++;
		}
    }

    return self;
}

- saveAll:sender
/*
 * Saves all the documents.
 */
{
    int count;
    id windowList;

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

    return self;
}

- print:sender
{
    return [[[self currentDocument] view] printPSCode:sender];
}

- mailToMe:sender
// Stolen from Opener 3.0.  Thanks, Michael!
{
    char subj[256], w[256] = "whoami";
    char body[4096]="\
Subrata:\n\n\
Great source code!  It runs like beauty in the night...\n\
BUT!  I do have a few comments:\n\n\
   <insert accolades, criticisms & suggestions here>\n\n\
             Sincerely,\n\
             ";
#define call(a,b) [s performRemoteMethod:a with:b length:strlen(b)+1]
    id s = [NXApp appSpeaker];
    port_t mailPort = NXPortFromName("Mail", NULL); // make sure app is launched

	mailPort = NXPortFromName("MailSendDemo",NULL);
	if (mailPort == PORT_NULL) {
		Notify(LocalString("Suggestion attempt failed with Missing Port to Mail"),LocalString("Check if Mail is running."));
		return self;
	}
    [s setSendPort:mailPort];

    sprintf(subj,"Comments and suggestions for ``%s'', Version %.2f ",theAppName,theVersion);
    strcat(body,execstr(w)); strcat(body,"\n");
    call("setTo:","ssircar@canon.com");
    call("setSubject:",subj);
    call("setBody:",body);
    
    return self;
}


/* Automatic update methods */

- (BOOL)menuItemUpdate:menuCell
/*
 * Method called by all menu items which send their actions to the
 * First Responder.  First, if the object which would respond if the
 * action was sent down the responder chain also responds to the message
 * validateCommand:, then it is sent validateCommand: to determine
 * whether that command is valid now; otherwise, if there is a responder
 * to the message, then it is assumed that the item is valid.
 * The method returns YES if the cell has changed its appearance (so that
 * the caller (a Menu) knows to redraw it).
 * This method also traps menu items sending to this class, and sets the
 * action the same way.
 */
{
    SEL action;
    id responder, target;
    BOOL enable;

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

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

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

- (BOOL)validateCommand:menuCell
// The only messages the delegate is asked to validate are SaveAll and Print.
{
    SEL action = [menuCell action];

    if ((action == @selector(saveAll:)) || (action == @selector(print:))) return findDocument(NULL) ? YES : NO;

    return YES;
}

- setupPageLayout:(float)lm :(float)rm :(float)tm :(float)bm 
{
	if (!pageMargin) {
		pageMargin = [PageMargin new];
		[pageMargin setPlpAccessory:plpAccessory];
		[pageMargin setFrameUsingName:PAGE_LAYOUT];
	}
	[pageMargin setValues:lm right:rm top:tm bottom:bm];
	[pageMargin writePrintInfo];
	return self;
}

/* Application Delegate Methods */

- appWillInit:sender
{
	char temp[MAXPATHLEN+1];
	float lm,rm,tm,bm;

#ifndef DEBUG			/* Don't Dump Core if not Debugging */
	struct rlimit rl={ 0, 0};
    getrlimit( RLIMIT_CORE, &rl);
    rl.rlim_cur=0;
    setrlimit( RLIMIT_CORE, &rl);
#endif

	/* Set the document class variable to point to us */
	[DocClass setAppDelegate:self];
	
	/* Check and load the application defaults.  The basics are the quit */
	/* behavior and the page margins.  Also sets the menu updating flag. */
	
	if (!NXGetDefaultValue(theAppName,LocalString("SaveAll"))) sprintf(temp,noValue);
	else sprintf(temp,NXGetDefaultValue(theAppName,LocalString("SaveAll")));
	if (!strcmp(temp,yesValue)) saveAll = YES;
	if (!NXGetDefaultValue(theAppName,LocalString("DumpAll"))) sprintf(temp,noValue);
	else sprintf(temp,NXGetDefaultValue(theAppName,LocalString("DumpAll")));
	if (!strcmp(temp,yesValue)) dumpAll = YES;
	
	if (!NXGetDefaultValue(theAppName,LocalString("LeftMargin"))) sprintf(temp,"36.0");
	else sprintf(temp,NXGetDefaultValue(theAppName,LocalString("LeftMargin")));
	sscanf(temp,"%f",&lm);
	if (!NXGetDefaultValue(theAppName,LocalString("RightMargin"))) sprintf(temp,"36.0");
	else sprintf(temp,NXGetDefaultValue(theAppName,LocalString("RightMargin")));
	sscanf(temp,"%f",&rm);
	if (!NXGetDefaultValue(theAppName,LocalString("TopMargin"))) sprintf(temp,"36.0");
	else sprintf(temp,NXGetDefaultValue(theAppName,LocalString("TopMargin")));
	sscanf(temp,"%f",&tm);
	if (!NXGetDefaultValue(theAppName,LocalString("BottomMargin"))) sprintf(temp,"36.0");
	else sprintf(temp,NXGetDefaultValue(theAppName,LocalString("BottomMargin")));
	sscanf(temp,"%f",&bm);
	
	[self setupPageLayout:lm :rm :tm :bm];
	
	[NXApp setAutoupdate:YES];

    return self;
}

- appDidInit:sender
/*
 * Register our window with the Workspace for icon dropping.
 * Check for files to open specified on the command line.
 * Initialize the menus and check if we were opened to print or quit.
 * If there are no open documents, then open blank documents.
 */
{
    int 		i;
    char 		buffer[MAXPATHLEN+1], temp[MAXPATHLEN+1];
    char 		*directory, *name, *ext;
//	NXRect		theRect;
//	View 		*appIconView = [[NXApp appIcon] contentView];
//	id 			myDropView = [DropClass allocFromZone:[appIconView zone]];
	    
	/* register our app icon view for image dragging destination */
//	[appIconView getFrame:&theRect];
//	[myDropView initFrame:&theRect];
//	[appIconView addSubview:myDropView];
	
//	[myDropView registerForDraggedTypes:&NXFilenamePboardType count:1];
//	[myDropView registerForDraggedTypes:&NXAsciiPboardType count:1];
	
	launchDir = appDirectory();
	[self setDefaultDir:NXHomeDirectory()];
	
	/* Check for command line files to open */
    if (NXArgc > 1) {
		for (i = 1; i < NXArgc; i++) {
			strcpy(buffer, NXArgv[i]);
			ext = rindex(buffer, '.');
			if (!ext) {
				strcat(buffer,".");
				strcat(buffer, DocExtension);
			}
			if (*buffer == '/') {
				directory = "/";
				name = buffer;
				name++;
			} else {
				name = rindex(buffer, '/');
				if (name) {
					*name++ = '\0';
					sprintf(temp,"%s/%s",launchDir,buffer);
					directory = temp;
				} else {
					name = buffer;
					directory = NULL;
				}
			}
			haveOpenedDocument = openFile(directory, name, YES) || haveOpenedDocument;
		}
    }

    if (!haveOpenedDocument) [self new:self];	/* if none opened, open one */
    if (NXGetDefaultValue(theAppName,LocalString("Quit"))) {
		[NXApp activateSelf:YES];
		NXPing();
		[NXApp terminate:self];
    }

    initMenu([NXApp mainMenu]);

    return self;
}
- app:sender willShowHelpPanel:panel
{
	if (!helpPanel) {
		helpPanel = panel;
		[helpPanel setFrameUsingName:HELP_PANEL];
	}
	return self;
}


- appWillTerminate:sender
/*
 * Overridden to be sure all documents get an opportunity to be saved
 * before exiting the program.  Save the defaults too.
 */
{
	float lm,rm,tm,bm;
	char temp[100];
    id window, document;
	id windowList = [NXApp windowList];
    int choice = 0, count = [windowList count];

	[pageMargin saveFrameUsingName:PAGE_LAYOUT];
	if (helpPanel) [helpPanel saveFrameUsingName:HELP_PANEL];
	if (infoPanel) [infoPanel saveFrameUsingName:INFO_PANEL];
	if (prefPanel) [prefPanel saveFrameUsingName:PREF_PANEL];
	[pageMargin getValues:&lm right:&rm top:&tm bottom:&bm];
	sprintf(temp,"%f",lm);
	NXWriteDefault([NXApp appName],LocalString("LeftMargin"),temp);
	sprintf(temp,"%f",rm);
	NXWriteDefault([NXApp appName],LocalString("RightMargin"),temp);
	sprintf(temp,"%f",tm);
	NXWriteDefault([NXApp appName],LocalString("TopMargin"),temp);
	sprintf(temp,"%f",bm);
	NXWriteDefault([NXApp appName],LocalString("BottomMargin"),temp);

	if (saveAll) {
		while (count--) {
			document = [[windowList objectAt:count] delegate];
			if ([document respondsTo:@selector(needsSaving)] && [document needsSaving]) {
				[document save:self];
			}
		}
		NXWriteDefault(theAppName,LocalString("SaveAll"),yesValue);
		NXWriteDefault(theAppName,LocalString("DumpAll"),noValue);
	} else if (dumpAll) {
		NXWriteDefault(theAppName,LocalString("DumpAll"),yesValue);
		NXWriteDefault(theAppName,LocalString("SaveAll"),noValue);
	} else {
		NXWriteDefault(theAppName,LocalString("DumpAll"),noValue);
		NXWriteDefault(theAppName,LocalString("SaveAll"),noValue);
		while (count--) {
			window = [windowList objectAt:count];
			document = [window delegate];
			if ([document respondsTo:@selector(needsSaving)] && [document needsSaving]) {
				choice = NXRunAlertPanel(LocalString("Quit"),
										 LocalString("You have unsaved documents."),
										 LocalString("Review Unsaved"),
										 LocalString("Quit Anyway"),
										 LocalString("Cancel"));
				if (choice == NX_ALERTOTHER) return nil;
				else if (choice == NX_ALERTALTERNATE) return self;
				else if (choice == NX_ALERTDEFAULT) {
					count = 0;
					choice = [windowList count];
					while (choice--) {
						window = [windowList objectAt:choice];
						document = [window delegate];
						if ([document respondsTo:@selector(windowWillClose:)]) {
							if ([document windowWillClose:self]) [window close];
							else return nil;
						}
					}
				}
	    	}
		}
    }

    return self;
}
    
- (int)appOpenFile:(const char *)path type:(const char *)type
// This method is performed whenever a user double-clicks on an icon
// in the Workspace Manager representing a app program document.
// It is also called via command-drag-n-drop.
{
    char *name;
    char directory[MAXPATHLEN+1];

    if (type && !strcmp(type, DocExtension)) {
		strcpy(directory, path);
		name = rindex(directory, '/');
		if (name) {
#ifdef DEBUG
			printf("Directory %s: File %s\n",directory,name);
#endif
			if (name != index(directory, '/')) {
				*name++ = '\0';
				if (openFile(directory, name, YES)) {
					haveOpenedDocument = YES;
					return YES;
				}
			} else {
				name++;
				if (openFile("/", name, YES)) {
					haveOpenedDocument = YES;
					return YES;
				}
			}
		}
    } else {
		sprintf(directory,LocalString("%s is not a file %s can open.  An attempt to filter appropriate data will be made."),path,[NXApp appName]);
		Notify(LocalString("File Open Warning"),directory);
		if ([DocClass newFromPasteboard:[Pasteboard newByFilteringFile:path]]) return YES;
	}
    return NO;
}

- (BOOL)appAcceptsAnotherFile:sender
/*
 * We accept any number of appOpenFile:type: messages.
 */
{
    return YES;
}
- (int)app:sender unmounting:(const char *)fullPath
{
	id	windowList	= [NXApp windowList];
	int	count		= [windowList count];
    id	document	= NULL;
	
/* if any of our documents are in that path, close them and notify the user */

    while (count--) {
		document = documentInWindow([windowList objectAt:count]);
		if (document && (!strncmp([document directory],fullPath,strlen(fullPath)))) {
			Notify(fullPath,LocalString("is being unmounted; documents here will close."));
		/* Use performClose so that the window delegate will be notified. */
			[[windowList objectAt:count] performClose:self];
		}
		
    }
/* the current directory is wherever our current document is so we're set */
    return 1;
}		


/* Listener/Speaker remote methods */

// Filename and path methods
- (int)msgDirectory:(const char **)fullPath ok:(int *)flag
{
    *fullPath = [self currentDirectory];
    if (*fullPath) *flag = YES;
    else {
		*fullPath = "";
		*flag = NO;
    }
    return 0;
}

- (int)msgFile:(const char **)fullPath ok:(int *)flag
{
    const char *file;

    file = [[self currentDocument] fileName];
    if (file) {
		*fullPath = file;
		*flag = YES;
    } else {
		*fullPath = "";
		*flag = NO;
    }

    return 0;
}

// Miscellaneous messages we might want to handle
- (int)msgPrint:(const char *)fullPath ok:(int *)flag
{
    BOOL close;
    id document = nil;
    char *directory, *name;
    char temp[MAXPATHLEN+1];
    char path[MAXPATHLEN+1];
    char buffer[MAXPATHLEN+1];

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

    return 0;
}

- (int)msgVersion:(const char **)aString ok:(int *)flag
{
    char buf[20];

    sprintf(buf, VersionFormat, theVersion);
    *aString = NXCopyStringBuffer(buf);
    *flag = YES;

    return 0;
}

- (int)msgQuit:(int *)flag
{
    *flag = ([NXApp terminate:self] ? NO : YES);
    return 0;
}

@end

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