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.