_MAKING SMALLTALK WITH WIDGETS_ by Kenneth E. Ayers [LISTING ONE] "The Appointment class definition" Object subclass: #Appointment instanceVariableNames: 'user date startTime endTime notifyTime text ' classVariableNames: '' poolDictionaries: '' "**** Appointment class methods ****" timePrintString:aTime "Answer a string with a representation of aTime formatted as hh:mm AM/PM" | amPM hours minutes minStr | amPM := 'AM'. hours := aTime hours. minutes := aTime minutes. hours = 0 ifTrue: [hours := 12. amPM := 'PM'] ifFalse:[ hours >= 12 ifTrue:[ amPM := 'PM'. hours > 12 ifTrue:[hours := hours - 12]]]. minutes < 10 ifTrue: [minStr := '0', minutes printString] ifFalse:[minStr := minutes printString]. ^hours printString, ':', minStr, ' ', amPM. "**** Appointment instance methods ****" < anAppointment "Answer true if the receiver's date and notify time are earlier than those of anAppointment" ^(date < anAppointment date) or:[(date = anAppointment date) and:[notifyTime < anAppointment notifyTime]]. <= anAppointment "Answer true if the receiver's date and notify time are the same or earlier than those of anAppointment" ^(self < anAppointment) or:[self = anAppointment]. = anAppointment "Answer true if the receiver's date and notify time are the same as those of anAppointment" ^(date = anAppointment date) and:[notifyTime = anAppointment notifyTime]. > anAppointment "Answer true if the receiver's date anf notify time are later than those of anAppointment" ^(self <= anAppointment) not. >= anAppointment "Answer true if the receiver's date and notify time are the same or later than those of anAppointment" ^(self < anAppointment) not. checkTime:timeNow date:dateToday "Answer true if it is time to notify the user of his or her appointment" ^(date < dateToday) or:[(date = dateToday) and:[notifyTime < timeNow]]. date "Answer a Date, the appointment's date" ^date. date:aDate "Set the appointment's date to aDate" date := aDate. endTime "Answer a Time, the appointment's end time" ^endTime. endTime:aTime "Set the appointment's end time to aTime" endTime := aTime. info "Answer a String with the information on this appointment" ^OrderedCollection new add:user, ' has an appointment'; add:'on ', date formPrint, ' at ', (self timePrintString:startTime); add:String new; addAll:text; yourself. notifyTime "Answer a Time, the time at which the user is to be notified" ^notifyTime. notifyTime:aTime "Set the time at which the user is to be notified to aTime" notifyTime := aTime. printOn:aStream "Add a representation of the receiver to aStream" aStream nextPutAll:date formPrint; nextPutAll:' @ '; nextPutAll:(self timePrintString:startTime); nextPutAll:' - '; nextPutAll:(text at:1). startTime "Answer a Time, the appointment's start time" ^startTime. startTime:aTime "Set the appointment's start time to aTime" startTime := aTime. text "Answer an Array containing the lines of text that describe the appointment" ^text. text:aTextArray "Set aTextArray as the lines of text that describe the appointment" text := aTextArray. timePrintString:aTime "Answer a string with a representation of aTime formatted as hh:mm AM/PM" ^self class timePrintString:aTime. user "Answer the user for whom the appointment was created" ^user. user:userName "Set the user for whom the appointment was created to userName" user := userName. [LISTING TWO] "The AppointmentBrowser class definition" ApplicationWindow subclass: #AppointmentBrowser instanceVariableNames: 'user appointments appointmentList dateEditor textEditor notifyTimeEditor endTimeEditor startTimeEditor ' classVariableNames: '' poolDictionaries: '' "**** AppointmentBrowser class methods ****" open "Prompt for a user name and then open an AppointmentBrowser for that user" | userName | Cursor offset:Display boundingBox center. userName := PromptDialog prompt:'Enter user name'. userName isNil ifTrue:[^nil]. ^self openOn:userName. openOn:aUserName "Open an AppointmentBrowser for aUserName" ^super new openWithInitializeMethod:#initializeUser: arguments:(Array with:aUserName). "**** AppointmentBrowser instance methods ****" addAppointment "The user as pushed the 'ADD' button so we need to construct a new appointment record and add it to the user's list" | appointment index | appointment := Appointment new user:user; date:dateEditor date; startTime:startTimeEditor time; endTime:endTimeEditor time; notifyTime:notifyTimeEditor time; text:textEditor stringList deepCopy; yourself. appointments add:appointment. index := appointments indexOf:appointment. self updateAppointmentList. appointmentList selectItem:index. changeAppointment "The user has pushed the 'CHANGE' button so we need to remove the currently selected appointment and then add a new on with the current values" appointmentList disableDrawing. self removeAppointment. appointmentList enableDrawing. self addAppointment. createWindow "This method was generated by the Widgets/V 286 Interface Editor" ^TitledWindow new yourself; title: 'Appointment Browser'; closable: true; iconizable: true; size: 415 @ 322; addWidget: ( appointmentList := ListBox new yourself; nameForInstVar: 'appointmentList'; on: #select send: #selectAppointment:; title: 'Appointments'; size: 390 @ 133 ) position: 12 @ 9; addWidget: ( dateEditor := DateWidget new yourself; nameForInstVar: 'dateEditor'; title: 'DATE:'; date: ( Date newDay: 16 month: #December year: 1990 ); size: 114 @ 18 ) position: 47 @ 156; addWidget: ( TitledPane new yourself; title: 'WHAT FOR?'; size: 233 @ 116; addWidget: ( textEditor := TextEditWidget new yourself; nameForInstVar: 'textEditor'; verticalScrollBar: true; horizontalScrollBar: false; size: 219 @ 90 ) framer: ( FramingParameters new xCentered; yCentered ) ) position: 170 @ 151; addWidget: ( notifyTimeEditor := TimeWidget new yourself; nameForInstVar: 'notifyTimeEditor'; title: 'NOTIFY AT:'; time: ( Time new seconds: 74673 ); size: 144 @ 18 ) position: 16 @ 269; addWidget: ( TitledButton new yourself; on: #release send: #changeAppointment; title: 'CHANGE'; size: 77 @ 19 ) position: 326 @ 269; addWidget: ( endTimeEditor := TimeWidget new yourself; nameForInstVar: 'endTimeEditor'; title: 'END TIME:'; time: ( Time new seconds: 74673 ); size: 136 @ 18 ) position: 24 @ 230; addWidget: ( startTimeEditor := TimeWidget new yourself; nameForInstVar: 'startTimeEditor'; on: #valueChanged send: #startTimeChanged:; title: 'START TIME:'; time: ( Time new seconds: 74613 ); size: 148 @ 18 ) position: 12 @ 192; addWidget: ( TitledButton new yourself; on: #release send: #addAppointment; title: 'ADD'; size: 77 @ 19 ) position: 170 @ 269; addWidget: ( TitledButton new yourself; on: #release send: #removeAppointment; title: 'REMOVE'; size: 77 @ 19 ) position: 248 @ 269. initializeUser:argArray "Initialize an AppointmentBrowser for the user whose name is given in argArray" user := argArray. (Appointments includesKey:user) ifFalse:[ Appointments at:user put:(SortedCollection sortBlock:[:a :b| a < b])]. appointments := Appointments at:user. self window on:#activated send:#updateAppointmentList; title:'Appointments for ', user. dateEditor date:Date today. startTimeEditor time:(Time fromSeconds:32400). "9:00 AM" self startTimeChanged:startTimeEditor time; updateAppointmentList. removeAppointment "The user has pushed the 'REMOVE' button so we need to remove the selected appointment from the user's list" | index appointment | appointments size = 0 ifTrue:[^self]. index := appointmentList selectionIndex. appointment := appointments at:index. appointments remove:appointment ifAbsent:[]. self updateAppointmentList. index > appointments size ifTrue:[index := appointments size]. appointmentList selectItem:index. selectAppointment:aString "The user has selected an appointment so we need to fill-in all of the appropriate field editors" | appointment | appointment := appointments at:appointmentList selectionIndex. dateEditor date:appointment date. startTimeEditor time:appointment startTime. endTimeEditor time:appointment endTime. notifyTimeEditor time:appointment notifyTime. textEditor stringList:appointment text. startTimeChanged:aString "The user has changed the start time so we need to supply rerasonable defaults for the end time and the notify time" | aTime | aTime := startTimeEditor time. "Assume appointment is one hour long" endTimeEditor time:(aTime addTime:(Time fromSeconds:3600)). "Assume notify 5-minutes before" notifyTimeEditor time:(aTime subtractTime:(Time fromSeconds:300)). updateAppointmentList "Update the list of appointments" | size list | (size := appointments size) = 0 ifTrue:[^self]. list := Array new:size. 1 to:size do:[:index| list at:index put:(appointments at:index) printString]. appointmentList stringList:list. user "Answer the name of the user for which this browser was opened" ^user. windowLocation "Make the window centered on the screen" ^#center. [LISTING THREE] "The AppointmentNotifier class definition" ApplicationWindow subclass: #AppointmentNotifier instanceVariableNames: 'active running minute text appointment ' classVariableNames: '' poolDictionaries: '' "**** AppointmentNotifier instance methods ****" acknowledge "The user has pushed th 'OK' button, so we need to remove the current appointment from the user's list" | user list | appointment isNil ifTrue:[^self]. user := appointment user. (list := Appointments at:user ifAbsent:[nil]) isNil ifFalse:[ list remove:appointment ifAbsent:[]]. text stringList:#(); display. appointment := nil. minute := nil. activateFor:anAppointment "Display the details of anAppointment and bring this window to the top" appointment isNil ifTrue:[ "Previous appointment has been dismissed" text disableDrawing; stringList:anAppointment info; enableDrawing. appointment := anAppointment]. Terminal bell; bell. Cursor offset:window origin. ScreenManager activateWindow:self window. clockEvent "A clock tick has been received so we have to determine if the minute has rolled over and, if so, are any appointments ready" | now thisMinute today list appointment | now := Time now. (thisMinute := now minutes) = minute ifTrue:[^self]. minute := thisMinute. today := Date today. Appointments associationsDo:[:anEntry| (list := anEntry value) size > 0 ifTrue:[ ((appointment := list first) checkTime:now date:today) ifTrue:[self activateFor:appointment]]]. closeWindow "Before closing this window, terminate the process that's monitoring clock events" active := false. "Wait for the process to terminate" [running] whileTrue:[Processor yield]. super closeWindow. createWindow "This method was generated by the Widgets/V 286 Interface Editor" ^TitledWindow new yourself; title: 'Appointment Notifier'; closable: true; size: 183 @ 155; addWidget: ( text := TextEditWidget new yourself; nameForInstVar: 'text'; verticalScrollBar: true; horizontalScrollBar: false; size: 169 @ 91 ) position: 5 @ 6; addWidget: ( TitledButton new yourself; on: #release send: #acknowledge; title: 'OK'; default: true; size: 57 @ 23 ) framer: ( FramingParameters new xCentered; originY: 103 relativeTo: #origin ). initialize "Initialize a new AppointmentNotifier" active := false. running := false. minute := Time now minutes. [self run] forkAt:Processor highUserPriority. run "Run the process that handles clock events" | timerSemaphore | (timerSemaphore := Smalltalk at:#TimerSemaphore ifAbsent:[nil]) isNil ifTrue:[ timerSemaphore := Semaphore new. Smalltalk at:#TimerSemaphore put:timerSemaphore]. active := true. running := true. [active] whileTrue:[ timerSemaphore wait. self clockEvent]. Smalltalk removeKey:#TimerSemaphore. running := false. windowLocation "Make the window centered on the screen" ^#center. [LISTING FOUR] "Modifications to the Process class methods" timerInterrupt "Implement the timer interrupt." | timerSemaphore | PendingEvents add: (Message new selector: #clockEvent:; arguments: (Array with: 1)). KeyboardSemaphore signal. "********************************************** Added by Ken Ayers to support the Appointment Manager Application" timerSemaphore := Smalltalk at:#TimerSemaphore ifAbsent:[nil]. timerSemaphore notNil ifTrue:[timerSemaphore signal]. "**********************************************" self enableInterrupts: true. [LISTING FIVE] "Corrections to TimeWidget methods" "**** TimeWidget instance methods ****" time | hours | hours := self hours. (self meridianEditor value = 'PM') ifTrue:[ hours < 12 ifTrue:[hours := hours + 12]] ifFalse:[ hours = 12 ifTrue:[ hours := 0]]. ^(Time fromSeconds:0) hours:hours; minutes:self minutes. time: newTime | hours | self time = newTime ifTrue: [^self]. hours := newTime hours. (hours >= 12) ifTrue: [ self meridianEditor value: 'PM'. hours > 12 ifTrue:[hours := hours - 12]] ifFalse: [ self meridianEditor value: 'AM'. hours = 0 ifTrue:[hours := 12]]. self hourEditor value: hours. self minuteEditor value: newTime minutes.