-- /ivy/binding/remember/rememberImplB.mesa -- Last edited by: Binding, August 22, 1984 3:56:07 pm PDT DIRECTORY Atom USING [MakeAtom, GetProp, PutProp, RemProp], CommandTool USING[DoCommand], BasicTime USING[nullGMT, earliestGMT], FS USING [Error], Icons USING [IconFlavor], IconRegistry USING [GetIcon], IO USING[ BreakProc, IDProc, EndOfStream, Flush, GetChar, GetTokenRope, PutF, RIS, STREAM, time], Menus USING [SetLine, GetNumberOfLines, MenuEntry], MessageWindow USING [Append, Blink], Process USING[Pause, SecondsToTicks, Detach], RememberDefs, RememberDefsPrivate, Rope USING[ROPE, Cat, Concat, Equal, Substr, Find, Size], Tempus USING [ defaultTime, Error, MakeRope, Packed, PackedSeconds, Seconds, PackedToSeconds, SecondsToPacked, Unintelligible], TiogaExtraOps USING [GetFile], TiogaOps USING [SetStyle, Break, SetNodeFormat, Ref, FirstChild, Jump, GetRope, CaretAfter, SaveSpanForPaste, LastLocWithin, SelectDocument, Paste, Next, LockSel, UnlockSel, SaveSelB, RestoreSelB, ViewerDoc], ViewerClasses USING[Viewer], ViewerSpecs USING [openBottomY], ViewerTools USING[MakeNewTextViewer, SetContents], ViewerOps USING[AddProp, PaintViewer, BlinkIcon, DestroyViewer, FindViewer], WalnutDisplayerOps USING [StuffMsgContents] ; RememberImplB: CEDAR MONITOR LOCKS RememberDefsPrivate.protData IMPORTS Atom, BasicTime, CommandTool, FS, IconRegistry, IO, Menus, MessageWindow, Process, RememberDefsPrivate, Rope, Tempus, TiogaExtraOps, TiogaOps, ViewerOps, ViewerSpecs, ViewerTools, WalnutDisplayerOps EXPORTS RememberDefsPrivate = BEGIN OPEN RememberDefs, RememberDefsPrivate, RememberDefsPrivate.protData; GetEventsFromHickory: PROC = BEGIN -- wakes up every hour and looks into the data base for new events DO -- forever Process.Pause[ Process.SecondsToTicks[ 2*3600]]; Construct[]; ENDLOOP; END; --GetEventsFromHickory EventMinder: PROC = { DO EnterEventMinder[itIsNow]; Process.Pause[Process.SecondsToTicks[60]]; -- every minute ENDLOOP; }; EnterEventMinder: PUBLIC ENTRY PROC [itIsNow: Packed, itIsLater: Packed _ BasicTime.nullGMT] = { ENABLE UNWIND => NULL; now: PackedSeconds _ Tempus.PackedToSeconds[itIsNow]; later: PackedSeconds _ IF itIsLater#BasicTime.nullGMT THEN Tempus.PackedToSeconds[itIsLater] ELSE now; IF eventMinderDisabled OR now > later THEN RETURN; FOR el: LIST OF Event _ eventList, el.rest UNTIL el = NIL DO -- for each event. event: Event = el.first; startTime: PackedSeconds _ [event.timeToStartNotification - event.leadTime]; IF now > startTime AND event.repeat # NIL THEN { -- repeating event. compute most recent startTime, otherwise, if duration specified, may think this event has lapsed and there may be a later time which would satisfy. t1: PackedSeconds _ event.timeToStartNotification; IF itIsNow # Tempus.defaultTime THEN { --pretending with repeated event event.trueTimeToStartNotification _ event.timeToStartNotification; event.trueNextNotification _ event.nextNotification; event.restoreTrueTimes _ TRUE; }; DO ENABLE { Tempus.Error, Tempus.Unintelligible => GOTO Bogus; }; t1 _ IF event.nextNotification # 0 THEN event.nextNotification ELSE ComputeRepetitionInterval[event.repeat, t1]; IF t1 > now THEN {event.nextNotification _ t1; EXIT}; event.newStartTime _ TRUE; event.nextNotification _ [0]; event.timeToStartNotification _ startTime _ t1; REPEAT Bogus => LOOP; ENDLOOP; }; IF later > startTime THEN { event.justPretending _ (itIsNow # Tempus.defaultTime); IF event.newStartTime AND (event.durationTime = 0 OR now < startTime + event.durationTime OR (event.repeat # NIL AND later > event.nextNotification)) THEN { viewer: Viewer _ event.viewer; text: ROPE _ Rope.Cat["Time: ", Tempus.MakeRope[time: Tempus.defaultTime, includeDayOfWeek: TRUE], "\nEventTime: ", Tempus.MakeRope[time: Tempus.SecondsToPacked[startTime], includeDayOfWeek: TRUE], "\n", event.text]; event.newStartTime _ FALSE; event.blink _ TRUE; IF viewer = NIL OR viewer.destroyed THEN { viewer _ ViewerTools.MakeNewTextViewer[ info: [ name: event.text, data: text, icon: IconRegistry.GetIcon[IF el.first.iconFlavor # NIL THEN el.first.iconFlavor ELSE "Remember.defaultIcon", IconRegistry.GetIcon["Remember.defaultIcon", document]]], -- uses el.first.iconFlavor if it can be found, otherwise Remember.defaultIcon otherwise document. The reason for the NIL check instead of just looking up NIL and letting it fail is that somebody actually managed to register an icon for NIL. paint: FALSE]; Menus.SetLine[menu: viewer.menu, line: Menus.GetNumberOfLines[viewer.menu], entryList: reminderButtons]; }; IF event.message # NIL THEN { IF walnutUser THEN { IF NOT WalnutDisplayerOps.StuffMsgContents[viewer, event.message] THEN ViewerTools.SetContents[viewer: viewer, contents: Rope.Cat[text, "\n(Could not find message: \"", event.message, "\")\n"], paint: FALSE] } ELSE IF peanutUser THEN { fileAtom: ATOM; ropeIndex: INT; message: ROPE; mailViewer: ViewerClasses.Viewer; mailDoc: TiogaOps.Ref; stream: IO.STREAM = IO.RIS[event.message]; fileName: ROPE _ stream.GetTokenRope[IO.IDProc].token; IF (ropeIndex _ Rope.Find[s1: fileName, s2: "!", case: FALSE])#-1 THEN fileName _ Rope.Substr[base: fileName, start: 0, len: ropeIndex]; fileAtom _ Atom.MakeAtom[fileName]; [] _ IO.GetChar[stream]; -- the extra space message _ IO.GetTokenRope[stream, EveryThing ! IO.EndOfStream => CONTINUE].token; TRUSTED {mailDoc _ LOOPHOLE[Atom.GetProp[atom: fileAtom, prop: $Root]]}; IF mailDoc # NIL THEN NULL ELSE IF (mailViewer _ ViewerOps.FindViewer[fileName]) # NIL THEN { mailDoc _ TiogaOps.ViewerDoc[mailViewer]; Atom.RemProp[atom: fileAtom, prop: $Root]; -- if a reminder is posted from a mail file which does not have a viewer, thereby caching the reminder, and then user opens a viewer, invalidate the cache because user might edit the viewer. In other words, cache is used only during those periods where no viewer is around, and hence it is known that user is not editing the mail file } ELSE Atom.PutProp[atom: fileAtom, prop: $Root, val: mailDoc _ TiogaExtraOps.GetFile[fileName ! FS.Error => CONTINUE]]; IF mailDoc = NIL THEN ViewerTools.SetContents[viewer: viewer, contents: Rope.Cat[text, "\n(Could not find mail file: ", fileName, ") ", message], paint: FALSE] ELSE { node: TiogaOps.Ref _ TiogaOps.FirstChild[mailDoc]; DO IF Rope.Equal[TiogaOps.GetRope[node], message] THEN { ENABLE UNWIND => TiogaOps.UnlockSel[]; TiogaOps.LockSel[]; TiogaOps.SaveSelB[]; TiogaOps.SelectDocument[viewer: viewer, pendingDelete: FALSE]; TiogaOps.CaretAfter[]; TiogaOps.Break[]; --root node TiogaOps.SaveSpanForPaste[startLoc: [node, 0], endLoc: [node, Rope.Size[message]]]; TiogaOps.Paste[]; TiogaOps.Break[]; --first sibling of root TiogaOps.SetStyle[style: "Mail", which: root]; TiogaOps.SetNodeFormat[format: "header", node: TiogaOps.Next[TiogaOps.FirstChild[TiogaOps.ViewerDoc[viewer]]]]; TiogaOps.SaveSpanForPaste[startLoc: [node, Rope.Size[message]], endLoc: TiogaOps.LastLocWithin[node]]; TiogaOps.Paste[]; TiogaOps.Jump[viewer: viewer, loc: [TiogaOps.ViewerDoc[viewer], 0]]; viewer.newVersion _ FALSE; viewer.icon _ IconRegistry.GetIcon["Remember.defaultIcon", document]; TiogaOps.RestoreSelB[]; TiogaOps.UnlockSel[]; EXIT; } ELSE IF (node _ TiogaOps.Next[node]) = NIL THEN { text _ Rope.Cat[text, "\n(Could not find message: \"", message, "\"\n"]; text _ Rope.Cat[text, "in mail file: ", fileName, ")\n"]; ViewerTools.SetContents[viewer: viewer, contents: text, paint: FALSE]; EXIT; }; ENDLOOP; }; } ELSE ViewerTools.SetContents[viewer: viewer, contents: Rope.Cat[text, "\n(Walnut/Peanut not loaded, could not include message: ", event.message, ")\n"], paint: FALSE] ; }; IF event.iconLabel # NIL THEN ViewerOps.AddProp[viewer, $IconLabel, event.iconLabel]; ViewerOps.AddProp[viewer, $Remember, el.first]; -- for menus event.viewer _ viewer; ViewerOps.PaintViewer[viewer, all]; IF logEvents THEN { eventLogStream.PutF["(Following Reminder posted at %t.)\n", IO.time[itIsNow]]; LogEvent[event, eventLogStream]; eventLogStream.Flush[]; }; }; IF event.durationTime # 0 -- 0 means forever AND now > startTime + event.durationTime THEN { IF event.justPretending THEN NULL ELSE IF event.repeat # NIL THEN TRUSTED { IF event.nextNotification = 0 THEN ERROR; event.timeToStartNotification _ event.nextNotification; event.newStartTime _ TRUE; Process.Detach[FORK Save[]]; } ELSE IF NOT event.destroyed THEN TRUSTED { event.destroyed _ TRUE; Process.Detach[FORK Save[]]; }; IF NOT event.justPretending AND event.viewer # NIL AND NOT event.viewer.destroyed THEN -- destroy an existing viewer if a duration is specified and it has lapsed ViewerOps.DestroyViewer[event.viewer]; }; IF event.restoreTrueTimes THEN TRUSTED {Process.Detach[FORK Save[]]}; IF event.getSeriousAfter # 0 AND now > startTime + event.getSeriousAfter AND NOT event.destroyed AND NOT event.newStartTime THEN TRUSTED { IF event.viewer # NIL AND NOT event.viewer.destroyed AND event.viewer.iconic THEN -- waste the sucker [] _ CommandTool.DoCommand[commandLine: Rope.Cat["_ SpecialCache.NameIt[\"", event.viewer.name,"\"]"], parent: NIL]; MessageWindow.Append[Rope.Concat["Important! ", event.text], TRUE]; MessageWindow.Blink[]; }; IF event.viewer # NIL AND NOT event.viewer.destroyed AND event.viewer.iconic AND event.blink THEN { IF event.viewer.wy >= ViewerSpecs.openBottomY THEN { -- icon is not visible MessageWindow.Append[Rope.Concat["Reminder: ", event.text], TRUE]; MessageWindow.Blink[]; } ELSE ViewerOps.BlinkIcon[event.viewer]; }; }; ENDLOOP; }; EveryThing: IO.BreakProc = {RETURN[other]}; DisableEventMinder: PUBLIC ENTRY PROC = {eventMinderDisabled _ TRUE}; EnableEventMinder: PUBLIC ENTRY PROC = {eventMinderDisabled _ FALSE}; TRUSTED {Process.Detach[FORK EventMinder[]]}; TRUSTED { Process.Detach[ FORK GetEventsFromHickory[]]}; END. ZEvent Minder IF event.justPretending AND itIsNow = Tempus.defaultTime THEN { -- event posted as a result of just pretend event.justPretending _ FALSE; event.newStartTime _ TRUE; }; nextNotification is the first time past the now when the event will occur. It is kept (cached) in the event structure to avoid recomputation. It is only recomputed when now becomes > nextNotification. Note that the latest time of notification before now may or may not cause the event to be posted, depending on whether or not there is a duration, and whether it has lapsed. create a new viewer if there just occurred a new start time (the first qualifies) and its duration has not expired --strip off the version number because the "current" version won't last blink the viewer if there is an active one and it is iconic and the blinker is still active. INITIALLY Κ ˜J˜+J˜:J˜šΟk ˜ Jšœœ'˜1Jšœ œ ˜Jšœ œ˜&Jšœœ ˜Jšœœ˜Jšœ œ ˜JšœœFœœ˜aJšœœ(˜3Jšœœ˜$Jšœœ ˜-Jšœ ˜ Jšœ˜Jšœœœ*˜9Jšœœs˜Jšœœ ˜Jšœ œΒ˜ΠJšœœ ˜Jšœ œ˜ Jšœ œ!˜2Jšœ œ=˜LJšœœ˜+Jšœ˜J˜—šΟl œœ˜J˜Jšœ˜"J˜Jšœœœ”˜ΞJ˜Jšœ˜J˜—JšœœœB˜Nhead™ šΟnœœ˜"J˜BšœΟc ˜ J˜0J˜ Jšœ˜—šœ ˜J˜——šŸ œœ˜š˜Jšœ˜J˜:Jšœ˜—Jšœ˜—J˜šŸœœœœ;˜^šœœœœ˜Jšœ5˜5Jšœœœ#œ˜fJšœœ œœ˜2š œœœœœœ ˜QJ˜JšœL˜Lšœœœ +™kJšœœ™Jšœœ™J™—š œœœœ §˜ΩJšœ2˜2šœœ  ˜GJšœB˜BJšœ4˜4Jšœœ˜J˜—š˜šœ˜Jšœ'œ˜2J˜—Jšœœœœ.˜qJ™χJšœ œœ˜5Jšœœ˜Jšœ˜Jšœ/˜/š˜Jšœ œ˜—Jšœ˜—J˜J˜—šœœ˜Jšœs™sJšœ6˜6šœœœ&œœœ"œ˜œJšœ˜JšœœRœ_œ˜ΨJšœœ˜Jšœœ˜šœ œœœ˜,šœ'˜'šœ˜Jšœ˜Jšœ ˜ Jš œœœœœT ς˜›—Jšœœ˜—J˜hJ˜—šœœœ˜šœ œ˜Jšœœ=œƒœ˜ΠJ˜—šœœ œ˜Jšœ œ˜Jšœ œ˜Jšœ œ˜J˜!J˜Jš œœœœœ˜*Jšœ œœ˜6J™Gšœ5œ˜FJšœA˜A—Jšœ#˜#Jšœœ ˜+Jšœ œ#œœ˜QJšœ œ-˜HJšœ œœ˜šœœ1œœ˜BJšœ)˜)Jšœ, Ξ˜ϊJšœ˜—Jšœ[œ œ˜wJšœ œœ„œ˜Ÿšœ˜Jšœ2˜2š˜šœ-œ˜5Jšœœ˜&J˜J˜Jšœ7œ˜>J˜Jšœ  ˜J˜TJ˜Jšœ ˜)Jšœ.˜.Jšœo˜oJ˜fJ˜J˜EJšœœ˜JšœE˜EJ˜Jšœ˜Jšœ˜Jšœ˜—šœœ œœ˜2JšœH˜HJšœ9˜9Jšœ?œ˜FJšœ˜J˜—Jšœ˜—Jšœ˜—J˜—Jšœœœ˜¨J˜—Jšœœœ9˜VJšœ1  ˜>Jšœ˜J˜#šœ œ˜Jšœ<œ˜NJšœ ˜ J˜J˜—Jšœ˜—šœ ˜-šœ&œ˜/Jšœœ˜!š œœœœœ˜)Jšœœœ˜)Jšœ7˜7Jšœœ˜Jšœœ ˜J˜—š œœœœœ˜*Jšœœ˜Jšœœ ˜J˜—šœœœœœœœ J˜‘Jšœ&˜&—J˜——J™Jšœœœœ ˜Ešœœ)œœœœœœ˜Šš œœœœœ ˜fJšœoœ˜t—Jšœ=œ˜CJ˜Jšœ˜—Jšœ\™\šœœœœœœ œ˜cšœ,œ ˜KJšœ<œ˜BJ˜J˜—Jšœ#˜'J˜—J˜—Jšœ˜——J˜—J˜JšΟb œœœ ˜+J˜JšŸœ œœœ˜EJ˜Jš Ÿœœœœœ˜EJ˜Jšœ ™ J™Jšœœ˜-Jšœœ˜8J˜Jšœ˜——…—&2