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

This is editDayAppointments.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 <appkit/Text.h>
#import <appkit/ScrollView.h>
#import <misckit/MiscAppDefaults.h>
#import "myText.h"
#import "editDayAppointments.h"
#import "../anAppointment.h"
#import "../DayView.h"
#import "../CalendarController.h"

#define BLANKPATH "Blank"

@implementation editDayAppointments

- initFromBundle: aBundle withDelegate: newDelegate forDay:(MiscTime *) theDay withList:(List *) aList inSuperView:theView forWarnings:(BOOL)isForWarnings
{
                        [self initFromBundle:aBundle withDelegate:newDelegate];
    if (theView != nil)
    {
	[self beginEdit];
    }
    [self editForDay:theDay withList:aList];

    return self;
}

- initFromBundle:aBundle withDelegate:newDelegate
{
    [super init];
    [self initFromBundle:aBundle];
    [self setDelegate:newDelegate];
    return self;
}

- initFromBundle:aBundle
{
    char                buf[MAXPATHLEN];
    NXRect              superFrame,
                        maxSize;

    id                  aSuperView,
                        newText;

 /*
    This is dumb.  I should not have to set an arbitrary limit for the
    maximum size of my text.  If it is the default of 0.0,0.0 and it does
    resize it should just resize forever. 
 */
    NXSetRect (&maxSize, 0.0, 0.0, 9999.99, 9999.99);
    NXSetRect (&superFrame, 0.0, 0.0, 0.0, 0.0);

    [aBundle getPath:buf forResource:"editDayAppointments" ofType:"nib"];
    [NXApp loadNibFile:buf owner:self withNames:NO];
    [appointmentBrowser setDelegate:self];
    [appointmentBrowser setTarget:self];
    [appointmentBrowser setAction:@selector (selectAppointment:)];

 /* Set some help pointers */
    [NXHelpPanel attachHelpFile:"Editing.rtfd" markerName:"" to:editWindow];
    [NXHelpPanel attachHelpFile:"Editing.date.rtfd" markerName:"" to:dateForm];
    [NXHelpPanel attachHelpFile:"Editing.mods.rtfd" markerName:"" to:modsForm];
    [NXHelpPanel attachHelpFile:"Editing.time.rtfd" markerName:"" to:timeForm];
    [NXHelpPanel attachHelpFile:"Editing.expire.rtfd" markerName:"" to:untilDateForm];

 /*
    All this to modify the behavior of the Text object!  
 */
    aSuperView = appointmentText;
    [aSuperView setBorderType:NX_BEZEL];
    [aSuperView setBackgroundGray:NX_WHITE];
    [aSuperView setVertScrollerRequired:YES];

    [aSuperView getContentSize:&superFrame.size];

    newText = [[myText alloc] initFrame:&superFrame];
    [newText setVertResizable:YES];
    [newText setHorizResizable:NO];
    [newText notifyAncestorWhenFrameChanged:YES];
    [newText setMaxSize:&maxSize.size];
    [newText setMinSize:&superFrame.size];
    [aSuperView setDocView:newText];
    [aSuperView resizeSubviews:&superFrame.size];
    appointmentText = newText;

    [appointmentText setDelegate:[appointmentBrowser delegate]];
    [appointmentSummaryForm setNextText:appointmentText];

    return self;
}

- beginEdit
{
 /* Keep away the flicker by displaying before we are in view. */
    [self displayAll];

    [editWindow makeKeyAndOrderFront:self];
    [editWindow makeFirstResponder:appointmentBrowser];
    [appointmentBrowser acceptArrowKeys:YES andSendActionMessages:YES];
    [appointmentBrowser setNextResponder:self];
    return self;
}

- leaveEditAppointment:sender
{
    [editWindow makeFirstResponder:editWindow];

 /* To clear the editing state. */
    [editWindow endEditingFor:appointmentText];

 /* Tell the calendar we're done editing. */
    [myDelegate doneEditing];
    [editWindow performClose:self];

    return self;
}

- displayAll
{
#ifdef DEBUG
    printf ("Displaying all\n");
#endif
    [editWindow disableFlushWindow];
    [self displayDate];
    [self displayMods];
    [self displayTime];
    [self displayUntilDate];
    [self displayVarious];

 /*
    Needed to flush the appointment text in case there was text and now there
    is no selected appointment. 
 */
    if (currentlyEditedAppointment == nil)
    {
	[editWindow display];
    }
    [[editWindow reenableFlushWindow] flushWindow];

    return self;
}

 /*
    This method displays the Month, Day, Year fields and sets the 'repeat
    days' field appropriately (editable or no).
 */
- displayDate
{
#ifdef DEBUG
    printf ("Displaying Date\n");
#endif
    if (currentlyEditedAppointment == nil)
    {
	[dayField setEnabled:NO];
	[monthField setEnabled:NO];
	[yearField setEnabled:NO];

	[dayField setStringValue:""];
	[monthField setStringValue:""];
	[yearField setStringValue:""];

	return self;
    }
    [dayField setEnabled:[currentlyEditedAppointment isEditable]];
    [monthField setEnabled:[currentlyEditedAppointment isEditable]];
    [yearField setEnabled:[currentlyEditedAppointment isEditable]];

    if ([currentlyEditedAppointment day] != -1)
	[dayField setIntValue:[currentlyEditedAppointment day] + 1];
    else
    {
	[dayField setStringValue:"All"];
    }

    if ([currentlyEditedAppointment month] != -1)
	[monthField setIntValue:[currentlyEditedAppointment month] + 1];
    else
    {
	[monthField setStringValue:"All"];
    }

    if ([currentlyEditedAppointment year] != -1)
	[yearField setIntValue:[currentlyEditedAppointment year]];
    else
    {
	[yearField setStringValue:"All"];
    }

    if (([currentlyEditedAppointment year] >= 0) && ([currentlyEditedAppointment month] >= 0) && ([currentlyEditedAppointment day] >= 0))
    {
	[repeatDaysField setEnabled:[currentlyEditedAppointment isEditable]];
	if (![repeatDaysField intValue])
	    [repeatDaysField setIntValue:0];
    }
    else
    {
	[repeatDaysField setStringValue:"N/A"];
	[repeatDaysField setEnabled:NO];
    }

    return self;
}

 /*
    This is where we do the initial displaying and enabling for the mod
    fields. 
 */
- displayMods
{
#ifdef DEBUG
    printf ("Displaying Mods\n");
#endif
    if (currentlyEditedAppointment == nil)
    {
	[weekDayButton setEnabled:NO];
	[[[weekDayButton target] itemList] selectCellWithTag:-1];
	[weekDayButton setTitle:
	 [[[[weekDayButton target] itemList] selectedCell] title]];

	[priorityFieldButton setEnabled:NO];
	[[[priorityFieldButton target] itemList] selectCellWithTag:-1];
	[priorityFieldButton setTitle:
	 [[[[priorityFieldButton target] itemList] selectedCell] title]];

	[backDaysField setEnabled:NO];
	[warningDaysField setEnabled:NO];
	[repeatDaysField setEnabled:NO];
	[backDaysField setStringValue:""];
	[warningDaysField setStringValue:""];
	[repeatDaysField setStringValue:""];
	return self;
    }

    [weekDayButton setEnabled:[currentlyEditedAppointment isEditable]];
    [priorityFieldButton setEnabled:[currentlyEditedAppointment isEditable]];
    [backDaysField setEnabled:[currentlyEditedAppointment isEditable]];
    [warningDaysField setEnabled:[currentlyEditedAppointment isEditable]];

    [[[weekDayButton target] itemList] selectCellWithTag:
     [currentlyEditedAppointment weekDay]];
    [weekDayButton setTitle:
     [[[[weekDayButton target] itemList] selectedCell] title]];

    [[[priorityFieldButton target] itemList] selectCellWithTag:
     [currentlyEditedAppointment priority]];
    [priorityFieldButton setTitle:
     [[[[priorityFieldButton target] itemList] selectedCell] title]];

    [backDaysField setIntValue:[currentlyEditedAppointment back]];
    [warningDaysField setIntValue:[currentlyEditedAppointment delta]];
    if ([repeatDaysField isEnabled])
	[repeatDaysField setIntValue:[currentlyEditedAppointment repeat]];

    return self;
}

 /*
    Here we handle the displaying and {en,dis}abling of the time fields. We
    do not set any values here except to display them. 
 */
- displayTime
{
#ifdef DEBUG
    printf ("Displaying Time\n");
#endif
    if (currentlyEditedAppointment == nil)
    {
	[hourField setEnabled:NO];
	[minuteField setEnabled:NO];
	[warningMinutesField setEnabled:NO];
	[repeatMinutesField setEnabled:NO];
	[noneButton setEnabled:NO];

	[hourField setStringValue:""];
	[minuteField setStringValue:""];
	[warningMinutesField setStringValue:""];
	[repeatMinutesField setStringValue:""];
	return self;
    }

    [hourField setEnabled:[currentlyEditedAppointment isEditable]];
    [minuteField setEnabled:[currentlyEditedAppointment isEditable]];

    if (([currentlyEditedAppointment hour] == -1) &&
	([currentlyEditedAppointment minute] == -1))
    {
	[hourField setStringValue:"None"];
	[minuteField setStringValue:"None"];
	[noneButton setEnabled:NO];
    }
    else
    {
	[noneButton setEnabled:[currentlyEditedAppointment isEditable]];
	if ([currentlyEditedAppointment hour] != -1)
	{
	    [hourField setIntValue:[currentlyEditedAppointment hour]];
	}
	else
	{
	    [hourField setStringValue:""];
	}
	[minuteField setIntValue:[currentlyEditedAppointment minute]];
    }

    if (([currentlyEditedAppointment hour] != -1) &&
	([currentlyEditedAppointment minute] != -1))
    {
	[warningMinutesField setEnabled:[currentlyEditedAppointment isEditable]];
	[warningMinutesField setIntValue:[currentlyEditedAppointment tdelta]];
	[repeatMinutesField setEnabled:[currentlyEditedAppointment isEditable]];
	[repeatMinutesField setIntValue:[currentlyEditedAppointment trepeat]];
    }
    else
    {
	[warningMinutesField setEnabled:NO];
	[warningMinutesField setStringValue:""];
	[repeatMinutesField setEnabled:NO];
	[repeatMinutesField setStringValue:""];
    }

    return self;
}

/* Display the until date and enable if appropriate. */
- displayUntilDate
{
    int                 aYear,
                        aMonth,
                        aDay;

#ifdef DEBUG
    printf ("Displaying Until\n");
#endif
    if ((currentlyEditedAppointment == nil)	/* ||
						   (![currentlyEditedAppointme
	    nt repeat]) */ )
    {
    /* If it's just going off clear it's values. */
	if ([untilDateForm isEnabled])
	{
	    [currentlyEditedAppointment clearUntilDate];
	}
	[untilDateForm setStringValue:"N/A" at:0];
	[untilDateForm setStringValue:"N/A" at:1];
	[untilDateForm setStringValue:"N/A" at:2];
	[untilDateForm setEnabled:NO];
	[untilDateButton setEnabled:NO];
	return self;
    }
 /* If it's just coming on give it some good values. */
    [currentlyEditedAppointment untilYear:&aYear Month:&aMonth Day:&aDay];

 /*
    The following lines are no longer needed (or useful) if ((![untilDateForm
    isEnabled]) && (aMonth < 0)) { [currentlyEditedAppointment
    setUntilYear:[currentlyEditedAppointment year]
    Month:[currentlyEditedAppointment month] Day:[currentlyEditedAppointment
    day]]; } End useless code... 
 */
    [untilDateForm setEnabled:[currentlyEditedAppointment isEditable]];
    [untilDateButton setEnabled:[currentlyEditedAppointment isEditable]];
    if (aYear == -1)
    {
	[untilDateForm setStringValue:"N/A" at:0];
	[untilDateForm setStringValue:"N/A" at:1];
	[untilDateForm setStringValue:"N/A" at:2];
    }
    else
    {
	[untilDateForm setIntValue:aMonth + 1 at:0];
	[untilDateForm setIntValue:aDay + 1 at:1];
	[untilDateForm setIntValue:aYear at:2];
    }
    return self;
}

/* Method to display everything else .. Buttons, the ScrollText, etc */
- displayVarious
{
#ifdef DEBUG
    printf ("Displaying Various\n");
#endif
    [newButton setEnabled:(myDayList != nil)];
    [removeButton setEnabled:
     ((currentlyEditedAppointment != nil) && (myDayList != nil) &&
      [currentlyEditedAppointment isEditable])];
    [abortButton setEnabled:NO];/* Not yet functional */

    if (currentlyEditedAppointment == nil)
    {
	[[appointmentText setText:""] setEditable:NO];
	[[appointmentSummary setStringValue:""] setEditable:NO];
    }
    else
    {
	[appointmentSummary setStringValue:
	 [currentlyEditedAppointment appointmentTextFirstLine]];
	[appointmentText selectAll:self];
	[appointmentText replaceSel:
	 [currentlyEditedAppointment appointmentTextSansFirstLine]];
	[appointmentText setEditable:[currentlyEditedAppointment isEditable]];
	[appointmentSummary setEditable:[currentlyEditedAppointment isEditable]];
    }

    return self;
}


 /*
    This method checks and sets the Date info from user input (nib). 
 */
- modifyDate:sender
{
    if ([dayField intValue] > 0)
    {
	int                 goodDay = [dayField intValue];

	if (([monthField intValue] > 0) && ([monthField intValue] <= 12) &&
	    ([yearField intValue]))
	    goodDay = MIN (goodDay,[MiscTime daysInMonth:([monthField intValue] - 1) ofYear :[yearField intValue]]);
	[currentlyEditedAppointment setDay:goodDay - 1];
    }
    else
	[currentlyEditedAppointment setDay:-1];


    if ([monthField intValue] > 0)
    {
	int                 goodMonth = [monthField intValue];

	goodMonth = MIN (goodMonth, 12);
	[currentlyEditedAppointment setMonth:goodMonth - 1];
    }
    else
	[currentlyEditedAppointment setMonth:-1];

    if ([yearField intValue] > 0)
    {
	int                 goodYear = [yearField intValue];

	[currentlyEditedAppointment setYear:goodYear];
    }
    else
	[currentlyEditedAppointment setYear:-1];

    [self displayDate];

    return self;
}

/* Sets the weekday from a selection of the weekday popup button. */
- setTargetsWeekDay:sender
{
    [currentlyEditedAppointment setWeekDay:[[sender selectedCell] tag]];
    [myDelegate notifyChange:currentlyEditedAppointment];
    return self;
}

/* Handles user modification of the date modifiers. */
- modifyModifiers:sender
{
    [currentlyEditedAppointment setDelta:[warningDaysField intValue]];
    [currentlyEditedAppointment setRepeat:[repeatDaysField intValue]];
    [currentlyEditedAppointment setBack:[backDaysField intValue]];
    [self displayUntilDate];	/* Because repeat affects untildate. */
    [self displayMods];
    return self;
}

/* Sets the priority from a selection of the priority popup button. */
- changePriority:sender
{
    [currentlyEditedAppointment setPriority:[[sender selectedCell] tag]];
    [myDelegate notifyChange:currentlyEditedAppointment];
    return self;
}


 /*
    Here we handle the input of fields by the user.  We do not handle
    displaying excpet where it occurs due to setting.  {dis,en}abling are
    done at display time 
 */
- modifyTime:sender
{
    if ((strpbrk ([hourField stringValue], "0123456789")) ||
	(strpbrk ([minuteField stringValue], "0123456789")))
    {
	[currentlyEditedAppointment setHour:([hourField intValue] % 24)];
	[currentlyEditedAppointment setMinute:([minuteField intValue] % 60)];
    }
    else
    {
	[currentlyEditedAppointment setHour:-1];
	[currentlyEditedAppointment setMinute:-1];
    }

 /*
    Set the tdelta and trepeat based on the fields, or set them to 0 if the
    hour and minute have not been set 
 */
    if (([currentlyEditedAppointment hour] != -1) &&
	([currentlyEditedAppointment minute] != -1))
    {
	[currentlyEditedAppointment setTdelta:[warningMinutesField intValue]];
	[currentlyEditedAppointment setTrepeat:[repeatMinutesField intValue]];
    }
    else
    {
	[currentlyEditedAppointment setTdelta:0];
	[currentlyEditedAppointment setTrepeat:0];
    }

    [self displayTime];
    [[sender nextText] selectText:sender];
    return self;
}

/* Activated by the 'clear' button. */
- clearTime:sender
{
    [hourField setStringValue:"None"];
    [minuteField setStringValue:"None"];
    [self modifyTime:timeForm];
    [myDelegate notifyChange:currentlyEditedAppointment];
    return self;
}

- modifyUntilDate:sender
{
    [currentlyEditedAppointment setUntilYear:[untilDateForm intValueAt:2]
     Month:([untilDateForm intValueAt:0] % 13) - 1
     Day:([untilDateForm intValueAt:1] % 32) - 1];
    [self displayUntilDate];
    return self;
}

/* Activated by the 'clear' button. */
- clearUntilDate:sender
{
    [currentlyEditedAppointment clearUntilDate];
    [self displayUntilDate];
    [myDelegate notifyChange:currentlyEditedAppointment];
    return self;
}

/* Should be set at init time. */
- setDelegate:newDelegate
{
    myDelegate = newDelegate;
    return self;
}

/* Set the date of the appointments we're editing (used for the creation of new appointments. */
- setDate:(MiscTime *) theDay
{
    myDay = theDay;
    return self;
}

/* Set the list of appointments we're editing. */
- setList:(List *) aList
{
    myDayList = aList;
    [appointmentBrowser loadColumnZero];
    return self;
}

- addNewAppointment:sender
{
    currentlyEditedAppointment = [[anAppointment alloc] init];
    if (![myDelegate add_Appointment:currentlyEditedAppointment])
    {
	[currentlyEditedAppointment free];
	currentlyEditedAppointment = nil;
	return self;
    }
    [currentlyEditedAppointment copyTimeFrom:myDay];
    [currentlyEditedAppointment setHour:-1];
    [currentlyEditedAppointment setMinute:-1];
    [currentlyEditedAppointment setDayOfWeek:-1];
    [currentlyEditedAppointment setPriority:
     [NXApp defaultIntValue:"defaultWarning"]];
    [[currentlyEditedAppointment appointmentText] setStringValue:BLANKPATH];

    [myDayList addObject:currentlyEditedAppointment];

    [appointmentBrowser loadColumnZero];
    [appointmentBrowser setPath:BLANKPATH];
    [self editAppointment:currentlyEditedAppointment];
    [appointmentSummaryForm selectTextAt:0];
    return self;
}

- deleteAppointment:sender
{
    if (currentlyEditedAppointment)
    {
	[myDelegate remove_Appointment:currentlyEditedAppointment];
	[[myDayList removeObject:currentlyEditedAppointment] free];
	[appointmentBrowser loadColumnZero];
	currentlyEditedAppointment = nil;
	[self displayAll];
    }
    return self;
}

/* Start an edit session for a day with a list of events. */
- editForDay:theDay withList:(List *) aList
{
    id                  tempPtr;

    [self setDate:theDay];

    currentlyEditedAppointment = nil;

    [self setList:aList];

    tempPtr = [[MiscString alloc] initString:"Appointments for "];

    [tempPtr catFromFormat:"%2d/%2d/%4d",[theDay month] + 1,[theDay day] + 1,[theDay year]];
    [appointmentBrowser setTitle:[tempPtr stringValue] ofColumn:0];
    [tempPtr free];

    return self;
}

/* Activated from the appointment browser. */
- selectAppointment:sender
{
    int                 row,
                        col;

    [[sender matrixInColumn:0] getRow:&row andCol:&col ofCell:
     [sender selectedCell]];
    [self editAppointment:[myDayList objectAt:row]];

 /*
    [[appointmentBrowser window] makeFirstResponder:appointmentBrowser];
    Thanks to Ivo Rothschild for pointing out that this crashes due to
    "removing a view from the heirarchy that's been lockfocus'd" 
 */
 
    [editWindow perform:@selector (makeFirstResponder:)
     with :appointmentBrowser
     afterDelay:10		/* milliseconds */
     cancelPrevious:NO];

    return self;
}

/* Set the current appointment (used to do more stuff...). */
- editAppointment:theAppointment
{
    currentlyEditedAppointment = theAppointment;

    [self displayAll];

    return self;
}


- keyUp:(NXEvent *)theEvent
{
    return nil;
}

- keyDown:(NXEvent *)theEvent
{
    switch (theEvent -> data.key.charCode)
    {
    case '\r':
	{
	    [doneButton performClick:self];
	    break;
	}
    case '_':
    case '-':
	{
	    [removeButton performClick:self];
	    break;
	}
    case '=':
    case '+':
	{
	    [newButton performClick:self];
	    break;
	}
    case '	':		/* That's a tab */
    case ' ':			/* That's a space */
	{
	    if (currentlyEditedAppointment != nil)
	    {
		[appointmentSummaryForm selectTextAt:0];
	    }
	    break;
	}
    default:
	{
	    return nil;
	}
    }
    return self;
}

 /* Delegate Methods */
 /* For the Browser */
- (int)browser:sender fillMatrix:matrix inColumn:(int)column
{
    int                 i;
    id                  aCell,
                        tempAppointment;

    for (i = 0; i < [myDayList count]; i++)
    {
	[matrix addRow];
	aCell = [matrix cellAt:i :column];
	tempAppointment = [myDayList objectAt:i];
	[[[[aCell setStringValue:[tempAppointment appointmentTextFirstLine]]
	   setLoaded:YES] setLeaf:YES] setEnabled:YES];
    }
    return[myDayList count];
}

 /* Delegate Methods */
 /* For the Text */
- (BOOL)textWillChange:sender
{
    [editWindow setDocEdited:YES];
    return NO;
}

- textDidEnd:sender endChar:(unsigned short)whyEnd
{
    NXStream           *aStream;

    [editWindow setDocEdited:NO];
    [myDelegate notifyChange:currentlyEditedAppointment];
    if ((sender == appointmentText) || (sender == [[appointmentSummaryForm subviews] objectAt:0]))
    {
	id                  tempString;

    /* Set up a temp string to buffer data. */
	tempString = [[MiscString alloc] init];

    /* Open the a stream from the appointmentText. */
	aStream = NXOpenMemory (NULL, 0, NX_READWRITE);
	[appointmentText writeText:aStream];
	NXSeek (aStream, 0, NX_FROMSTART);

    /* Set the appointment text to the summary. */
	[[currentlyEditedAppointment appointmentText] setString:
	 [appointmentSummary stringValue]];

    /* Now append the stream data to the summary. */
	while (EOF != [tempString streamGets:aStream keepNewline:NO])
	{
	/* Append a carriage return. */
	    [[currentlyEditedAppointment appointmentText] cat:"\n"];
	/* Append another line of text. */
	    [[currentlyEditedAppointment appointmentText] concatenate:
	     tempString];
	}
    /* Append a carriage return. */
	[[currentlyEditedAppointment appointmentText] cat:"\n"];
    /* Append another line of text. */
	[[currentlyEditedAppointment appointmentText] concatenate:tempString];

	NXClose (aStream);

    /* Refresh the browser since the text changed */
	[appointmentBrowser loadColumnZero];
	[appointmentBrowser setPath:
	 [currentlyEditedAppointment appointmentTextFirstLine]];
	[tempString free];
	if ((sender == appointmentText) && (whyEnd == NX_TAB))
	{
	    [[appointmentBrowser window]
	     makeFirstResponder:appointmentBrowser];
	}
    }
    else
    {
	if ([sender superview] == dateForm)
	{
	    [self modifyDate:dateForm];
	}
	if ([sender superview] == modsForm)
	{
	    [self modifyModifiers:modsForm];
	}
	if ([sender superview] == timeForm)
	{
	    [self modifyTime:timeForm];
	}
	if ([sender superview] == untilDateForm)
	{
	    [self modifyUntilDate:untilDateForm];
	}
    }
    if (whyEnd == NX_RETURN)
    {
	[[appointmentBrowser window] makeFirstResponder:appointmentBrowser];
    }

    return self;
}

@end

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