ftp.nice.ch/pub/next/tools/calendars/KFC.1.0.s.tar.gz#/KFC/CalendarController.m

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

/*
    kfc - Kurt's Free Calendar
    Copyright 1995 Kurt Werle
  
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.
  
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
  
    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#import <objc/List.h>
#import <stdio.h>
#import <appkit/Matrix.h>
#import <misckit/MiscThreeStateButton.h>
#import <misckit/MiscAppDefaults.h>
#import "CalendarController.h"
#import "MonthView.h"
#import "anAppointment.h"
#import "editDayAppointments.h"
#import "appointmentPanelController.h"
#import "appointmentFile.h"

/* Because I don't like the way MiscTimes init (and because the MiscTime object is in transition) I wrote this function to smooth things out.  It may well go away in the future (I hope). */
void
fix_initWithCurrentTime_bug (id anID)
{
    int                 aYear = [anID year];

    if (aYear < 0)
    {
	return;
    }
    if (aYear < 70)
    {
	[anID setYear:aYear + 2000];
    }
    if (aYear < 100)
    {
	[anID setYear:aYear + 1900];
    }
}

@implementation CalendarController

- init
{
    [super init];

#ifdef DEBUG
    printf ("doing init\n");
#endif
    currentDay = [[MiscTime alloc] initWithCurrentTime];
    fix_initWithCurrentTime_bug (currentDay);
    appointmentFileList = [[List alloc] init];
    expiredList = [[List alloc] init];
    defaultAppointmentFile = nil;
    return self;
}

- appDidInit:sender
{
    id                  tempString;
    static NXDefaultsVector myDefaults = {
					  {"hiddenOnLaunch", "NO"},
					  {"beepForWarningPanels", "NO"},
					  {"defaultWarning", "0"},
					  {"sendMailEveryTime", "NO"},
					  {"justSaveFileNQA", "NO"},
					  {"lastUsed", ""},
					  {"defaultFile", ""},
					  {NULL}
    };

#ifdef DEBUG
    printf ("appDidInit\n");
#endif

    [NXApp registerDefaults:myDefaults];
    [calendarWindow setFrameUsingName:"calendar"];
    [myMonthView setActiveDay:currentDay];
    [calendarWindow makeFirstResponder:myMonthView];

    tempString = [[MiscString alloc] init];
    [tempString setString:[NXApp defaultValue:"startFiles"]];
    
    	/* If we didn't start with a file we load the newuser kfc file. */
    if ([appointmentFileList count] == 0)
       {
	List               *fileList;

	fileList = [[List alloc] init];
	[tempString tokenize:";" into:fileList];
	if (![fileList count])
	{			/* No files -- give 'em the beginner file. */
	    [tempString setString:
	     [(NXBundle *)[NXBundle mainBundle] directory]];
	    [tempString cat:"/Appointments/newuser.kfc"];
	    [self app:self openFile:[tempString stringValue] type:"kfc"];
	}
	else
	{			/* Open the files listed in the defaults. */
	    int                 i;

	    for (i = 0; i < [fileList count]; i++)
	    {
		[self app:self openFile:
		 [[fileList objectAt:i] stringValue] type:"kfc"];
	    }
	}

	[fileList freeObjects];
	[fileList free];
    }

    [calendarWindow makeKeyAndOrderFront:self];
    
    if ([NXApp defaultBoolValue:"hiddenOnLaunch"] && [NXApp defaultBoolValue:"NXAutoLaunch"])
    {
	[calendarWindow performMiniaturize:self];
	calendarWindowNeedsMaking = YES;
    }
    else
    {
	calendarWindowNeedsMaking = NO;
    }

    [tempString free];
    return self;
}

/* Loads the editor if we never loaded it before. */
- _initializeEditor
{
    if (!editController)
    {
	id                  tempstring,
	                    editBundle;

	tempstring = [[MiscString alloc] initString:
		      [(NXBundle *)[NXBundle mainBundle] directory]];
	[tempstring cat:"/editDayAppointments.bundle"];

	editBundle = [[NXBundle alloc] initForDirectory:[tempstring stringValue]];
	if (!editBundle)
	{
		NXRunAlertPanel("KFC.app", "Bogus.  Tell Kurt you can't load the editBundle (Send Mail->Bugs).", NULL, NULL, NULL);
	}

	editController = [[[editBundle principalClass] alloc]
			  initFromBundle:editBundle withDelegate:self];
	if (!editController)
	{
		NXRunAlertPanel("KFC.app", "Bogus.  Tell Kurt you can't load the editController (Send Mail->Bugs).", NULL, NULL, NULL);
	}

	[tempstring free];
	[editBundle free];
    }
    return self;
}

/* Loads my file panel if we never loaded it before. */
- _filePanel
{
    if (!filePanel)
	[NXApp loadNibSection:"myFilePanel.nib" owner:self withNames:NO];
    return filePanel;
}

/* Tell the user about copying this app. */
- Copying:sender
{
    if (!copyPanel)
	[NXApp loadNibSection:"copy.nib" owner:self withNames:NO];
    [copyPanel makeKeyAndOrderFront:sender];
    return self;
}

/* Tell the user about this app. */
- InfoBahn:sender
{
    if (!infoPanel)
	[NXApp loadNibSection:"Info.nib" owner:self withNames:NO];
    [infoPanel makeKeyAndOrderFront:sender];
    return self;
}

- free
{
if (currentDay)
	{
    [currentDay free];
	}
	if (appointmentFileList)
	{
		[appointmentFileList free];
	}
	if (expiredList)
	{
		[expiredList free];
	}
    return [super free];
}

/* Make the window if we need to. */
- appDidUnhide:sender
{
#ifdef DEBUG
    printf ("doing appDidUnhide\n");
#endif
    if (calendarWindowNeedsMaking)
    {
	[calendarWindow makeKeyAndOrderFront:self];
	calendarWindowNeedsMaking = NO;
    }
    return self;
}

/* Should we give the user the option to cancel this exit?  Not if the power is going off... */
- terminateWithCancel:(BOOL)withCancel
{
    MiscTime           *exitTime;
    MiscString         *exitTimeString;

    [self checkEdited];
    if ([calendarWindow isDocEdited])
    {
	int                 shouldExit;

	if (withCancel)
	{
	    shouldExit = NXRunAlertPanel ("Quit", "There are edited files.", "Save All", "Quit Anyway", "Cancel");
	}
	else
	{
	    shouldExit = NXRunAlertPanel ("Quit", "There are edited files.", "Save All", "Quit Anyway", NULL);
	}
	switch (shouldExit)
	{
	case NX_ALERTDEFAULT:
	    {
		[self saveAll:self];
		break;
	    }
	case NX_ALERTALTERNATE:
	    {
		break;
	    }
	case NX_ALERTOTHER:
	default:
	    {
		return nil;
	    }
	}
    }
    exitTime = [[MiscTime alloc] initWithCurrentTime];
    fix_initWithCurrentTime_bug (exitTime);
    [calendarWindow saveFrameUsingName:"calendar"];
    exitTimeString = [[MiscString alloc] init];
    [exitTimeString setFromFormat:"%02d:%02d:%02d:%02d:%02d",[exitTime year],[exitTime month],[exitTime day],[exitTime hour],[exitTime minute]];
    [NXApp setDefault:"lastUsed" to:[exitTimeString stringValue]];
    [exitTime free];
    return self;
}

/* Handle some app events. */
- app:sender powerOffIn:(int)ms andSave:(int)aFlag
{
    return[self terminateWithCancel:NO];
}

- appWillTerminate:sender
{
    return[self terminateWithCancel:YES];
}

- load_Warnings_For_File:theFile
{
    int                 i;
    List               *appointmentList;

#ifdef DEBUG
    printf ("load_Warnings_For_File: theFile\n");
#endif

 /* Go through the whole list of appointments. */

    appointmentList = [theFile appointmentList];
    if (appointmentList == nil)
    {
	return self;
    }
    for (i = 0; i < [appointmentList count]; i++)
    {
	[self queue_Warning:[appointmentList objectAt:i]];
    }

    return self;
}

/* Do something useful with an appointment warning depending on when it becomes active - never (the past or distant future), now, or later. */
- queue_Warning:theWarning
{
    MiscTime           *nowTime;

/* 
#ifdef DEBUG
    printf ("queue_Warning: %s\n",[[theWarning appointmentText] stringValue]);
#endif
*/

    nowTime = [[MiscTime alloc] initWithCurrentTime];
    fix_initWithCurrentTime_bug (nowTime);


 /* Is this warning applicable today? */
    switch ([theWarning warnForDay:nowTime])
    {
    case -1:
	{
	/* Does not apply to today. */
	    break;
	}
    case 0:
	{
	    [self execute_Warning:theWarning];
	    break;
	}
    default:
	{
	/* Must be run in the near future. */
	    int                 delaySeconds = [theWarning warnForDay:nowTime];

	    if (delaySeconds > 0)
	    {
		[self perform:@selector (execute_Warning:)
		 with :theWarning afterDelay:delaySeconds * 1000
		 cancelPrevious:NO];
	    }
	    break;
	}
    }

    [nowTime free];
    return self;
}

/* Do whatever is required by this timely warning. */
- execute_Warning:theWarning
{
    MiscTime           *lastCheck = nil;

 /* Are we supposed to do repeat mailings? */
    if (![NXApp defaultBoolValue:"sendMailEveryTime"])
    {
	MiscString         *tempString;
	List               *timeList;

	timeList = [[List alloc] init];
	tempString = [[MiscString alloc] initString:
		      [NXApp defaultValue:"lastUsed"]];
	[tempString tokenize:":" into:timeList];
	if ([timeList count] == 5)
	{
	    lastCheck = [[MiscTime alloc] initWithCurrentTime];
	    fix_initWithCurrentTime_bug (lastCheck);
	    [lastCheck setYear:[[timeList objectAt:0] intValue]];
	    [lastCheck setMonth:[[timeList objectAt:1] intValue]];
	    [lastCheck setDay:[[timeList objectAt:2] intValue]];
	    [lastCheck setHour:[[timeList objectAt:3] intValue]];
	    [lastCheck setMinute:[[timeList objectAt:4] intValue]];
	}
	[tempString free];
	[[timeList freeObjects] free];
    }

	if ([theWarning wantsPanel])
	{
	    if ([NXApp defaultBoolValue:"beepForWarningPanels"])
	    {
		NXBeep ();
	    }
	    [[appointmentPanelController alloc] initWithController:self
	     withAppointment:theWarning];
	}
	if ([theWarning wantsMailing])
	{
	    if ((lastCheck == nil) || ([theWarning warnForDay:lastCheck] != 0))
		[self mailAppointment:theWarning];
	}

    if ([theWarning trepeat])
    {
	[self perform:@selector (queue_Warning:)
	 with :theWarning afterDelay:[theWarning trepeat] * 60 * 1000
	 cancelPrevious:NO];
    }
    if (lastCheck != nil)
    {
	[lastCheck free];
    }
    return self;
}

- mailAppointment:theAppointment
{
    FILE               *a_mail_file;
    MiscString         *tempString;

    tempString = [[MiscString alloc] initString:"/usr/ucb/mail -s \""];
    [tempString cat:[theAppointment appointmentTextFirstLine]];
    [tempString cat:"\" "];
    [tempString cat:getlogin ()];
    a_mail_file = popen ([tempString stringValue], "wt");
    if (a_mail_file)
    {
	fprintf (a_mail_file,
		 "This is an appointment warning from kfc.app.\n");
	fprintf (a_mail_file, "%s\n",
		 [[theAppointment appointmentText] stringValue]);
	if ([theAppointment weekDay] != -1)
	    {

	    fprintf (a_mail_file, [theAppointment format:" The first %A following "] + 1);
} 
	if ([theAppointment month] != -1)
	{
	    fprintf (a_mail_file, "%d ",[theAppointment month] + 1);
	}
	else
	{
	    fprintf (a_mail_file, "of every month ");
	}
	if ([theAppointment day] != -1)
	{
	    fprintf (a_mail_file, "%d ",[theAppointment day] + 1);
	}
	else
	{
	    fprintf (a_mail_file, "Every day ");
	}
	if ([theAppointment year] != -1)
	{
	    fprintf (a_mail_file, "%d\n",[theAppointment year] + 1);
	}
	else
	{
	    fprintf (a_mail_file, "of every year.\n");
	}
	if ([theAppointment hour] != -1)
	{
	    fprintf (a_mail_file, " AT %2d:%2d\n",[theAppointment hour],[theAppointment minute]);
	}
	fprintf (a_mail_file, "%c\n\n\n", EOF);
	pclose (a_mail_file);
    }
    else
    {
	NXRunAlertPanel ("Sending mail for this appointment failed!",
			 [[theAppointment appointmentText] stringValue],
			 NULL, NULL, NULL);
    }
    return self;
}

/* Edit a list of expired appointments. */
- expired_Appointments:sender
{
    List               *appointmentList;
    int                 i,
                        j;
    MiscTime           *todaysDate;

    [expiredList init];
    todaysDate = [[MiscTime alloc] initWithCurrentTime];
    fix_initWithCurrentTime_bug (todaysDate);

    [self _initializeEditor];

    for (j = 0; j < [appointmentFileList count]; j++)
    {

	appointmentList = [[appointmentFileList objectAt:j] appointmentList];
	if (appointmentList == nil)
	{
	    continue;
	}
	for (i = 0; i < [appointmentList count]; i++)
	{
	    if ([[appointmentList objectAt:i]
		 isExpiredForDay:[todaysDate day]
		 Month:[todaysDate month]
		 Year:[todaysDate year]])
	    {
		[expiredList addObject:[appointmentList objectAt:i]];
	    }
	} /* One file's List */
    } /* All the open files */

    [editController editForDay:todaysDate withList:expiredList];
    [editController beginEdit];

    return self;
}

/* Make the currently pointed at day today. */
- today:sender
{
    [currentDay initWithCurrentTime];
    fix_initWithCurrentTime_bug (currentDay);
    [self update_MonthView_With_CurrentDay];
    return self;
}

/* Go to last month. */
- Last_Month:sender
{
    [currentDay subtractMonths:1];
    [currentDay setDay:MIN ([currentDay day],
			    [MiscTime daysInMonth:[currentDay month]
			     ofYear:[currentDay year]] - 1)];
    [self update_MonthView_With_CurrentDay];
    return self;
}

/* Go to next month. */
- Next_Month:sender
{
    if (sender == lastMonthView)
    {
	return[self Last_Month:sender];
    }
    [currentDay addMonths:1];
    [currentDay setDay:MIN ([currentDay day],
			    [MiscTime daysInMonth:[currentDay month]
			     ofYear:[currentDay year]] - 1)];
    [self update_MonthView_With_CurrentDay];
    return self;
}

- (BOOL)add_Appointment:newAppointment
{
    if ([appointmentFileList count] == 0)
    {
	NXRunAlertPanel ("New Appointment.",
		"You can not create a new appointment without a file open!",
			 NULL, NULL, NULL);
	return NO;
    }

 /*
    There's a way to do the logic of this if statement without so many
    parens, but I'm feeling lazy and the compiler optimizes, right? 
 */
 /* Should we prompt the user to see which file to save the new appointment to? */
    if (!((([NXApp defaultBoolValue:"justSaveFileNQA"]) &&
	   ([appointmentFileList indexOf:defaultAppointmentFile] != NX_NOT_IN_LIST)) ||
	  ([appointmentFileList count] == 1)))
    {
	id                  theFilePanel;
	id                  tempView;
	id                  aCell;
	int                 defaultFileNumber,
	                    i;
	NXRect              myViewRect;

	theFilePanel = [self _filePanel];
	[filePanelBox setTitle:"New Appointment For Which File"];
	[filePanelBox display];
	[filePanelActionButton setTarget:self];
	[filePanelActionButton setAction:@selector (newForFile:)];
	[filePanelActionButton setTitle:"Add"];

	[filePanelCancelButton setTarget:self];
	[filePanelCancelButton setAction:@selector (cancelNew:)];

	[filePanelScrollView setVertScrollerRequired:YES];
	[filePanelScrollView setBorderType:NX_LINE];
	[filePanelScrollView getDocVisibleRect:&myViewRect];
	tempView = [filePanelScrollView setDocView:[[Matrix alloc] initFrame:&myViewRect
						    mode:NX_RADIOMODE
						cellClass:[ButtonCell class]
						    numRows:0
						    numCols:1]];
	if (tempView)
	    [tempView free];
	tempView = [filePanelScrollView docView];
	[tempView setEmptySelectionEnabled:YES];
	{
	    NXSize              cellSize;

	    [tempView getCellSize:&cellSize];
	    cellSize.width = myViewRect.size.width;
	    [tempView setCellSize:&cellSize];
	}
	aCell = [[ButtonCell alloc] init];
	[aCell setType:NX_ONOFF];
	[aCell setBordered:NO];
	[aCell setAlignment:NX_LEFTALIGNED];
	aCell = [tempView setPrototype:aCell];
	if (aCell)
	    [aCell free];
	[self loadMatrix:tempView withFiles:LOADED_FILES];
	[tempView setTarget:self];
	[tempView setDoubleAction:@selector (newForFile:)];
	[tempView clearSelectedCell];
	if ((defaultFileNumber = [appointmentFileList indexOf:defaultAppointmentFile]) != NX_NOT_IN_LIST)
	{
	    [tempView selectCellAt:defaultFileNumber :0];
	    [tempView scrollCellToVisible:defaultFileNumber :0];
	}
	for (i = 0; i < [appointmentFileList count]; i++)
	{
	    if (MISCFILE_SUCCESS != (int)[[[appointmentFileList objectAt:i] myFile] access:MISCFILE_WRITE])
	    {
		[[tempView cellAt:i :0] setEnabled:NO];
	    }
	}
	[tempView update];
	{
	    int                 listNumber = [NXApp runModalFor:theFilePanel];

	    if (listNumber != -1)
	    {
		[[appointmentFileList objectAt:listNumber] addAppointment:newAppointment];
		return YES;
		[self checkEdited];
	    }
	    else
	    {
		return NO;
	    }
	}
    }
    if ([appointmentFileList count] == 1)
    {
	if (MISCFILE_SUCCESS != (int)[[[appointmentFileList objectAt:0] myFile] access:MISCFILE_WRITE])
	{
	    NXRunAlertPanel ("New Appointment",
			     "You can not write to the Appointment File you have open.  Open another.",
			     NULL, NULL, NULL);
	    return NO;
	}
	[[appointmentFileList objectAt:0] addAppointment:newAppointment];
    }
    else
    {
	if (MISCFILE_SUCCESS != (int)[[[appointmentFileList objectAt:0] myFile] access:MISCFILE_WRITE])
	{
	    NXRunAlertPanel ("New Appointment",
			     "You can not write to the Default Appointment File.  Select another.",
			     NULL, NULL, NULL);
	    return NO;
	}
	[defaultAppointmentFile addAppointment:newAppointment];
    }
    return YES;
}

- remove_Appointment:oldAppointment
{

 /*
    Quick hack that will do the job.  This way appointments do not have to
    know what file they belong to (which is how it should be). 
 */
 /* Make all the open appointment files try to remove the deleted appointments. */
    [appointmentFileList makeObjectsPerform:
     @selector (removeAppointment:) with :oldAppointment];
    [self update_MonthView_With_CurrentDay];
    [self checkEdited];
    return self;
}

/* Don't add that new appointment. */
- cancelNew:sender
{
    [NXApp stopModal:-1];
    [filePanel close];
    return self;
}

/* Add that new appointment. */
- newForFile:sender
{
    if ([[filePanelScrollView docView] selectedRow] != NX_NOT_IN_LIST)
    {
	int                 i;
	id                  theFileToSave;

	theFileToSave = [allFileList objectAt:[[filePanelScrollView docView] selectedRow]];
	for (i = 0; i < [appointmentFileList count]; i++)
	{
	    if ([[appointmentFileList objectAt:i] myFile] == theFileToSave)
	    {
		[NXApp stopModal:i];
		[filePanel close];
		return self;
	    }
	}
    }
    [NXApp stopModal:-1];

    [filePanel close];
    return self;
}

/* Check to see if any of the appointment files need saving. */
- checkEdited
{
    int                 i;
    BOOL                haveEdited = NO;

    for (i = 0; i < [appointmentFileList count]; i++)
    {
	haveEdited |= [[appointmentFileList objectAt:i] hasBeenEdited];
    }
    [calendarWindow setDocEdited:haveEdited];

    return self;
}

/* Display the mini-month views. */
- displayAppointments_For_Next_and_Last
{
    int                 i,
                        j;
    List               *displayTimes;
    id                  nextMonth,
                        lastMonth;

    nextMonth = [currentDay copy];
    lastMonth = [currentDay copy];
    [nextMonth addMonths:1];
    [lastMonth subtractMonths:1];

#ifdef DEBUG
    printf ("doing displayAppointments\n");
#endif
/* Go through the whole list of appointments. */
    for (j = 0; j < [appointmentFileList count]; j++)
    {
	List               *appointmentList;

	appointmentList = [[appointmentFileList objectAt:j] appointmentList];
	if (appointmentList == nil)
	{
	    continue;
	}
	for (i = 0; i < [appointmentList count]; i++)
	{

	/*
	   Get a list of appointments for the curent item for next month. 
	*/
	    displayTimes = [[appointmentList objectAt:i]
			    displayTimesForMonth:[nextMonth month]
			    andYear:[nextMonth year]];

	/* Tell the current month to display the list for the curent item. */
	    [nextMonthView displayAppointment:[appointmentList objectAt:i]
	     atTimes:displayTimes];

	/* Free the list's contents and the list itself. */
	    [[displayTimes freeObjects] free];

	/*
	   Get a list of appointments for the curent item for last month. 
	*/
	    displayTimes = [[appointmentList objectAt:i]
			    displayTimesForMonth:[lastMonth month]
			    andYear:[lastMonth year]];

	/* Tell the current month to display the list for the curent item. */
	    [lastMonthView displayAppointment:[appointmentList objectAt:i]
	     atTimes:displayTimes];

	/* Free the list's contents and the list itself. */
	    [[displayTimes freeObjects] free];
	}
    }

    [nextMonth free];
    [lastMonth free];

    return self;
}

- displayAppointments
{
    int                 i,
                        j;
    List               *displayTimes;

    [self displayAppointments_For_Next_and_Last];
#ifdef DEBUG
    printf ("doing displayAppointments\n");
#endif
/* Go through the whole list of appointments. */
    for (j = 0; j < [appointmentFileList count]; j++)
    {
	List               *appointmentList;

	appointmentList = [[appointmentFileList objectAt:j] appointmentList];
	if (appointmentList == nil)
	{
	    continue;
	}
	for (i = 0; i < [appointmentList count]; i++)
	{

	/*
	   Get a list of appointments for the curent item for the current
	   month. 
	*/
	    displayTimes = [[appointmentList objectAt:i]
			    displayTimesForMonth:[currentDay month]
			    andYear:[currentDay year]];

	/* Tell the current month to display the list for the curent item. */
	    [myMonthView displayAppointment:[appointmentList objectAt:i]
	     atTimes:displayTimes];

	/* Free the list's contents and the list itself. */
	    [[displayTimes freeObjects] free];
	}
    }

    return self;
}

- update_MonthView_With_CurrentDay
{
    char                TempString[20];
    char                MonthText[12][20] = {"January", "February", "March",
					     "April", "May", "June", "July",
					   "August", "September", "October",
					     "November", "December"};

#ifdef DEBUG
    printf ("doing update_MonthView_With_CurrentDay\n");
#endif

    [calendarWindow disableFlushWindow];
    /* Set up the Next and Last month mini-views. */
    {
	id                  nextMonth,
	                    lastMonth;

	nextMonth = [currentDay copy];
	lastMonth = [currentDay copy];
	[[nextMonth setDay:0] addMonths:1];
	[[lastMonth setDay:0] subtractMonths:1];
	[myMonthView initMonthWithFirstDay:currentDay];
	[nextMonthView initMonthWithFirstDay:nextMonth];
	[lastMonthView initMonthWithFirstDay:lastMonth];
	[nextMonth free];
	[lastMonth free];
    }

    sprintf (TempString, "%d",[currentDay month] + 1);
    [monthText setStringValue:MonthText[[currentDay month]]];

    sprintf (TempString, "%d",[currentDay year]);
    [[[yearBox superview] superview] sizeBy:1 :0];
    [yearBox setTitle:TempString];
    [yearBox sizeTo:0 :0];
    [yearBox sizeToFit];
    [yearBox display];

    [self displayAppointments];
    [myMonthView display];
    [nextMonthView display];
    [lastMonthView display];
    [[calendarWindow reenableFlushWindow] flushWindow];

    return self;
}

/* Load a matrix with the appropriate list of files depending on the circumstances. */
- loadMatrix:theMatrix withFiles:(int)kindOfFiles
{
    int                 i;
    id                  aCell;
    MiscFile           *userDirectory,
                       *localLibraryDirectory,
                       *kfcDirectory;
    appointmentFile    *oneAppointmentFile;
    MiscString         *tempString = [[MiscString alloc] init];

    if (allFileList)
    {
	[allFileList free];
    }
    allFileList = [[List alloc] init];


 /*
    Now allFileList has a list of all the files.  Let's get rid of the ones
    we don't want for this matrix. 
 */
    switch (kindOfFiles)
    {
    case EDITED_FILES:
    case LOADED_FILES:
	{
	    for (i = 0; i < [appointmentFileList count]; i++)
	    {
		oneAppointmentFile = [appointmentFileList objectAt:i];
		if (([oneAppointmentFile hasBeenEdited] &&
		     (kindOfFiles == EDITED_FILES)) ||
		    (kindOfFiles == LOADED_FILES))
		    [allFileList addObject:[oneAppointmentFile myFile]];
	    }
	    break;
	}
    case YET_TO_LOAD_FILES:
    case ALL_FILES:
    default:
	{
	    for (i = 0; i < [appointmentFileList count]; i++)
	    {
		oneAppointmentFile = [appointmentFileList objectAt:i];
		if (([oneAppointmentFile hasBeenEdited] &&
		     (kindOfFiles == EDITED_FILES)) ||
		    (kindOfFiles == LOADED_FILES) ||
		    (kindOfFiles == ALL_FILES))
		    [allFileList addObject:[oneAppointmentFile myFile]];
	    }

	/* Find any files in the local Library. */
	    localLibraryDirectory = [[MiscFile alloc] initWithPath:
				     "/LocalLibrary/kfc"];
	    [self addFilesFrom:localLibraryDirectory toList:allFileList];

	/* Find any files in the kfc Appointments directory. */
	    [tempString setString:[(NXBundle *)[NXBundle mainBundle] directory]];
	    [tempString cat:"/Appointments"];
	    kfcDirectory = [[MiscFile alloc] initWithPath:
			    [tempString stringValue]];
	    [self addFilesFrom:kfcDirectory toList:allFileList];

	/* Find any files in the user's directory. */
	    [tempString setString:NXHomeDirectory ()];
	    [tempString cat:"/Library/kfc"];
	    userDirectory = [[MiscFile alloc] initWithPath:
			     [tempString stringValue]];
	    [self addFilesFrom:userDirectory toList:allFileList];

	    if (kindOfFiles == YET_TO_LOAD_FILES)
	    {
	    /* What files do we have loaded. */
		for (i = 0; i < [appointmentFileList count]; i++)
		{
		    oneAppointmentFile = [[appointmentFileList objectAt:i]
					  myFile];
		/* So we only list this object once. */
		    [allFileList removeObject:oneAppointmentFile];
		}
	    }
	    break;
	}
    }

    for (i = 0; i < [allFileList count]; i++)
    {
	oneAppointmentFile = [allFileList objectAt:i];
	[theMatrix addRow];
	aCell = [theMatrix cellAt:i :0];
	[aCell setTitle:[[allFileList objectAt:i] filename]];
	if ([appointmentFileList indexOf:oneAppointmentFile] != NX_NOT_IN_LIST)
	{
	    [aCell setIntValue:1];
	}
    }

    [theMatrix sizeToCells];

    [tempString free];
    return self;
}

/* Get a list of kfc files from a given directory... */
- addFilesFrom:(MiscFile *) appointmentDirectory toList:(List *) theList
{
    id                  myList = theList;
    BOOL                returnList = NO;

    if (theList == nil)
    {
	returnList = YES;
	myList = [[List alloc] init];
    }
    if (appointmentDirectory != nil)
    {
	List               *listToAdd = [appointmentDirectory children];
	int                 i;

	for (i = 0; i < [listToAdd count]; i++)
	{
	    [myList addObjectIfAbsent:[listToAdd objectAt:i]];
	}
    }
    if (returnList)
    {
	return myList;
    }
    return self;
}

/* Notify the correct file that it has a modified appointment. */
- notifyChange:changedAppointment
{
    int                 i;

    for (i = 0; i < [appointmentFileList count]; i++)
    {
	if ([[[appointmentFileList objectAt:i] appointmentList]
	     indexOf:changedAppointment] != NX_NOT_IN_LIST)
	{
	    [[appointmentFileList objectAt:i]
	     appointmentChanged:changedAppointment];
	    [self checkEdited];
	    return self;
	}
    }
    return self;
}

- save_Appointments:sender
{
    int                 i;

    for (i = 0; i < [appointmentFileList count]; i++)
    {
	[[appointmentFileList objectAt:i] saveFile:self];
    }
    [self checkEdited];
    return self;
}

- load_Appointments:sender
{
    int                 i;

#ifdef DEBUG
    printf ("load_Appointments:\n");
#endif
    for (i = 0; i < [appointmentFileList count]; i++)
    {
	[[appointmentFileList objectAt:i] loadFile:self];
    }
    return self;
}

@end

/* Delegate methods */
@implementation CalendarController (Delegate_Methods)

- windowDidResize:sender
{
    [self update_MonthView_With_CurrentDay];
    return self;
}

- editDaysAppointments:theDay
{
    [self _initializeEditor];

    [editController editForDay:[theDay myDate] withList:[theDay appointmentList]];
    [editController beginEdit];

    return self;
}

- edit_Appointment:theAppointment;
{
    [self _initializeEditor];

    [editController editAppointment:theAppointment];
    [editController beginEdit];

    return self;
}

- addNote:(char *)TheNote forDay:(int)monthDay
{
    return self;
}

- doneEditing
{
    [self update_MonthView_With_CurrentDay];

    return self;
}

@end

@implementation CalendarController (Application_Delegate_Methods)
- (BOOL)appAcceptsAnotherFile:sender
{
    return YES;
}

- (int)app:sender openFile:(const char *)filename type:(const char *)aType
{
    MiscFile           *newFile;
    appointmentFile    *newappointmentFile;
    int                 i;

#ifdef DEBUG
    printf ("openFile:\n");
#endif

    newFile = [[MiscFile alloc] initWithPath:filename];
    if (!newFile)
    {
	return NO;
    }
    for (i = 0; i < [appointmentFileList count]; i++)
    {
	if (newFile == [[appointmentFileList objectAt:i] myFile])
	{
	    return NO;
	}
    }
    newappointmentFile = [[appointmentFile alloc] initWithPath:filename
			  WithController:self];
    [appointmentFileList addObject:newappointmentFile];
    if ([NXApp defaultValue:"defaultFile"] && strstr ([[newappointmentFile myFile] fullPath],[NXApp defaultValue:"defaultFile"]))
    {
	defaultAppointmentFile = newappointmentFile;
    }

    [newappointmentFile loadFile:self];
    [self load_Warnings_For_File:newappointmentFile];
    [self update_MonthView_With_CurrentDay];

    return YES;
}

@end

@implementation CalendarController (File_Operations)
- open:sender
{
    id                  theFilePanel;
    id                  tempView;
    id                  aCell;
    NXRect              myViewRect;

    theFilePanel = [self _filePanel];
    [filePanelActionButton setTarget:self];
    [filePanelActionButton setAction:@selector (openTheFile:)];
    [filePanelActionButton setTitle:"Open"];

    [filePanelCancelButton setTarget:self];
    [filePanelCancelButton setAction:@selector (cancelFile:)];
    [filePanelBox setTitle:"Double Click to Open"];
    [filePanelBox display];

    [filePanelScrollView setVertScrollerRequired:YES];
    [filePanelScrollView setBorderType:NX_LINE];
    [filePanelScrollView getDocVisibleRect:&myViewRect];
    tempView = [filePanelScrollView setDocView:[[Matrix alloc] initFrame:&myViewRect
						mode:NX_RADIOMODE
						cellClass:[ButtonCell class]
						numRows:0
						numCols:1]];
    if (tempView)
	[tempView free];
    tempView = [filePanelScrollView docView];
    [tempView setEmptySelectionEnabled:YES];
    {
	NXSize              cellSize;

	[tempView getCellSize:&cellSize];
	cellSize.width = myViewRect.size.width;
	[tempView setCellSize:&cellSize];
    }
    aCell = [[ButtonCell alloc] init];
 /* [aCell setTarget:self]; */
    [aCell setType:NX_ONOFF];
    [aCell setBordered:NO];
    [aCell setAlignment:NX_LEFTALIGNED];
    aCell = [tempView setPrototype:aCell];
    if (aCell)
	[aCell free];
    [self loadMatrix:tempView withFiles:YET_TO_LOAD_FILES];
    [tempView setTarget:self];
    [tempView setDoubleAction:@selector (openTheFile:)];
    [tempView clearSelectedCell];
    [tempView update];
    [NXApp runModalFor:theFilePanel];

    return self;
}

- cancelFile:sender
{
    [filePanel close];
    [NXApp stopModal];
    return self;
}

- openTheFile:sender
{
    [NXApp stopModal];
    [filePanel close];
    if ([[filePanelScrollView docView] selectedRow] == -1)
    {
	id                  myOpenPanel;
	int                 i;

	myOpenPanel = [OpenPanel new];
	i = [myOpenPanel runModalForTypes:("kfc", NULL)];
	if (i == NX_OKTAG)
	{
	    [self app:self openFile:[myOpenPanel filename] type:"kfc"];
	}
    }
    else
    {
	[self app:self openFile:[[allFileList objectAt:
				[[filePanelScrollView docView] selectedRow]]
				 fullPath] type:"kfc"];
    }
    return self;
}

- new:sender
{
    id                  mySavePanel;
    int                 saveStatus;

    mySavePanel = [SavePanel new];
    saveStatus = [mySavePanel runModal];
    if (saveStatus == NX_OKTAG)
    {
	FILE               *newfile;
	MiscString         *newFileName;

	newFileName = [[MiscString alloc] initString:[mySavePanel filename]];
	if (![newFileName grep:".kfc$" caseSensitive:YES])
	{
	    [newFileName cat:".kfc"];
	}

	newfile = fopen ([newFileName stringValue], "wt");
	if (!newfile)
	{
	    NXRunAlertPanel ("New File Failed",
			     "Could not open the new file %s",
			     NULL, NULL, NULL,[mySavePanel filename]);
	}
	else
	{
	    fclose (newfile);
	}
	[self app:self openFile:[newFileName stringValue] type:"kfc"];
	[newFileName free];
    }
    return self;
}

- save:sender
{
    id                  theFilePanel;
    id                  tempView;
    id                  aCell;
    NXRect              myViewRect;

    theFilePanel = [self _filePanel];
    [filePanelBox setTitle:"Double Click to Save"];
    [filePanelBox display];
    [filePanelActionButton setTarget:self];
    [filePanelActionButton setAction:@selector (saveTheFile:)];
    [filePanelActionButton setTitle:"Save"];

    [filePanelCancelButton setTarget:self];
    [filePanelCancelButton setAction:@selector (cancelFile:)];

    [filePanelScrollView setVertScrollerRequired:YES];
    [filePanelScrollView setBorderType:NX_LINE];
    [filePanelScrollView getDocVisibleRect:&myViewRect];
    tempView = [filePanelScrollView setDocView:[[Matrix alloc] initFrame:&myViewRect
						mode:NX_RADIOMODE
						cellClass:[ButtonCell class]
						numRows:0
						numCols:1]];
    if (tempView)
	[tempView free];
    tempView = [filePanelScrollView docView];
    [tempView setEmptySelectionEnabled:YES];
    {
	NXSize              cellSize;

	[tempView getCellSize:&cellSize];
	cellSize.width = myViewRect.size.width;
	[tempView setCellSize:&cellSize];
    }
    aCell = [[ButtonCell alloc] init];
 /* [aCell setTarget:self]; */
    [aCell setType:NX_ONOFF];
    [aCell setBordered:NO];
    [aCell setAlignment:NX_LEFTALIGNED];
    aCell = [tempView setPrototype:aCell];
    if (aCell)
	[aCell free];
    [self loadMatrix:tempView withFiles:EDITED_FILES];
    [tempView setTarget:self];
    [tempView setDoubleAction:@selector (saveTheFile:)];
    [tempView clearSelectedCell];
    [tempView update];
    [NXApp runModalFor:theFilePanel];

    return self;
}

- saveTheFile:sender
{
    [NXApp stopModal];
    [filePanel close];
    if ([[filePanelScrollView docView] selectedRow] != NX_NOT_IN_LIST)
    {
	int                 i;
	id                  theFileToSave;

	theFileToSave = [allFileList objectAt:[[filePanelScrollView docView] selectedRow]];
	for (i = 0; i < [appointmentFileList count]; i++)
	{
	    if ([[appointmentFileList objectAt:i] myFile] == theFileToSave)
	    {
		[[appointmentFileList objectAt:i] saveFile:self];
		[self checkEdited];
		return self;
	    }
	}
    }
    [self checkEdited];

    return self;
}

- saveAll:sender
{
    int                 i;

    for (i = 0; i < [appointmentFileList count]; i++)
    {
	if ([[appointmentFileList objectAt:i] hasBeenEdited])
	{
	    [[appointmentFileList objectAt:i] saveFile:self];
	}
    }
    [self checkEdited];
    return self;
}

- close:sender
{
    id                  theFilePanel;
    id                  tempView;
    id                  aCell;
    NXRect              myViewRect;

    theFilePanel = [self _filePanel];
    [filePanelBox setTitle:"Double Click to Close"];
    [filePanelBox display];
    [filePanelActionButton setTarget:self];
    [filePanelActionButton setAction:@selector (closeTheFile:)];
    [filePanelActionButton setTitle:"Close"];

    [filePanelCancelButton setTarget:self];
    [filePanelCancelButton setAction:@selector (cancelFile:)];

    [filePanelScrollView setVertScrollerRequired:YES];
    [filePanelScrollView setBorderType:NX_LINE];
    [filePanelScrollView getDocVisibleRect:&myViewRect];
    tempView = [filePanelScrollView setDocView:[[Matrix alloc] initFrame:&myViewRect
						mode:NX_RADIOMODE
						cellClass:[ButtonCell class]
						numRows:0
						numCols:1]];
    if (tempView)
	[tempView free];
    tempView = [filePanelScrollView docView];
    [tempView setEmptySelectionEnabled:YES];
    {
	NXSize              cellSize;

	[tempView getCellSize:&cellSize];
	cellSize.width = myViewRect.size.width;
	[tempView setCellSize:&cellSize];
    }
    aCell = [[ButtonCell alloc] init];
    [aCell setType:NX_ONOFF];
    [aCell setBordered:NO];
    [aCell setAlignment:NX_LEFTALIGNED];
    aCell = [tempView setPrototype:aCell];
    if (aCell)
	[aCell free];
    [self loadMatrix:tempView withFiles:LOADED_FILES];
    [tempView setTarget:self];
    [tempView setDoubleAction:@selector (closeTheFile:)];
    [tempView clearSelectedCell];
    [tempView update];
    [NXApp runModalFor:theFilePanel];

    return self;
}

- closeTheFile:sender
{
    [NXApp stopModal];
    [filePanel close];
    if ([[filePanelScrollView docView] selectedRow] != NX_NOT_IN_LIST)
    {
	int                 i;
	id                  theFileToClose;

	theFileToClose = [allFileList objectAt:[[filePanelScrollView docView] selectedRow]];
	for (i = 0; i < [appointmentFileList count]; i++)
	{
	    if ([[appointmentFileList objectAt:i] myFile] == theFileToClose)
	    {
		id                  appointmentFileToClose;

		appointmentFileToClose = [appointmentFileList objectAt:i];
		if ([appointmentFileToClose hasBeenEdited])
		{
		    int                 cancelValue;

		    cancelValue = NXRunAlertPanel ("Close", "Save changes to %s", "Cancel", "Don't Save", "Save",[[appointmentFileToClose myFile] filename]);

		    switch (cancelValue)
		    {
		    case NX_ALERTDEFAULT:
			{
			    [appointmentFileToClose saveFile:self];
			    break;
			}
		    case NX_ALERTOTHER:
			{
			    return self;
			}
		    case NX_ALERTALTERNATE:
		    default:
			{
			    break;
			}
		    } /* switch */
		} /* hasBeenEdited */
		[appointmentFileList removeObject:appointmentFileToClose];
		[appointmentFileToClose free];
		[self update_MonthView_With_CurrentDay];
		[self checkEdited];
		return self;
	    } /* ([[appointmentFileList objectAt:i] myFile] ==
	         theFileToClose) */
	} /* (i = 0; i < [appointmentFileList count]; i++) */
    } /* ([[filePanelScrollView docView] selectedRow] != NX_NOT_IN_LIST) */
    return self;
} /* closeTheFile:sender */

@end				/* (File_Operations) */

@implementation CalendarController (Preferences_Methods)
- preferences:sender;
{
    int                 i;
    id                  aCell;

    if (!prefPanel)
    {
	NXSize              myCellSize,
	                    myFrameSize;

	[NXApp loadNibSection:"Preferences.nib" owner:self withNames:NO];
	[prefFileBrowser setDocView:prefFileSwitcheMatrix];
	[prefFileBrowser setVertScrollerRequired:YES];
	[prefFileBrowser setBorderType:NX_BEZEL];
	while ([prefFileSwitcheMatrix cellCount])
	{
	    [prefFileSwitcheMatrix removeRowAt:0 andFree:YES];
	}
	[prefFileSwitcheMatrix getCellSize:&myCellSize];
	[prefFileBrowser getContentSize:&myFrameSize];
	myCellSize.width = myFrameSize.width;
	[prefFileSwitcheMatrix setCellSize:&myCellSize];
	aCell = [prefFileSwitcheMatrix prototype];
	[aCell setTarget:self];
	[aCell setAction:@selector (fileState:)];
	aCell = [prefFileSwitcheMatrix setPrototype:aCell];
    }
 /* Clear the Matrix for another go 'round. */
    while ([prefFileSwitcheMatrix cellCount])
    {
	[prefFileSwitcheMatrix removeRowAt:0 andFree:YES];
    }
    [self loadMatrix:prefFileSwitcheMatrix withFiles:ALL_FILES];
    for (i = 0; i < [prefFileSwitcheMatrix cellCount]; i++)
    {
	id                  oneAppointmentFile = [allFileList objectAt:i];

	aCell = [prefFileSwitcheMatrix cellAt:i :0];
	[aCell setAltTitle:[aCell title]];
	[aCell setThirdTitle:[aCell title]];
	if ([NXApp defaultValue:"startFiles"] && strstr ([NXApp defaultValue:"startFiles"],[oneAppointmentFile fullPath]))
	{
	    [aCell setIntValue:1];
	    if ([NXApp defaultValue:"defaultFile"] && strstr ([NXApp defaultValue:"defaultFile"],[oneAppointmentFile fullPath]))
	    {
		[aCell setIntValue:2];
	    }
	}
    }
    [prefFileSwitcheMatrix update];
    [[prefSwitches cellAt:0 :0] setIntValue:
     [NXApp defaultBoolValue:"hiddenOnLaunch"]];
    [[prefSwitches cellAt:1 :0] setIntValue:
     [NXApp defaultBoolValue:"beepForWarningPanels"]];
    [[prefSwitches cellAt:2 :0] setIntValue:
     [NXApp defaultBoolValue:"sendMailEveryTime"]];
    [[prefSwitches cellAt:3 :0] setIntValue:
     [NXApp defaultBoolValue:"justSaveFileNQA"]];

    [[[defaultWarningPopupButton target] itemList]
     selectCellWithTag:[NXApp defaultIntValue:"defaultWarning"]];
    [defaultWarningPopupButton setTitle:
     [[[[defaultWarningPopupButton target] itemList] selectedCell] title]];

    [NXApp runModalFor:prefPanel];

    return self;
}

- cancelPreferences:sender
{

 /*
    We can not free the contents of allFileList as we may be using them in
    other lists. 
 */
    [allFileList free];
    allFileList = nil;
    [[sender window] close];
    [NXApp stopModal];
    return self;
}

- savePreferences:sender
{
    int                 i;
    MiscString         *listOfFiles;

    listOfFiles = [[MiscString alloc] initString:""];

    [NXApp setDefault:"hiddenOnLaunch" toBool:[[prefSwitches cellAt:0 :0] intValue]];
    [NXApp setDefault:"beepForWarningPanels" toBool:[[prefSwitches cellAt:1 :0] intValue]];
    [NXApp setDefault:"sendMailEveryTime" toBool:[[prefSwitches cellAt:2 :0] intValue]];
    [NXApp setDefault:"justSaveFileNQA" toBool:[[prefSwitches cellAt:3 :0] intValue]];

    [NXApp setDefault:"defaultWarning" toInt:
     [[[[defaultWarningPopupButton target] itemList] selectedCell] tag]];
 /* Blank it if they don't want a default file. */
    [NXApp setDefault:"defaultFile" to:""];
    for (i = 0; i < [prefFileSwitcheMatrix cellCount]; i++)
    {
	if ([[prefFileSwitcheMatrix cellAt:i :0] intValue])
	{
	    [listOfFiles cat:[[allFileList objectAt:i] fullPath]];
	    [listOfFiles cat:";"];
	}
	if ([[prefFileSwitcheMatrix cellAt:i :0] intValue] == 2)
	{
	    [NXApp setDefault:"defaultFile" to:[[allFileList objectAt:i] fullPath]];
	}
    }
    [NXApp setDefault:"startFiles" to:[listOfFiles stringValue]];
 /* Call cancel to free everything... */
    [self cancelPreferences:sender];
    [listOfFiles free];

    return self;
}

- fileState:sender
{
    int                 i;

    if ([[prefFileSwitcheMatrix selectedCell] intValue] != 2)
    {
	return self;
    }
    for (i = 0; i < [prefFileSwitcheMatrix cellCount]; i++)
    {
	id                  aCell = [prefFileSwitcheMatrix cellAt:i:0];

	if (([aCell state] == 2) && ([prefFileSwitcheMatrix selectedCell] != aCell))
	{
	    [aCell setState:1];
	    [aCell display];
	}
    }
    return self;
}

@end				/* Preferences_Methods */

#import <misckit/MiscMailApp.h>
#import <appkit/Panel.h>
/* Methods to handle mailing. */
@implementation CalendarController (MailHandler)

- droppingLine:sender
{
    id                  addressString = [[MiscString alloc] initString:
					 "rmyers@dec5200.acs.uci.edu"],
                        subjectString = [[MiscString alloc] initString:
					 "Note RE: kfc.app"],
                        messageString = [[MiscString alloc] initString:
		"Kurt, great app.\nThought you might like to know that..."];


    if (myMailer == nil)
    {
	myMailer = [MiscMailApp localMailer];
    }
    if (myMailer == nil)
    {

    }
    else
    {
	[myMailer sendMailTo:addressString subject:subjectString
	 body:messageString];
    }
    [addressString free];
    [subjectString free];
    [messageString free];
    return self;
}

- bugReport:sender
{
    id                  addressString = [[MiscString alloc] initString:
					 "rmyers@dec5200.acs.uci.edu"],
                        subjectString = [[MiscString alloc] initString:
					 "BUG IN kfc.app"],
                        messageString = [[MiscString alloc] initString:
				 "Kurt, kfc.app blew chunks when I was:\n"];


    if (myMailer == nil)
    {
	myMailer = [MiscMailApp localMailer];
    }
    if (myMailer == nil)
    {
	NXRunAlertPanel ("MailApp Failure",
			 "The mail connection has failed.\nIt may be because you quit Mail recently.\nYou may restart kfc.app, or mail kurt at rmyers@dec5200.acs.uci.edu",
			 NULL, NULL, NULL);
    }
    else
    {
	[myMailer sendMailTo:addressString subject:subjectString
	 body:messageString];
    }
    [addressString free];
    [subjectString free];
    [messageString free];
    return self;
}

- hireMe:sender
{
    id                  addressString = [[MiscString alloc] initString:
					 "rmyers@dec5200.acs.uci.edu"],
                        subjectString = [[MiscString alloc] initString:
					 "kfc.app referral"],
                        messageString = [[MiscString alloc] initString:
					 "Kurt,\nkfc.app is a nice product.\nI know someone who could use a programmer like you.\n"];


    if (myMailer == nil)
    {
	myMailer = [MiscMailApp localMailer];
    }
    if (myMailer == nil)
    {

    }
    else
    {
	[myMailer sendMailTo:addressString subject:subjectString
	 body:messageString];
    }
    [addressString free];
    [subjectString free];
    [messageString free];
    return self;
}

@end

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