RemindUI.mesa
Copyright Ó 1990, 1992 by Xerox Corporation. All rights reserved.
David Goldberg November 13, 1989 8:28:33 pm PST
Peter B. Kessler, January 10, 1990 10:46:47 am PST
Theimer, September 10, 1990 2:12 pm PDT
Last tweaked by Mike Spreitzer July 20, 1992 3:09 pm PDT
Willie-s, May 5, 1992 12:20 pm PDT
DIRECTORY
Basics USING [CompareInt],
BasicTime,
Commander USING [CommandProc, Register],
CommanderOps,
Convert USING [RopeFromTime, IntFromRope],
IO,
List USING [CompareProc, LORA, Sort],
Remind,
Rope,
TimeParse,
UserProfile USING [CallWhenProfileChanges, ProfileChangedProc, Number];
Commander commands for accessing Remind functionality. Also adds button to walnut (this in here mainly to save a GFI).
RemindUI: CEDAR PROGRAM
IMPORTS
Basics, BasicTime, Commander, CommanderOps, Convert, IO, List, Remind, Rope, TimeParse, UserProfile
= BEGIN OPEN Remind;
ROPE: TYPE = Rope.ROPE;
alNumRope: Rope.ROPE = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
Compare: List.CompareProc = {
PROC[ref1: REF ANY, ref2: REF ANY] RETURNS [Comparison];
p1, p2: REF TimeParse.PieceType;
p1 ¬ NARROW[ref1];
p2 ¬ NARROW[ref2];
RETURN[Basics.CompareInt[p1.start, p2.start]];
};
WhiteSpace: IO.BreakProc = {
RETURN[SELECT char FROM ' , '\n, '\t, '\l => sepr, ENDCASE => other];
};
StripTrailingNewline: PROC[inStr: Rope.ROPE] RETURNS [Rope.ROPE] = {
ch: CHAR;
ch ¬ Rope.Fetch[inStr, Rope.Length[inStr]-1];
IF ch = '\n OR ch = '\r THEN inStr ¬ Rope.Substr[inStr, 0, Rope.Length[inStr] - 1];
RETURN[inStr];
};
OldRemind: Commander.CommandProc = {
PROC [cmd: Handle] RETURNS [result: REFNIL, msg: ROPENIL];
time: BasicTime.GMT;
inStr: Rope.ROPE;
i: INT;
IF (i ¬ Rope.Find[cmd.commandLine, "/"]) # -1 THEN
inStr ¬ Rope.Substr[cmd.commandLine, 0, i]
ELSE
inStr ¬ cmd.commandLine;
inStr ¬ StripTrailingNewline[inStr];
time ¬ TimeParse.Parse[inStr, BasicTime.Now[] !
TimeParse.ParseError => {cmd.out.PutRope["Couldn't parse the time\n"]; GOTO done;};
].time;
CreateAndAddMeeting[time, inStr, cmd.out, IO.RIS[Rope.Substr[cmd.commandLine, i+1]], i # -1];
EXITS
done => NULL;
};
Remind: Commander.CommandProc = {
PROC [cmd: Handle] RETURNS [result: REFNIL, msg: ROPENIL];
time: BasicTime.GMT;
inStr, newStr: Rope.ROPE;
i: INT;
ok: BOOLEAN;
IF (i ¬ Rope.Find[cmd.commandLine, "/"]) # -1 THEN
inStr ¬ Rope.Substr[cmd.commandLine, 0, i]
ELSE
inStr ¬ cmd.commandLine;
inStr ¬ StripTrailingNewline[inStr];
[time, newStr, ok] ¬ RopeToTime[inStr, cmd.out];
IF ok THEN
CreateAndAddMeeting[time, inStr, cmd.out, IO.RIS[Rope.Substr[cmd.commandLine, i+1]], i # -1];
};
CreateAndAddMeeting: PROC[time: BasicTime.GMT, str: Rope.ROPE, outStrm, optStrm: IO.STREAM, options: BOOLEAN] = {
meeting: Meeting;
rmd: Reminder;
token1, token2: Rope.ROPE;
remindLeadTime, remindDuration: INT;
meeting ¬ NEW[MeetingRec];
meeting.duration ¬ defaultDuration;
remindDuration ¬ defaultRemindDuration;
remindLeadTime ¬ defaultRemindLeadTime;
outStrm.PutF1["Time parsed as %g\n", IO.time[time]];
IF options THEN {
WHILE TRUE DO {
ENABLE BadData => {outStrm.PutF1["Couldn't parse %g\n", IO.rope[token1]]; LOOP};
token1 ¬ IO.GetTokenRope[optStrm, WhiteSpace ! IO.EndOfStream => EXIT].token;
token2 ¬ IO.GetTokenRope[optStrm, WhiteSpace ! IO.EndOfStream => EXIT].token;
SELECT TRUE FROM
Rope.Equal[token1, "repeat", FALSE] => meeting.repeat ¬ RepetitionFromRope[token2];
Rope.Equal[token1, "duration", FALSE] => meeting.duration ¬ Convert.IntFromRope[token2];
Rope.Equal[token1, "RemindLeadTime", FALSE] => remindLeadTime ¬ Convert.IntFromRope[token2];
Rope.Equal[token1, "RemindDuration", FALSE] => remindDuration ¬ Convert.IntFromRope[token2];
Rope.Equal[token1, "more", FALSE] => meeting.more ¬ token2;
Rope.Equal[token1, "iconLabel", FALSE] => meeting.iconLabel ¬ token2;
Rope.Equal[token1, "iconFlavor", FALSE] => meeting.iconFlavor ¬ token2;
ENDCASE => outStrm.PutF1["Couldn't parse %g\n", IO.rope[token1]];
};
ENDLOOP;
};
meeting.explanation ← newStr;
meeting.explanation ¬ str;
meeting.start ¬ time;
rmd ¬ NEW [ReminderRecord ¬ [NIL, alert[
start: BasicTime.Update[time, -60*remindLeadTime],
stop: BasicTime.Update[time, 60*(-remindLeadTime + remindDuration)]
]]];
meeting.reminders ¬ LIST[rmd];
[] ¬ AddMeeting[meeting];
};
RopeToTime: PROC[str: Rope.ROPE, strm: IO.STREAM] RETURNS [time: BasicTime.GMT, newStr: Rope.ROPE, ok: BOOLEAN ¬ FALSE] = {
cnt, len: INT;
pieces: TimeParse.PiecesType;
piecesLora: List.LORA;
piece: REF TimeParse.PieceType;
ListToLora: PROC[ls: TimeParse.PiecesType] RETURNS [lr: List.LORA ¬ NIL] = {
WHILE ls # NIL DO
lr ¬ CONS[ls.first, lr];
ls ¬ ls.rest;
ENDLOOP;
};
BEGIN
ENABLE TimeParse.ParseError => {
SELECT errorType FROM
noTime => IO.PutRope[strm, "No time specified\n"];
yearOrMonthButNoDay => IO.PutRope[strm, "Doesn't make sense to have year/month but no day\n"];
yearButNoMonth => IO.PutRope[strm, "Doesn't make sense to have year but no month\n"];
dayWeekdayMismatch => IO.PutRope[strm, "Mismatch between weekday and date\n"];
badYearInSlash => IO.PutRope[strm, "mm/dd/yy has invalid value for year\n"];
twoYears => IO.PutRope[strm, "The year is specified in two different ways\n"];
ENDCASE => NULL;
GOTO done;
};
[time, pieces] ¬ TimeParse.Parse[str, BasicTime.Now[]];
cnt ¬ 0;
piecesLora ¬ List.Sort[ListToLora[pieces], Compare];
WHILE piecesLora # NIL DO
piece ¬ NARROW[piecesLora.first];
len ¬ piece.len;
if there is only punctuation between two pieces, remove the punctuation
IF piecesLora.rest # NIL AND Rope.SkipTo[str, piece.start - cnt + len, alNumRope] >= NARROW[pieces.rest.first, REF TimeParse.PieceType].start - cnt THEN
len ¬ NARROW[pieces.rest.first, REF TimeParse.PieceType].start - piece.start;
str ¬ Rope.Replace[str, piece.start - cnt, len];
cnt ¬ cnt + len;
piecesLora ¬ piecesLora.rest;
ENDLOOP;
ok ¬ TRUE;
EXITS
done => NULL;
END;
newStr ¬ str;
};
DeleteByTime: Commander.CommandProc = {
PROC [cmd: Handle] RETURNS [result: REFNIL, msg: ROPENIL];
time: BasicTime.GMT;
ok: BOOLEAN;
cnt: INT ¬ 0;
ls: LIST OF Meeting;
[time: time, ok: ok] ¬ RopeToTime[cmd.commandLine, cmd.out];
IF ok THEN {
ls ¬ ListMeetings[time, time];
FOR aList: LIST OF Meeting ¬ ls, aList.rest UNTIL aList = NIL DO
IF aList.first.start = time THEN
{IF DeleteMeeting[aList.first.start, aList.first.uniqID] THEN cnt ¬ cnt + 1;};
ENDLOOP;
};
cmd.out.PutF["Deleted %g meeting%g\n", IO.int[cnt], IO.char[IF cnt = 1 THEN ' ELSE 's]];
};
DeleteAll: Commander.CommandProc = {
PROC [cmd: Handle] RETURNS [result: REFNIL, msg: ROPENIL];
cnt: INT ¬ 0;
ls: LIST OF Meeting;
str: Rope.ROPE;
IO.PutRope[cmd.out, "Really Delete everything? "];
str ¬ IO.GetLineRope[cmd.in];
IF Rope.Fetch[str, 0] = 'y OR Rope.Fetch[str, 0] = 'Y THEN {
cnt ¬ DeleteAllMeetings[];
cmd.out.PutF["Deleted %g meeting%g\n", IO.int[cnt], IO.char[IF cnt = 1 THEN ' ELSE 's]];
};
};
Prune: Commander.CommandProc = {
PROC [cmd: Handle] RETURNS [result: REFNIL, msg: ROPENIL];
cnt: INT ¬ 0;
ls: LIST OF Meeting;
str: Rope.ROPE;
time: BasicTime.GMT;
IF CommanderOps.NumArgs[cmd] > 1 THEN {
time ¬ TimeParse.Parse[cmd.commandLine, BasicTime.Now[] ! TimeParse.ParseError =>
{cmd.out.PutRope["Couldn't parse the time\n"]; GOTO done;};
].time
}
ELSE {
time ¬ BasicTime.Now[];
time ¬ TimeParse.Adjust[baseTime: time, precisionOfResult: days].time;
};
IO.PutF1[cmd.out, "This will delete all one-time meetings Previous to %g, OK? ", IO.time[time]];
str ¬ IO.GetLineRope[cmd.in];
IF Rope.Fetch[str, 0] = 'y OR Rope.Fetch[str, 0] = 'Y THEN {
ls ¬ ListMeetings[from: time.Update[-86400*365*2], to: time, all: FALSE];
FOR aList: LIST OF Meeting ¬ ls, aList.rest UNTIL aList = NIL DO
IF aList.first.repeat = once AND DeleteMeeting[aList.first.start, aList.first.uniqID, FALSE] THEN {
cnt ¬ cnt + 1;
cmd.out.PutRope["*"];
}
IF aList.first.repeat = once THEN {cmd.out.PutF["Deleted %g\n", IO.rope[aList.first.explanation]]; cnt ← cnt + 1};
ENDLOOP;
cmd.out.PutF["\nDeleted %g meeting%g\n", IO.int[cnt], IO.char[IF cnt = 1 THEN ' ELSE 's]];
cmd.out.PutRope["Compacting Log.... "];
CompactDb[];
cmd.out.PutRope["\n"];
CloseDb[];
};
EXITS
done => NULL;
};
Compact: Commander.CommandProc = {
PROC [cmd: Handle] RETURNS [result: REFNIL, msg: ROPENIL];
cmd.out.PutRope["Compacting Log.... "];
CompactDb[];
cmd.out.PutRope["\n"];
CloseDb[];
};
CompareUsingStart: List.CompareProc = {
PROC [ref1: REF ANY, ref2: REF ANY] RETURNS [Comparison]
m1, m2: Meeting;
m1 ¬ NARROW[ref1];
m2 ¬ NARROW[ref2];
RETURN[Basics.CompareInt[0, BasicTime.Period[m1.start, m2.start]]];
};
Read: Commander.CommandProc = {
PROC [cmd: Handle] RETURNS [result: REFNIL, msg: ROPENIL];
argv: CommanderOps.ArgumentVector ~ CommanderOps.Parse[cmd];
ls: LIST OF Meeting;
lsLora: List.LORA;
ListToLora: PROC[ls: LIST OF Meeting] RETURNS [lr: List.LORA ¬ NIL] = {
WHILE ls # NIL DO
lr ¬ CONS[ls.first, lr];
ls ¬ ls.rest;
ENDLOOP;
};
SELECT argv.argc FROM
1, 2 => {
date: BasicTime.GMT ~ IF argv.argc>1
THEN TimeParse.Parse[argv[1], BasicTime.Now[], heuristic, FALSE, FALSE].time
ELSE BasicTime.Now[];
upkd: BasicTime.Unpacked ¬ date.Unpack[];
start: BasicTime.GMT;
upkd.hour ¬ upkd.minute ¬ upkd.second ¬ 0;
start ¬ upkd.Pack[];
ls ¬ ListMeetings[start, start.Update[86399]]};
3 => {
from: BasicTime.GMT ~ TimeParse.Parse[argv[1], BasicTime.Now[], heuristic, FALSE, FALSE].time;
to: BasicTime.GMT ~ TimeParse.Parse[argv[2], BasicTime.Now[], heuristic, FALSE, FALSE].time;
ls ¬ ListMeetings[from, to]};
ENDCASE => CommanderOps.Failed[Rope.Cat["Usage: ", argv[0], " [date | from-time to-time]"]];
lsLora ¬ List.Sort[ListToLora[ls], CompareUsingStart];
FOR aList: List.LORA ¬ lsLora, aList.rest UNTIL aList = NIL DO
PrintMeeting[cmd.out, NARROW[aList.first]];
cmd.out.PutRope["\n"];
ENDLOOP;
RETURN};
PrintMeeting: PROC[strm: IO.STREAM, meeting: Meeting] = {
strm.PutF["start: %g\n", IO.rope[Convert.RopeFromTime[from: meeting.start, end: seconds]]];
strm.PutF1["start: %g\n", IO.rope[Convert.RopeFromTime[meeting.start]]];
strm.PutF1["duration: %g\n", IO.int[meeting.duration]];
strm.PutF1["explanation: %g\n", IO.rope[meeting.explanation]];
IF meeting.repeat # once THEN
strm.PutF1["repeated %g\n", IO.rope[RopeFromRepetition[meeting.repeat]]];
FOR rl: RemindList ¬ meeting.reminders, rl.rest WHILE rl#NIL DO
WITH rl.first SELECT FROM
x: REF ReminderRecord[alert] => strm.PutF[
"Alert Lead Time: %g\nAlert Duration: %g\n",
[integer[(BasicTime.Period[x.start, meeting.start] + 30)/60]],
[integer[(BasicTime.Period[x.start, x.stop] + 30)/60]]
];
x: REF ReminderRecord[mail] => strm.PutF[
"Mail Lead Time: %g, Recipient: %g\n",
[integer[(BasicTime.Period[x.when, meeting.start] + 30)/60]],
[rope[x.to]] ];
ENDCASE => ERROR;
ENDLOOP;
IF meeting.more # NIL AND NOT Rope.IsEmpty[meeting.more] THEN
strm.PutF1["More: %g\n", IO.rope[meeting.more]];
IF meeting.iconLabel # NIL AND NOT Rope.IsEmpty[meeting.iconLabel] THEN
strm.PutF1["Icon Label: %g\n", IO.rope[meeting.iconLabel]];
IF meeting.iconFlavor # NIL AND NOT Rope.IsEmpty[meeting.iconFlavor] THEN
strm.PutF1["Icon Flavor: %g\n", IO.rope[meeting.iconFlavor]];
};
UpdateDefaults: UserProfile.ProfileChangedProc = {
PROC [reason: UserProfile.ProfileChangeReason]
defaultRemindLeadTime ¬ UserProfile.Number["Remind.RemindLeadTime", 20];
defaultRemindDuration ¬ UserProfile.Number["Remind.RemindDuration", 20];
defaultDuration ¬ UserProfile.Number["Remind.Duration", 60];
};
GetParameters: PROC[meeting: Meeting, str: Rope.ROPE, outStrm: IO.STREAM] RETURNS [timeSpecified: BOOLEAN ¬ FALSE, remindDuration, remindLeadTime: INT]= {
token1, token2: Rope.ROPE;
optStrm: IO.STREAM ¬ IO.RIS[str];
remindDuration ¬ defaultRemindDuration;
remindLeadTime ¬ defaultRemindLeadTime;
meeting.duration ¬ defaultDuration;
WHILE TRUE DO {
ENABLE BadData => {outStrm.PutF1["Couldn't parse %g\n", IO.rope[token1]]; LOOP};
token1 ¬ IO.GetTokenRope[optStrm, WhiteSpace ! IO.EndOfStream => EXIT].token;
token2 ¬ IO.GetTokenRope[optStrm, WhiteSpace ! IO.EndOfStream => EXIT].token;
SELECT TRUE FROM
Rope.Equal[token1, "start", FALSE] => {
IF switchToNew THEN meeting.start ¬ TimeParse.Parse[token2, BasicTime.Now[]].time
ELSE
meeting.start ¬ TimeParse.Parse[token2, BasicTime.Now[]].time;
timeSpecified ¬ TRUE;
};
Rope.Equal[token1, "repeat", FALSE] => meeting.repeat ¬ RepetitionFromRope[token2];
Rope.Equal[token1, "duration", FALSE] => meeting.duration ¬ Convert.IntFromRope[token2];
Rope.Equal[token1, "RemindLeadTime", FALSE] => remindLeadTime ¬ Convert.IntFromRope[token2];
Rope.Equal[token1, "RemindDuration", FALSE] => remindDuration ¬ Convert.IntFromRope[token2];
Rope.Equal[token1, "more", FALSE] => meeting.more ¬ token2;
Rope.Equal[token1, "iconLabel", FALSE] => meeting.iconLabel ¬ token2;
Rope.Equal[token1, "iconFlavor", FALSE] => meeting.iconFlavor ¬ token2;
ENDCASE => outStrm.PutF1["Couldn't parse %g\n", IO.rope[token1]];
};
ENDLOOP;
};
ParseErrorTypeName: ARRAY TimeParse.ParseErrorType OF ROPE
~ [noTime: "noTime", yearOrMonthButNoDay: "yearOrMonthButNoDay", yearButNoMonth: "yearButNoMonth", dayWeekdayMismatch: "dayWeekdayMismatch", twoYears: "twoYears", badYearInSlash: "badYearInSlash"];
TestParse: Commander.CommandProc ~ {
argv: CommanderOps.ArgumentVector ~ CommanderOps.Parse[cmd];
direction: TimeParse.DirectionType ¬ heuristic;
insistTime, insistDay: BOOL ¬ TRUE;
FOR i: INT IN (0..argv.argc) DO
arg: ROPE ~ argv[i];
SELECT TRUE FROM
arg.Equal["heuristic"] => direction ¬ heuristic;
arg.Equal["forward"] => direction ¬ forward;
arg.Equal["backward"] => direction ¬ backward;
arg.Equal["-insistTime"] => insistTime ¬ FALSE;
arg.Equal["+insistTime"] => insistTime ¬ TRUE;
arg.Equal["-insistDay"] => insistDay ¬ FALSE;
arg.Equal["+insistDay"] => insistDay ¬ TRUE;
ENDCASE => {
now: BasicTime.GMT ~ BasicTime.Now[];
time: BasicTime.GMT;
pieces: TimeParse.PiecesType;
[time, pieces] ¬ TimeParse.Parse[arg, now, direction, insistTime, insistDay ! TimeParse.ParseError => {
cmd.err.PutF1["ParseError[%g]\n", [rope[ParseErrorTypeName[errorType]]] ];
GOTO Not}];
cmd.out.PutRope[Convert.RopeFromTime[from: time, end: seconds]];
FOR pieces ¬ pieces, pieces.rest WHILE pieces#NIL DO
cmd.out.PutF[" [%g, %g]", [integer[pieces.first.start]], [integer[pieces.first.len]] ];
ENDLOOP;
cmd.out.PutRope["\n"];
EXITS Not => NULL};
ENDLOOP;
RETURN};
PrecisionNames: ARRAY TimeParse.Precision OF ROPE
~ [years: "years", months: "months", days: "days", hours: "hours", minutes: "minutes", seconds: "seconds", unspecified: "unspecified"];
TestTempusParse: Commander.CommandProc ~ {
argv: CommanderOps.ArgumentVector ~ CommanderOps.Parse[cmd];
search: BOOL ¬ TRUE;
FOR i: INT IN (0..argv.argc) DO
arg: ROPE ~ argv[i];
SELECT TRUE FROM
arg.Equal["-search"] => search ¬ FALSE;
arg.Equal["+search"] => search ¬ TRUE;
ENDCASE => {
now: BasicTime.GMT ~ BasicTime.Now[];
time: BasicTime.GMT;
precision: TimeParse.Precision;
start, length: NAT;
[time, precision, start, length] ¬ TimeParse.TempusParse[arg, now, search ! TimeParse.ParseError => {
cmd.err.PutF1["ParseError[%g]\n", [rope[ParseErrorTypeName[errorType]]] ];
GOTO Not}];
cmd.out.PutFL["time=%g, precision=%g, start=%g, length=%g\n",
LIST[[rope[Convert.RopeFromTime[from: time, end: seconds]]],
[rope[PrecisionNames[precision]]],
[integer[start]], [integer[length]]] ];
EXITS Not => NULL};
ENDLOOP;
RETURN};
defaultRemindLeadTime, defaultRemindDuration, defaultDuration: INT ¬ 0;
walnutUser: BOOLEAN ¬ FALSE;
peanutUser: BOOLEANFALSE;
switchToNew: BOOLEAN ¬ FALSE;
UserProfile.CallWhenProfileChanges[UpdateDefaults];
Commander.Register[
key: "NewRemind",
proc: Remind,
doc: "Add item to database in style of \"remember\" using new parser" ];
Commander.Register[
key: "remind",
proc: OldRemind,
doc: "Add item to database in style of \"remember\" using tempus parser" ];
Commander.Register[
key: "DeleteByTimeFromCal",
proc: DeleteByTime,
doc: "Delete all meetings at given time from database" ];
Commander.Register[
key: "DeleteAllFromCalendar",
proc: DeleteAll,
doc: "Delete all meetings" ];
Commander.Register[
key: "PruneCalendar",
proc: Prune,
doc: " CalPrune [Date]\n\tDelete old meetings and then Compact the loganberry files" ];
Commander.Register[
key: "PackCalendar",
proc: Compact,
doc: " Compact the loganberry files" ];
Commander.Register[
key: "ShowCalendar",
proc: Read,
doc: "[date | from to] --- print contents of database" ];
Commander.Register[
key: "TimeParse.Parse",
proc: TestParse,
doc: "(heuristic|forward|backward|(+|-)(insistTime|insistDay)|<str>)* --- test TimeParse.Parse" ];
Commander.Register[
key: "TimeParse.TempusParse",
proc: TestTempusParse,
doc: "((+|-)search|<rope>)* --- test TimeParse.TempusParse" ];
END.