RememberOpsImpl.mesa; 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
DIRECTORY
Commander USING [Register, CommandProc],
IO USING [BreakProc, STREAM, Flush, PutRope, SkipWhitespace, RIS, GetLineRope, GetTokenRope, EndOfStream],
Menus USING [AppendMenuEntry, ReplaceMenuEntry, MenuEntry, FindEntry, ClickProc, CreateEntry, GetLine, CreateMenu, Menu],
MessageWindow USING [Append, Blink],
PeanutWindow USING [AddCommand],
PrincOpsUtils USING [IsBound],
Process USING [Pause, Detach, SecondsToTicks],
RememberDefs USING [Error, Parameter, ReadParameters, RegisterEvent],
RememberDefsPrivate 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],
WalnutDisplayerOps USING [GetMsgName, msgMenu, msgMenuAvailable],
ViewerTools USING [GetSelectionContents],
ViewerClasses USING [Viewer]
;
RememberOpsImpl: CEDAR PROGRAM
IMPORTS Commander, IO, Menus, MessageWindow, PeanutWindow, PrincOpsUtils, Process, Rope, RememberDefs, RememberDefsPrivate, Tempus, TiogaOps, ViewerIO, ViewerOps, ViewerTools, WalnutDisplayerOps
EXPORTS RememberDefsPrivate
SHARES RememberDefsPrivate
= BEGIN OPEN RememberDefsPrivate;
ROPE: TYPE = Rope.ROPE;
Remember CommandProc
Remember: Commander.CommandProc = {
err, offender: ROPE;
h: IO.STREAM = IO.RIS[cmd.commandLine];
token, line: ROPENIL;
parameters: LIST OF REF RememberDefs.Parameter;
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 RememberDefs.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 ← RememberDefs.ReadParameters[IO.RIS[Rope.Substr[base: line, start: i + 1]]];
line ← Rope.Substr[base: line, len: i];
};
RememberDefs.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]};
Reminder buttons
Blinker: Menus.ClickProc -- [parent: REF ANY, clientData: REF ANY ← NIL, mouseButton: Menus.MouseButton ← red, shift: BOOL ← FALSE, control: BOOL ← FALSE] -- = {
viewer: ViewerClasses.Viewer = NARROW[parent];
event: 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 -- [parent: REF ANY, clientData: REF ANY ← NIL, mouseButton: Menus.MouseButton ← red, shift: BOOL ← FALSE, control: BOOL ← FALSE] -- = {
viewer: ViewerClasses.Viewer = NARROW[parent];
event: 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 -- [parent: REF ANY, clientData: REF ANY ← NIL, mouseButton: Menus.MouseButton ← red, shift: BOOL ← FALSE, control: BOOL ← FALSE] -- = {
viewer: ViewerClasses.Viewer = NARROW[parent];
event: 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[EventRecord ← event^];
event.repeat ← NIL;
event.nextNotification ← [0]; -- paranoid
AddEvent[event];
};
IF NOT shift THEN event.timeToStartNotification ← [MAX[Tempus.PackedToSeconds[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 Save[]]};
event.blink ← FALSE;
MessageWindow.Append[Rope.Concat["Reminder reset for ", Tempus.MakeRope[time: Tempus.SecondsToPacked[event.timeToStartNotification], includeDayOfWeek: TRUE]], TRUE];
};
ResetTime: Menus.ClickProc -- [parent: REF ANY, clientData: REF ANY ← NIL, mouseButton: Menus.MouseButton ← red, shift: BOOL ← FALSE, control: BOOL ← FALSE] -- = {
viewer: ViewerClasses.Viewer = NARROW[parent];
event: Event = NARROW[ViewerOps.FetchProp[viewer, $Remember]];
sel: ROPE = ViewerTools.GetSelectionContents[];
t: Tempus.Packed;
{t ← Tempus.Parse[rope: sel, baseTime: Tempus.SecondsToPacked[[MAX[Tempus.PackedToSeconds[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 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 -- [parent: REF ANY, clientData: REF ANY ← NIL, mouseButton: Menus.MouseButton ← red, shift: BOOL ← FALSE, control: BOOL ← FALSE] -- = {
viewer: ViewerClasses.Viewer = NARROW[parent];
event: 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 Save[]]};
IF mouseButton = blue THEN ViewerOps.DestroyViewer[viewer];
};
Interfacing with mail readers
stream: IO.STREAMNIL;
RemindProc: Menus.ClickProc = TRUSTED {
viewer: ViewerClasses.Viewer = NARROW[parent];
text, messageName, err, sel: ROPE;
parameters: LIST OF REF RememberDefs.Parameter;
i: INT;
oldNow: Tempus.Packed ← RememberDefsPrivate.itIsNow;
messageName ← WalnutDisplayerOps.GetMsgName[viewer];
sel ← ViewerTools.GetSelectionContents[];
TiogaOps.SaveSelB[];
BEGIN
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];
RememberDefsPrivate.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 ← RememberDefs.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
RememberDefs.RegisterEvent[
scanThis: Rope.Cat[sel, " | ", text],
parameters: CONS[
NEW[RememberDefs.Parameter ← [text, text]],
CONS[NEW[RememberDefs.Parameter ← [message, messageName]], parameters]],
out: stream
! RememberDefs.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[];
RememberDefsPrivate.itIsNow ← oldNow;
};
END;
};
PeanutRemindProc: Menus.ClickProc = TRUSTED {
text, err, sel: ROPE;
parameters: LIST OF REF RememberDefs.Parameter;
i: INT;
mailViewer: ViewerClasses.Viewer;
messageNode: TiogaOps.Ref;
start, end: TiogaOps.Location;
oldNow: Tempus.Packed ← RememberDefsPrivate.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];
RememberDefsPrivate.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 ← RememberDefs.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
RememberDefs.RegisterEvent[
scanThis: Rope.Cat[sel, " | ", text],
parameters: CONS[
NEW[RememberDefs.Parameter ← [text, text]],
CONS[NEW[RememberDefs.Parameter ←
[
message,
Rope.Cat[mailViewer.name, " ", TiogaOps.GetRope[messageNode]]
]],
parameters]],
out: stream
! RememberDefs.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[];
RememberDefsPrivate.itIsNow ← oldNow;
};
END;
};
PostRemindButton: PROC = TRUSTED {
DO
IF PrincOpsUtils.IsBound[WalnutDisplayerOps.GetMsgName] AND WalnutDisplayerOps.msgMenuAvailable THEN {
MenuEntry[WalnutDisplayerOps.msgMenu, "Remind", RemindProc];
RememberDefsPrivate.walnutUser ← TRUE;
EXIT;
}
ELSE IF PrincOpsUtils.IsBound[PeanutWindow.AddCommand] THEN {
PeanutWindow.AddCommand[name: "Remind", proc: PeanutRemindProc, fork: FALSE];
RememberDefsPrivate.peanutUser ← TRUE;
EXIT;
};
Process.Pause[Process.SecondsToTicks[10]];
ENDLOOP;
};
Miscellaneous
MenuEntry: PROC [menu: Menus.Menu, name: ROPE, proc: Menus.ClickProc] = {
entry: Menus.MenuEntry = Menus.CreateEntry[name: name, proc: proc, fork: FALSE];
oldEntry: Menus.MenuEntry = Menus.FindEntry[menu, name];
IF oldEntry = NIL THEN Menus.AppendMenuEntry[menu: menu, entry: entry]
ELSE Menus.ReplaceMenuEntry[menu: menu, oldEntry: oldEntry, newEntry: entry];
};
Initialization
remindMenu: Menus.Menu ← Menus.CreateMenu[];
reminderButtons: PUBLIC Menus.MenuEntry;
Commander.Register["Remember", Remember, "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.