ftp.nice.ch/pub/next/connectivity/infosystems/Archie.2.18.s.tar.gz#/Archie/ArchieApp.m

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

{\rtf0\ansi{\fonttbl\f0\fswiss Helvetica;\f1\fmodern Courier;}
\paperw11760
\paperh9980
\margl120
\margr120
{\colortbl;\red0\green0\blue0;}
\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b\i0\ulnone\fs24\fc0\cf0 #import <appkit/appkit.h>\
#import "ArchieApp.h"\
#import "ArchieSession.h"\
#import "FTPObject.h"\
#import "InspectorManager.h"\
#import "MorphView.h"\
#import "NetrcEntry.h"\
#import "Preferences.h"\
#import "psfunc.h"\
\

\pard\tx0\tx0\tx0\tx0\tx0\tx0\tx0\tx0\tx0\tx0\fc1\cf1 #include <sys/dir.h>\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc0\cf0 \
@implementation ArchieApp\
\
/* The pasteboard types Archie can provide services for.  It is\
	initialize in our appDidInit: method */\
#define N_PASTEBORD_TYPES 1\
static NXAtom gAppPasteboardTypes[N_PASTEBORD_TYPES];\
/* Variables used by the animation methods for the first images of\
	the Info panel */\
const int LAST_FRAME = 20;\
NXColor green, cyan;\
\
static void InitMenu(Menu *menu)\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc0\cf0 /*
{{\NeXTHelpMarker622 \markername InitMenu;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc0\cf0  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul ReturnValue:
\i\ulnone  None;\
	
\i0\ul Description:
\i\ulnone  This is the 
\b initMenu
\b0  method from the Draw NextDeveloper\
		examples.  Here is the description found with the method in DrawApp.m:\

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

\i 		The updateAction of the menu cells is set to the 
\b menuItemUpdate:
\b0 \
		method of this class.;\
	
\i0\ul\fc1\cf1 Args:
\i\ulnone\fc0\cf0 \
		menu: The Menu object whose cells are to be initialized;\
*/\

\f0\b\i0 int count;\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc0\cf0 Matrix *matrix;\
MenuCell *cell;\
id matrixTarget, cellTarget;\
\
	matrix = [menu itemList];\
	matrixTarget = [matrix target];\
	\
	count = [matrix cellCount];\
	while ( count -- > 0 )\
	\{\
		cell = [matrix cellAt: count : 0];\
		cellTarget = [cell target];\
		
\f1\b0\i /* If the cell and matrix target are nil, NXApp will determine the\
			cell target at runtime using the Responder chain.  We set the\
			
\b updateAction:
\b0  method of these cells to our own 
\b menuItemUpdate:
\b0  */
\f0\b\i0 \
		if ( ! matrixTarget && ! cellTarget )\
			[cell setUpdateAction: @selector(menuItemUpdate:) forMenu: menu];\
		else if ( [cell hasSubmenu] == YES )\
			
\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc0\cf0 InitMenu
\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc0\cf0 (cellTarget);\
	\}\
\} 
\b0\i // End InitMenu()
\b\i0 \
\
\
/*\\ ---------------------- Document Methods ---------------------- \\*/\
- new: sender\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc0\cf0 /*
{{\NeXTHelpMarker2127 \markername new:;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc0\cf0  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul\fc1\cf1 ReturnValue:
\i\ulnone\fc0\cf0  The new ArchieSession;\
	
\i0\ul\fc1\cf1 Description:
\i\ulnone\fc0\cf0  Open a new untitled session using the ArchieSession\
		
\b new
\b0  method;\
	
\i0\ul\fc1\cf1 Args:
\i\ulnone\fc0\cf0  \
		sender: the Matrix associated with the Document submenu;\
*/
\f0\b\i0 \

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc0\cf0 ArchieSession *newDoc;\
id window = mainWindow;\
\
	newDoc = [ArchieSession new];\
	if( window == nil && remoteLaunch == NO )\
		emptyDoc = newDoc;	
\b0\i\fc1\cf1 // Save a reference to the empty doc
\b\i0\fc0\cf0 \
\
	return newDoc;\
\} 
\b0\i // End new:
\b\i0 \
\
- open: sender\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc0\cf0 /*
{{\NeXTHelpMarker2569 \markername open:;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc0\cf0  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul\fc1\cf1 ReturnValue:
\i\ulnone\fc0\cf0  self;\
	
\i0\ul\fc1\cf1 Description:
\i\ulnone\fc0\cf0  Create a new session from an ArchieSession archive file.\
		The user is prompted for a file from within ArchieSession's
\b  newFromFile
\b0 .;\
	
\i0\ul\fc1\cf1 Args:
\i\ulnone\fc0\cf0  \
		sender: the Matrix associated with the Document submenu;\
*/\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b\i0\fc0\cf0 	
\b0\i\fc1\cf1 /* If we opened an empty doc on startup, close it if the user did not use it */
\b\i0\fc0\cf0 \
	if( emptyD
\fc1\cf1 oc == [mainWindow delegate] && [emptyDoc isEdited] == NO )\
		[emptyDoc close: self];\

\fc0\cf0 	
\b0\i\fc1\cf1 /* Ask the ArchieSession class to prompt the user for a document */
\b\i0\fc0\cf0 \
	[ArchieSession newFromFile];\
	\
	return self;\
\} 
\b0\i // End open:
\b\i0 \
\
- editedSessionList: winList\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc0\cf0 /*
{{\NeXTHelpMarker3171 \markername saveAll:;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc0\cf0  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul\fc1\cf1 ReturnValue:
\i\ulnone\fc0\cf0  A List of unsaved ArchieSessions;\
	
\i0\ul\fc1\cf1 Description:
\i\ulnone\fc0\cf0  We go through the 
\fc1\cf1 winList
\fc0\cf0  and ask each Window\
		whose 
\b isDocEdited
\b0  method answers YES if their delegate\
		
\b isKindOf:
\b0  ArchieSession.  We add those who say they\
		are to a session List;\
	
\i0\ul\fc1\cf1 Args:
\i\ulnone\fc0\cf0  \
		winList: A list of Window objects. Generally this is our\
			windowList instance variable.;\
*/\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b\i0\fc0\cf0 int index = 0;\
List *sessionList;\
id window, winDelegate;\
\
	sessionList = [[List alloc] initCount: 0];\
	if( sessionList == nil )\
		[self error: INTERNAL_ERROR method:_cmd key: OBJ_ALLOC_FAILED];\
	while( (window = [windowList objectAt: index]) != nil )\
	\{\
		winDelegate = [window delegate];\
		if( [window isDocEdited] == YES &&\
				[winDelegate isKindOf: [ArchieSession class]] == YES )\
			[sessionList addObject: winDelegate];\
		index ++;\
	\}\
\
	return sessionList;\
\} 
\b0\i // End editedSessionList:
\b\i0 \
\
- saveAll: sender\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc0\cf0 /*
{{\NeXTHelpMarker4058 \markername saveAll:;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc0\cf0  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul\fc1\cf1 ReturnValue:
\i\ulnone\fc0\cf0  self;\
	
\i0\ul\fc1\cf1 Description:
\i\ulnone\fc0\cf0  This method requests that each session\
		returned by our 
\b editedSessionList:
\b0  method to 
\b save:
\b0  itself;\
	
\i0\ul\fc1\cf1 Args:
\i\ulnone\fc0\cf0  \
		sender: the Matrix associated with the Document submenu;\
*/\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b\i0\fc0\cf0 List *sessionList;\
\
	sessionList = [self editedSessionList: windowList];\
	
\f1\b0\i /* I don't think it hurts asking an empty list to perform this method.\
		Passing a nil sender to the ArchieSession 
\b save:
\b0  method tells it\
		not to display a save panel unless it is an untitled doc. */
\f0\b\i0 \
	[sessionList makeObjectsPerform: @selector(save:) with: nil];\
	[sessionList free];\
\
	return self;\
\} 
\b0\i // End saveAll:
\b\i0 \
\

\i\fs28 /*\\ ----------------------- Panel Display Methods ----------------------- \\*/
\i0\fs24 \
- infoPanel: sender\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc0\cf0 /*
{{\NeXTHelpMarker4780 \markername infoPanel:;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc0\cf0  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul\fc1\cf1 ReturnValue:
\i\ulnone\fc0\cf0  self if the panel is displayed, nil if it is not;\
	
\i0\ul\fc1\cf1 Description:
\i\ulnone\fc0\cf0  Brings up our app info panel by loading\
		the Info.nib section;\
	
\i0\ul\fc1\cf1 Args:
\i\ulnone\fc0\cf0  \
		sender: the Matrix associated with the Info submenu;\
*/\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b\i0\fc0\cf0 const char *version;\
TextField *versionField;\
NXImage *image;\
List *images
\fc1\cf1 ;\
Box *morphBox;\
MorphView *mor
\fc0\cf0 phView;\
\
	if( infoPanelID == nil )\
	\{	
\b0\i //Load our Info panel nib
\b\i0 \
		[NXApp loadNibSection: "Info.nib" owner: self withNames: YES];\
		if( infoPanelID == nil )\
		\{	/* Not a fatal error, just an inconvience */\
			[self error: INTERNAL_SORRY method:_cmd key: INFO_PANEL_FAILED];\
			return nil;\
		\}\
	\}\
	
\b0\i // Get and set the current version string
\b\i0 \
	version = [msgStringTable valueForStringKey: "archie_version"];\
	if( version == 0 )\
		version = "Archie version unknown";\
	versionField =
\fc1\cf1  NXGetNamedObject("VersionString", infoPanelID);\
	[
\fc0\cf0 versionField
\fc1\cf1  setStringValue: version];\
	// Setup the MorphView
\fc0\cf0 \
	images = [[List alloc] initCount: 0];\
	image = [NXImage findImageNamed: "scottInfo"];\
	[images addObject: image];\
	image = [NXImage findImageNamed: "otherInfo"];\
	[images addObject: image];\
	image = [NXImage findImageNamed: "otherInfo2"];\
	[images addObject: image];\
	/* Apparently you can't name name a subview of a box and then load it\
		using the panel as its owner? Here I get the Box, its contentView, its\
		subviews, and then the first view in this list.  Gee this is easy! */\
	morphBox = NXGetNamedObject("MorphBox", infoPanelID);\
	morphView = [[[morphBox contentView] subviews] objectAt: 0];\
	[[morphView setImages: images delay: 1 delta: 0.05] setDelegate: self];\
	[images free];\
	[infoPanelID makeKeyAndOrderFront: self];\
\
	return self;\
\} 
\b0\i // End InfoPanel:
\b\i0 \
\

\b0\i\fc1\cf1 // MorphView Delegate methods
\b\i0\fc0\cf0 \
- (BOOL) hasDrawMethodFor: image imageNum:(int) num\
\{\
	
\b0\i\fc1\cf1 // We only add an animation to the first image
\b\i0\fc0\cf0 \
	if( num == 0 )\
		return YES;\
	return NO;\
\}\
\
void timerFunction(DPSTimedEntry teNum, double now, ArchieApp *self)\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc0\cf0 /*
{{\NeXTHelpMarker6726 \markername infoPanel:;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc0\cf0  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul\fc1\cf1 ReturnValue:
\i\ulnone\fc0\cf0  None;\
	
\i0\ul\fc1\cf1 Description:
\i\ulnone\fc0\cf0  A timed entry callback that sends the ArchieApp\
		instance a nextFrame message to display the next frame in\
		the current animation sequence;\
	
\i0\ul\fc1\cf1 Args:
\i\ulnone\fc0\cf0  \
		sender:\
			teNum: The timed entry tag returned
\fc1\cf1  by DPSAddTimedEntry();\
			now: The number of seconds since an arbitrary time;\
			self: The ArchieApp instance that implements the nextFrame method;
\fc0\cf0 \
*/\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b\i0\fc0\cf0 	if( [self nextFrame] == nil )\
	\{	
\b0\i // The sequence is over, remove the timed entry & abort the modal loop
\b\i0 \
		DPSRemoveTimedEntry(teNum);\
		[NXApp stopModal];\
	\}\
\} 
\b0\i\fc1\cf1 // End timerFunction()
\b\i0\fc0\cf0 \
\
- performDrawFor: image imageNum:(int) num inView: view\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc0\cf0 /*
{{\NeXTHelpMarker7379 \markername infoPanel:;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc0\cf0  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul\fc1\cf1 ReturnValue:
\i\ulnone\fc0\cf0  self;\
	
\i0\ul\fc1\cf1 Description:
\i\ulnone\fc0\cf0  This delegate method of MorphView is called for the\
		first image each time a morph: message is sent to the MorphView\
		in the info panel. It does a cheesy animiation sequence using\
		instance drawing and pswrap Postscript. The animiation is done\
		by starting a timed entry and sitting in a modal event loop\
		until all of the frames have been displayed;\
	
\i0\ul\fc1\cf1 Args:
\i\ulnone\fc0\cf0  \
		sender:\
			image: The NXImage that provides the view background
\fc1\cf1 ;\
			num: The number of the image in the morph sequence;\
			view: The MorphView that is performing the morph: action;
\fc0\cf0 \
*/\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b\i0\fc0\cf0 NXModalSession *drawSession;\
NXColorList *NeXTcolors;\

\fc1\cf1 DPSTimedEntry drawTE;\
NXWindowDepth depth;\
\
	depth = [[view window] depthLimit];\
	if( depth == NX_DefaultDepth )\
		depth = [Window defaultDepthLimit];\
\
	
\b0\i // Load the NeXT PANTONE colors list
\b\i0 \

\fc0\cf0 	NeXTcolo
\fc1\cf1 rs = [NXColorList findColorListNamed: "NeXT"];\
	if( depth == NX_TwoBitGrayDepth )\
		animInfo.color = NXConvertGrayToColor(NX_DKGRAY);\
	else\

\fc0\cf0 		animInfo.color = [NeXTcolors colorNamed: "Green"];\
	PSsetinstance(YES);\
	animInfo.frameNum = 0;\
	animInfo.drawState = SendMsg;\
	
\b0\i // This is the little request packet rectangle
\b\i0 \
	animInfo.packet.origin.x = 78;\
	animInfo.packet.origin.y = 56;\
	animInfo.packet.size.width = 13;\
	animInfo.packet.size.height = 6;\
	drawTE = DPSAddTimedEntry(0.10, &timerFunction, self,\
		NX_MODALRESPTHRESHOLD+1);\
	drawSession = [NXApp beginModalSession: 0 for: [view window]];\
	NXSetColor(animInfo.color);\
	NXRectFill(&animInfo.packet);\
	
\b0\i // Run a modal loop until the user kills us or all frame have been drawn
\b\i0 \
	while( [NXApp runModalSession: drawSession] == NX_RUNCONTINUES )\
	\{\
		if( NXUserAborted() == YES )\
			[NXApp stopModal];\
		NXResetUserAbort();\
	\}\
	[NXApp endModalSession: drawSession];\
	PSsetinstance(NO);\
\
	return self;\
\} 
\b0\i\fc1\cf1 // End performDrawFor:imageNum:inView:
\b\i0\fc0\cf0 \
\
- nextFrame\
\{\
float theta;\
\
	 if( animInfo.frameNum > LAST_FRAME )\
		animInfo.frameNum = 0;\
\
	PSnewinstance();\
	switch( animInfo.drawState )\
	\{\
		case SendMsg:\
			NXSetColor(animInfo.color);\
			animInfo.packet.origin.x += 7;\
			NXRectFill(&animInfo.packet);\
			if( animInfo.frameNum == LAST_FRAME )\
				animInfo.drawState = ProcessMsg;\
		break;\
		case RecvMsg:\
			NXSetColor(animInfo.color);\
			animInfo.packet.origin.x -= 7;\
			NXRectFill(&animInfo.packet);\
			if( animInfo.frameNum == LAST_FRAME )\
				animInfo.drawState = DrawingDone;\
		break;\
\
		case ProcessMsg:\
			theta = animInfo.frameNum * 360.0 / LAST_FRAME;\
			PSWcogwheel(5, 242, 58, theta);\
			PSWcogwheel(5, 257, 63, theta+30.0);\
			if( animInfo.frameNum == LAST_FRAME )\
				animInfo.drawState = RecvMsg;\
		break;\
\
		case DrawingDone:\
			return nil;\
	\}\
	animInfo.frameNum ++;\
\
	return self;\
\} 
\b0\i\fc1\cf1 // End nextFrame:
\b\i0\fc0\cf0 \
\
- preferences: sender\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc0\cf0 /*
{{\NeXTHelpMarker10134 \markername preferences:;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc0\cf0  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul\fc1\cf1 ReturnValue:
\i\ulnone\fc0\cf0  self;\
	
\i0\ul\fc1\cf1 Description:
\i\ulnone\fc0\cf0  Brings up our preferences InspectorPanel by\
		messaging the application Preferneces object;\
	
\i0\ul\fc1\cf1 Args:
\i\ulnone\fc0\cf0  \
		sender: the Matrix associated with the Info submenu;\
*/\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b\i0\fc0\cf0 	[preferences display];\
\
	return self;\
\} 
\b0\i // End preferences:
\b\i0 \
\
- mailScott: sender\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc0\cf0 /*
{{\NeXTHelpMarker10436 \markername preferences:;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc0\cf0  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul\fc1\cf1 ReturnValue:
\i\ulnone\fc0\cf0  self;\
	
\i0\ul\fc1\cf1 Description:
\i\ulnone\fc0\cf0  Brings up a Mail.app compose window with my current\
		email address and a template body. This code is based on an\
		EPS(eps@cs.sfsu.edu) posting.;\
	
\i0\ul\fc1\cf1 Args:
\i\ulnone\fc0\cf0  \
		sender: the Matrix associated with the Info submenu;\
*/\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b\i0\fc0\cf0 port_t port;\
Speaker *mailSpeaker;\
int win;\
const char *myEmailAddr, *myWorkEmailAddr, *emailTemplate;\
\
	if( [[Application workspace]
\fc1\cf1  launchApplication: "Mail"] == NO )\
	\{	// Workspace could not launch mail for some damn reason\

\fc0\cf0 		[self alertOk: "Failed to launch Mail aplication" quit: NO];\
		return self;\

\fc1\cf1 	\}
\fc0\cf0 \
	
\b0\i\fc1\cf1 // Get the port for Mail.app speaker object on local host
\b\i0\fc0\cf0 \
	port =  NXPortNameLookup("MailSendDemo", 0);\
	
\fc1\cf1 if ( port == PORT_NULL )
\fc0\cf0 \
	\{	
\b0\i\fc1\cf1 // Didn't get send rights to the port
\b\i0\fc0\cf0 \
		[self alertOk: "Failed to obtain 'MailSendDemo' port" quit: NO];\
		return self;\
	\}\
	mailSpeaker = [[Speaker alloc] init];\
	[mailSpeaker setSendPort: port];\
\
	
\b0\i\fc1\cf1 // Open a new Send window, obtain magic cookie for subsequent calls
\b\i0\fc0\cf0 \
	[mailSpeaker selectorRPC: "openSend:" paramTypes: "I", &win];\
	
\b0\i\fc1\cf1 // Fill in the blanks
\b\i0\fc0\cf0 \
	myEmailAddr = [msgStringTable valueForStringKey: EMAIL_ADDR];\
	myWorkEmailAddr = [msgStringTable valueForStringKey: WORK_EMAIL_ADDR];\
	
\b0\i\fc1\cf1 // Email template contains a  %s format for the Archie version
\b\i0\fc0\cf0 \
	emailTemplate = [self buildMsg: \
		[msgStringTable valueForStringKey: EMAIL_TEMPLATE],\
		[msgStringTable valueForStringKey: ARCHIE_VERSION]];\
	[mailSpeaker selectorRPC: "setTo:inWindow:" paramTypes: "ci",\
		myEmailAddr, win];\
	[mailSpeaker selectorRPC: "setSubject:inWindow:" paramTypes: "ci",\
		"Archie Bug/Comment", win];\
	if( myWorkEmailAddr )\
		[mailSpeaker selectorRPC: "setCc:inWindow:" paramTypes: "ci",\
			myWorkEmailAddr, win];\
	[mailSpeaker selectorRPC: "setBody:inWindow:" paramTypes: "ci",\
		emailTemplate, win];\
	free(emailTemplate);\
\
	[mailSpeaker free];\
	port_deallocate(task_self(),  port);\
	
\b0\i\fc1\cf1 // Hide myself so that Mail becomes the active app
\b\i0\fc0\cf0 \
	[self hide: self];\
\
	return self;\
\} 
\b0\i\fc1\cf1 // End mailScott:
\b\i0\fc0\cf0 \
\
- whatIsArchie: sender\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc0\cf0 /*
{{\NeXTHelpMarker12444 \markername generalFTPLogin:;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc0\cf0  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul\fc1\cf1 ReturnValue:
\i\ulnone\fc0\cf0  self;\
	
\i0\ul\fc1\cf1 Description:
\i\ulnone\fc0\cf0  Brings up the help panel and ;\
	
\i0\ul\fc1\cf1 Args:
\i\ulnone\fc0\cf0  \
		sender: the Matrix associated with the Info submenu;\
*/\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b\i0\fc0\cf0 NXHelpPanel *helpPanel;\
char archieDoc[MAXPATHLEN+1];\
\
	
\b0\i // Get the app help panel
\b\i0 \
	helpPanel = [NXHelpPanel new];\
	sprintf(archieDoc, "%s/
\fc1\cf1 ArchieDoc.rtf", [helpPanel helpDirectory]);
\fc0\cf0 \

\fc1\cf1 	[helpPanel showFile:  archieDoc atMarker: 0];\
	[helpPanel makeKeyAndOrderFront: self];\
\
	return se
\fc0\cf0 lf;\
\} 
\b0\i\fc1\cf1 // End whatIsArchie:
\b\i0\fc0\cf0 \
\
- generalFTPLogin: sender\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc0\cf0 /*
{{\NeXTHelpMarker12938 \markername generalFTPLogin:;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc0\cf0  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul\fc1\cf1 ReturnValue:
\i\ulnone\fc0\cf0  self;\
	
\i0\ul\fc1\cf1 Description:
\i\ulnone\fc0\cf0  Brings up the ªGeneral FTP Loginº panel and initiates\
		an interactive ftp session using the login information;\
	
\i0\ul\fc1\cf1 Args:
\i\ulnone\fc0\cf0  \
		sender: the Matrix associated with the FTP submenu;\
*/\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b\i0\fc0\cf0 FTPObject *ftpSession;\
\
	ftpSession = [FTPObject alloc];\
	[ftpSession initForGeneralLogin];\
\
	return self;\
\} 
\b0\i // End generalFTPLogin:
\b\i0 \
\

\f1 - showFTPLog: sender\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\b0\i\fc1\cf1 /*
{{\NeXTHelpMarker13332 \markername showFTPLog:;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc1\cf1  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul ReturnValue:
\i\ulnone  self;\
	
\i0\ul Description:
\i\ulnone  Displays a panel with a scrollable Text object to which\
		the output from the remote ftpd is dumped. This is the backup action\
		that is invoked only when there is no interactive FTP session. When\
		an interactive FTP session is running the session window delegate\
		FTPObject will handle this action.;\
	
\i0\ul Args:
\i\ulnone \
		sender: The Matrix from the FTP submenu;\
*/\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\b\i0\fc1\cf1 id ftpLogPanel;\
\
	ftpLogPanel = 
\fc0\cf0 NXGetNamedObject
\fc1\cf1 ("FTPLog", NXApp);\

\fc0\cf0 	[
\fc1\cf1 ftpLogPanel
\fc0\cf0  orderFront: self];\
\
	return self;\
\} 
\b0\i // End showFTPLog:
\b\i0 \

\f0 \
- inspector: sender\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc1\cf1 /*
{{\NeXTHelpMarker13907 \markername showFTPLog:;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc1\cf1  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul ReturnValue:
\i\ulnone  self;\
	
\i0\ul Description:
\i\ulnone  Displays the File object inspector. If the fileInspector\
		has not been created yet, we do so and load the Nibs that contain\
		the offscreen windows containing the views swapped in and out by\
		the InspectorManager instance.;\
	
\i0\ul Args:
\i\ulnone \
		sender: The main menu Matrix;\
*/
\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b\i0\fc0\cf0 \
id inspectorDelegate, attributesBoxID, contentsBoxID;\
id attributesWindow, contentsWindow;\
int attributesGrp, contentsGrp;\
\
	if( fileInspector == nil )\
	\{	
\b0\i\fc1\cf1 // Allocate the inspector manager and assign the preferences groups
\b\i0\fc0\cf0 \
		fileInspector = [[InspectorManager alloc] initFromNib: "FileInspector.nib"];\
		if( [fileInspector panel] == nil )\
		\{\
			[self error: INTERNAL_SORRY method:_cmd key:\
				"Failed to load FileInspector.nib"];\
			return nil;\
		\}\
		[fileInspector hideRevertOK];\
		[[fileInspector 
\fc1\cf1 panel] setBecomeKeyOnlyIfNeeded: YES];\
		
\b0\i // Load the Nibs with the views used by the f
\f1 ileInspector
\f0\b\i0 \

\fc0\cf0 		[NXApp loadNibSection: "FileInfo.nib" owner: self withNames: YES];\
		[NXApp loadNibSection: "FileContents.nib" owner: self withNames: YES];\
		attributesWindow = NXGetNamedObject(ATTRIBUTES_WINDOW, NXApp);\
		contentsWindow = NXGetNamedObject(CONTENTS_WINDOW, NXApp);\
		attributesBoxID = NXGetNamedObject(FILE_INFO_BOXVIEW, attributesWindow);\
		contentsBoxID = NXGetNamedObject(DIR_SORT_BOXVIEW, contentsWindow);\
		
\b0\i\fc1\cf1 // Define the inspector groups
\b\i0\fc0\cf0 \
		attributesGrp = [fileInspector addGroup: "Attributes"];\
		contentsGrp = [fileInspector addGroup: "Contents"];\
\
		
\b0\i\fc1\cf1 //Add the Box views to the inspector
\b\i0\fc0\cf0 \
		[fileInspector addInspector: attributesBoxID title: "File Information"\
			atLocation: 10 : 40 cached: YES\
			cacheWindow: attributesWindow];\
		[fileInspector addInspector: contentsBoxID title: "File Information"\
			atLocation: 10 : 40 cached: YES\
			cacheWindow: contentsWindow];\
	\}\
\
	
\b0\i /* Find the inspector delegate object.  This will be a responder object\
		that implements the inspectorIsActive: method */
\b\i0 \
	inspectorDelegate = [self calcTargetForAction: @selector(inspectorIsActive:)];\

\f1\b0\fs20 [self debug: MAX_DEBUG method:_cmd, "FileInspectorDelegate = %p(%s)", inspectorDelegate,\
	(inspectorDelegate == nil ? "NONE" : [inspectorDelegate name])];
\f0\b\fs24 \
	[fileInspector setDelegate: inspectorDelegate];\
	if( inspectorDelegate == nil )\
		[fileInspector showMessage: "No Valid Selection"];\
	else\
		[inspectorDelegate inspectorIsActive: fileInspector];\
	[[fileInspector panel] orderFront: self];\
\
	return self;\
\} 
\b0\i // End inspector:
\b\i0 \
\

\i\fs28 /*\\ --------------------- App Value Access Methods --------------------- \\*/
\i0\fs24 \
- imageBundle\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc0\cf0 /*
{{\NeXTHelpMarker16446 \markername preferences;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc0\cf0  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul\fc1\cf1 ReturnValue:
\i\ulnone\fc0\cf0  The application images NXBundle object;\
*/\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b\i0\fc0\cf0 	return imageBundle;\
\} 
\b0\i //
\fc1\cf1  End imageBundle
\b\i0\fc0\cf0 \
\
- preferences\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc0\cf0 /*
{{\NeXTHelpMarker16588 \markername preferences;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc0\cf0  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul\fc1\cf1 ReturnValue:
\i\ulnone\fc0\cf0  The application Preferences object;\
*/\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b\i0\fc0\cf0 	return preferences;\
\} 
\b0\i // End preferences
\b\i0 \
\
- errTable\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc0\cf0 /*
{{\NeXTHelpMarker16723 \markername errTable;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc0\cf0  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul\fc1\cf1 ReturnValue:
\i\ulnone\fc0\cf0  The application NXStringTable object containing\
		the application error strings.  Constants for the error keys\
		are in the errMessages.keys file.;\
*/\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b\i0\fc0\cf0 	return errStringTable;\
\} 
\b0\i // End errTable
\b\i0 \
\
- msgTable\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc0\cf0 /*
{{\NeXTHelpMarker16970 \markername msgTable;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc0\cf0  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul\fc1\cf1 ReturnValue:
\i\ulnone\fc0\cf0  The application NXStringTable object containing\
		the application message strings.  Constants for the error\
		keys are in the appMessages.keys file.;\
*/\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b\i0\fc0\cf0 	return msgStringTable;\
\} 
\b0\i // End msgTable
\b\i0 \
\

\i\fs28 /*\\ ---------------------- App Delegate Methods ---------------------- \\*/
\i0\fs24 \
- appDidInit: sender\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc0\cf0 /*
{{\NeXTHelpMarker17304 \markername appDidInit:;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc0\cf0  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul\fc1\cf1 ReturnValue:
\i\ulnone\fc0\cf0  self;\
	
\i0\ul\fc1\cf1 Description:
\i\ulnone\fc0\cf0  This is where we perform the final\
		app initializations;\
	
\i0\ul\fc1\cf1 Args:
\i\ulnone\fc0\cf0 \
		sender: The Application object being inited, namely ourselves;\
*/\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b\i0\fc1\cf1 char tiffPath[MAXPATHLEN+1], *endPath = tiffPath, *extension;\

\fc0\cf0 NXBundle *mainBundle;\
NXImage *theImage;\

\pard\tx0\tx0\tx0\tx0\tx0\tx0\tx0\tx0\tx0\tx0\fc1\cf1 struct direct **imageList;\
int imageCount;\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc0\cf0 BOOL openEmptyDoc;\
NXAtom nullReturnTypes[] = \{NULL\};\

\f1\b0\fs20 \
long startTime;\
time(&startTime);\
[self debug: LOW_DEBUG method:_cmd, "\\n---------------  Begin Archie Application[%d] %s", getpid(), ctime(&startTime)];\
\

\f0\b\fs24 	
\b0\i /* Set the application error table */
\b\i0 \
	[self setErrorTable: errStringTable];\
\
	
\b0\i /* Unfortunately, when one places bundle files within the .app bundle,\
		they are not automatically searched by methods such as\
		the 
\b findNamedImage:
\b0  of NXImage.  Rather than changing\
		all of the [NXImage 
\b findNamedImage:
\b0 ] calls in the various\
		classes, I get the path to our TIFF.bundle and assign names\
		to every image in the bundle.  This should not hurt the launch time\
		since nearly every image would have been loaded from the\
		
\b findNamedImage:
\b0  messages anyway. */
\b\i0 \
	mainBundle = 
\fc1\cf1 [NXBundle mainBundle];
\fc0\cf0 \

\fc1\cf1 	[mainBundle getPath: tiffPath forResource: "TIFF" ofType: "bundle" ];\
	strcat(tiffPath, "/");\
	endPath += strlen(tiffPath);\

\fc0\cf0 	
\fc1\cf1 imageCount = scandir
\fc0\cf0 (tiffPath, &imageList, NULL, NULL);\
	while( imageCount > 0 )\
	\{	
\b0\i /* Load the images and set their names so that 
\b findNamedImage:
\b0 \
			can find what it is looking for */
\b\i0 \
		imageCount --;\
		bcopy(imageList[imageCount]->d_name, endPath,\
			imageList[imageCount]->d_namlen+1);\
		theImage = [[NXImage alloc] initFromFile: tiffPath];\
		extension = rindex(imageList[imageCount]->d_name, '.');\
		if( extension != NULL )\
			*extension = '\\0';\
		[theImage setName: imageList[imageCount]->d_name];\
		free(imageList[imageCount]);\
	\}\
\
	
\b0\i /* Allocate the app Preferences object */
\b\i0 \
	preferences = [[Preferences alloc] init];\
\
	
\b0\i /* Initialize the updateAction: methods of the menu items and enable\
		autoupdating to ensure that the menus are kept in sync with the app*/
\b\i0 \
	
\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc0\cf0 InitMenu
\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc0\cf0 ([sender mainMenu]);\
	[sup
\fc1\cf1 er setAutoupdate: YES];\

\fc0\cf0 \
	
\b0\i /* If we were launched by clicking on the app icon and not\
		from a remote message, open an empty document if we don't have a main\
		window and the user's preferences says to.\
		The value for 
\b\fc1\cf1 NXGetDefaultValue
\b0 () is
\fc0\cf0  nonNULL when we were\
		launched remotely.*/
\b\i0 \
	remoteLaunch = (NXGetDefaultValue([self appName], "NXServiceLaunch") != NULL);\
	openEmptyDoc = (mainWindow == nil && remoteLaunch == NO &&\
		[preferences untitledFirstWindow] == YES);\

\f1\b0\fs20 [self debug
\fc1\cf1 : MAX_DEBUG method: _cmd, "remote = %d, empty = %d, threadCount = %d", remoteLaunch,\
	openEmptyDoc, cthread_count()
\fc0\cf0 ];
\f0\b\fs24 \
	if( openEmptyDoc == YES )\
		[self new: self];\
\
	
\b0\i /* Declare the types of pasteboard data we can provide services for */
\b\i0 \
	gAppPasteboardTypes[0] = NXAsciiPboardType;\
	gAppPasteboardTypes[1] = NULL;\
	[sup
\fc1\cf1 er registerServicesMenuSendTypes:gAppPasteboardTypes\
		 andReturnTypes: nullReturnTypes];\
	[[super appListener] setServicesDelegate: self] ;\
\

\fc0\cf0 	return self;\
\} 
\b0\i // End appDidInit:
\b\i0 \
\
- appDidUnhide: sender\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc0\cf0 /*
{{\NeXTHelpMarker20375 \markername appDidUnhide:;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc0\cf0  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul\fc1\cf1 ReturnValue:
\i\ulnone\fc0\cf0  self;\
	
\i0\ul\fc1\cf1 Description:
\i\ulnone\fc0\cf0  Makes sure that the main window is on screen;\
	
\i0\ul\fc1\cf1 Args:
\i\ulnone\fc0\cf0 \
		sender: The Application object which is ourselves;\
*/\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\i0\fs20\fc0\cf0 [self debug: MAX_DEBUG method: _cmd, NULL];
\f0\b\fs24 \
	[mainWindow orderFront: self];\
\
	return self;\
\} 
\b0\i // End appDidUnhide:
\b\i0 \
\
- (BOOL) appAcceptsAnotherFile: sender\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc0\cf0 /*
{{\NeXTHelpMarker20700 \markername appDidInit:;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc0\cf0  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul\fc1\cf1 ReturnValue:
\i\ulnone\fc0\cf0  YES;\
	
\i0\ul\fc1\cf1 Description:
\i\ulnone\fc0\cf0  We can have any number of ArchieSession docs\
		open so will always return YES;\
	
\i0\ul\fc1\cf1 Args:
\i\ulnone\fc0\cf0 \
		sender: probably us, but it doesn't matter;\
*/
\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b\i0\fc0\cf0 \
	return YES;\
\} 
\b0\i // End appAcceptsAnotherFile:
\b\i0 \
\
- (int) app: sender openFile:(const char *) filename type:(const char *) aType\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc0\cf0 /*
{{\NeXTHelpMarker21021 \markername app:openFile:type:;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc0\cf0  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul\fc1\cf1 ReturnValue:
\i\ulnone\fc0\cf0  YES if we opened the file, NO otherwise;\
	
\i0\ul\fc1\cf1 Description:
\i\ulnone\fc0\cf0  This method is invoked from our super's\
		
\b openFile:ok:
\b0  method to do the actuall opening.  We create\
		an ArchieSession using its 
\b newFromFile:
\b0 , method passing\
		
\i0 filename
\i  as its arg. Note that we do not make any attempt\
		to explicitly keep track of the application docs. We can\
		only find out about them by asking the Windows in our window\
		List if their delegates are ArchieSession objects.  We are\
		a lazy manager.;\
	
\i0\ul\fc1\cf1 Args:
\i\ulnone\fc0\cf0 \
		sender: probably self?;\
		filename: The full pathname of the file to open;\
		aType: The type (extension) of the file;\
*/
\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b\i0\fc0\cf0 \
	if( [ArchieSession canOpenFileType: aType] == YES )\
	\{\

\f1\b0\fs20 [self debug: MAX_DEBUG method: _cmd, "Try to open = %s", filename];
\f0\b\fs24 \
		if( [ArchieSession newFromFile: filename] != nil )\
		\{	
\b0\i /* File was opened sucessfully */
\b\i0 \

\f1\b0\fs20\fc1\cf1 [self debug: MAX_DEBUG method: _cmd, "Opened file = %s", filename];
\f0\b\fs24\fc0\cf0 \
			return YES;\
		\}\

\f1\b0\fs20\fc1\cf1 [self debug: MAX_DEBUG method: _cmd, "Failed to open file = %s", filename];
\f0\b\fs24\fc0\cf0 \
	\}\
\
	return NO;\
\} 
\b0\i // End app: openFile: type:
\b\i0 \
\
- appWillTerminate: sender\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc1\cf1 /*
{{\NeXTHelpMarker22120 \markername appWillTerminate:;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc1\cf1  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul ReturnValue:
\i\ulnone  nil if the app should terminate, self if\
		it should not;\
	
\i0\ul Description:
\i\ulnone  This method checks the application window\
		list for edited ArchieSessions. We give the user\
		the option of reviewing docs, quitting, or cancelling.\
		If the user chooses to review the unsaved sessions,\
		each edited session is sent an 
\b appWillTerminate:
\b0  message,\
		which will cause it to ask the user if changes should be\
		saved. If the user cancels any window save, the application will\
		resume. This is also where any FTP hosts the user has added are\
		saved.;\
	
\i0\ul Args:
\i\ulnone \
		sender: self;\
*/
\f0\b\i0\fc0\cf0 \

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc0\cf0 int index = 0;\
List *editedSessions;\
\
	editedSessions = [self editedSessionList: windowList];\

\f1\b0\fs20 [self debug: MAX_DEBUG method:_cmd, "edited count = %d",[editedSessions count]];
\f0\b\fs24 \
\
	/* Report if there are any unsaved docs */\
	if( [editedSessions count] > 0 )\
	\{\
	int btn;\
	ArchieSession *session;\
		btn = [self alert: "Quit" msg: "There are edited windows."\
				btn1: "Review Unsaved" btn2: "Quit Anyway" btn3: "Cancel"];\
		switch( btn )\
		\{\
			case NX_ALERTDEFAULT:\
				
\b0\i /* Review Unsaved */
\b\i0 \
				index = 0;\
				while( (session = [editedSessions objectAt: index]) != nil )\
				\{	
\b0\i /* Tell each ArchieSession we are quitting */
\b\i0 \
					if( [session appWillTerminate: self] == nil )\
					\{	
\b0\i /* User cancelled document update, cancel quit */
\b\i0 \

\f1\b0\fs20\fc1\cf1 [self debug: MAX_DEBUG method:_cmd, "Cancel save, cancel quit"];
\f0\b\fs24\fc0\cf0 \
						[editedSessions free];\
						return nil;\
					\}\
					index ++;\
				\}\
				break;\
			case NX_ALERTALTERNATE:\
				
\b0\i /* Quit Anyway */
\b\i0 \
				break;\
			case NX_ALERTOTHER:\
				
\b0\i /* Cancel */
\b\i0 \

\f1\b0\fs20\fc1\cf1 [self debug: MAX_DEBUG method:_cmd, "Cancel quit"];
\f0\b\fs24\fc0\cf0 \
				[editedSessions free];\
				return nil;\
				break;\
		\}\
	\}\
	
\b0\i\fc1\cf1 // Save any changes to the FTP host list
\b\i0 \
	[NetrcEntry saveNetrcEntries];\

\f1\b0\fs20 [self debug: MAX_DEBUG method:_cmd, "Quitting\\n"];
\f0\b\fs24\fc0\cf0 \
\
	return self;\
\} 
\b0\i // End appWillTerminate:
\b\i0 \
\
/*\\ ---------------------- Menu Update Methods ---------------------- \\*/\
- (BOOL) menuItemUpdate:(MenuCell *) menuCell\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc0\cf0 /*
{{\NeXTHelpMarker24105 \markername menuItemUpdate:;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc0\cf0  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul\fc1\cf1 ReturnValue:
\i\ulnone\fc0\cf0  YES if the menuCell should be redrawn, NO otherwise;\
	
\i0\ul\fc1\cf1 Description:
\i\ulnone\fc0\cf0  This method is also taken from the Draw app.  Here is\
		the original method description:\
		
\i0 * Method called by all menu items which send their actions to the\
		* First Responder.  First, if the object which would respond were the\
		* action sent down the responder chain also responds to the message\
		* validateCommand:, then it is sent validateCommand: to determine\
		* whether that command is valid now, otherwise, if there is a responder\
		* to the message, then it is assumed that the item is valid.\
		* The method returns YES if the cell has changed its appearance \
		* (so that the caller (a Menu) knows to redraw it).
\i \
		Now for some expounding. When a MenuCell has a nil target, the\
		target is determined by the NXApp object as described in the Control\
		class description.  The Application method which accomplishes this\
		is 
\b calcTargetForAction:
\b0 .  We use this method below to find the target,\
		and ask it if it responds to validateCommand: to determine if the\
		cell should be enabled.  If a target is found but does not respond\
		to 
\b validateCommand:
\b0 , we assume the cell should be enabled.  If no\
		target can be found the cell is disabled.;\
	
\i0\ul Args:
\i\ulnone  \
		menuCell: the MenuCell whose status is being checked;\
*/
\f0\b\i0 \

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc0\cf0 SEL action;\
id responder, target;\
BOOL enable;\
\
	target = [menuCell target];\
	enable = [menuCell isEnabled];\
\
	responder = nil;\
	if ( ! target )\
	\{	
\b0\i /* Try to find the cell's target.  If found we ask it to\
			determine the cell state if it can, else we assume the\
			menu should be enabled. */
\b\i0 \
		action = [menuCell action];\
		responder = [self calcTargetForAction: action];\
		if ( [responder respondsTo: @selector(validateCommand:)] )\
			enable = [responder validateCommand: menuCell];\
		else\
	    		enable = responder ? YES : NO;\
	\}\

\f1\b0\fs20 [self debug: SUPER_DEBUG method:_cmd, "item = %s;[%d] r = %p", [menuCell title], enable, responder];
\f0\b\fs24 \
\
	if ( [menuCell isEnabled] != enable )\
	\{	
\b0\i /* The state of the MenuCell should be different from\
			its current state.  Update its enabled status and return\
			YES to have the cell redrawn. */
\b\i0 \
		[menuCell setEnabled: enable];\
		return YES;\
	\}\
\
	return NO;\
\} 
\b0\i // End menuItemUpdate:
\b\i0 \
\
- (BOOL) validateCommand:(MenuCell *) menuCell\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc0\cf0 /*
{{\NeXTHelpMarker26397 \markername validateCommand:;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc0\cf0  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul\fc1\cf1 ReturnValue:
\i\ulnone\fc0\cf0  YES if the menuCell should be redrawn, NO otherwise;\
	
\i0\ul\fc1\cf1 Description:
\i\ulnone\fc0\cf0  This method determines the enabled state of\
		its argument MenuCell based on the current value of the\
		application items which the item can affect;\
	
\i0\ul\fc1\cf1 Args:
\i\ulnone\fc0\cf0  \
		menuCell: the MenuCell whose status is being checked;\
*/
\f0\b\i0 \

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc0\cf0 SEL action = [menuCell action];\
BOOL 
\fc1\cf1 okByMe = YES
\fc0\cf0 ;\
\
	
\b0\i /* Determine which menu item the cell represents by its\
		action method and set its state.  Menu item actions that\
		are sent to us but are not caught in the if statements\
		are always enabled because of the default YES return. */
\b\i0 \
	if ( action == @selector(saveAll:) )\
	\{	
\b0\i\fc1\cf1 // If there aren't any windows we don't want this item
\b\i0\fc0\cf0 \
		if( [(List *)windowList count] == 0 )\
			
\fc1\cf1 okByMe =
\fc0\cf0  NO;\
	\}\
	else if( action == @selector(showFTPLog:) )\
	\{	
\b0\i\fc1\cf1 /* This is only valid if an FTP event occurred previously. If we can\
			get the named panel then we allow this menu item */
\b\i0 \
	id ftpLogPanel;\
		ftpLogPanel = NXGetNamedObject("FTPLog", NXApp);\
		if( ftpLogPanel == nil )\
			okByMe = NO;\
	\}\

\fc0\cf0 \
	return 
\fc1\cf1 okByMe
\fc0\cf0 ;\
\} 
\b0\i // End validateCommand:
\b\i0 \
\
/*\\ ---------------------------------- Service Methods ---------------------------------- \\*/\
- serviceFtpToHost: pasteboard userData:(const char *) serviceInfo\
	error:(char **) errorMsg\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc0\cf0 /*
{{\NeXTHelpMarker27678 \markername serviceFtpToHost:userData:error:;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc0\cf0  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul\fc1\cf1 ReturnValue:
\i\ulnone\fc0\cf0  self if successful, nil otherwise;\
	
\i0\ul\fc1\cf1 Description:
\i\ulnone\fc0\cf0  This method initiates a general or anonymous ftp\
		session with the host whose hostname is on the pasteboard.\
		If we fail to connect we return a message indicating this in\
		the errorMsg buffer;\
	
\i0\ul\fc1\cf1 Args:
\i\ulnone\fc0\cf0  \
		pasteboard: the Pasteboard object containing the hostname;\
		serviceInfo: either the string ªGeneralº or ªAnonymousº,\
			indicating which service to provide;\
		errorMsg: the pointer to update should an error occur;\
*/
\f0\b\i0 \
FTPO
\fc1\cf1 bject *ftpServer;\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc1\cf1 const NXAtom *types;\
char *data, *hostname = 0, *path, *tmp, *end;\
int hostnameLength;\
\
	
\b0\i /* Check the pasteboard types.  This is probably uneccessary. */
\b\i0 \
	types = [pasteboard types];\
	while( *types != NULL && *types != NXAsciiPboardType )\
		types ++;\
	if( *types == NULL )\
	\{	
\b0\i /* Just not my type */
\b\i0 \
		*
\fc0\cf0 errorMsg
\fc1\cf1  = "Archie cannot handle the type of data sent";\

\f1\b0\fs20\fc0\cf0 [self debug: LOW_DEBUG method:_cmd, "errMsg = %s", errorMsg];
\f0\b\fs24\fc1\cf1 		return nil;\
	\}\
\
	
\b0\i\fc0\cf0 // Retrie
\fc1\cf1 ve the hostname from the pasteboard 
\b\i0 \
	if( [pasteboard readType: NXAsciiPboardType data: &data\
		length: &hostnameLength] == nil )\
	\{	
\b0\i // Pasteboard contents must have changed 
\b\i0 \
		*
\fc0\cf0 errorMsg
\fc1\cf1  = "Pasteboard contents appear to have changed to an invalid type";\

\f1\b0\fs20\fc0\cf0 [self debug: LOW_DEBUG method:_cmd, "errMsg = %s", errorMsg];
\f0\b\fs24\fc1\cf1 		return nil;\
	\}\

\f1\b0\fs20\fc0\cf0 [self debug: MAX_DEBUG method:_cmd, "service = %s; data = %p[%.*s]", serviceInfo, data, hostnameLength, data];\

\f0\b\fs24\fc1\cf1 \
	
\b0\i /* Parse the hostname and start the ftp service. Formats we can parse are:\
		hostname\
		hostname:path
\b\i0 \

\b0\i 		ftp://ftpHostname/path */\

\b\i0 	data[hostnameLength] = 0;\
	tmp = data;\
	path = 0;\
	
\b0\i // Remove leading and trailing white space
\b\i0 \
	while( isspace(*tmp) )\
		tmp ++;\
	end = strrchr(data, 0);\
	end --;\
	while( isspace(*end) )\
		end --;\
	end[1] = 0;\
	
\b0\i // Parse the string
\b\i0 \
	if( strncmp(tmp, "ftp://", 6) == 0 )\
	\{	
\b0\i // www type path
\b\i0 \
		hostname = tmp + 6;\
		path = strchr(hostname, '/');\
		if( path != 0 )\
		\{	
\b0\i // Null terminate the hostname and advance path to the start of the file path
\b\i0 \
			*path = 0;\
			path ++;\
		\}\
	\}\
	else\
	\{\
		if( (path = strchr(tmp, ':')) != 0 )\
		\{	
\b0\i // hostname:path format
\b\i0 \
			*path = 0;\
			path ++;\
		\}\
		hostname = tmp;\
	\}\
\
	if( strcmp(se
\fc0\cf0 rviceInfo, "General") == 0 )\
		ftpServer = [[FTPObject alloc] initForGeneralLogin: hostname 
\fc1\cf1 initialPath
\fc0\cf0 : path];\
	else\
		ftpSer
\fc1\cf1 ver = [[FTPObject alloc] initForHost: hostname interactive: YES initialPath: path];\

\fc0\cf0 	if( ftpServer == nil )\
	\{	
\b0\i /* Failed to connect to ftp server.  Quit if this was a remote launch. */
\b\i0 \
		*errorMsg = "Archie failed to initiate the service request";\

\f1\b0\fs20 [self debug: LOW_DEBUG method:_cmd, "errMsg = %s", errorMsg];
\f0\b\fs24 \
		return nil;\
	\}\

\fc1\cf1 	[pasteboard deallocatePasteboardData: data length: hostnameLength];\

\fc0\cf0 \
	return self;\
\} 
\b0\i // End serviceFtpToHost: userData: error:
\b\i0 \
\
- serviceArchieQuery: pasteboard userData:(const char *) serviceInfo\
	error:(char **) errorMsg\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc0\cf0 /*
{{\NeXTHelpMarker30533 \markername serviceFtpToHost:userData:error:;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc0\cf0  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul\fc1\cf1 ReturnValue:
\i\ulnone\fc0\cf0  self;\
	
\i0\ul\fc1\cf1 Description:
\i\ulnone\fc0\cf0  This method creates a new ArchieSession doc initiates\
		a query using the string located on the pasteboard;\
	
\i0\ul\fc1\cf1 Args:
\i\ulnone\fc0\cf0  \
		pasteboard: the Pasteboard object containing the hostname;\
		serviceInfo: additional info not currently used;\
		errorMsg: the pointer to update should an error occur;\
*/
\f0\b\i0 \

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc1\cf1 ArchieSession *newDoc;\
\

\f1\b0\fs20\fc0\cf0 [self debug: MAX_DEBUG method:_cmd, "service = %s", serviceInfo];\

\f0\b\fs24\fc1\cf1 	newDoc = [self new: self];\
	[newDoc 
\fc0\cf0 serviceArchieQuery: pasteboard userData: serviceInfo error: errorMsg];\
\
	return self;\
\} 
\b0\i\fc1\cf1 // End serviceArchieQuery: userData: error:
\b\i0\fc0\cf0 \
\

\fc1\cf1 - validRequestorForSendType:(NXAtom) typeSent andReturnType:(NXAtom) typeReturned\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc0\cf0 /*
{{\NeXTHelpMarker31225 \markername validRequestorForSendType:andReturnType:;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc0\cf0  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul\fc1\cf1 ReturnValue:
\i\ulnone\fc0\cf0  self;\
	
\i0\ul\fc1\cf1 Description:
\i\ulnone\fc0\cf0  This method checks to see if we provide a service\
		that accepts typeSent data and returns typeReturned data in\
		our current state;\
	
\i0\ul\fc1\cf1 Args:
\i\ulnone\fc0\cf0  \
		typeSent: the Pasteboard type that will be sent to us;\
		typeReturned: the Pasteboard type that we should return;\
*/
\f0\b\i0 \

\fc1\cf1 	if( typeSent == NXAsciiPboardType && typeReturned == NULL )\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc1\cf1 		return self;\
\
	return [super validRequestorForSendType: typeSent andReturnType: typeReturned];\
\} 
\b0\i // End validRequestorForSendType: andReturnType:
\b\i0 \
\
@end\

}

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