RemindEditImpl.mesa
Copyright Ó 1990, 1992 by Xerox Corporation. All rights reserved.
David Goldberg December 6, 1989 10:37:53 pm PST
Peter B. Kessler, January 10, 1990 1:02:58 pm PST
Brian Oki, March 28, 1990 10:15 am PST
Kenneth A. Pier, April 19, 1990 11:44 am PDT
Theimer, September 18, 1990 7:47 pm PDT
Last tweaked by Mike Spreitzer August 27, 1993 8:56 am PDT
Willie-s, May 6, 1992 12:55 pm PDT
DIRECTORY
BasicTime,
Buttons USING [Button, ButtonProc, Create, ReLabel],
Containers,
Convert,
IO,
Menus USING [AppendMenuEntry, CreateEntry, CreateMenu, Menu, MenuProc],
PFS,
PFSCanonicalNames USING [ParseName, UnparseName],
PFSNames USING [Directory],
Remind,
RemindEdit,
Rope,
SimpleFeedback,
SystemNames USING [MachineName, UserCedarDir, UserName],
TimeParse USING [Adjust, Parse, ParseError],
UserProfile USING [Boolean, CallWhenProfileChanges, Number, ProfileChangedProc, Token],
VFonts,
ViewerClasses,
ViewerOps,
ViewerSpecs,
ViewerTools;
RemindEditImpl: CEDAR MONITOR
IMPORTS BasicTime, Buttons, Containers, Convert, IO, Menus, PFS, PFSCanonicalNames, PFSNames, Remind, Rope, SimpleFeedback, SystemNames, TimeParse, UserProfile, VFonts, ViewerOps, ViewerSpecs, ViewerTools
EXPORTS RemindEdit
= {
ROPE: TYPE = Rope.ROPE;
Viewer: TYPE ~ ViewerClasses.Viewer;
Meeting: TYPE = Remind.Meeting;
Reminder: TYPE ~ Remind.Reminder;
ReminderRecord: TYPE ~ Remind.ReminderRecord;
Categories: TYPE = {start, duration, explanation, repeat, more, leadtime, remindduration, mailto};
SingleCategories: TYPE ~ Categories[start..more];
MultiCategories: TYPE ~ Categories[leadtime..mailto];
EditData: TYPE = REF EditDataRec;
EditDataRec: TYPE = RECORD [
outerViewer: Viewer ¬ NIL, -- handle for the enclosing container
inputs: ARRAY SingleCategories OF InputViewers, -- for user input
multis: MultupleList ¬ NIL,
privacyButton: Buttons.Button ¬ NIL,
ref: REF Viewer, -- kludge because can't modify viewers data
oldMtg: Meeting, --already in DB
newMtg: Meeting, --being edited
seminarViewer: Viewer ¬ NIL, -- Used for seminar meetings to pass in the viewer containing the seminar announcement message to send.
name: ROPE,
edge: INTEGER ¬ 0, -- wx for next component
bot: INTEGER ¬ 0 -- wy for next component
];
InputViewers: TYPE ~ RECORD [
prompt: Buttons.Button, -- prompting button
input: Viewer -- Text Box for user input
];
Multuple: TYPE ~ RECORD [
rmd: Reminder,
delete: Buttons.Button,
iv: ARRAY MultiCategories OF InputViewers];
MultupleList: TYPE ~ LIST OF Multuple;
RemindData: TYPE ~ REF RemindDataRecord;
RemindDataRecord: TYPE ~ RECORD [editData: EditData, rmd: Reminder];
entryVSpace: CARDINAL = 3;  -- vertical leading space between lines
entryHeight: CARDINAL = 15; -- how tall to make each line of items
botSpace: INTEGER = 1; --extra space at the bottom
secsPerDay: INT ~ 60*60*24;
secsPerWeek: INT ~ secsPerDay*7;
CommandMeetingLength: INT = 15;
This is the smallest duration that will show up on smaller CalendarTool viewer sizes.
doAlert, doMail: BOOL ¬ TRUE;
leadTime: INT ¬ 0; -- value of Remind.RemindLeadTime
remindDuration: INT ¬ 10; -- value of Remind.RemindDuration
mailLeadTime: INT ¬ 0; -- value of Remind.MailLeadTime
mailRecipient: ROPE ¬ "???@???"; -- value of Remind.MailTo
seminarMsgFormName: ROPE ¬ "/cedar/forms/SeminarMsg.form";
dfFileDirectory: ROPE ¬ NIL; -- Directory in which the CalendarTool DF file resides.
SpecifyMeeting: PUBLIC PROC[proto: Remind.Meeting, existing: BOOL, dbName: ROPE, column: ViewerClasses.Column] = {
meeting: Remind.Meeting ¬ proto;
editData: EditData ¬ NEW[EditDataRec];
myMenu: Menus.Menu ¬ Menus.CreateMenu[];
nLines: INTEGER;  -- number of lines in tool
meetingName: ROPE;
estWH: INTEGER;
IF NOT existing THEN {
IF meeting.type = meeting OR meeting.type = seminar THEN {
IF doAlert THEN meeting.reminders ¬ CONS[
NEW[ReminderRecord ¬ [NIL, alert[
start: TimeParse.Adjust[baseTime: meeting.start, minutes: -leadTime].time,
stop: TimeParse.Adjust[baseTime: meeting.start, minutes: remindDuration-leadTime].time
]]],
meeting.reminders];
IF doMail THEN meeting.reminders ¬ CONS[
NEW[ReminderRecord ¬ [NIL, mail[
when: TimeParse.Adjust[baseTime: meeting.start, minutes: -mailLeadTime].time,
to: mailRecipient
]]],
meeting.reminders];
IF meeting.type = meeting THEN {
meetingName ¬ "Create New Meeting";
IF meeting.explanation=NIL THEN meeting.explanation ¬ "Explanation";
}
ELSE {
meetingName ¬ "Create New Seminar";
IF meeting.explanation=NIL THEN meeting.explanation ¬ "Subject";
};
}
ELSE { -- command; protectedCmd can't happen
IF doAlert THEN meeting.reminders ¬ CONS[
NEW[ReminderRecord ¬ [NIL, alert[
start: meeting.start,
stop: meeting.start
]]],
meeting.reminders];
meetingName ¬ "Create New Command";
IF meeting.explanation=NIL THEN meeting.explanation ¬ "Command";
};
}
ELSE {
meetingName ¬ meeting.explanation;
};
nLines ¬ 4 --Start, Explanation(2), Repeat--;
IF meeting.type=meeting OR meeting.type=seminar THEN {
nLines ¬ nLines+1--Duration--;
FOR rl: Remind.RemindList ¬ meeting.reminders, rl.rest WHILE rl#NIL DO
nLines ¬ nLines+1;
ENDLOOP};
IF meeting.more#NIL AND meeting.type=meeting THEN nLines ¬ nLines + 2;
Menus.AppendMenuEntry[
menu: myMenu,
entry: Menus.CreateEntry[name: "OK", proc: OKFunction, clientData: editData]
];
Menus.AppendMenuEntry[
menu: myMenu,
entry: Menus.CreateEntry[name: "Add Alert", proc: AddAlertFunction, clientData: editData]
];
Menus.AppendMenuEntry[
menu: myMenu,
entry: Menus.CreateEntry[name: "Add Mail", proc: AddMailFunction, clientData: editData]
];
editData.oldMtg ¬ IF existing THEN proto ELSE NIL;
editData.newMtg ¬ IF existing THEN NEW [Remind.MeetingRec ¬ proto­] ELSE proto;
editData.name ¬ dbName;
estWH ¬ nLines*(entryHeight + entryVSpace) + botSpace + ViewerSpecs.windowBorderSize*2 + ViewerSpecs.captionHeight + ViewerSpecs.menuHeight + ViewerSpecs.menuBarHeight;
editData.outerViewer ¬ Containers.Create[info: [
name: meetingName, 
data: editData,
openHeight: estWH,
iconic: FALSE,
column: column,
menu: myMenu,
icon: rememberIcon,
scrollable: FALSE]];
editData.inputs[start].prompt ¬ MakeButton[editData, "Start:"];
editData.inputs[start].input ¬ MakeViewer[editData, Convert.RopeFromTime[from: meeting.start, includeDayOfWeek: TRUE]];
IF meeting.type=meeting OR meeting.type=seminar THEN {
editData.inputs[duration].prompt ¬ MakeButton[editData, "Duration:"];
editData.inputs[duration].input ¬ MakeViewer[editData, Convert.RopeFromInt[meeting.duration]];
};
editData.inputs[explanation].prompt ¬ MakeButton[editData,
SELECT meeting.type FROM
meeting => "Explanation:",
command => "Command:",
seminar => "Seminar subject:",
protectedCmd => "Protected cmd:",
ENDCASE => ERROR];
editData.inputs[explanation].input ¬ MakeViewer[editData, meeting.explanation, 2];
editData.privacyButton ¬ MakePrivacyToggle[editData];
editData.inputs[repeat].prompt ¬ MakeButton[editData, "repeat:", editData.edge];
editData.inputs[repeat].input ¬ MakeViewer[editData, Remind.RopeFromRepetition[meeting.repeat]];
IF meeting.more # NIL AND meeting.type=meeting THEN {
editData.inputs[more].prompt ¬ MakeButton[editData, "more:"];
editData.inputs[more].input ¬ MakeViewer[editData, meeting.more, 2];
};
IF meeting.type=meeting OR meeting.type=seminar THEN {
FOR rl: Remind.RemindList ¬ meeting.reminders, rl.rest WHILE rl#NIL DO
AddReminder[editData, rl.first];
ENDLOOP;
};
ViewerTools.SetSelection[editData.inputs[explanation].input, NIL];
IF meeting.type=seminar THEN {
info: ViewerClasses.ViewerRec;
msgViewer: Viewer;
ut: BasicTime.Unpacked ¬ BasicTime.Unpack[meeting.start];
secs: CARD;
dateRope, dateRope1: ROPE;
info.name ¬ "Seminar Msg";
info.iconic ¬ FALSE;
info.file ¬ seminarMsgFormName;
msgViewer ¬ ViewerTools.MakeNewTextViewer[info];
ViewerOps.SetNewFile[msgViewer];
dateRope ¬ Rope.Cat[Convert.RopeFromCard[ut.year], "-", Convert.RopeFromCard[ORD[ut.month]+1], "-", Convert.RopeFromCard[ut.day]];
dateRope ¬ Rope.Cat[dateRope, "-", Convert.RopeFromCard[ut.hour], "-", Convert.RopeFromCard[ut.minute]];
dateRope1 ¬ Rope.Cat[dateRope, "-", Convert.RopeFromCard[ut.second]];
msgViewer.file ¬ Rope.Cat[dfFileDirectory, dateRope1, ".tioga"];
secs ¬ ut.second;
WHILE PFS.FileLookup[PFS.PathFromRope[msgViewer.file], NIL] # NIL DO
A file already exists with this name. Advance the date by a second to get a new file name. Note: It doesn't matter if the new date generated is a legal date; so long as the file name generated is unique.
secs ¬ secs + 1;
dateRope1 ¬ Rope.Cat[dateRope, "-", Convert.RopeFromCard[secs]];
msgViewer.file ¬ Rope.Cat[dfFileDirectory, dateRope1, ".tioga"];
ENDLOOP;
editData.seminarViewer ¬ msgViewer;
};
};
MakePrivacyToggle: PROC [editData: EditData, wx: INTEGER ¬ 0] RETURNS [b : Buttons.Button] = {
b ¬ Buttons.Create[
info: [
name: "private",
wh: entryHeight,
wx: wx,
wy: editData.bot-1,
parent: editData.outerViewer,
border: TRUE ],
proc: TogglePrivacy,
clientData: editData];
IF editData.newMtg.public THEN Buttons.ReLabel[b, "public"];
editData.edge ¬ b.wx + b.ww + 13;
RETURN};
TogglePrivacy: PROC [parent: Viewer, clientData: REF ANY ¬ NIL, mouseButton: ViewerClasses.MouseButton ¬ red, shift, control: BOOL ¬ FALSE] = {
ed: EditData = NARROW[clientData];
ed.newMtg.public ¬ NOT ed.newMtg.public;
Buttons.ReLabel[ed.privacyButton, IF ed.newMtg.public THEN "public" ELSE "private"];
RETURN};
MakeButton: PROC [editData: EditData, name: ROPE, wx: INTEGER ¬ 0] RETURNS [b : Buttons.Button] = {
viewRef: REF Viewer ¬ NEW[Viewer];
b ¬ Buttons.Create[
info: [
name: name,
default the width so that it will be computed for us --
wh: entryHeight, -- specify rather than defaulting so line is uniform
wx: wx,
wy: editData.bot-1,
parent: editData.outerViewer,
border: FALSE ],
proc: Prompt,
clientData: viewRef]; -- this will be passed to our button proc
editData.ref ¬ viewRef;
editData.edge ¬ b.wx + b.ww + 3;
RETURN};
MakeViewer: PROC [editData: EditData, viewerData: ROPE, nLines: INT ¬ 1, w: INTEGER ¬ INTEGER.LAST] RETURNS [v: Viewer] = { --the previous MakeButton was for this text viewer
v ¬ ViewerTools.MakeNewTextViewer[
info: [
parent: editData.outerViewer,
wx: editData.edge,
wy: editData.bot,
ww: MIN[editData.outerViewer.cw - editData.edge, w],
wh: entryHeight*nLines + entryVSpace*(nLines-1),
data: viewerData,
scrollable: nLines>1,
border: FALSE]];
IF w=INTEGER.LAST
THEN {Containers.ChildXBound[editData.outerViewer, v];
editData.bot ¬ editData.bot + entryVSpace + entryHeight*nLines}
ELSE editData.edge ¬ v.wx + v.ww + 6;
Can't set button's clientData directly, need to use this indirect method
editData.ref­ ¬ v;
};
DeleteReminder: Buttons.ButtonProc ~ {
rd: RemindData ~ NARROW[clientData];
editData: EditData ~ rd.editData;
m: Multuple ~ FindMultuple[editData.multis, rd.rmd];
prevRL: Remind.RemindList ¬ NIL;
prevML: MultupleList ¬ NIL;
y0: INTEGER ~ m.iv[leadtime].input.wy;
dy: INTEGER ~ -(entryVSpace+entryHeight);
FixViewer: PROC [v: Viewer] ~ {
IF v#NIL AND v.wy>y0 THEN ViewerOps.MoveViewer[v, v.wx, v.wy+dy, v.ww, v.wh, FALSE];
RETURN};
FOR curRL: Remind.RemindList ¬ editData.newMtg.reminders, curRL.rest WHILE curRL#NIL DO
IF curRL.first = rd.rmd THEN {
IF prevRL#NIL
THEN prevRL.rest ¬ curRL.rest
ELSE editData.newMtg.reminders ¬ curRL.rest;
EXIT};
prevRL ¬ curRL;
REPEAT
FINISHED => ERROR
ENDLOOP;
ViewerOps.DestroyViewer[m.delete, FALSE];
FOR i: MultiCategories IN MultiCategories DO
IF m.iv[i].prompt#NIL THEN ViewerOps.DestroyViewer[m.iv[i].prompt, FALSE];
IF m.iv[i].input#NIL THEN ViewerOps.DestroyViewer[m.iv[i].input, FALSE];
ENDLOOP;
FOR curML: MultupleList ¬ editData.multis, curML.rest WHILE curML#NIL DO
IF curML.first.rmd = rd.rmd THEN {
IF prevML#NIL
THEN prevML.rest ¬ curML.rest
ELSE editData.multis ¬ curML.rest;
}
ELSE {
FOR i: MultiCategories IN MultiCategories DO
FixViewer[curML.first.iv[i].prompt];
FixViewer[curML.first.iv[i].input];
ENDLOOP;
FixViewer[curML.first.delete]};
prevML ¬ curML;
ENDLOOP;
editData.bot ¬ editData.bot + dy;
ViewerOps.SetOpenHeight[editData.outerViewer, editData.bot + botSpace];
ViewerOps.ComputeColumn[editData.outerViewer.column];
ViewerOps.PaintViewer[editData.outerViewer, all];
RETURN};
FindMultuple: PROC [ml: MultupleList, rmd: Reminder] RETURNS [Multuple] ~ {
FOR ml ¬ ml, ml.rest WHILE ml#NIL DO
IF ml.first.rmd = rmd THEN RETURN [ml.first];
REPEAT
FINISHED => ERROR;
ENDLOOP;
};
Prompt: Buttons.ButtonProc = {
PROC [parent: Viewer,
clientData: REF ANYNIL,
mouseButton: MouseButton ← red,
shift, control: BOOLFALSE];
force the selection into the user input field, erase if blue
str: ROPE;
view: REF Viewer ¬ NARROW[clientData]; -- get our data
SELECT mouseButton FROM
red => NULL;
yellow => {
str ¬ ViewerTools.GetSelectionContents[];
ViewerTools.SetContents[view­, str];
};
blue => {
ViewerTools.SetContents[view­, NIL];
};
ENDCASE => ERROR;
ViewerTools.SetSelection[view­, NIL]
};
AddAlertFunction: Menus.MenuProc = {
editData: EditData ¬ NARROW[clientData]; -- get our data
meeting: Meeting ¬ editData.newMtg;
AddNewReminder[editData, NEW[ReminderRecord ¬ [NIL, alert[
start: TimeParse.Adjust[baseTime: meeting.start, minutes: -leadTime].time,
stop: TimeParse.Adjust[baseTime: meeting.start, minutes: remindDuration-leadTime].time]]] ];
};
AddMailFunction: Menus.MenuProc = {
editData: EditData ¬ NARROW[clientData]; -- get our data
meeting: Meeting ¬ editData.newMtg;
AddNewReminder[editData, NEW[ReminderRecord ¬ [NIL, mail[
when: TimeParse.Adjust[baseTime: meeting.start, minutes: -mailLeadTime].time,
to: mailRecipient]]] ];
};
AddNewReminder: PROC [editData: EditData, rmd: Reminder] ~ {
ViewerOps.SetOpenHeight[editData.outerViewer, editData.bot + entryVSpace+entryHeight + botSpace];
ViewerOps.ComputeColumn[editData.outerViewer.column];
editData.newMtg.reminders ¬ CONS[rmd, editData.newMtg.reminders];
AddReminder[editData, rmd];
RETURN};
AddReminder: PROC [editData: EditData, rmd: Reminder] ~ {
rd: RemindData ~ NEW [RemindDataRecord ¬ [editData, rmd]];
meeting: Meeting ~ editData.newMtg;
mtp: Multuple ¬ [rmd,
Buttons.Create[
info: [name: "Delete",
wy: editData.bot-1, wh: entryHeight, wx: 0,
parent: editData.outerViewer,
border: TRUE],
proc: DeleteReminder, clientData: rd],
ALL[[NIL, NIL]]];
editData.edge ¬ mtp.delete.wx + mtp.delete.ww + 6;
WITH rmd SELECT FROM
x: Remind.AlertReminder => {
mtp.iv[leadtime].prompt ¬ MakeButton[editData, "Alert Lead Time:", editData.edge];
mtp.iv[leadtime].input ¬ MakeViewer[editData, Convert.RopeFromInt[BasicTime.Period[x.start, meeting.start]/60], 1, 11*VFonts.CharWidth['0] ];
mtp.iv[remindduration].prompt ¬ MakeButton[editData, "Duration:", editData.edge];
mtp.iv[remindduration].input ¬ MakeViewer[editData, Convert.RopeFromInt[BasicTime.Period[x.start, x.stop]/60] ];
};
x: Remind.MailReminder => {
mtp.iv[leadtime].prompt ¬ MakeButton[editData, "Mail Lead Time:", editData.edge];
mtp.iv[leadtime].input ¬ MakeViewer[editData, Convert.RopeFromInt[BasicTime.Period[x.when, meeting.start]/60], 1, 11*VFonts.CharWidth['0] ];
mtp.iv[mailto].prompt ¬ MakeButton[editData, "Recipient:", editData.edge];
mtp.iv[mailto].input ¬ MakeViewer[editData, x.to];
};
ENDCASE => ERROR;
editData.multis ¬ CONS[mtp, editData.multis];
RETURN};
OKFunction: Menus.MenuProc = {
PROC [parent: Viewer,
clientData: REF ANYNIL,
mouseButton: MouseButton ← red,
shift, control: BOOLFALSE];
editData: EditData ¬ NARROW[clientData]; -- get our data
meeting: Meeting ¬ editData.newMtg;
remindLeadTime, remindDuration: INT;
MyGet: PROC[i: Categories, rmd: Reminder ¬ NIL] RETURNS [str: ROPE] = {
IF i IN SingleCategories
THEN str ¬ ViewerTools.GetContents[editData.inputs[i].input]
ELSE {
m: Multuple ~ FindMultuple[editData.multis, rmd];
str ¬ ViewerTools.GetContents[m.iv[i].input];
};
IF Rope.IsEmpty[str] THEN RETURN[NIL];
IF Rope.SkipOver[str, 0, " \t\n\r"] = Rope.Length[str] THEN RETURN[NIL];
RETURN};
meeting.start ¬ TimeParse.Parse[MyGet[start], BasicTime.Now[] ! TimeParse.ParseError => {
SimpleFeedback.Append[$CalendarTool, oneLiner, $Error, "Can't parse \"Start:\" field"];
SimpleFeedback.Blink[$CalendarTool, $Error];
GOTO done;
}].time;
meeting.explanation ¬ MyGet[explanation];
meeting.repeat ¬ Remind.RepetitionFromRope[MyGet[repeat]];
IF meeting.type = meeting THEN meeting.more ¬ MyGet[more];
IF meeting.type = meeting OR meeting.type = seminar THEN {
meeting.duration ¬ Convert.IntFromRope[MyGet[duration]];
FOR rl: Remind.RemindList ¬ meeting.reminders, rl.rest WHILE rl#NIL DO
WITH rl.first SELECT FROM
x: Remind.AlertReminder => {
remindLeadTime ¬ Convert.IntFromRope[MyGet[leadtime, x]];
remindDuration ¬ Convert.IntFromRope[MyGet[remindduration, x]];
x.start ¬ BasicTime.Update[meeting.start, -60*remindLeadTime];
x.stop ¬ BasicTime.Update[meeting.start, 60*(-remindLeadTime + remindDuration)];
};
x: Remind.MailReminder => {
remindLeadTime ¬ Convert.IntFromRope[MyGet[leadtime, x]];
x.when ¬ BasicTime.Update[meeting.start, -60*remindLeadTime];
x.to ¬ MyGet[mailto, x];
};
ENDCASE => ERROR;
ENDLOOP;
}
ELSE { -- command; protectedCmd can't happen.
meeting.duration ¬ CommandMeetingLength;
meeting.reminders ¬ LIST[NEW[ReminderRecord ¬ [NIL, alert[
start: meeting.start, stop: meeting.start]]] ];
};
IF meeting.type = seminar THEN {
Create protected commands to display the seminar announcement in a viewer.
ScheduleSeminarAnnouncements[meeting, editData.seminarViewer, editData.name, editData.oldMtg#NIL, editData.oldMtg.start];
};
IF editData.oldMtg # NIL
THEN [] ¬ Remind.DeleteMeeting[start: editData.oldMtg.start, id: editData.oldMtg.uniqID, closeDb: FALSE, dbName: editData.name];
[] ¬ Remind.AddMeeting[meeting: meeting, dbName: editData.name];
ViewerOps.DestroyViewer[editData.outerViewer];
EXITS
done => NULL;
};
ScheduleSeminarAnnouncements: PROC [meeting: Meeting, seminarViewer: Viewer, dbName: ROPE, alreadyExists: BOOL, oldStart: BasicTime.GMT ¬ BasicTime.nullGMT] =
Create protected commands to display the seminar announcement in a viewer.
BEGIN
semMsgContents: ViewerTools.TiogaContents ¬ ViewerTools.GetTiogaContents[seminarViewer];
cmd1: Meeting ¬ NEW [Remind.MeetingRec];
cmd2: Meeting ¬ NEW [Remind.MeetingRec];
cmd1ID, cmd2ID: INT;
msgFileName: ROPE ¬ seminarViewer.file;
cmd1.start ¬ BasicTime.Update[meeting.start, -secsPerWeek];
Post an announcement out a week in advance.
IF BasicTime.Period[cmd1.start, BasicTime.Now[]] > 0 THEN {
First announcement would be scheduled for the past. Schedule it for (one minute from) now instead.
cmd1.start ¬ BasicTime.Update[BasicTime.Now[], 60];
};
cmd1.type ¬ protectedCmd;
cmd1.explanation ¬ IO.PutFR1["Open %g", IO.rope[msgFileName]];
cmd1.repeat ¬ meeting.repeat;
cmd1.duration ¬ CommandMeetingLength;
cmd1.reminders ¬ LIST[NEW[ReminderRecord ¬ [NIL, alert[
start: cmd1.start, stop: cmd1.start]]] ];
cmd2­ ¬ cmd1­;
cmd2.start ¬ BasicTime.Update[meeting.start, -secsPerDay];
Post a reminder out a day in advance.
cmd2.reminders ¬ LIST[NEW[ReminderRecord ¬ [NIL, alert[
start: cmd2.start, stop: cmd2.start]]] ];
[] ¬ ViewerOps.SaveViewer[seminarViewer];
ViewerOps.DestroyViewer[seminarViewer];
IF alreadyExists THEN {
from: BasicTime.GMT ~ oldStart.Update[-secsPerWeek*2];
ids: IO.STREAM ¬ IO.RIS[meeting.more];
uniqID1: INT ¬ IO.GetInt[ids];
uniqID2: INT ¬ IO.GetInt[ids];
cmd1ID ¬ Remind.AddMeeting[meeting: cmd1, closeDb: FALSE, dbName: dbName];
[] ¬ Remind.DeleteMeetingInRange[from: from, to: oldStart, id: uniqID1, dbName: dbName];
cmd2ID ¬ Remind.AddMeeting[meeting: cmd2, closeDb: FALSE, dbName: dbName];
[] ¬ Remind.DeleteMeeting[start: oldStart.Update[-secsPerDay], id: uniqID2, dbName: dbName];
}
ELSE {
cmd1ID ¬ Remind.AddMeeting[meeting: cmd1, dbName: dbName];
cmd2ID ¬ Remind.AddMeeting[meeting: cmd2, dbName: dbName];
};
meeting.more ¬ IO.PutFR["%g %g %g", IO.int[cmd1ID], IO.int[cmd2ID], IO.rope[msgFileName]];
END;
CacheProfileData: ENTRY UserProfile.ProfileChangedProc = {
PROC [reason: UserProfile.ProfileChangeReason]
ENABLE UNWIND => NULL;
dbName: ROPE ¬ Remind.GetDefaultDbName[];
flatdbName: ROPE ¬ Rope.Flatten[dbName];
doAlert ¬ UserProfile.Boolean["Remind.Alert", TRUE];
leadTime ¬ UserProfile.Number["Remind.AlertLeadTime", UserProfile.Number["Remind.RemindLeadTime", 20]];
remindDuration ¬ UserProfile.Number["Remind.AlertDuration", UserProfile.Number["Remind.RemindDuration", 20]];
doMail ¬ UserProfile.Boolean["Remind.Mail", TRUE];
mailLeadTime ¬ UserProfile.Number["Remind.MailLeadTime", 30];
mailRecipient ¬ UserProfile.Token["Remind.MailTo", SystemNames.UserName[].Cat["@", SystemNames.MachineName[]] ];
seminarMsgFormName ¬ UserProfile.Token["CalendarTool.SeminarMsgForm", seminarMsgFormName];
IF Rope.IsEmpty[dbName] THEN
SimpleFeedback.Append[$Remind, oneLiner, $Warning, "No GetDefaultDbName in RemindEditImpl\n"]
ELSE {
IF dbName.Find["@"] >= 0
THEN dfFileDirectory ¬ SystemNames.UserCedarDir["appts"]
ELSE dfFileDirectory ¬ PFSCanonicalNames.UnparseName[PFSNames.Directory[PFSCanonicalNames.ParseName[dbName]]];
};
};
UserProfile.CallWhenProfileChanges[CacheProfileData];
}.