RememberOpsImpl.mesa
Copyright © 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
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];
Reminder buttons
Blinker: Menus.ClickProc = {
PROC [parent: REF ANY, clientData: REF ANY ← NIL, mouseButton: Menus.MouseButton ← red, shift: BOOL ← FALSE, control: BOOL ← FALSE]
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 = {
PROC [parent: REF ANY, clientData: REF ANY ← NIL, mouseButton: Menus.MouseButton ← red, shift: BOOL ← FALSE, control: BOOL ← FALSE]
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 = {
PROC [parent: REF ANY, clientData: REF ANY ← NIL, mouseButton: Menus.MouseButton ← red, shift: BOOL ← FALSE, control: BOOL ← FALSE]
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 = {
PROC [parent: REF ANY, clientData: REF ANY ← NIL, mouseButton: Menus.MouseButton ← red, shift: BOOL ← FALSE, control: BOOL ← FALSE]
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 = {
PROC [parent: REF ANY, clientData: REF ANY ← NIL, mouseButton: Menus.MouseButton ← red, shift: BOOL ← FALSE, control: BOOL ← FALSE]
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];
};
Interfacing with mail readers
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;
};
Initialization
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[]]};