This is Converse.m in view mode; [Download] [Up]
//** Craig Laurent #import "Converse.h" #import "TextAppend.h" //** for appending to Text Object #import "MessageProtocol.h" //** communication protocols #import <remote/NXProxy.h> //* setProtocolForProxy #import <stdlib.h> //** for malloc() #import <string.h> //** for string functions //** SERVERNAME used in Communication and Converse #define SERVERNAME "ConverseServer" //**************************************** /* Fix for Malloc leak with unarchiving. from NeXT. */ //**************************************** @implementation Converse //********** - init { if (self = [super init]) { //** initialize unfilled objects prefs = nil; infoCtlr = nil; commCtlr = nil; userInfo = nil; addressList = nil; filename = nil; msgSound = nil; animator = nil; [self setPrefs:[[Preferences alloc] init]]; //** create preferences data object //** load filename from prefs before loading address list [self setFilename:[prefs addressFile]]; //** Start communication receiving [self createCommZone]; [self setServerConnection:[NXConnection registerRoot:[self commCtlr] withName:SERVERNAME]]; if ([self serverConnection]) { [[self serverConnection] runFromAppKit]; } [self setUserInfo:[[User alloc] init]]; [[self userInfo] loadUser]; [self setMsgSoundWithName:[[prefs soundFile] cString]]; return self; } return nil; } - free { if ([self msgSound]) [self setMsgSound:[[self msgSound] free]]; if ([self addressList]) [self setAddressList:nil]; //** set will autorelease if ([self infoCtlr]) { //** free unique memory zone also NSZone *memZone = [[self infoCtlr] zone]; [self setInfoCtlr:nil]; NSRecycleZone(memZone); } if ([self commCtlr]) { //** free unique memory zone also NSZone *memZone = [[self commCtlr] zone]; [self setCommCtlr:nil]; NSRecycleZone(memZone); } if ([self prefs]) [self setPrefs:nil]; if ([self serverConnection]) [self setServerConnection:[[self serverConnection] free]]; if ([self userInfo]) [self setUserInfo:nil]; //** will autorelease if ([self animator]) [self setAnimator:nil]; return [super free]; } //**************************************** //** instance methods - (Preferences*)prefs { return prefs; } - (void)setPrefs:(Preferences*)newPrefs { [prefs autorelease]; prefs = [newPrefs retain]; } - (Communication*)commCtlr { return commCtlr; } - (void)setCommCtlr:(Communication*)newComm { [commCtlr autorelease]; commCtlr = [newComm retain]; } - (NXConnection*)serverConnection { return serverConnection; } - (void)setServerConnection:(NXConnection*)newServer { serverConnection = newServer; } - (InfoController*)infoCtlr { return infoCtlr; } - (void)setInfoCtlr:(InfoController*)newInfo { [infoCtlr autorelease]; infoCtlr = [newInfo retain]; } - (User*)userInfo { return userInfo; } - (void)setUserInfo:(User*)newUser { [userInfo autorelease]; userInfo = [newUser retain]; } - (NSMutableArray*)addressList { return addressList; } - (void)setAddressList:(NSMutableArray*)anArray { [addressList autorelease]; addressList = [anArray retain]; } - (NSString*)filename { return filename; } - (void)setFilename:(NSString*)string { [filename autorelease]; filename = [string retain]; } - (Sound*)msgSound { return msgSound; } - (void)setMsgSound:(Sound*)newSound { [msgSound free]; msgSound = newSound; } - (void)setMsgSoundWithName:(const char*)newName { Sound *newSound; if (newName) { newSound = [[Sound alloc] initFromSoundfile:newName]; if (!newSound) newSound = [Sound findSoundFor:newName]; [self setMsgSound:newSound]; } else [self setMsgSound:nil]; } - (IconAnimator*)animator { return animator; } - (void)setAnimator:(IconAnimator*)newIA { [animator autorelease]; animator = [newIA retain]; } //**************************************** //** Communication & logging methods /* createCommZone - creates a new memory zone and instantiates a Communication Object in the new zone. */ - (void)createCommZone { NSZone *commZone; //** perform communication in separate memory zone if (![self commCtlr]) { commZone = NSCreateZone(vm_page_size, vm_page_size, YES); [self setCommCtlr:[[Communication allocWithZone:commZone] initWithCtlr:self]]; } } /* sendMessage: - executed to send the entered message to the selected users. */ - sendMessage:sender { NSString *newMessage; //** get message text, log to self, send to others newMessage = [self messageFromText]; //* message not retained //* log message on sending machine [self logMessage:newMessage fromAuthor:[self userInfo] alert:NO]; //* send message to selected machines [self transferMessage:newMessage]; return self; } /* messageFromText - returns the message that is entered in the input portion of the log window. */ - (NSString*)messageFromText { int numChar; char *message; NSString *string; //** get message from Text in ScrollView numChar = [inputView textLength]; message = malloc(numChar + 1); [inputView getSubstring:message start:0 length:numChar]; message[numChar] = '\0'; //** ensure end of string! string = [NSString stringWithCString:message]; free(message); return string; } /* logMessage:fromAuthor:alert: - display the provided message and user information in the log window. Run alerts if indicated. */ - (void)logMessage:(NSString*)message fromAuthor:author alert:(BOOL)yn { unsigned int row; NSString *nickname; //** get nickname to identify author nickname = [author userNicknameWithID:[[self prefs] discloseID] andName:[[self prefs] discloseName] andMachine:[[self prefs] discloseMachine]]; //** put new message at the end of the Log //** add Author (in bold) [logView appendString:nickname withFontStyle:NX_BOLD]; //** add Author/message separator (not bold) [logView appendString:@"\t> " withFontStyle:NX_UNBOLD]; //** add message (regular format) [logView appendString:message]; //** add newline at the end [logView appendString:@"\n"]; //** run Alerts if indicated if (yn) { //** play Sound as alert if ([[self prefs] alertAudio]) { [[self msgSound] play]; } //** flash Icon as alert (if application is not active) if ((![NXApp isActive]) && ([[self prefs] alertIcon])) { if (![self animator]) { //** prepare data and instantiate IconAnimator NSMutableArray *images; NSMutableArray *pattern; //** create array of images images = [NSMutableArray array]; [images addObject:[NXImage findImageNamed:"IconConverse"]]; [images addObject:[NXImage findImageNamed:"IconConverseNewColor"]]; [images addObject:[NXImage findImageNamed:"IconConverseNew"]]; //** create array of pattern order pattern = [NSMutableArray array]; [pattern addObject:[NSNumber numberWithInt:0]]; [pattern addObject:[NSNumber numberWithInt:1]]; [pattern addObject:[NSNumber numberWithInt:2]]; [pattern addObject:[NSNumber numberWithInt:1]]; [pattern addObject:[NSNumber numberWithInt:2]]; [pattern addObject:[NSNumber numberWithInt:1]]; [pattern addObject:[NSNumber numberWithInt:2]]; [pattern addObject:[NSNumber numberWithInt:1]]; [pattern addObject:[NSNumber numberWithInt:2]]; [pattern addObject:[NSNumber numberWithInt:1]]; [pattern addObject:[NSNumber numberWithInt:2]]; [pattern addObject:[NSNumber numberWithInt:1]]; [pattern addObject:[NSNumber numberWithInt:0]]; [self setAnimator:[[IconAnimator alloc] initWithImageArray:images animationPattern:pattern andPatternLoops:1]]; } [[self animator] startAnimation:nil]; } //** show Window as alert if ([[self prefs] alertWindow]) { [[inputView window] makeKeyAndOrderFront:self]; } //** make this application the Active application [NXApp activateSelf:[[self prefs] alertApplication]]; [inputView selectAll:self]; //** prep message for typeover //** if author unique (not already in list) if ([self uniqueAddress:author inRow:&row]) { //** set machine name to reflect incoming machine [machineField setStringValue:[[author userMachine] cString]]; [machineField selectText:self]; [addButton setEnabled:YES]; } else { //** select row in the address list [[machineBrowser matrixInColumn:0] selectCellAt:row :0]; [sendInButton setEnabled:YES]; } } } /* senderIsInvalid: - sent by DO proxy when it is removed. */ - senderIsInvalid:sender { //** proxy connection lost NXRunAlertPanel("Communication Alert", "Connection to another machine was lost.", NULL, NULL, NULL); //** currently don't keep connections so this isn't an issue. //** FUTURE enhancements will be keeping connections. //** Then we will want to remove the connection from connection //** list. (maybe don't even notify about connection //** being removed?) return self; } //**************************************** /* add: - add machine to address list */ - add:sender { User *newUser; //** verify that machine name exists if ((![machineField stringValue]) || (0 == strcmp("", [machineField stringValue]))) { [addButton setEnabled:NO]; return nil; } //** create new User, fill with new machine information newUser = [[User alloc] init]; [newUser setUserMachine:[NSString stringWithCString:[machineField stringValue]]]; //** check if machine already in list before adding!! if ([uniqueCheck state]) { if ([self uniqueAddress:newUser inRow:NULL]) { //** object is unique, add to the list. [[self addressList] addObject:newUser]; } else { NXRunAlertPanel("Add Address", "This machine was added previously.", NULL, NULL, NULL); [machineField selectText:self]; return nil; } } else { [[self addressList] addObject:newUser]; } [self refreshAddress]; //** refresh screen information [addressWindow setDocEdited:YES]; //** set window dirty [machineField selectText:self]; return self; } /* delayRemove: - send remove: message after a delay. */ - delayRemove:sender { [self perform: @selector(remove:) with:nil afterDelay:0 cancelPrevious:NO]; //** (delay to finish "click" processing return self; } - remove:sender { List *selectedList; Cell *currentSelectedCell; Matrix *columnMatrix; int counter = 0; int row, col; [removeButton setEnabled:NO]; selectedList = [[List alloc] init]; //* create List to load columnMatrix = [machineBrowser matrixInColumn:0]; //** load list of selected rows (cells in browser) [machineBrowser getSelectedCells:selectedList]; counter = [selectedList count] - 1; //* set to last index if (counter >= 0) [addressWindow setDocEdited:YES]; //** set window dirty //** loop through all valid selected rows in the browser //** work from end, otherwise addressList index would be wrong. //** indexing starts at ZERO while (counter >= 0) { //** make sure get an object before messaging it if (currentSelectedCell = [selectedList objectAt:counter]) { [columnMatrix getRow:&row andCol:&col ofCell:currentSelectedCell]; [[self addressList] removeObjectAtIndex:row]; } counter--; } [selectedList free]; [self refreshAddress]; //** refresh screen with address info return self; } /* machineClicked: - sent when user clicks on machine in the address list. */ - machineClicked:sender { if ([[machineBrowser matrixInColumn:0] selectedRow] >= 0) { [removeButton setEnabled:YES]; [sendInButton setEnabled:YES]; } return self; } /* refreshAddress - reload address, reset buttons appropriately. */ - (void)refreshAddress { [machineBrowser loadColumnZero]; //** reload screen // [machineBrowser perform: @selector(loadColumnZero) with:nil afterDelay:0 cancelPrevious:NO]; //** reload screen (delay to finish "click" processing [machineField selectText:self]; //** set Buttons to appropriate status if ([[machineBrowser matrixInColumn:0] selectedRow] >= 0) { [removeButton setEnabled:YES]; [sendInButton setEnabled:YES]; } else { [removeButton setEnabled:NO]; [sendInButton setEnabled:NO]; } } /* loadAddressList - load address list from archived file. */ - (void)loadAddressList { [self setAddressList:[NSMutableArray array]]; //** default load from a file if ([self filename]) { [self openFile:nil]; [self showAddress:nil]; //** show address window when open. } //** cause address list to be displayed [self refreshAddress]; //** refresh screen with address info } /* uniqueAddress: - determine if newAddr is unique to the address list (no other machine in the list with the same name). */ - (BOOL)uniqueAddress:(User*)newAddr inRow:(unsigned int*)row { NSEnumerator *enumerator; User *anAddr; NSString *newMachineName; newMachineName = [newAddr userMachine]; //** loop through all machines in the address list. //** if machine name matches, bail-out because not unique enumerator = [[self addressList] objectEnumerator]; while (anAddr = [enumerator nextObject]) { //** if same machine name, new address is NOT unique if (NSOrderedSame == [newMachineName compare:[anAddr userMachine]]) { //** if row not NULL, fill with index if (row) *row = [addressList indexOfObject:anAddr]; return NO; } } return YES; //** object is unique } //****** file stuff /* new: - create new address list */ - new:sender { //** set will autorelease old list and retain the new list. [self setAddressList:[NSMutableArray array]]; //** create new list [self setFilename:nil]; //** blank out filename [self refreshAddress]; //** refresh screen with address info [[sender findCellWithTag:15] setEnabled:NO]; //** disable REVERT menu item [addressWindow setDocEdited:NO]; //** set window clean [self showAddress:nil]; //** show address window when open. return self; } //********** /* open: - get filename and load new address list */ - open:sender { if (NO == [self newOpenFilename]) return nil; [self openFile:sender]; [self showAddress:nil]; //** show address window when open. return self; } //********** /* newOpenFilename - get name of file to retrieve addresses */ - (BOOL)newOpenFilename { OpenPanel *oPanel = [OpenPanel new]; const char *types[] = {"cvs", NULL}; const char *tempName; const char *tempDir = "~/Library/Converse/"; if ([self filename]) tempDir = [[[self filename] stringByDeletingLastPathComponent] cString]; //** get file to open if ([oPanel runModalForDirectory:tempDir file:NULL types:types]) { tempName = [oPanel filename]; if (NULL == tempName) return NO; else { [self setFilename:[NSString stringWithCString:tempName]]; return YES; } } return NO; } //********** /* openFile: - open file and load new address list */ - openFile:sender { //** if no filename, get one if (nil == [self filename]) { if (NO == [self newOpenFilename]) return nil; } //** open file and read data [self setAddressList:[NSUnarchiver unarchiveObjectWithFile:[self filename]]]; if (![self addressList]) { NXRunAlertPanel("File Alert", "Open failed! Unable to read file.", NULL, NULL, NULL); return nil; } [self refreshAddress]; //** refresh screen with address info [[sender findCellWithTag:15] setEnabled:YES]; //** enable REVERT menu item [addressWindow setDocEdited:NO]; //** set window clean return self; } //********** /* saveAs: - get filename and save new address list */ - saveAs:sender { if (NO == [self newSaveFilename]) return nil; else return [self save:nil]; } //********** /* newSaveFilename - get name of file to save addresses */ - (BOOL)newSaveFilename { SavePanel *sPanel = [SavePanel new]; const char *tempFile = NULL; const char *tempDir = "~/Library/Converse/"; const char *tempName = NULL; //** if filename, break apart to pull up in save panel if ([self filename]) { tempDir = [[[self filename] stringByDeletingLastPathComponent] cString]; tempName = [[[self filename] lastPathComponent] cString]; } if ([sPanel runModalForDirectory:tempDir file:tempName]) { tempFile = [sPanel filename]; if (NULL == tempFile) return NO; else { [self setFilename:[NSMutableString stringWithCString:tempFile]]; //** make sure ".cvs" at end of filename //** change types in InfoController as well!! if (NSOrderedSame != [[[self filename] pathExtension] compare:@"cvs"]) { [(NSMutableString*)[self filename] appendString:@".cvs"]; } return YES; } } return NO; } //********** /* save: - open file and save address list */ - save:sender { //** if no filename, get one if (NULL == [self filename]) { if (NO == [self newSaveFilename]) return nil; } //** open file and save data if (![NSArchiver archiveRootObject:[self addressList] toFile:[self filename]]) { NXRunAlertPanel("File Alert", "Save failed! Unable to write file.", NULL, NULL, NULL); return nil; } [addressWindow setDocEdited:NO]; //** set window clean return self; } //**************************************** //** menu methods /* createInfoZone - create new memory zone and instantiate InfoController in this zone. */ - (void)createInfoZone { NSZone *infoZone; //** create info & preference panels in their own memory zone if (![self infoCtlr]) { infoZone = NSCreateZone(vm_page_size, vm_page_size, YES); [self setInfoCtlr:[[InfoController allocWithZone:infoZone] initWithPrefMaster:self]]; } } /* showInfoPanel: - display info panel. */ - showInfoPanel:sender { if (![self infoCtlr]) [self createInfoZone]; return [[self infoCtlr] showInfoPanel:sender]; } /* showPreferences: - display preferences panel */ - showPreferences:sender { if (![self infoCtlr]) [self createInfoZone]; return [[self infoCtlr] showPreferences:sender]; } /* showLogInput: - display log/input window. */ - showLogInput:sender { [[inputView window] makeKeyAndOrderFront:self]; return self; } /* showAddress: - display address window */ - showAddress:sender { [[machineField window] makeKeyAndOrderFront:self]; return self; } //**************************************** //** delegated methods - awakeFromNib { [self loadAddressList]; //** refresh screen with address info //** prep the machine browser [[inputView window] makeKeyAndOrderFront:self]; [inputView selectAll:self]; [machineBrowser setDoubleAction:(@selector(delayRemove:))]; //** set action for double-click return self; } - appDidBecomeActive:sender { [[self animator] stopAnimation]; return self; } - appWillTerminate:sender { int result; if ([addressWindow isDocEdited]) { //** if window dirty, verify exit result = NXRunAlertPanel("Exit Alert", "Address File not saved. Do you want to save?", "Save", "Don't Save", "Cancel"); if (NX_ALERTDEFAULT == result) { if (nil == [self save:nil]) return nil; //** didn't save, don't exit return self; } else if (NX_ALERTALTERNATE == result) return self; else if (NX_ALERTOTHER == result) return nil; } return self; } - textDidGetKeys:sender isEmpty:(BOOL)flag { //** if machine name is blank, don't allow add if (flag) { [addButton setEnabled:NO]; } else { [addButton setEnabled:YES]; } return nil; } //********** //** NXBrowser delegate methods - (int)browser:sender getNumRowsInColumn:(int)column { //** return number of objects in the list return [[self addressList] count]; } - browser:sender loadCell:cell atRow:(int)row inColumn:(int)column { NSString *nickname; nickname = [[[self addressList] objectAtIndex:row] userNicknameWithID:[[self prefs] discloseID] andName:[[self prefs] discloseName] andMachine:[[self prefs] discloseMachine]]; [cell setStringValue:[nickname cString]]; [cell setLoaded:YES]; //** tell cell it is loaded [cell setLeaf:YES]; //** tell cell it is a leaf (no sub info) return self; } //**************************************** /* Future enhancement: this process should be made cleaner/faster, create list of connections, (if not already connected) add to connection list before sending, disconnect before exiting program. */ /* transferMessage:fromAuthor: - transfer message to selected users. */ - (void)transferMessage:(NSString*)message { User *msgAuthor; List *selectedList; Cell *currentSelectedCell; Matrix *columnMatrix; int updates = 0; int counter = 0; int row, col; User *proxyUser; User *newProxyUser; NSString *proxyMachineName; id proxyObj; msgAuthor = [self userInfo]; selectedList = [[List alloc] init]; //* create List to load columnMatrix = [machineBrowser matrixInColumn:0]; //** load list of selected rows (cells in browser) [machineBrowser getSelectedCells:selectedList]; counter = [selectedList count] - 1; //* set to last index //** loop through all valid selected rows in the browser //** work from end, otherwise addressList index would be wrong. //** indexing starts at ZERO while (counter >= 0) { //** make sure get an object before messaging it if (currentSelectedCell = [selectedList objectAt:counter]) { [columnMatrix getRow:&row andCol:&col ofCell:currentSelectedCell]; proxyUser = [[self addressList] objectAtIndex:row]; proxyMachineName = [proxyUser userMachine]; //** setup proxy connection proxyObj = [NXConnection connectToName:SERVERNAME onHost:[proxyMachineName cString]]; if (proxyObj) { //** get connection from proxy and use that to register... [[proxyObj connectionForProxy] registerForInvalidationNotification:self]; //** notify proxy what protocol will be conformed [proxyObj setProtocolForProxy:@protocol(MessageProtocol)]; //** if this user info is incomplete, get full info if (([updateCheck state]) && (nil == [proxyUser userID])) { //** check same machine sending to and from if (NSOrderedSame == [[proxyUser userMachine] compare:[[self userInfo] userMachine]]) { [[self addressList] replaceObjectAtIndex:row withObject:[self userInfo]]; } else { //** the following code will fail if you are accessing the same //** machine that is sending this!! client == server newProxyUser = [proxyObj author]; [[self addressList] replaceObjectAtIndex:row withObject:newProxyUser]; } updates++; //** set counter for # of updates } //** transmit message [proxyObj postMessage:[message cString] fromAuthor:msgAuthor]; //** message sent, disconnect //** get connection from proxy and use that to unregister... [[proxyObj connectionForProxy] unregisterForInvalidationNotification:self]; //** free connection and proxy proxyObj = [proxyObj freeProxy]; } else { NXRunAlertPanel("Communication Alert", "Unable to connection to machine named: %s.", NULL, NULL, NULL, [proxyMachineName cString]); } } counter--; } [selectedList free]; //** if updates have been made, process needed changes if (updates > 0) { [self refreshAddress]; //** refresh screen with address info [addressWindow setDocEdited:YES]; //** set window dirty } } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.