-- /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;
Event Minder
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 event.justPretending AND itIsNow = Tempus.defaultTime THEN { -- event posted as a result of just pretend
event.justPretending ← FALSE;
event.newStartTime ← TRUE;
};
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];
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.
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 {
create a new viewer if there just occurred a new start time (the first qualifies) and its duration has not expired
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;
--strip off the version number because the "current" version won't last
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[];
};
blink the viewer if there is an active one and it is iconic and the blinker is still active.
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};
INITIALLY
TRUSTED {Process.Detach[FORK EventMinder[]]};
TRUSTED { Process.Detach[ FORK GetEventsFromHickory[]]};
END.