DIRECTORY Commander USING [Register, CommandProc], IO USING [BreakProc, STREAM, Flush, PutRope, SkipWhitespace, RIS, GetLineRope, GetTokenRope, EndOfStream], Menus USING [AppendMenuEntry, MenuEntry, ClickProc, CreateEntry, GetLine, CreateMenu, Menu], MessageWindow USING [Append, Blink], PeanutWindow USING [AddCommand], PrincOpsUtils USING [IsBound], Process USING [Pause, Detach, SecondsToTicks], Remember, RememberPrivate USING [Event, EventRecord, itIsNow, peanutUser, walnutUser, AddEvent, Save], Rope USING [ROPE, Cat, Concat, Equal, Find, Substr, Length], Tempus USING [MakeRope, SecondsToPacked, Unintelligible, Seconds, Packed, Parse, PackedToSeconds], TiogaOps USING [CancelSelection, SaveSelB, SelectPoint, FindWord, RestoreSelB, Location, GetSelection, GetRope, Ref, Parent], ViewerIO USING [CreateMessageWindowStream], ViewerOps USING [FetchProp, AddProp, PaintViewer, DestroyViewer], WalnutWindow USING [AddToMsgMenu, GetMsgName], ViewerTools USING [GetSelectionContents], ViewerClasses USING [Viewer]; RememberOpsImpl: CEDAR PROGRAM IMPORTS Commander, IO, Menus, MessageWindow, PeanutWindow, PrincOpsUtils, Process, Rope, Remember, RememberPrivate, Tempus, TiogaOps, ViewerIO, ViewerOps, ViewerTools, WalnutWindow EXPORTS RememberPrivate SHARES RememberPrivate = BEGIN ROPE: TYPE = Rope.ROPE; RememberProc: Commander.CommandProc = { err, offender: ROPE; h: IO.STREAM = IO.RIS[cmd.commandLine]; token, line: ROPE _ NIL; parameters: Remember.ParameterList; i: INT; token _ h.GetTokenRope[WhiteSpace ! IO.EndOfStream => CONTINUE].token; IF Rope.Equal[token, "to", FALSE] THEN { -- more aesthetic for reminder to say phone mumble then to phone mumble. [] _ h.SkipWhitespace[]; line _ IO.GetLineRope[h ! IO.EndOfStream => CONTINUE]; } ELSE line _ Rope.Concat[token, IO.GetLineRope[h ! IO.EndOfStream => CONTINUE]]; { ENABLE Remember.Error => { IF ec = timeRopeFormatError THEN err _ "" ELSE IF ec = formatError THEN err _ "Format Error: " ELSE IF ec = timeNotSpecified THEN err _ "No Time Specified" ELSE REJECT; offender _ msg; GOTO Out; }; IF (i _ Rope.Find[line, "/ "]) # -1 THEN { parameters _ Remember.ReadParameters[IO.RIS[Rope.Substr[base: line, start: i + 1]]]; line _ Rope.Substr[base: line, len: i]; }; Remember.RegisterEvent[parameters: parameters, scanThis: line, out: cmd.out]; EXITS Out => NULL; }; IF err # NIL THEN cmd.err.PutRope[Rope.Cat[err, offender, "\n"]] ; }; WhiteSpace: IO.BreakProc = { RETURN[SELECT char FROM ' , '\n, '\t, '\l => sepr, ENDCASE => other]; }; Blinker: Menus.ClickProc = { viewer: ViewerClasses.Viewer = NARROW[parent]; event: RememberPrivate.Event = NARROW[ViewerOps.FetchProp[viewer, $Remember]]; event.blink _ NOT event.blink; MessageWindow.Append[Rope.Concat["Blinker turned ", IF event.blink THEN "on." ELSE "off."], TRUE]; }; Relabel: Menus.ClickProc = { viewer: ViewerClasses.Viewer = NARROW[parent]; event: RememberPrivate.Event = NARROW[ViewerOps.FetchProp[viewer, $Remember]]; sel: ROPE = ViewerTools.GetSelectionContents[]; event.iconLabel _ sel; ViewerOps.AddProp[viewer, $IconLabel, event.iconLabel]; viewer.name _ sel; ViewerOps.PaintViewer[viewer, caption]; }; SnoozeAlarm: Menus.ClickProc = { viewer: ViewerClasses.Viewer = NARROW[parent]; event: RememberPrivate.Event _ NARROW[ViewerOps.FetchProp[viewer, $Remember]]; delta: Tempus.Seconds = (IF mouseButton = red THEN 15*60 ELSE IF mouseButton = yellow THEN 60*60 ELSE LONG[60]*60*24); -- this should actually call AdjustTime, i.e. tomorrow. IF event.repeat # NIL THEN { -- must create a new event because there is no way to write out a single event consisting of a repeating event, and a specific instance with a different time, and we have to write this out so that if user rollsback or boots, the event that has been reset will still be remembered. event _ NEW[RememberPrivate.EventRecord _ event^]; event.repeat _ NIL; event.nextNotification _ [0]; -- paranoid RememberPrivate.AddEvent[event]; }; IF NOT shift THEN event.timeToStartNotification _ [MAX[Tempus.PackedToSeconds[RememberPrivate.itIsNow], event.timeToStartNotification - event.leadTime] + delta] -- first snooze means from now, subsequent snooze means from that point. ELSE event.timeToStartNotification _ [event.timeToStartNotification - delta]; -- this is so you can reset for several hours, then back up 15 minutes. etc. event.leadTime _ 0; event.newStartTime _ TRUE; TRUSTED {Process.Detach[FORK RememberPrivate.Save[]]}; event.blink _ FALSE; MessageWindow.Append[Rope.Concat["Reminder reset for ", Tempus.MakeRope[time: Tempus.SecondsToPacked[event.timeToStartNotification], includeDayOfWeek: TRUE]], TRUE]; }; ResetTime: Menus.ClickProc = { viewer: ViewerClasses.Viewer = NARROW[parent]; event: RememberPrivate.Event = NARROW[ViewerOps.FetchProp[viewer, $Remember]]; sel: ROPE = ViewerTools.GetSelectionContents[]; t: Tempus.Packed; { t _ Tempus.Parse[rope: sel, baseTime: Tempus.SecondsToPacked[[MAX[Tempus.PackedToSeconds[RememberPrivate.itIsNow], event.timeToStartNotification - event.leadTime]]] ! Tempus.Unintelligible => GOTO Bogus].time; event.timeToStartNotification _ Tempus.PackedToSeconds[t]; event.leadTime _ 0; event.newStartTime _ TRUE; TRUSTED {Process.Detach[FORK RememberPrivate.Save[]]}; MessageWindow.Append[Rope.Concat["Reminder reset for ", Tempus.MakeRope[t]], TRUE]; EXITS Bogus => {MessageWindow.Append[Rope.Concat["Unintelligible time: ", sel], TRUE]; MessageWindow.Blink[]}; }; }; Cancel: Menus.ClickProc = { viewer: ViewerClasses.Viewer = NARROW[parent]; event: RememberPrivate.Event = NARROW[ViewerOps.FetchProp[viewer, $Remember]]; event.repeat _ NIL; event.justPretending _ FALSE; event.destroyed _ TRUE; MessageWindow.Append["Event now forgotten.", TRUE]; TRUSTED {Process.Detach[FORK RememberPrivate.Save[]]}; IF mouseButton = blue THEN ViewerOps.DestroyViewer[viewer]; }; stream: IO.STREAM _ NIL; RemindProc: Menus.ClickProc = TRUSTED { viewer: ViewerClasses.Viewer = NARROW[parent]; text, messageName, err, sel: ROPE; parameters: Remember.ParameterList; i: INT; oldNow: Tempus.Packed _ RememberPrivate.itIsNow; messageName _ WalnutWindow.GetMsgName[viewer]; sel _ ViewerTools.GetSelectionContents[]; TiogaOps.SaveSelB[]; { TiogaOps.CancelSelection[]; IF TiogaOps.FindWord[viewer: viewer, rope: "Date:", whichDir: anywhere] THEN { end: TiogaOps.Location; i: INT; [, , end] _ TiogaOps.GetSelection[]; text _ TiogaOps.GetRope[end.node]; text _ Rope.Substr[base: text, start: end.where + 1]; IF (i _ Rope.Find[text, "\n"]) # -1 THEN text _ Rope.Substr[base: text, len: i]; RememberPrivate.itIsNow _ Tempus.Parse[rope: text ! Tempus.Unintelligible => { err _ Rope.Concat["Unintelligible time: ", text]; GOTO Out; }].time; -- e.g. if message sent on Tuesday, not read until Thursday, and message identifies a time Thursday 2pm means that thursday. } ELSE { err _ "No Date field in message"; GOTO Out; }; TiogaOps.CancelSelection[]; IF TiogaOps.FindWord[viewer: viewer, rope: "Subject:", whichDir: anywhere] THEN { end: TiogaOps.Location; i: INT; [, , end] _ TiogaOps.GetSelection[]; text _ TiogaOps.GetRope[end.node]; text _ Rope.Substr[base: text, start: end.where + 1]; IF (i _ Rope.Find[text, "\n"]) # -1 THEN text _ Rope.Substr[base: text, len: i]; } ELSE { err _ "No Subject field in message"; GOTO Out; }; TiogaOps.RestoreSelB[]; IF (i _ Rope.Find[sel, "/ "]) # -1 THEN { parameters _ Remember.ReadParameters[IO.RIS[Rope.Substr[base: sel, start: i + 1]]]; sel _ Rope.Substr[base: sel, len: i]; }; stream _ ViewerIO.CreateMessageWindowStream[]; --EVERY time to work around bug Remember.RegisterEvent[ scanThis: Rope.Cat[sel, " | ", text], parameters: CONS[ NEW[Remember.Parameter _ [text, text]], CONS[NEW[Remember.Parameter _ [message, messageName]], parameters]], out: stream ! Remember.Error => { SELECT ec FROM timeRopeFormatError => err _ Rope.Concat["TimeAndDate is ", msg]; timeNotSpecified => err _ "Specify (select) a time for the event."; formatError => err _ Rope.Concat["Format Error: ", msg]; ENDCASE => REJECT; CONTINUE; }; ]; GOTO Out; EXITS Out => { IF err # NIL THEN { MessageWindow.Append[err, TRUE]; MessageWindow.Blink[]; } ELSE stream.Flush[]; RememberPrivate.itIsNow _ oldNow; }; }; }; PeanutRemindProc: Menus.ClickProc = TRUSTED { text, err, sel: ROPE; parameters: Remember.ParameterList; i: INT; mailViewer: ViewerClasses.Viewer; messageNode: TiogaOps.Ref; start, end: TiogaOps.Location; oldNow: Tempus.Packed _ RememberPrivate.itIsNow; [mailViewer, start, end] _ TiogaOps.GetSelection[]; BEGIN IF mailViewer = NIL OR Rope.Find[s1: mailViewer.name, s2: ".mail", case: FALSE] = -1 THEN { err _"First make a selection in the corresponding message."; GOTO Out; }; messageNode _ start.node; DO r: TiogaOps.Ref _ TiogaOps.Parent[messageNode]; IF r = NIL OR TiogaOps.Parent[r] = NIL THEN EXIT; messageNode _ r; ENDLOOP; TiogaOps.SaveSelB[]; TiogaOps.SelectPoint[viewer: mailViewer, caret: [messageNode, 0]]; IF TiogaOps.FindWord[viewer: mailViewer, rope: "Date:", whichDir: anywhere] THEN { end: TiogaOps.Location; i: INT; [, , end] _ TiogaOps.GetSelection[]; text _ TiogaOps.GetRope[end.node]; text _ Rope.Substr[base: text, start: end.where + 1]; IF (i _ Rope.Find[text, "\n"]) # -1 THEN text _ Rope.Substr[base: text, len: i]; RememberPrivate.itIsNow _ Tempus.Parse[rope: text ! Tempus.Unintelligible => { err _ Rope.Concat["Unintelligible time: ", text]; GOTO Out; }].time; -- e.g. if message sent on Tuesday, not read until Thursday, and message identifies a time Thursday 2pm means that thursday. } ELSE { err _ "No Date field in message"; GOTO Out; }; IF TiogaOps.FindWord[viewer: mailViewer, rope: "Subject:", whichDir: anywhere] THEN { end: TiogaOps.Location; i: INT; [, , end] _ TiogaOps.GetSelection[]; text _ TiogaOps.GetRope[end.node]; text _ Rope.Substr[base: text, start: end.where + 1]; IF (i _ Rope.Find[text, "\n"]) # -1 THEN text _ Rope.Substr[base: text, len: i]; } ELSE { err _ "No Subject field in message"; GOTO Out; }; TiogaOps.RestoreSelB[]; sel _ ViewerTools.GetSelectionContents[]; IF Rope.Length[sel] = 1 THEN sel _ NIL; IF (i _ Rope.Find[sel, "/ "]) # -1 THEN { parameters _ Remember.ReadParameters[IO.RIS[Rope.Substr[base: sel, start: i + 1]]]; sel _ Rope.Substr[base: sel, len: i]; }; stream _ ViewerIO.CreateMessageWindowStream[]; -- EVERY time to work around bug Remember.RegisterEvent[ scanThis: Rope.Cat[sel, " | ", text], parameters: CONS[ NEW[Remember.Parameter _ [text, text]], CONS[NEW[Remember.Parameter _ [ message, Rope.Cat[mailViewer.name, " ", TiogaOps.GetRope[messageNode]] ]], parameters]], out: stream ! Remember.Error => { SELECT ec FROM timeRopeFormatError => err _ Rope.Concat["TimeAndDate is ", msg]; timeNotSpecified => err _ "Specify (select) a time for the event."; formatError => err _ Rope.Concat["Format Error: ", msg]; ENDCASE => REJECT; CONTINUE; }; ]; GOTO Out; EXITS Out => { IF err # NIL THEN { MessageWindow.Append[err, TRUE]; MessageWindow.Blink[]; } ELSE stream.Flush[]; RememberPrivate.itIsNow _ oldNow; }; END; }; PostRemindButton: PROC = TRUSTED { DO IF PrincOpsUtils.IsBound[LOOPHOLE[WalnutWindow.GetMsgName]] THEN { WalnutWindow.AddToMsgMenu[label: "Remind", proc: RemindProc]; RememberPrivate.walnutUser _ TRUE; EXIT; } ELSE IF PrincOpsUtils.IsBound[LOOPHOLE[PeanutWindow.AddCommand]] THEN { PeanutWindow.AddCommand[name: "Remind", proc: PeanutRemindProc, fork: FALSE]; RememberPrivate.peanutUser _ TRUE; EXIT; }; Process.Pause[Process.SecondsToTicks[10]]; ENDLOOP; }; remindMenu: Menus.Menu _ Menus.CreateMenu[]; reminderButtons: PUBLIC Menus.MenuEntry; Commander.Register["Remember", RememberProc, "Register a reminder. Form is: Remember ... at time"]; Menus.AppendMenuEntry[menu: remindMenu, entry: Menus.CreateEntry["Blinker", Blinker]]; Menus.AppendMenuEntry[menu: remindMenu, entry: Menus.CreateEntry["Relabel", Relabel]]; Menus.AppendMenuEntry[menu: remindMenu, entry: Menus.CreateEntry[name: "15 min/hour/day", proc: SnoozeAlarm, fork: FALSE]]; -- so if user clicks snooze and destroy rapidly, the snooze will happen before the destroy. Menus.AppendMenuEntry[menu: remindMenu, entry: Menus.CreateEntry[name: "NewTime", proc: ResetTime, fork: FALSE]]; Menus.AppendMenuEntry[menu: remindMenu, entry: Menus.CreateEntry[name: "Forget", proc: Cancel, fork: FALSE]]; reminderButtons _ Menus.GetLine[remindMenu, 0]; TRUSTED {Process.Detach[FORK PostRemindButton[]]}; END. όRememberOpsImpl.mesa Copyright c 1985 by Xerox Corporation. All rights reserved. Edited by Teitelman on September 15, 1983 11:01 am Converted by: Maxwell, January 6, 1984 2:01 pm Last Edited by: Pier, May 7, 1984 12:08:43 pm PDT Rick Beach, May 28, 1985 8:42:55 pm PDT Remember CommandProc Reminder buttons PROC [parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE] PROC [parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE] PROC [parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE] PROC [parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE] PROC [parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE] Interfacing with mail readers Initialization Κ ά– "Cedar" style˜codešΟc™Kšœ Οmœ1™ŸœŸœ ˜ΡKšœ:˜:K˜KšœŸœ˜KšŸœŸœ˜6KšœMŸœ˜SšŸœ˜KšœJŸœ˜h—Kšœ˜—Kšœ˜—K˜š‘œ˜KšŸœ ŸœŸœŸœŸœŸœ/ŸœŸœ ŸœŸœ™ƒKšœŸœ ˜.KšœŸœ)˜NKšœŸœ˜KšœŸœ˜KšœŸœ˜Kšœ-Ÿœ˜3KšŸœŸœ˜6KšŸœŸœ!˜;Kšœ˜——™KšœŸœŸœŸœ˜K˜š‘ œŸœ˜'KšœŸœ ˜.KšœŸœ˜"Kšœ#˜#KšœŸœ˜K˜0Kšœ.˜.K˜*K˜šœ˜K˜šŸœFŸœ˜NK˜KšœŸœ˜Kšœ$˜$Kšœ"˜"Kšœ5˜5KšŸœ"Ÿœ(˜Pšœ1˜1šœ˜Kšœ1Ÿœ˜;—šœ˜Kšœ|˜~——Kšœ˜—šŸœ˜K˜!KšŸœ˜ K˜—K˜šŸœIŸœ˜QK˜KšœŸœ˜Kšœ$˜$Kšœ"˜"Kšœ5˜5KšŸœ"Ÿœ(˜PKšœ˜—šŸœ˜K˜$KšŸœ˜ K˜—K˜šŸœ!Ÿœ˜)Kšœ%ŸœŸœ(˜SK˜%K˜—Kšœ/˜Nšœ˜Kšœ'˜'šœ Ÿœ˜KšŸœ$˜'KšŸœŸœ<˜D—Kšœ ˜ šœ˜šŸœŸ˜KšœB˜BKšœC˜CKšœ9˜9KšŸœŸœ˜—KšŸœ˜ K˜—Kšœ˜—KšŸœ˜ šŸ˜šœ ˜ šŸœŸœŸœ˜KšœŸœ˜ K˜K˜—KšŸœ˜K˜!K˜——Kšœ˜—Kšœ˜K˜—š‘œŸœ˜-KšœŸœ˜Kšœ#˜#KšœŸœ˜Kšœ!˜!K˜K˜K˜0K˜3KšŸ˜š ŸœŸœŸœ3ŸœŸœ˜[Kšœ<˜