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; 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, 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 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, 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; 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 = { 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 = { 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 { 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] = 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]; IF BasicTime.Period[cmd1.start, BasicTime.Now[]] > 0 THEN { 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]; 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 = { 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]; }. ๎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 This is the smallest duration that will show up on smaller CalendarTool viewer sizes. icon: rememberIcon, 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. default the width so that it will be computed for us -- Can't set button's clientData directly, need to use this indirect method PROC [parent: Viewer, clientData: REF ANY _ NIL, mouseButton: MouseButton _ red, shift, control: BOOL _ FALSE]; force the selection into the user input field, erase if blue PROC [parent: Viewer, clientData: REF ANY _ NIL, mouseButton: MouseButton _ red, shift, control: BOOL _ FALSE]; Create protected commands to display the seminar announcement in a viewer. Create protected commands to display the seminar announcement in a viewer. Post an announcement out a week in advance. First announcement would be scheduled for the past. Schedule it for (one minute from) now instead. Post a reminder out a day in advance. PROC [reason: UserProfile.ProfileChangeReason] ส;•NewlineDelimiter –(cedarcode) style˜code– "Cedar" stylešœ™K– "Cedar" stylešœ ฯeœ7™BKšœ/™/K™1K™&K™,K™'K™:K™"K™—šฯk ˜ Kšœ ˜ Kšœžœ'˜4K˜ Kšœ˜Kšžœ˜Kšœžœ<˜GKšœ˜Kšœžœ˜1Kšœ žœ ˜Kšœ˜K˜ Kšœ˜Kšœ˜Kšœ žœ'˜8Kšœ žœ˜,Kšœ žœF˜WK˜Kšœ˜Kšœ ˜ K˜ Kšœ ˜ —K˜šฯn ะlnœžœž˜Kšžœ*žœ žœ˜ฬKšžœ ˜—Kšœ˜K˜K– 4 in tabStopsšžœžœžœ˜K– 4 in tabStopsšœžœ˜$K– 4 in tabStopsšœ žœ˜K– 4 in tabStopsšœ žœ˜!K– 4 in tabStopsšœžœ˜-K– 4 in tabStops˜K˜Kšœ žœR˜bKšœžœ˜1Kšœžœ ˜5K˜Kšœ žœžœ ˜!šœ žœžœ˜K– 4 in tabStopsšœžœฯc%˜@K– 4 in tabStopsšœžœžœก˜AK– 4 in tabStopsšœžœ˜K– 4 in tabStopsšœ žœ˜$K– 4 in tabStopsšœžœ ก+˜K˜PK˜—˜K˜9K˜=K˜K˜—Kšžœžœ˜—Kšžœ˜—K˜—šžœก&˜.K˜(šœžœžœžœ˜:Kšœ/˜/—K˜—šžœžœ˜ ™JKšœ]žœ˜y—K˜—šžœž˜Kšžœ^žœ˜€—K˜@Kšœ.˜.šž˜Kšœžœ˜ —Kšœ˜—K˜š Ÿœžœ3žœžœžœ˜žK™JKšž˜K˜XKšœžœ˜(Kšœžœ˜(Kšœžœ˜Kšœ žœ˜'K˜˜;K™+—šžœ2žœ˜;™cK˜3—K˜—K˜Kšœžœžœ˜>K˜K˜%šœžœžœžœ˜7Kšœ)˜)—K˜K˜˜:K™%—šœžœžœžœ˜7Kšœ)˜)—K˜K˜)Kšœ'˜'K˜šžœžœ˜Kšœžœ#˜6Kš œžœžœžœžœ˜&Kšœ žœžœ ˜Kšœ žœžœ ˜Kšœ3žœ˜JK˜XKšœ3žœ˜JK˜\K˜—šžœ˜K˜:K˜:Kšœ˜—Kš œžœžœžœžœ˜ZKšžœ˜—K˜šŸœžœ#˜:Kšžœ*™.Kšžœžœžœ˜Kšœžœ˜)Kšœ žœ˜(Kšœ.žœ˜4K˜gK˜mKšœ,žœ˜2K˜=K˜pK˜Zšžœž˜K˜]šžœ˜šžœ˜Kšžœ4˜8Kšžœj˜nK˜———Kšœ˜—K˜Kšœ5˜5K˜Kšœ˜—…—K~hง