DIRECTORY Basics USING [CompareInt], BasicTime, Commander USING [CommandProc, Handle, Register], CommanderOps USING [NumArgs, ArgN], Convert USING [BoolFromRope, IntFromRope, RopeFromCard, RopeFromInt, RopeFromTime], Icons USING [DrawIcon, IconFlavor, NewIconFromFile], Imager USING [ClipRectangleI, Color, Context, DoSave, Font, MaskRectangle, MaskRectangleI, MaskVector, SetColor, SetFont, SetXY, ShowRope], ImagerColorPrivate USING [ColorFromStipple], ImagerFont USING [Find, Font], ImagerSample USING [Function], IO USING [int, GetInt, GetTokenRope, IDProc, PutFR, PutFR1, RIS, rope, STREAM], List USING [Append, CompareProc, LORA, DRemove, Sort], Menus USING [AppendMenuEntry, CreateEntry, CreateMenu, Menu, MenuProc], PFS, PFSCanonicalNames USING [ParseName], Process USING [Detach, Pause, SecondsToTicks], Remind, RemindEdit USING [SpecifyMeeting], RemindReset, Rope, SimpleFeedback, TimeParse, TIPUser USING [InstantiateNewTIPTable, TIPScreenCoords], UserProfile USING [CallWhenProfileChanges, ProfileChangedProc, Token], VFonts USING [CharWidth, FontHeight, StringWidth], ViewerClasses, ViewerOps, ViewerPrivate USING [selectedIcon], ViewerTools; CalendarTool: CEDAR MONITOR IMPORTS Basics, BasicTime, Commander, CommanderOps, Convert, Icons, Imager, ImagerColorPrivate, ImagerFont, IO, List, Menus, PFS, PFSCanonicalNames, Process, Remind, RemindEdit, RemindReset, Rope, SimpleFeedback, TimeParse, TIPUser, UserProfile, ViewerPrivate, VFonts, ViewerOps, ViewerTools = BEGIN Views: TYPE = RECORD[ Draw: PROC[context: Imager.Context, data: CalendarData], Select: PROC[context: Imager.Context, data: CalendarData, unselect: BOOLEAN ¬ FALSE], Margins: PROC[data: CalendarData] RETURNS [leftMargin, bottomMargin, rightMargin, topMargin, wd, ht, nRows, nCols, rowHt: INT], MouseToSel: PROC[data: CalendarData] RETURNS [time: BasicTime.GMT, curSel: CurSel], DateToSel: PROC[data: CalendarData, now: BasicTime.GMT] RETURNS [curSel: CurSel, repaint: BOOLEAN] ]; meetingly: Views = [DrawMeetingly, SelectMeetingly, MarginOfMeetingly, MouseToSelMeetingly, DateToSelMeetingly]; daily: Views = [DrawDaily, SelectDaily, MarginOfDaily, MouseToSelDaily, DateToSelDaily]; weekly: Views = [DrawWeekly, SelectWeekly, MarginOfWeekly, MouseToSelWeekly, DateToSelWeekly]; monthly: Views = [DrawMonthly, SelectMonthly, MarginOfMonthly, MouseToSelMonthly, DateToSelMonthly]; yearly: Views = [DrawYearly, SelectMonthly, MarginOfYearly, MouseToSelMonthly, DateToSelYear]; ROPE: TYPE = Rope.ROPE; Meeting: TYPE = Remind.Meeting; ReminderRecord: TYPE ~ Remind.ReminderRecord; CommandMeetingLength: INT = 15; WhatChangedRec: TYPE = RECORD[ newMouseLoc: BOOLEAN ¬ FALSE, -- the only thing that changed was the selection, the calendar area won't be cleared changeSelView: BOOLEAN ¬ FALSE, -- the view has changed setToday: BOOLEAN ¬ FALSE ]; WhatChanged: TYPE = REF WhatChangedRec; CurSelRec: TYPE = RECORD[ -- this should really be a union row, col: INT ¬ 0, -- when monthly, yearly curMeeting: Meeting ¬ NIL -- XXX: this is also used to keep track of position when nexting thru meetingly views ]; CurSel: TYPE = REF CurSelRec; CalendarData: TYPE = REF CalendarDataRec; CalendarDataRec: TYPE = RECORD [ name: ROPE ¬ NIL, -- calendar being used. NIL = your profile outerViewer: ViewerClasses.Viewer ¬ NIL, -- handle for the enclosing viewer calenderViewer: ViewerClasses.Viewer ¬ NIL, -- contains drawing of calendar and labels textViewer: ViewerClasses.Viewer ¬ NIL, view: Views ¬ weekly, x,y: INT, -- communicate between NotifyCalendar and PaintCalendar selDate: BasicTime.GMT, -- perhaps this should be part of curSel curSel: CurSel ¬ NIL, resizing: BOOLEAN ¬ FALSE, -- set when window is resized boundingMode: BOOLEAN ¬ FALSE, -- set when in bounding mode selectionMode: Remind.MeetingType ¬ none, -- set when selecting a specific time for a meeting. stopBounding: BOOLEAN ¬ FALSE, -- stop bounding mode startBoundX, startBoundY: INT ¬ -1, -- coords of start of bounding box oldX, oldY: INT ¬ -1, -- previous coords of bounding box oldDay, oldWeek: BasicTime.GMT, oldDailyL, oldWeeklyL: List.LORA ¬ NIL ]; AddSeminarButton: BOOLEAN ¬ FALSE; -- Only add seminar button to CalendarTool if this variable is set to TRUE. xTab: INT = 10; -- left margin on calendar startHour: INT ¬ 9; -- earliest time in display stopHour: INT ¬ 19; -- latest time in display round: INT ¬ 30; -- default value of Remind.NewMeetingRound viewerList: List.LORA ¬ NIL; smallIconFont: Imager.Font ¬ ImagerFont.Find["Xerox/TiogaFonts/TimesRoman8B"]; iconFont: Imager.Font ¬ ImagerFont.Find["Xerox/TiogaFonts/TimesRoman12B"]; littleLabelFont: Imager.Font ¬ ImagerFont.Find["Xerox/TiogaFonts/Helvetica6"]; labelFont: Imager.Font ¬ ImagerFont.Find["Xerox/TiogaFonts/Helvetica8"]; boldLabelFont: Imager.Font ¬ ImagerFont.Find["Xerox/TiogaFonts/Helvetica8B"]; bigLabelFont: Imager.Font ¬ ImagerFont.Find["Xerox/TiogaFonts/Helvetica10"]; dailyFont: Imager.Font ¬ ImagerFont.Find["Xerox/TiogaFonts/Helvetica10"]; dailyHeaderFont: Imager.Font ¬ ImagerFont.Find["Xerox/TiogaFonts/Helvetica10B"]; black: CARDINAL = 0FFFFH; grey: CARDINAL = 05A5AH; lightGrey: CARDINAL = 8241H; white: CARDINAL = 00000H; greyOR: Imager.Color ¬ ImagerColorPrivate.ColorFromStipple[word: grey, function: [or, null]]; lightGreyOR: Imager.Color ¬ ImagerColorPrivate.ColorFromStipple[word: lightGrey, function: [or, null]]; blackOR: Imager.Color ¬ ImagerColorPrivate.ColorFromStipple[word: black, function: [or, null]]; blackXOR: Imager.Color ¬ ImagerColorPrivate.ColorFromStipple[word: black, function: [xor, null]]; whiteAND: Imager.Color ¬ ImagerColorPrivate.ColorFromStipple[word: white, function: [and, null]]; secsPerDay: INT ~ 60*60*24; secsPerWeek: INT ~ secsPerDay*7; monthNames: ARRAY [0..11] OF ROPE = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; ListToLora: PROC[ls: LIST OF Meeting] RETURNS [lr: List.LORA ¬ NIL] = { WHILE ls # NIL DO lr ¬ CONS[ls.first, lr]; ls ¬ ls.rest; ENDLOOP; }; Pred: PROC[view: Views] RETURNS [Views] = { RETURN [SELECT view FROM meetingly => meetingly, daily => meetingly, weekly => daily, monthly => weekly, yearly => monthly, ENDCASE => daily]}; Succ: PROC[view: Views] RETURNS [Views] = { RETURN [SELECT view FROM meetingly => daily, daily => weekly, weekly => monthly, monthly => yearly, ENDCASE => yearly]}; First: PROC[view: Views] RETURNS [BOOLEAN] = {RETURN[view = meetingly]}; Last: PROC[view: Views] RETURNS [BOOLEAN] = {RETURN[view = yearly]}; GetFirstOfMonth: PROC [day: BasicTime.GMT] RETURNS [first: BasicTime.GMT] = { unp: BasicTime.Unpacked; unp ¬ BasicTime.Unpack[day]; first ¬ TimeParse.Adjust[baseTime: day, days: -unp.day + 1].time }; GetFirstOfYear: PROC [day: BasicTime.GMT] RETURNS [first: BasicTime.GMT] = { unp: BasicTime.Unpacked; unp ¬ BasicTime.Unpack[day]; first ¬ TimeParse.Adjust[baseTime: day, months: -ORD[unp.month], precisionOfResult: months].time; }; LastOfMonth: PROC [day: BasicTime.GMT] RETURNS [last: BasicTime.GMT] = { last ¬ TimeParse.Adjust[months: 1, baseTime: day, precisionOfResult: months].time; last ¬ TimeParse.Adjust[baseTime: last, days: -1].time; }; NWeeks: PROC [day: BasicTime.GMT] RETURNS [nWeeks: CARDINAL] = { first: BasicTime.GMT; wDay: CARDINAL; -- day of week, where Sun=0, Sat=6 first ¬ GetFirstOfMonth[day]; wDay ¬ ORD[BasicTime.Unpack[first].weekday] + 1; IF wDay = 7 THEN wDay ¬ 0; nWeeks ¬ (BasicTime.Unpack[LastOfMonth[first]].day + wDay - 1)/7 + 1; }; GetSunday: PROC [day: BasicTime.GMT] RETURNS [sunday: BasicTime.GMT] = { unp: BasicTime.Unpacked; unp ¬ BasicTime.Unpack[day]; IF unp.weekday = Sunday THEN sunday ¬ day ELSE sunday ¬ TimeParse.Adjust[baseTime: day, days: -ORD[unp.weekday]-1].time }; RowColInMonthToDate: PROC [row, col: INT, day: BasicTime.GMT] RETURNS [BasicTime.GMT] = { wDay: INT; -- day of week, where Sun=0, Sat=6 unp: BasicTime.Unpacked; unp ¬ BasicTime.Unpack[GetFirstOfMonth[day]]; wDay ¬ ORD[unp.weekday] + 1; IF wDay = 7 THEN wDay ¬ 0; unp.day ¬ row*7 + col - wDay + 1; RETURN[BasicTime.Pack[unp]]; }; DateToRowColInMonth: PROC [day: BasicTime.GMT] RETURNS [row, col: INT] = { wDay: INT; -- day of week, where Sun=0, Sat=6 unp: BasicTime.Unpacked; unp ¬ BasicTime.Unpack[day]; wDay ¬ ORD[BasicTime.Unpack[GetFirstOfMonth[day]].weekday] + 1; IF wDay = 7 THEN wDay ¬ 0; row ¬ (unp.day - 1 + wDay)/7; col ¬ (unp.day - 1 + wDay) MOD 7; }; DateToRowColInYear: PROC [day: BasicTime.GMT] RETURNS [row, col: INT] = { unp: BasicTime.Unpacked; unp ¬ BasicTime.Unpack[day]; row ¬ ORD[unp.month]/4; --XXX: 4 should be constant col ¬ ORD[unp.month] MOD 4; }; RowColInYearToDate: PROC [row, col: INT, day: BasicTime.GMT] RETURNS [BasicTime.GMT] = { unp: BasicTime.Unpacked; i: INTEGER; unp ¬ BasicTime.Unpack[day]; i ¬ 4*row + col; --XXX: 4 should be constant unp.month ¬ VAL[i]; unp.day ¬ 2; -- because of daylights savings time, day=1 doesn't work RETURN[BasicTime.Pack[unp]]; }; DataChangedProc: PROC[data: REF] = { TRUSTED {Process.Detach[FORK DoDataChangedProc[data]]}; }; DoDataChangedProc: PROC[clientData: REF] = { data: CalendarData ¬ NARROW[clientData]; data.oldDailyL ¬ data.oldWeeklyL ¬ NIL; FOR vwL: List.LORA ¬ viewerList, vwL.rest UNTIL vwL = NIL DO ViewerOps.PaintViewer[viewer: NARROW[vwL.first], hint: client, clearClient: TRUE]; ENDLOOP; }; CacheProfileData: ENTRY UserProfile.ProfileChangedProc = { ENABLE UNWIND => NULL; vwL: List.LORA; newStart, newStop: INTEGER; AddSeminarButton ¬ Convert.BoolFromRope[UserProfile.Token["CalendarTool.AddSeminarButton", "FALSE"]]; newStart ¬ Convert.IntFromRope[UserProfile.Token["CalendarTool.StartHour", "9"]]; newStop ¬ Convert.IntFromRope[UserProfile.Token["CalendarTool.StopHour", "19"]]; round ¬ Convert.IntFromRope[UserProfile.Token["Remind.NewMeetingRound", "30"]]; IF newStart # startHour OR newStop # stopHour THEN { startHour ¬ newStart; stopHour ¬ newStop; vwL ¬ viewerList; WHILE vwL # NIL DO ViewerOps.PaintViewer[viewer: NARROW[vwL.first], hint: client, clearClient: TRUE]; vwL ¬ vwL.rest; ENDLOOP; }; }; OpenIt: ENTRY PROC[data: CalendarData] = { ENABLE UNWIND => NULL; -- release the monitor lock on ABORT ViewerOps.OpenIcon[data.outerViewer]; }; CalendarToolReset: Commander.CommandProc = { RemindReset.Reset[NIL]; }; MakeFirstCalendarTool: Commander.CommandProc = { MakeCalendarTool[ NIL, BasicTime.Now[], weekly, left, cmd, ]; }; MakeCalendarTool: PROC[name: ROPE, date: BasicTime.GMT, view: Views, col: ViewerClasses.Column, cmd: Commander.Handle ¬ NIL, curSel: CurSel ¬ NIL] = { data: CalendarData ¬ NEW[CalendarDataRec]; myMenu: Menus.Menu ¬ Menus.CreateMenu[]; i, nArgs: INT; arg: ROPE; open: BOOLEAN ¬ FALSE; IF cmd # NIL THEN { nArgs ¬ CommanderOps.NumArgs[cmd]; i ¬ 1; WHILE i < nArgs DO IF Rope.Fetch[arg ¬ CommanderOps.ArgN[cmd, i], 0] = '- THEN { SELECT Rope.Fetch[arg, 1] FROM 'l => col ¬ left; 'r => col ¬ right; 'o => open ¬ TRUE; 'c => open ¬ FALSE; ENDCASE => NULL; } ELSE name ¬ arg; i ¬ i + 1; ENDLOOP; } ELSE open ¬ TRUE; data.name ¬ name; data.selDate ¬ date; data.view ¬ view; data.curSel ¬ curSel; Remind.RegisterChangeProc[DataChangedProc, data, $calendartool]; Menus.AppendMenuEntry[ menu: myMenu, entry: Menus.CreateEntry[name: "Today", proc: TodayFunction, clientData: data] ]; Menus.AppendMenuEntry[ menu: myMenu, entry: Menus.CreateEntry[name: "Next", proc: NextFunction, clientData: data] ]; Menus.AppendMenuEntry[ menu: myMenu, entry: Menus.CreateEntry[name: "ZoomIn", proc: UpFunction, clientData: data] ]; Menus.AppendMenuEntry[ menu: myMenu, entry: Menus.CreateEntry[name: "Delete", guarded: TRUE, proc: DeleteFunction, clientData: data] ]; Menus.AppendMenuEntry[ menu: myMenu, entry: Menus.CreateEntry[name: "Copy", proc: CopyFunction, clientData: data] ]; Menus.AppendMenuEntry[ menu: myMenu, entry: Menus.CreateEntry[name: "Edit", proc: EditFunction, clientData: data] ]; Menus.AppendMenuEntry[ menu: myMenu, entry: Menus.CreateEntry[name: "ListMeetings", proc: ListFunction, clientData: data] ]; Menus.AppendMenuEntry[ menu: myMenu, entry: Menus.CreateEntry[name: "New Meeting", proc: NewMeetingFunction, clientData: data] ]; Menus.AppendMenuEntry[ menu: myMenu, entry: Menus.CreateEntry[name: "New Command", proc: NewCmdFunction, clientData: data] ]; IF AddSeminarButton THEN Menus.AppendMenuEntry[ menu: myMenu, entry: Menus.CreateEntry[name: "New Seminar", proc: NewSeminarFunction, clientData: data] ]; data.outerViewer ¬ ViewerOps.CreateViewer[ -- construct the outerViewer flavor: $OuterCalendar, info: [ name: "Calendar Tool", data: data, iconic: TRUE, column: col, menu: myMenu, icon: private, scrollable: FALSE ] ]; viewerList ¬ CONS[data.outerViewer, viewerList]; -- list of viewers used by FSWatcher data.calenderViewer ¬ ViewerOps.CreateViewer[ flavor: $Calendar, info: [ parent: data.outerViewer, data: data, wx: 0, ww: data.outerViewer.cw, border: FALSE, scrollable: FALSE] ]; data.textViewer ¬ ViewerTools.MakeNewTextViewer[ info: [ parent: data.outerViewer, data: "", wx: 0, ww: data.outerViewer.cw, border: FALSE, scrollable: TRUE] ]; IF open THEN TRUSTED {Process.Detach[FORK OpenIt[data]]}; }; TodayFunction: Menus.MenuProc = { data: CalendarData ¬ NARROW[clientData]; ViewerOps.PaintViewer[ viewer: data.calenderViewer, hint: client, whatChanged: NEW[WhatChangedRec ¬ [setToday: TRUE]], clearClient: FALSE]; }; NextFunction: Menus.MenuProc = { data: CalendarData ¬ NARROW[clientData]; sign: INTEGER; meeting: Meeting; IF mouseButton = yellow THEN RETURN; IF mouseButton = red THEN sign ¬ 1 ELSE sign ¬ -1; SELECT data.view FROM yearly => data.selDate ¬ TimeParse.Adjust[ baseTime: data.selDate, years: sign, precisionOfResult: days].time; monthly => data.selDate ¬ TimeParse.Adjust[ baseTime: data.selDate, months: sign, precisionOfResult: days].time; weekly => data.selDate ¬ TimeParse.Adjust[baseTime: data.selDate, days: sign*7].time; daily => data.selDate ¬ TimeParse.Adjust[baseTime: data.selDate, days: sign].time; meetingly => { IF data.curSel = NIL THEN data.curSel ¬ NEW[CurSelRec]; IF data.curSel.curMeeting = NIL THEN data.curSel.curMeeting ¬ MeetingFromTime[data.selDate, data]; IF data.curSel.curMeeting = NIL THEN RETURN; meeting ¬ FindNextMeeting[meeting: data.curSel.curMeeting, forward: sign = 1, data: data]; data.selDate ¬ meeting.start; data.curSel.curMeeting ¬ meeting; }; ENDCASE => {}; IF data.view # meetingly THEN data.curSel ¬ NIL; ViewerOps.PaintViewer[ viewer: data.calenderViewer, hint: client, whatChanged: NEW[WhatChangedRec ¬ [changeSelView: TRUE]], clearClient: TRUE]; }; FindNextMeeting: PROC[meeting: Meeting, forward:BOOLEAN, data: CalendarData] RETURNS [Meeting] = { takeTheNextOne: BOOLEAN ¬ FALSE; meetingL: List.LORA; mt: Meeting; date: BasicTime.GMT; cnt: INT ¬ 0; date ¬ meeting.start; WHILE TRUE DO cnt ¬ cnt + 1; meetingL ¬ GetMeetingL[date, daily, data]; IF NOT forward THEN meetingL ¬ List.Sort[List.Append[meetingL, NIL], CompareStartReverse]; FOR ml: List.LORA ¬ meetingL, ml.rest UNTIL ml = NIL DO mt ¬ NARROW[ml.first]; IF takeTheNextOne THEN RETURN[mt] ELSE IF mt = meeting THEN IF ml.rest # NIL THEN RETURN[NARROW[ml.rest.first]] ELSE {takeTheNextOne ¬ TRUE; EXIT}; ENDLOOP; date ¬ TimeParse.Adjust[baseTime: date, days: IF forward THEN 1 ELSE -1].time; IF cnt = 20 THEN RETURN[meeting]; ENDLOOP; RETURN[NIL]; }; ShowText: PROCEDURE[data: CalendarData, yes: BOOLEAN] = { IF yes THEN { ViewerOps.MoveViewer[viewer: data.calenderViewer, x: data.calenderViewer.wx, y: data.outerViewer.ch, w: data.outerViewer.ww, h: data.outerViewer.ch, paint: FALSE]; ViewerOps.MoveViewer[viewer: data.textViewer, x: data.textViewer.wx, y: 0, w: data.outerViewer.ww, h: data.outerViewer.ch, paint: FALSE]; } ELSE { ViewerOps.MoveViewer[viewer: data.calenderViewer, x: data.calenderViewer.wx, y: 0, w: data.outerViewer.ww, h: data.outerViewer.ch, paint: FALSE]; ViewerOps.MoveViewer[viewer: data.textViewer, x: data.textViewer.wx, y: data.outerViewer.ch, w: data.outerViewer.ww, h: data.outerViewer.ch, paint: FALSE]; }; ViewerOps.PaintViewer[viewer: data.calenderViewer, hint: client, clearClient: TRUE]; }; DeleteFunction: Menus.MenuProc = { data: CalendarData ¬ NARROW[clientData]; IF data.curSel # NIL AND data.curSel.curMeeting # NIL THEN { IF NOT DeleteMeeting[data] THEN { SimpleFeedback.Append[$CalendarTool, oneLiner, $Error, "Unable to Delete Message"]; SimpleFeedback.Blink[$CalendarTool, $Error]; } ELSE data.curSel ¬ NIL; } ELSE { SimpleFeedback.Append[$CalendarTool, oneLiner, $Error, "Select a Message First"]; SimpleFeedback.Blink[$CalendarTool, $Error]; }; }; DeleteMeeting: PROC [data: CalendarData] RETURNS [result: BOOLEAN] = BEGIN IF data.curSel.curMeeting.type = protectedCmd THEN RETURN [FALSE]; result ¬ Remind.DeleteMeeting[start: data.curSel.curMeeting.start, id: data.curSel.curMeeting.uniqID, dbName: data.name]; IF NOT result THEN RETURN [FALSE]; IF data.curSel.curMeeting.type = seminar THEN { from: BasicTime.GMT ~ data.curSel.curMeeting.start.Update[-secsPerWeek]; ids: IO.STREAM ¬ IO.RIS[data.curSel.curMeeting.more]; uniqID1: INT ¬ IO.GetInt[ids]; uniqID2: INT ¬ IO.GetInt[ids]; msgFileName: ROPE ¬ NIL; [msgFileName] ¬ IO.GetTokenRope[ids, IO.IDProc]; [] ¬ Remind.DeleteMeetingInRange[from: from, to: data.curSel.curMeeting.start, id: uniqID1, dbName: data.name]; [] ¬ Remind.DeleteMeeting[start: data.curSel.curMeeting.start.Update[-secsPerDay], id: uniqID2, dbName: data.name]; PFS.Delete[PFSCanonicalNames.ParseName[msgFileName]]; }; END; CopyOrEdit: PROC [clientData: REF ANY _ NIL, edit: BOOL ¬ TRUE] = { data: CalendarData ¬ NARROW[clientData]; meeting: Remind.Meeting; IF data.curSel = NIL OR data.curSel.curMeeting = NIL THEN { SimpleFeedback.Append[$CalendarTool, oneLiner, $Error, "Select a Meeting First"]; SimpleFeedback.Blink[$CalendarTool, $Error]; RETURN; }; meeting ¬ data.curSel.curMeeting; IF meeting.type = protectedCmd THEN { SimpleFeedback.Append[$CalendarTool, oneLiner, $Error, "Can't edit a Seminar Command event"]; SimpleFeedback.Blink[$CalendarTool, $Error]; RETURN; }; IF NOT edit -- copy -- THEN { newMeeting: Remind.Meeting ¬ NEW[Remind.MeetingRec ¬ meeting­]; meeting ¬ newMeeting; meeting.uniqID ¬ meeting.uniqID+1; }; RemindEdit.SpecifyMeeting[meeting, TRUE, data.name, data.outerViewer.column]; }; EditFunction: Menus.MenuProc = { CopyOrEdit[clientData, TRUE]; }; CopyFunction: Menus.MenuProc = { CopyOrEdit[clientData, FALSE]; }; NewMeetingFunction: Menus.MenuProc = { data: CalendarData ¬ NARROW[clientData]; IF data.view = weekly THEN { data.boundingMode ¬ TRUE; data.selectionMode ¬ meeting; SimpleFeedback.Append[$CalendarTool, oneLiner, $Error, "Use left mouse button to mark out time of meeting"]; } ELSE { SimpleFeedback.Append[$CalendarTool, oneLiner, $Error, "You must be viewing a week in order to create a new meeting"]; SimpleFeedback.Blink[$CalendarTool, $Error]; }; }; NewCmdFunction: Menus.MenuProc = { data: CalendarData ¬ NARROW[clientData]; IF data.view = weekly THEN { data.selectionMode ¬ command; SimpleFeedback.Append[$CalendarTool, oneLiner, $Error, "Use left mouse button to select time at which to execute command"]; } ELSE { SimpleFeedback.Append[$CalendarTool, oneLiner, $Error, "You must be viewing a week in order to create a new command"]; SimpleFeedback.Blink[$CalendarTool, $Error]; }; }; NewSeminarFunction: Menus.MenuProc = { data: CalendarData ¬ NARROW[clientData]; IF data.view = weekly THEN { data.boundingMode ¬ TRUE; data.selectionMode ¬ seminar; SimpleFeedback.Append[$CalendarTool, oneLiner, $Error, "Use left mouse button to mark out time of seminar"]; } ELSE { SimpleFeedback.Append[$CalendarTool, oneLiner, $Error, "You must be viewing a week in order to schedule a new seminar"]; SimpleFeedback.Blink[$CalendarTool, $Error]; }; }; ListFunction: Menus.MenuProc = { data: CalendarData ¬ NARROW[clientData]; ls: LIST OF Meeting; lr: List.LORA; mt: Meeting; from, to: BasicTime.GMT; name, dataStr: ROPE; day, curDay: INT; viewer: ViewerClasses.Viewer; Cleanup: PROC[str: ROPE] RETURNS [ROPE] = { start: INT ¬ 0; WHILE (start ¬ Rope.Find[str, "\n", start]) # -1 DO str ¬ Rope.Replace[str, start, 1, " "]; start ¬ start + 1; ENDLOOP; RETURN[str]; }; SELECT data.view FROM meetingly => RETURN; daily => { from ¬ TimeParse.Adjust[baseTime: data.selDate, days: 0].time; to ¬ TimeParse.Adjust[baseTime: data.selDate, days: 1].time; name ¬ "Day of "; }; weekly => { from ¬ GetSunday[data.selDate]; to ¬ TimeParse.Adjust[baseTime: from, days: 7].time; name ¬ "Week of "; }; monthly => { from ¬ TimeParse.Adjust[baseTime: data.selDate, months: 0].time; to ¬ TimeParse.Adjust[baseTime: data.selDate, months: 1].time; name ¬ "Month of "; }; yearly => { from ¬ TimeParse.Adjust[baseTime: data.selDate, years: 0].time; to ¬ TimeParse.Adjust[baseTime: data.selDate, years: 1].time; name ¬ "Year of "; }; ENDCASE => RETURN; ls ¬ Remind.ListMeetings[from: from, to: to, all: TRUE, dbName: data.name]; lr ¬ List.Sort[ListToLora[ls], CompareStart]; name ¬ Rope.Concat[name, Convert.RopeFromTime[from: from, end: days, includeDayOfWeek: TRUE]]; dataStr ¬ NIL; curDay ¬ -1; WHILE lr # NIL DO mt ¬ NARROW[lr.first]; IF curDay # (day ¬ BasicTime.Unpack[mt.start].day) THEN { dataStr ¬ Rope.Cat[dataStr, "\n", Convert.RopeFromTime[from: mt.start, end: days, includeDayOfWeek: TRUE], "\n"]; curDay ¬ day; }; dataStr ¬ Rope.Cat[dataStr, ShortTime[mt.start], " ", Cleanup[mt.explanation], "\n"]; lr ¬ lr.rest; ENDLOOP; viewer ¬ ViewerTools.MakeNewTextViewer[[ name: name, data: dataStr, label: name, iconic: FALSE ]]; }; UpFunction: Menus.MenuProc = { data: CalendarData ¬ NARROW[clientData]; IF mouseButton = blue THEN { -- up IF NOT Last[data.view] THEN { IF data.view = meetingly THEN ShowText[data, FALSE]; data.view ¬ Succ[data.view]; ViewerOps.PaintViewer[ viewer: data.calenderViewer, hint: client, whatChanged: NEW[WhatChangedRec ¬ [changeSelView: TRUE]], clearClient: TRUE]; } } ELSE { -- down IF NOT First[data.view] THEN { data.view ¬ Pred[data.view]; IF data.view = meetingly THEN ShowText[data, TRUE]; ViewerOps.PaintViewer[ viewer: data.calenderViewer, hint: client, whatChanged: NEW[WhatChangedRec ¬ [changeSelView: TRUE]], clearClient: TRUE]; }; }; IF data.view # meetingly THEN data.curSel ¬ NIL; }; GetMeetingL: PROC[date: BasicTime.GMT, view: Views, data: CalendarData] RETURNS[meetingL: List.LORA] = { ml: LIST OF Meeting; day, midnight: BasicTime.GMT; SELECT view FROM meetingly, daily => { midnight ¬ TimeParse.Adjust[baseTime: date, days: 0].time; IF data.oldDay = midnight AND data.oldDailyL # NIL THEN meetingL ¬ data.oldDailyL ELSE { ml ¬ Remind.ListMeetings[ from: midnight, to: TimeParse.Adjust[baseTime: TimeParse.Adjust[baseTime: midnight, days: 1].time, minutes: -1].time, dbName: data.name ]; meetingL ¬ List.Sort[ListToLora[ml], CompareStart]; data.oldDailyL ¬ meetingL; data.oldDay ¬ midnight; }; }; weekly => { day ¬ GetSunday[date]; IF data.oldWeek = day AND data.oldWeeklyL # NIL THEN meetingL ¬ data.oldWeeklyL ELSE { ml ¬ Remind.ListMeetings[ from: day, to: TimeParse.Adjust[baseTime: TimeParse.Adjust[baseTime: day, days: 7].time, minutes: -1].time, all: TRUE, dbName: data.name ]; meetingL ¬ ListToLora[ml]; data.oldWeeklyL ¬ meetingL; data.oldWeek ¬ day; }; }; ENDCASE => meetingL ¬ NIL; }; MaskBoxOutline: PROC [context: Imager.Context, x1, y1, x2, y2: INT, function: ImagerSample.Function] = { Imager.SetColor[ context: context, color: ImagerColorPrivate.ColorFromStipple[word: black, function: function]]; Imager.MaskVector[context: context, p1: [x1, y1], p2: [x1, y2]]; Imager.MaskVector[context: context, p1: [x2, y1], p2: [x2, y2]]; Imager.MaskVector[context: context, p1: [x1, y1], p2: [x2, y1]]; Imager.MaskVector[context: context, p1: [x1, y2], p2: [x2, y2]]; }; MaskBoxOutlineXor: PROC [context: Imager.Context, x1, y1, x2, y2: INT] = { Imager.SetColor[context: context, color: blackXOR]; Imager.MaskVector[context: context, p1: [x1, y1], p2: [x1, y2]]; Imager.MaskVector[context: context, p1: [x2, y1], p2: [x2, y2]]; Imager.MaskVector[context: context, p1: [x1, y1], p2: [x2, y1]]; Imager.MaskVector[context: context, p1: [x1, y2], p2: [x2, y2]]; }; DrawGrid: PROC [ context: Imager.Context, nRows, nCols: INT, leftMargin, bottomMargin: INT, wd, ht: INT ] = { FOR i: INT IN [0 .. nCols] DO Imager.MaskVector[context: context, p1: [leftMargin + (wd*i)/nCols, bottomMargin], p2: [leftMargin + (wd*i)/nCols, bottomMargin + ht]]; ENDLOOP; FOR i: INT IN [0 .. nRows] DO Imager.MaskVector[context: context, p1: [leftMargin, bottomMargin + (ht*i)/nRows], p2: [leftMargin + wd, bottomMargin + (ht*i)/nRows]]; ENDLOOP; }; WriteWeekDays: PROC[context: Imager.Context, leftMargin, bottomMargin, wd, ht: INT, today: INT ¬ -1] = { dayNames: ARRAY [1..7] OF ROPE = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; off: INT; Imager.SetFont[context, labelFont]; FOR i: INT IN [1 .. 7] DO off ¬ (wd/7 - VFonts.StringWidth[dayNames[i], labelFont])/2; Imager.SetXY[context, [leftMargin+((i-1)*wd)/7 + off, bottomMargin + ht + VFonts.FontHeight[labelFont]/2]]; IF i = today THEN Imager.SetFont[context, boldLabelFont]; Imager.ShowRope[context, dayNames[i]]; IF i = today THEN Imager.SetFont[context, labelFont]; ENDLOOP; }; CompareStart: List.CompareProc = { m1, m2: Meeting; m1 ¬ NARROW[ref1]; m2 ¬ NARROW[ref2]; RETURN[Basics.CompareInt[0, BasicTime.Period[m1.start, m2.start]]]; }; CompareStartReverse: List.CompareProc = { m1, m2: Meeting; m1 ¬ NARROW[ref1]; m2 ¬ NARROW[ref2]; RETURN[Basics.CompareInt[0, BasicTime.Period[m2.start, m1.start]]]; }; DrawDaysOfMonth: PROC[context: Imager.Context, first: BasicTime.GMT, bottomEdge, leftEdge, nRows, firstRow: INT, wd, ht: INT, slash: BOOLEAN ¬ FALSE] = { date: BasicTime.GMT; dateUnp: BasicTime.Unpacked; month: BasicTime.MonthOfYear; bottomOff, leftOff, row, last: INT; str: ROPE; fontHt: INTEGER; last ¬ BasicTime.Unpack[LastOfMonth[first]].day; date ¬ first; dateUnp ¬ BasicTime.Unpack[date]; month ¬ dateUnp.month; row ¬ firstRow; fontHt ¬ VFonts.FontHeight[]; bottomOff ¬ bottomEdge + row*ht/nRows - fontHt; WHILE dateUnp.month = month DO IF dateUnp.weekday = Sunday AND dateUnp.day # 1 THEN { row ¬ row - 1; bottomOff ¬ bottomEdge + row*ht/nRows - fontHt; }; leftOff ¬ ORD[dateUnp.weekday] + 1; IF leftOff = 7 THEN leftOff ¬ 0; leftOff ¬ leftEdge + leftOff*wd/7 + 1; IF NOT slash OR row >= 1 THEN { str ¬ Convert.RopeFromInt[dateUnp.day]; IF slash AND row = 1 AND dateUnp.day + 7 <= last THEN str ¬ Rope.Concat[str, "/"]; Imager.SetXY[context, [leftOff, bottomOff]]; Imager.ShowRope[context, str]; }; date ¬ TimeParse.Adjust[baseTime: date, days: 1].time; dateUnp ¬ BasicTime.Unpack[date]; ENDLOOP; }; UpdateSelDateFromMouse: PROC[data: CalendarData] RETURNS [time: BasicTime.GMT, curSel: CurSel] = { leftMargin, bottomMargin: INT; row, col: INT; ht, wd, nRows, nCols: INT; [leftMargin: leftMargin, bottomMargin: bottomMargin, wd: wd, ht: ht, nCols: nCols, nRows: nRows] ¬ data.view.Margins[data]; col ¬ (nCols*(data.x - leftMargin))/wd; row ¬ (nRows*(data.y - bottomMargin))/ht; row ¬ nRows - row - 1; IF row IN [0 .. nRows) AND col IN [0 .. nCols) THEN SELECT data.view FROM daily => time ¬ data.selDate; weekly => time ¬ TimeParse.Adjust[baseTime: GetSunday[data.selDate], days: col].time; monthly => { IF (row = 0 AND col < DateToRowColInMonth[GetFirstOfMonth[data.selDate]].col) OR (row = nRows-1 AND col > DateToRowColInMonth[LastOfMonth[data.selDate]].col) THEN { row ¬ -1; col ¬ -1; time ¬ data.selDate; } ELSE time ¬ RowColInMonthToDate[row, col, data.selDate]; }; yearly => time ¬ RowColInYearToDate[row, col, data.selDate]; ENDCASE => time ¬ data.selDate ELSE { row ¬ -1; col ¬ -1; time ¬ data.selDate; }; IF row = -1 THEN curSel ¬ NIL ELSE { curSel ¬ NEW[CurSelRec]; -- XXX: should be allocated once and for all, I think curSel.row ¬ row; curSel.col ¬ col; } }; MarginOfMeetingly: PROC[data: CalendarData] RETURNS[leftMargin, bottomMargin, rightMargin, topMargin, wd, ht, nRows, nCols, rowHt: INT] = { leftMargin ¬ xTab; bottomMargin ¬ xTab; rightMargin ¬ xTab; topMargin ¬ 3*VFonts.FontHeight[dailyHeaderFont]/2; wd ¬ data.calenderViewer.cw - leftMargin - rightMargin; ht ¬ data.outerViewer.ch - topMargin - bottomMargin; rowHt ¬ VFonts.FontHeight[dailyFont]; nCols ¬ nRows ¬ 1; -- to get rid of comiler warnings }; MarginOfDaily: PROC[data: CalendarData] RETURNS[leftMargin, bottomMargin, rightMargin, topMargin, wd, ht, nRows, nCols, rowHt: INT] = { leftMargin ¬ xTab; bottomMargin ¬ xTab; rightMargin ¬ xTab; topMargin ¬ 3*VFonts.FontHeight[dailyHeaderFont]/2; wd ¬ data.calenderViewer.cw - leftMargin - rightMargin; ht ¬ data.outerViewer.ch - topMargin - bottomMargin; rowHt ¬ VFonts.FontHeight[dailyFont]; nCols ¬ nRows ¬ 1; -- to get rid of compiler warnings }; MarginOfWeekly: PROC[data: CalendarData] RETURNS[leftMargin, bottomMargin, rightMargin, topMargin, wd, ht, nRows, nCols, rowHt: INT] = { nDigits: INT = 5; -- number of characters in times on left margin leftMargin ¬ xTab + (nDigits+1)*VFonts.CharWidth['0, labelFont]; bottomMargin ¬ 3*VFonts.FontHeight[labelFont]; rightMargin ¬ xTab; topMargin ¬ 3*VFonts.FontHeight[labelFont]/2; wd ¬ data.calenderViewer.cw - leftMargin - rightMargin; ht ¬ data.outerViewer.ch - topMargin - bottomMargin; nRows ¬ stopHour-startHour; nCols ¬ 7; rowHt ¬ 1; -- to get rid of compiler warnings }; MarginOfMonthly: PROC[data: CalendarData] RETURNS[leftMargin, bottomMargin, rightMargin, topMargin, wd, ht, nRows, nCols, rowHt: INT] = { leftMargin ¬ xTab; bottomMargin ¬ xTab; rightMargin ¬ xTab; topMargin ¬ 3*VFonts.FontHeight[labelFont]; wd ¬ data.calenderViewer.cw - leftMargin - rightMargin; ht ¬ data.outerViewer.ch - topMargin - bottomMargin; nCols ¬ 7; nRows ¬ IF NWeeks[GetFirstOfMonth[data.selDate]] = 6 THEN 6 ELSE 5; rowHt ¬ 1; -- to get rid of compiler warnings }; MarginOfYearly: PROC[data: CalendarData] RETURNS[leftMargin, bottomMargin, rightMargin, topMargin, wd, ht, nRows, nCols, rowHt: INT] = { nDigits: INT = 5; -- number of characters in times on left margin leftMargin ¬ xTab; bottomMargin ¬ xTab; rightMargin ¬ xTab; topMargin ¬ 3*VFonts.FontHeight[labelFont]/2; wd ¬ data.calenderViewer.cw - leftMargin - xTab; ht ¬ data.outerViewer.ch - bottomMargin - 3*VFonts.FontHeight[labelFont]/2; nRows ¬ 3; nCols ¬ 4; rowHt ¬ 1; -- to get rid of compiler warnings }; AdjustHeightMargin: PROC[data: CalendarData] RETURNS[ht: INT, stripHt: INT ¬ 0] = { nRows: INT; [nRows: nRows, ht: ht] ¬ data.view.Margins[data]; IF data.view = weekly THEN { stripHt ¬ 3*ht/(3*(3*nRows + 2)); ht ¬ ht - 2*stripHt; } }; SelectMeetingly: PROC[context: Imager.Context, data: CalendarData, unselect: BOOLEAN ¬ FALSE] = {}; SelectDaily: PROC[context: Imager.Context, data: CalendarData, unselect: BOOLEAN ¬ FALSE] = { leftMargin, bottomMargin, ht, wd, rowHt: INT; cnt: INT; meetingL: List.LORA; cnt ¬ 0; meetingL ¬ GetMeetingL[data.selDate, daily, data]; FOR ml: List.LORA ¬ meetingL, ml.rest UNTIL ml = NIL DO IF NARROW[ml.first, Meeting] = data.curSel.curMeeting THEN EXIT; cnt ¬ cnt + 1; ENDLOOP; [bottomMargin: bottomMargin, leftMargin: leftMargin, ht: ht, wd: wd, rowHt: rowHt] ¬ data.view.Margins[data]; MaskBoxOutlineXor[context, leftMargin, bottomMargin + ht - rowHt*cnt - 2, leftMargin + wd, bottomMargin + ht - rowHt*(cnt+1) - 2]; }; SelectWeekly: PROC[context: Imager.Context, data: CalendarData, unselect: BOOLEAN ¬ FALSE] = { leftMargin, bottomMargin, topMargin: INT; x1, y1, x2, y2: INT; wd, ht, nCols, nRows: INT; meeting: Meeting; mins, col: INT; stripHt: INT; -- height of strip for meetings that are off the chart meeting ¬ data.curSel.curMeeting; IF meeting = NIL THEN RETURN; col ¬ ORD[BasicTime.Unpack[data.curSel.curMeeting.start].weekday] + 1; IF col = 7 THEN col ¬ 0; [leftMargin: leftMargin, bottomMargin: bottomMargin, topMargin: topMargin, ht: ht, wd: wd, nCols: nCols, nRows: nRows] ¬ data.view.Margins[data]; [ht, stripHt] ¬ AdjustHeightMargin[data]; bottomMargin ¬ bottomMargin + stripHt; x1 ¬ leftMargin + (wd*col)/nCols + 1; x2 ¬ leftMargin + (wd*(col+1))/nCols + 1; mins ¬ BasicTime.Unpack[meeting.start].minute + BasicTime.Unpack[meeting.start].hour*60; y1 ¬ bottomMargin + (60*stopHour - mins - meeting.duration)*ht/(60*nRows); y2 ¬ y1 + meeting.duration*ht/(60*nRows) + 1; IF y1 >= bottomMargin + ht THEN MaskBoxOutlineXor[context, x1, bottomMargin+ht+1, x2, bottomMargin+ht+stripHt+1] ELSE IF y2 < bottomMargin THEN MaskBoxOutlineXor[context, x1, bottomMargin-stripHt+1, x2, bottomMargin+1] ELSE MaskBoxOutlineXor[context, x1, y1, x2, y2]; }; SelectMonthly: PROC[context: Imager.Context, data: CalendarData, unselect: BOOLEAN ¬ FALSE] = { leftMargin, bottomMargin: INT; x1, y1, x2, y2: INT; wd, ht, nCols, nRows: INT; [leftMargin: leftMargin, bottomMargin: bottomMargin, ht: ht, wd: wd, nCols: nCols, nRows: nRows] ¬ data.view.Margins[data]; x1 ¬ leftMargin + (wd*data.curSel.col)/nCols + 1; x2 ¬ leftMargin + (wd*(data.curSel.col+1))/nCols + 1; y1 ¬ bottomMargin + (ht*(nRows - data.curSel.row - 1))/nRows + 1; y2 ¬ bottomMargin + (ht*(nRows - data.curSel.row))/nRows + 1; MaskBoxOutlineXor[context, x1, y1, x2, y2]; }; DrawMeetings: PROC[context: Imager.Context, data: CalendarData, mLs: List.LORA, bottomMargin, ht: INT, extraLeftOffset: INT ¬ 0] = { unp: BasicTime.Unpacked; days, mins, duration: INT; boxY, boxHt: INT; leftMargin, wd, nCols, rowsPerBox: INT; mt: Meeting; [leftMargin: leftMargin, wd: wd, nCols: nCols] ¬ data.view.Margins[data]; [nRows: rowsPerBox] ¬ MarginOfWeekly[data]; FOR ml: List.LORA ¬ mLs, ml.rest UNTIL ml = NIL DO mt ¬ NARROW[ml.first]; unp ¬ BasicTime.Unpack[mt.start]; days ¬ ORD[unp.weekday] + 1; duration ¬ mt.duration; mins ¬ unp.hour*60 + unp.minute; WHILE duration > 0 DO boxY ¬ bottomMargin + (ht*(60*stopHour - mins - duration))/(60*rowsPerBox); boxHt ¬ (ht*duration)/(60*rowsPerBox); IF days = 7 THEN days ¬ 0; Imager.SetColor[ context: context, color: IF mt.repeat = once THEN greyOR ELSE lightGreyOR ]; IF data.view = weekly AND boxY > bottomMargin+ht THEN {boxY ¬ bottomMargin+ht; boxHt ¬ ht/(3*rowsPerBox)} ELSE IF data.view = weekly AND boxY + boxHt < bottomMargin THEN {boxY ¬ bottomMargin-ht/(3*rowsPerBox); boxHt ¬ ht/(3*rowsPerBox)}; Imager.MaskRectangle[context, [leftMargin + extraLeftOffset + (wd*days)/nCols + 1, boxY, wd/nCols - extraLeftOffset, boxHt]]; duration ¬ duration - (1440 - mins); -- 1440 is minutes in a day mins ¬ 0; IF days = 6 THEN EXIT; -- saturday is at the end of the week days ¬ days + 1; ENDLOOP; ENDLOOP; }; MeetingFromTime: PROC[date: BasicTime.GMT, data: CalendarData] RETURNS [meeting: Meeting ¬ NIL] = { meetingL: List.LORA; mt: Meeting; meetingL ¬ GetMeetingL[date, daily, data]; FOR ml: List.LORA ¬ meetingL, ml.rest UNTIL ml = NIL DO mt ¬ NARROW[ml.first]; IF mt.start = date THEN {meeting ¬ mt; EXIT;} ENDLOOP; IF meeting = NIL AND meetingL # NIL THEN meeting ¬ NARROW[meetingL.first]; }; DrawMeetingly: PROC[context: Imager.Context, data: CalendarData] = { TRUSTED {Process.Detach[FORK ActualDrawMeetingly[data: data]]}; }; ActualDrawMeetingly: PROC[data: CalendarData] = { meeting: Meeting ¬ NIL; day0, day1, day2: INT; str: ROPE ¬ NIL; IF data.curSel # NIL AND data.curSel.curMeeting # NIL THEN meeting ¬ data.curSel.curMeeting ELSE { meeting ¬ MeetingFromTime[data.selDate, data]; IF meeting = NIL THEN RETURN; IF data.curSel = NIL THEN data.curSel ¬ NEW[CurSelRec]; data.curSel.curMeeting ¬ meeting; }; str ¬ Rope.Concat["\nstart:\t", Convert.RopeFromTime[from: meeting.start, includeDayOfWeek: TRUE]]; IF meeting.type = meeting OR meeting.type = seminar THEN str ¬ Rope.Cat[str, "\nduration:\t", Convert.RopeFromInt[meeting.duration]]; IF meeting.type = meeting THEN str ¬ Rope.Cat[str, "\nexplanation:\t", meeting.explanation] ELSE IF meeting.type = command THEN str ¬ Rope.Cat[str, "\ncommand:\t", meeting.explanation] ELSE IF meeting.type = seminar THEN str ¬ Rope.Cat[str, "\nseminar subject:\t", meeting.explanation] ELSE str ¬ Rope.Cat[str, "\nproteced cmd:\t", meeting.explanation]; str ¬ Rope.Cat[str, "\nrepeat:\t", Remind.RopeFromRepetition[meeting.repeat]]; IF meeting.more # NIL AND meeting.type = meeting THEN str ¬ Rope.Cat[str, "\nmore:\t", meeting.more]; IF meeting.type = meeting OR meeting.type = seminar THEN { day0 ¬ BasicTime.Unpack[meeting.start].day; FOR rl: Remind.RemindList ¬ meeting.reminders, rl.rest WHILE rl#NIL DO WITH rl.first SELECT FROM x: REF ReminderRecord[alert] => { day1 ¬ BasicTime.Unpack[x.start].day; day2 ¬ BasicTime.Unpack[x.stop].day; str ¬ Rope.Cat[str, "\nstartReminding:\t", Convert.RopeFromTime[from: x.start, start: IF day0=day1 THEN hours ELSE years, includeZone: FALSE]]; IF day1 = day2 THEN str ¬ Rope.Cat[str, "\nstopReminding:\t", Convert.RopeFromTime[from: x.stop, start: hours, includeZone: FALSE]] ELSE str ¬ Rope.Cat[str, "\nstopReminding:\t", Convert.RopeFromTime[from: x.stop, start: months, useAMPM: TRUE, includeZone: FALSE]]; }; x: REF ReminderRecord[mail] => { day1 ¬ BasicTime.Unpack[x.when].day; str ¬ Rope.Cat[str, "\nmail at:\t", Convert.RopeFromTime[from: x.when, start: IF day0=day1 THEN hours ELSE years, includeZone: FALSE], "\nmail to:\t", x.to]; }; ENDCASE => ERROR; ENDLOOP; }; ViewerTools.EnableUserEdits[data.textViewer]; ViewerTools.SetContents[data.textViewer, str]; ViewerTools.InhibitUserEdits[data.textViewer]; }; ShortTime: PROC[time: BasicTime.GMT] RETURNS [ROPE] = { hr: INTEGER; hr ¬ BasicTime.Unpack[time].hour; IF hr > 12 THEN hr ¬ hr - 12; RETURN[IO.PutFR["%2g:%02g", IO.int[hr], IO.int[BasicTime.Unpack[time].minute]]]; }; DrawDaily: PROC[context: Imager.Context, data: CalendarData] = { mLs: List.LORA; leftMargin, bottomMargin, ht: INT; rowHt, xpos, timeOff: INT; date: BasicTime.GMT; mt: Meeting; Imager.SetFont[context, dailyFont]; [leftMargin: leftMargin, bottomMargin: bottomMargin, ht: ht, rowHt: rowHt] ¬ data.view.Margins[data]; date ¬ TimeParse.Adjust[baseTime: data.selDate, days: 0].time; mLs ¬ GetMeetingL[data.selDate, daily, data]; timeOff ¬ VFonts.StringWidth["9:99", bigLabelFont]; timeOff ¬ timeOff + timeOff/2; xpos ¬ ht + bottomMargin; rowHt ¬ VFonts.FontHeight[dailyFont]; Imager.SetFont[context, dailyHeaderFont]; Imager.SetXY[context, [leftMargin, xpos + rowHt/4]]; Imager.ShowRope[context, Convert.RopeFromTime[ from: data.selDate, end: days, includeDayOfWeek: TRUE, useAMPM: TRUE, includeZone: FALSE] ]; Imager.SetFont[context, dailyFont]; xpos ¬ xpos - rowHt; FOR ml: List.LORA ¬ mLs, ml.rest UNTIL ml = NIL DO mt ¬ NARROW[ml.first]; Imager.SetXY[context, [leftMargin, xpos]]; Imager.ShowRope[context, ShortTime[mt.start]]; Imager.SetXY[context, [leftMargin + timeOff, xpos]]; Imager.ShowRope[context, mt.explanation]; xpos ¬ xpos - rowHt; ENDLOOP; }; DrawWeekly: PROC[context: Imager.Context, data: CalendarData] = { monthNames: ARRAY [0..11] OF ROPE = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; leftMargin, bottomMargin, rightMargin, topMargin: INT; nRows, nCols: INT; wd, ht: INT; hr, off: INT; days: INT; sunday, saturday, now, dates: BasicTime.GMT; k: INT; stripHt: INT; -- height of strip for meetings that are off the chart str: ROPE; fontHt: INTEGER; today: INT ¬ -1; DrawMeetingsForWeek: PROC = { mLs: List.LORA; mLs ¬ GetMeetingL[data.selDate, weekly, data]; Imager.ClipRectangleI[context, leftMargin, bottomMargin, wd, ht]; DrawMeetings[context, data, mLs, bottomMargin + stripHt, ht - 2*stripHt]; }; [leftMargin, bottomMargin, rightMargin, topMargin, wd, ht, nRows, nCols] ¬ data.view.Margins[data]; stripHt ¬ AdjustHeightMargin[data].stripHt; DrawGrid[context: context, nRows: nRows, nCols: 7, leftMargin: leftMargin, bottomMargin: bottomMargin + stripHt, wd: wd, ht: ht - 2*stripHt]; DrawGrid[context: context, nRows: 1, nCols: 1, leftMargin: leftMargin, bottomMargin: bottomMargin, wd: wd, ht: ht]; Imager.SetFont[context, labelFont]; sunday ¬ GetSunday[data.selDate]; off ¬ (wd/7 - VFonts.StringWidth["00", labelFont])/2; now ¬ TimeParse.Adjust[baseTime: BasicTime.Now[], days: 0].time; FOR i: INT IN [1 .. 7] DO dates ¬ TimeParse.Adjust[baseTime: sunday, days: i-1].time; days ¬ BasicTime.Unpack[dates].day; Imager.SetXY[context, [leftMargin+((i-1)*wd)/7 + off, 3*VFonts.FontHeight[labelFont]/2]]; IF dates = now THEN {today ¬ i; Imager.SetFont[context, boldLabelFont]}; Imager.ShowRope[context, Convert.RopeFromInt[days]]; IF dates = now THEN Imager.SetFont[context, labelFont]; ENDLOOP; WriteWeekDays[context, leftMargin, bottomMargin, wd, ht, today]; saturday ¬ TimeParse.Adjust[baseTime: sunday, days: 6].time; IF BasicTime.Unpack[sunday].day < BasicTime.Unpack[saturday].day THEN { k ¬ ORD[BasicTime.Unpack[sunday].month]; str ¬ IO.PutFR["%g %g", IO.rope[monthNames[k]], IO.int[BasicTime.Unpack[sunday].year]]; off ¬ (wd - VFonts.StringWidth[str, labelFont])/2; Imager.SetXY[context, [leftMargin + off, VFonts.FontHeight[labelFont]/2]]; Imager.ShowRope[context, str]; } ELSE { k ¬ ORD[BasicTime.Unpack[sunday].month]; str ¬ IO.PutFR["%g %g", IO.rope[monthNames[k]], IO.int[BasicTime.Unpack[sunday].year]]; Imager.SetXY[context, [leftMargin, VFonts.FontHeight[labelFont]/2]]; Imager.ShowRope[context, str]; k ¬ ORD[BasicTime.Unpack[saturday].month]; off ¬ wd + leftMargin - VFonts.StringWidth[monthNames[k], labelFont]; Imager.SetXY[context, [off, VFonts.FontHeight[labelFont]/2]]; Imager.ShowRope[context, monthNames[k]]; }; IF ht < 10*nRows THEN { -- for small viewers, use a smaller font. 10 is esthetic constant Imager.SetFont[context, littleLabelFont]; fontHt ¬ VFonts.FontHeight[littleLabelFont]; } ELSE fontHt ¬ VFonts.FontHeight[labelFont]; FOR i: INT IN [1 .. nRows+1] DO off ¬ ((ht - 2*stripHt)*(i-1))/(nRows) - fontHt/3; Imager.SetXY[context, [xTab, bottomMargin + stripHt + off]]; hr ¬ IF ( hr ¬ stopHour - i + 1) > 12 THEN hr - 12 ELSE hr; Imager.ShowRope[context, IO.PutFR1["%2g:00", IO.int[hr]]]; ENDLOOP; Imager.DoSave[context, DrawMeetingsForWeek]; }; DrawMonthly: PROC[context: Imager.Context, data: CalendarData] = { leftMargin, bottomMargin, rightMargin, topMargin: INT; first: BasicTime.GMT; i, nRows, nCols: INT; wd, ht, leftOff: INT; month: BasicTime.MonthOfYear; fontBaseline: INT; title: ROPE; DrawMeetingsForMonth: PROC = { mLs: List.LORA; day: BasicTime.GMT; day ¬ GetSunday[GetFirstOfMonth[data.selDate]]; day ¬ TimeParse.Adjust[baseTime: day, days: 7*i].time; mLs ¬ GetMeetingL[day, weekly, data]; Imager.ClipRectangleI[context, leftMargin, bottomMargin + ht - ((i+1)*ht)/nRows, wd, ht/nRows]; DrawMeetings[context, data, mLs, bottomMargin + ht - ((i+1)*ht)/nRows, ht/nRows, wd/(nCols*4)]; }; [leftMargin: leftMargin, bottomMargin: bottomMargin, rightMargin: rightMargin, topMargin: topMargin, wd: wd, ht: ht, nRows: nRows, nCols: nCols] ¬ data.view.Margins[data]; first ¬ GetFirstOfMonth[data.selDate]; nRows ¬ IF NWeeks[first] = 6 THEN 6 ELSE 5; DrawGrid[context: context, nRows: nRows, nCols: 7, leftMargin: leftMargin, bottomMargin: bottomMargin, wd: wd, ht: ht]; WriteWeekDays[context, leftMargin, bottomMargin, wd, ht]; month ¬ BasicTime.Unpack[first].month; title ¬ IO.PutFR["%g %g", IO.rope[monthNames[ORD[month]]], IO.int[BasicTime.Unpack[first].year]]; Imager.SetFont[context, bigLabelFont]; fontBaseline ¬ bottomMargin + ht + 3*VFonts.FontHeight[labelFont]/2 + 1; leftOff ¬ (wd - VFonts.StringWidth[title, bigLabelFont])/2; Imager.SetXY[context, [leftMargin + leftOff, fontBaseline]]; Imager.ShowRope[context, title]; Imager.SetFont[context, labelFont]; DrawDaysOfMonth[context: context, first: first, bottomEdge: bottomMargin, leftEdge: leftMargin, nRows: nRows, firstRow: nRows, wd: wd, ht: ht, slash: FALSE]; FOR i ¬ 0, i+1 UNTIL i = nRows DO Imager.DoSave[context, DrawMeetingsForMonth]; ENDLOOP; }; DrawYearly: PROC[context: Imager.Context, data: CalendarData] = { str: ROPE; first: BasicTime.GMT; -- first of this year leftMargin, bottomMargin: INT; nCols, nRows: INT; wd, ht: INT; off, leftOff, bottomOff, fontht: INT; leftEdge, bottomEdge: INT; title: ROPE; font: Imager.Font; [leftMargin: leftMargin, bottomMargin: bottomMargin, wd: wd, ht: ht, nRows: nRows, nCols: nCols] ¬ data.view.Margins[data]; DrawGrid[context: context, nRows: nRows, nCols: nCols, leftMargin: leftMargin, bottomMargin: bottomMargin, wd: wd, ht: ht]; first ¬ GetFirstOfYear[data.selDate]; title ¬ Convert.RopeFromInt[BasicTime.Unpack[first].year]; leftOff ¬ (wd - VFonts.StringWidth[title, bigLabelFont])/2; Imager.SetFont[context, bigLabelFont]; Imager.SetXY[context, [leftMargin + leftOff, bottomMargin + ht + 3]]; Imager.ShowRope[context, title]; IF ht < 11*6*nRows THEN font ¬ littleLabelFont ELSE font ¬ labelFont; Imager.SetFont[context, font]; fontht ¬VFonts.FontHeight[font]; FOR i: INT IN [0 .. nRows-1] DO FOR j: INT IN [0 .. nCols-1] DO str ¬ monthNames[i*nCols + j]; off ¬ (wd/nCols - VFonts.StringWidth[str, font])/2; leftEdge ¬ leftMargin+(j*wd)/nCols; bottomEdge ¬ bottomMargin + ht*(nRows - i - 1)/nRows; bottomOff ¬ bottomEdge + ht/nRows - fontht; Imager.SetXY[context, [leftEdge + off, bottomOff + 3]]; Imager.ShowRope[context, str]; DrawDaysOfMonth[ context: context, first: first, bottomEdge: bottomMargin + ht*(nRows - i - 1)/nRows + 3, leftEdge: leftMargin+(j*wd)/nCols + 2, nRows: 6, firstRow: 5, wd: wd/nCols, ht: ht/nRows, slash: TRUE]; first ¬ TimeParse.Adjust[baseTime: first, months: 1, precisionOfResult: months].time; ENDLOOP; ENDLOOP; }; CurSelEqual: PROC[data: CalendarData, cur1, cur2: CurSel] RETURNS [BOOLEAN] = { IF cur1 = NIL OR cur2 = NIL THEN RETURN[FALSE]; RETURN [SELECT data.view FROM weekly, daily => cur1.curMeeting = cur2.curMeeting, monthly, yearly => cur1.row = cur2.row AND cur1.col = cur2.col, ENDCASE => TRUE] }; DateToSelMeetingly: PROC[data: CalendarData, now: BasicTime.GMT] RETURNS [curSel: CurSel, repaint: BOOLEAN] = { RETURN[NEW[CurSelRec], TRUE] }; DateToSelDaily: PROC[data: CalendarData, now: BasicTime.GMT] RETURNS [curSel: CurSel, repaint: BOOLEAN] = { RETURN[NEW[CurSelRec], TRUE] }; DateToSelWeekly: PROC[data: CalendarData, now: BasicTime.GMT] RETURNS [curSel: CurSel, repaint: BOOLEAN] = { unp, unpToday: BasicTime.Unpacked; unp ¬ BasicTime.Unpack[GetSunday[data.selDate]]; unpToday ¬ BasicTime.Unpack[GetSunday[now]]; repaint ¬ unp.year # unpToday.year OR unp.month # unpToday.month OR unp.day # unpToday.day; curSel ¬ NEW[CurSelRec]; }; DateToSelMonthly: PROC[data: CalendarData, now: BasicTime.GMT] RETURNS [curSel: CurSel, repaint: BOOLEAN] = { unp, unpToday: BasicTime.Unpacked; leftMargin, bottomMargin, wd, ht, nCols, nRows: INT; curSel ¬ NEW[CurSelRec]; unp ¬ BasicTime.Unpack[data.selDate]; unpToday ¬ BasicTime.Unpack[now]; repaint ¬ unp.month # unpToday.month OR unp.year # unpToday.year; [leftMargin: leftMargin, bottomMargin: bottomMargin, wd: wd, ht: ht, nCols: nCols, nRows: nRows] ¬ data.view.Margins[data]; [curSel.row, curSel.col] ¬ DateToRowColInMonth[now]; }; DateToSelYear: PROC[data: CalendarData, now: BasicTime.GMT] RETURNS [curSel: CurSel, repaint: BOOLEAN] = { unp, unpToday: BasicTime.Unpacked; curSel ¬ NEW[CurSelRec]; unp ¬ BasicTime.Unpack[data.selDate]; unpToday ¬ BasicTime.Unpack[now]; repaint ¬ unp.year # unpToday.year; [curSel.row, curSel.col] ¬ DateToRowColInYear[now]; }; MouseToSelMeetingly: PROC[data: CalendarData] RETURNS [time: BasicTime.GMT, curSel: CurSel] = { time ¬ BasicTime.nullGMT; -- compiler warning -- }; MouseToSelDaily: PROC[data: CalendarData] RETURNS [time: BasicTime.GMT, curSel: CurSel] = { bottomMargin, ht, rowHt: INT; found: Meeting ¬ NIL; row, cnt: INT; meetingL: List.LORA; meetingL ¬ GetMeetingL[data.selDate, daily, data]; [bottomMargin: bottomMargin, ht: ht, rowHt: rowHt] ¬ data.view.Margins[data]; row ¬ (ht + bottomMargin - data.y)/rowHt; IF row < 0 THEN {curSel ¬ NIL; time ¬ data.selDate; RETURN}; cnt ¬ 0; FOR ml: List.LORA ¬ meetingL, ml.rest UNTIL ml = NIL DO IF cnt = row THEN {found ¬ NARROW[ml.first, Meeting]; EXIT}; cnt ¬ cnt + 1; ENDLOOP; IF found # NIL THEN { curSel ¬ NEW[CurSelRec]; curSel.curMeeting ¬ found; time ¬ found.start; } ELSE { curSel ¬ NIL; time ¬ data.selDate; } }; MouseToSelWeekly: PROC[data: CalendarData] RETURNS [time: BasicTime.GMT, curSel: CurSel] = { nRows, nCols, bottomMargin, leftMargin, wd, ht: INT; meetingMins, cursorMins, days: INT; unp, unpSunday: BasicTime.Unpacked; found: Meeting ¬ NIL; col: INT; meetingL: List.LORA; stripHt: INT; -- height of strip for meetings that are off the chart mt: Meeting; unpSunday ¬ BasicTime.Unpack[GetSunday[data.selDate]]; meetingL ¬ GetMeetingL[data.selDate, weekly, data]; [nCols: nCols, nRows: nRows, bottomMargin: bottomMargin, leftMargin: leftMargin, ht: ht, wd: wd] ¬ data.view.Margins[data]; [ht, stripHt] ¬ AdjustHeightMargin[data]; bottomMargin ¬ bottomMargin + stripHt; col ¬ (nCols*(data.x - leftMargin))/wd; FOR ml: List.LORA ¬ meetingL, ml.rest UNTIL ml = NIL DO mt ¬ NARROW[ml.first]; unp ¬ BasicTime.Unpack[mt.start]; days ¬ ORD[unp.weekday] - ORD[unpSunday.weekday]; IF days < 0 THEN days ¬ days + 7; meetingMins ¬ unp.hour*60 + unp.minute; cursorMins ¬ stopHour*60 - (nRows*60*(data.y - bottomMargin))/ht; IF col = days THEN { IF meetingMins + INT[mt.duration] < startHour*60 AND cursorMins < startHour*60 THEN {found ¬ mt; EXIT}; IF meetingMins > stopHour*60 AND cursorMins > stopHour*60 THEN {found ¬ mt; EXIT}; IF meetingMins <= cursorMins AND cursorMins <= meetingMins + INT[mt.duration] THEN {found ¬ mt; EXIT}; }; ENDLOOP; IF found # NIL THEN { curSel ¬ NEW[CurSelRec]; curSel.curMeeting ¬ found; time ¬ found.start; } ELSE { curSel ¬ NIL; time ¬ data.selDate; } }; MouseToSelMonthly: PROC[data: CalendarData] RETURNS [time: BasicTime.GMT, curSel: CurSel] = { leftMargin, bottomMargin: INT; row, col: INT; ht, wd, nRows, nCols: INT; [leftMargin: leftMargin, bottomMargin: bottomMargin, wd: wd, ht: ht, nCols: nCols, nRows: nRows] ¬ data.view.Margins[data]; col ¬ (nCols*(data.x - leftMargin))/wd; row ¬ (nRows*(data.y - bottomMargin))/ht; row ¬ nRows - row - 1; IF row IN [0 .. nRows) AND col IN [0 .. nCols) THEN SELECT data.view FROM monthly => { IF (row = 0 AND col < DateToRowColInMonth[GetFirstOfMonth[data.selDate]].col) OR (row = nRows-1 AND col > DateToRowColInMonth[LastOfMonth[data.selDate]].col) THEN { row ¬ -1; col ¬ -1; time ¬ data.selDate; } ELSE time ¬ RowColInMonthToDate[row, col, data.selDate]; }; yearly => time ¬ RowColInYearToDate[row, col, data.selDate]; ENDCASE => time ¬ data.selDate ELSE { row ¬ -1; col ¬ -1; time ¬ data.selDate; }; IF row = -1 THEN curSel ¬ NIL ELSE { curSel ¬ NEW[CurSelRec]; -- XXX: should be allocated once and for all, I think curSel.row ¬ row; curSel.col ¬ col; } }; MouseToTimeWeekly: PROC[data: CalendarData] RETURNS [time: BasicTime.GMT] = { nRows, nCols, bottomMargin, leftMargin, wd, ht: INT; cursorMins: INT; sunday: BasicTime.GMT; unp: BasicTime.Unpacked; col: INT; stripHt: INT; -- height of strip for meetings that are off the chart sunday ¬ GetSunday[data.selDate]; [nCols: nCols, nRows: nRows, bottomMargin: bottomMargin, leftMargin: leftMargin, ht: ht, wd: wd] ¬ data.view.Margins[data]; [ht, stripHt] ¬ AdjustHeightMargin[data]; bottomMargin ¬ bottomMargin + stripHt; col ¬ (nCols*(data.x - leftMargin))/wd; cursorMins ¬ stopHour*60 - (nRows*60*(data.startBoundY - bottomMargin))/ht; IF col > 0 OR cursorMins > 3*60 THEN { unp ¬ BasicTime.Unpack[sunday]; unp.hour ¬ 2; sunday ¬ BasicTime.Pack[unp]; unp ¬ BasicTime.Unpack[sunday]; unp.hour ¬ 3; sunday ¬ BasicTime.Pack[unp]; cursorMins ¬ cursorMins - 3*60 }; cursorMins ¬ round*((cursorMins + (round/2))/round); time ¬ TimeParse.Adjust[baseTime: sunday, minutes: col*24*60 + cursorMins].time; }; PaintCalendar: ViewerClasses.PaintProc = { ENABLE ABORTED => GOTO Finished; -- return and unlock viewers data: CalendarData ¬ NARROW[self.data]; now: BasicTime.GMT; curSel: CurSel; repaint: BOOLEAN ¬ FALSE; changed: WhatChanged ¬ NARROW[whatChanged]; IF changed # NIL AND data.boundingMode AND data.startBoundX >= 0 THEN { IF data.stopBounding THEN { MaskBoxOutlineXor[context, data.startBoundX, data.startBoundY, data.oldX, data.oldY]; data.stopBounding ¬ FALSE; data.boundingMode ¬ FALSE; data.startBoundX ¬ -1; data.oldX ¬ -1; } ELSE IF data.x # data.oldX OR data.y # data.oldY THEN { IF data.oldX # -1 THEN MaskBoxOutlineXor[context, data.startBoundX, data.startBoundY, data.oldX, data.oldY]; MaskBoxOutlineXor[context, data.startBoundX, data.startBoundY, data.x, data.y]; data.oldX ¬ data.x; data.oldY ¬ data.y; }; RETURN; }; IF changed = NIL OR changed.changeSelView THEN { -- The window system asked for a repaint event data.view.Draw[context, data]; IF data.resizing AND data.curSel # NIL THEN data.view.Select[context: context, data: data]; data.resizing ¬ FALSE; RETURN; }; IF changed.setToday THEN { now ¬ BasicTime.Now[]; [curSel, repaint] ¬ data.view.DateToSel[data, now]; data.selDate ¬ now; IF repaint THEN { Imager.SetColor[ context: context, color: whiteAND]; Imager.MaskRectangleI[context, self.wx, self.wy, self.ww, self.wh]; Imager.SetColor[ context: context, color: blackOR]; data.view.Draw[context, data]; data.curSel ¬ NIL; }; }; IF changed.newMouseLoc OR changed.setToday THEN { IF NOT changed.setToday THEN [data.selDate, curSel] ¬ data.view.MouseToSel[data]; IF curSel = NIL THEN RETURN; IF NOT CurSelEqual[data, curSel, data.curSel] THEN { IF data.curSel # NIL THEN data.view.Select[context: context, data: data, unselect: TRUE]; data.curSel ¬ curSel; Imager.SetColor[ context: context, color: blackOR]; data.view.Select[context, data] }; } EXITS Finished => NULL; }; AdjustOuter: ViewerClasses.AdjustProc = { data: CalendarData ¬ NARROW[self.data]; v: ViewerClasses.Viewer ¬ data.calenderViewer; IF v # NIL THEN { IF data.view # meetingly THEN { ViewerOps.EstablishViewerPosition[v, 0, 0, data.outerViewer.ww, data.outerViewer.ch]; v ¬ data.textViewer; ViewerOps.EstablishViewerPosition[v, 0, data.outerViewer.ch, data.outerViewer.ww, data.outerViewer.ch]; } ELSE { ViewerOps.EstablishViewerPosition[v, 0, data.outerViewer.ch, data.outerViewer.ww, data.outerViewer.ch]; v ¬ data.textViewer; ViewerOps.EstablishViewerPosition[v, 0, 0, data.outerViewer.ww, data.outerViewer.ch]; }; data.resizing ¬ TRUE; }; RETURN[TRUE]; }; DestroyDisplay: ViewerClasses.DestroyProc = { TRUSTED {Process.Detach[FORK Unlink[self]]}; }; Unlink: ENTRY PROC[vw: ViewerClasses.Viewer] = { ENABLE UNWIND => NULL; viewerList ¬ List.DRemove[vw, viewerList]; }; PaintIcon: ViewerClasses.PaintProc = { ENABLE UNCAUGHT => IF debugging THEN REJECT ELSE GOTO Finished; -- return and unlock viewers unp: BasicTime.Unpacked; str: ROPE; wd, ht: INT; icon: Icons.IconFlavor; monthShortNames: ARRAY BasicTime.MonthOfYear[January .. December] OF ROPE = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; PutIcon: PROC = {Icons.DrawIcon[icon, context]}; IF self.iconic THEN { IF ViewerPrivate.selectedIcon = self THEN icon ¬ invertedCalendarIcon ELSE icon ¬ calendarIcon; Imager.DoSave[context, PutIcon]; IF ViewerPrivate.selectedIcon = self THEN Imager.SetColor[ context: context, color: whiteAND] ELSE Imager.SetColor[ context: context, color: blackOR]; unp ¬ BasicTime.Unpack[BasicTime.Now[]]; str ¬ Convert.RopeFromCard[unp.day]; wd ¬ VFonts.StringWidth[str, iconFont]; ht ¬ VFonts.FontHeight[iconFont]; Imager.SetXY[context, [41 + MAX[15-wd, 0]/2, 47 + MAX[12-ht, 0]/2]]; Imager.SetFont[context, iconFont]; Imager.ShowRope[context, str]; str ¬ monthShortNames[unp.month]; wd ¬ VFonts.StringWidth[str, smallIconFont]; ht ¬ VFonts.FontHeight[smallIconFont]; Imager.SetXY[context, [5 + MAX[15-wd, 0]/2, 49 + MAX[12-ht, 0]/2]]; Imager.SetFont[context, smallIconFont]; Imager.ShowRope[context, str]; }; EXITS Finished => NULL; }; NotifyCalendar: ViewerClasses.NotifyProc = { data: CalendarData ¬ NARROW[self.data]; mouse: TIPUser.TIPScreenCoords ¬ NARROW[input.first]; newView: Views; newTime, start: BasicTime.GMT; sel: CurSel; ht, nRows, length: INT; SELECT input.rest.first FROM --XXX: can paint be put outside of select? $CalendarSelect, $EndCalendarSelect => { IF data.boundingMode AND data.startBoundX = -1 THEN { data.startBoundX ¬ mouse.mouseX; data.startBoundY ¬ mouse.mouseY; }; data.x ¬ mouse.mouseX; data.y ¬mouse.mouseY; IF input.rest.first = $EndCalendarSelect AND data.boundingMode THEN { minX, minY, maxX, maxY: INT; minX _ MIN[data.startBoundX, data.x]; minY _ MIN[data.startBoundY, data.y]; maxX _ MAX[data.startBoundX, data.x]; maxY _ MAX[data.startBoundY, data.y]; data.startBoundX ¬ minX; data.startBoundY ¬ maxY; data.x ¬ maxX; data.y ¬ minY; data.stopBounding ¬ TRUE; start ¬ MouseToTimeWeekly[data]; [ht: ht, nRows: nRows] ¬ MarginOfWeekly[data]; ht ¬ AdjustHeightMargin[data].ht; length ¬ (60*(stopHour-startHour)*(data.startBoundY - data.y) + ht/2)/ht; length ¬ round*((length+ (round/2))/round); SimpleFeedback.ClearHerald[$CalendarTool, $Error]; IF length<=1 THEN { -- can't see it otherwise. KAP. September 18, 1992 SimpleFeedback.Append[$CalendarTool, oneLiner, $Error, "Please specify duration of at least 2 minutes."]; SimpleFeedback.Blink[$CalendarTool, $Error]; } ELSE RemindEdit.SpecifyMeeting[NEW[Remind.MeetingRec ¬ [type: data.selectionMode, start: start, duration: length, explanation: "", uniqID: 0, public: data.name#NIL]], FALSE, data.name, data.outerViewer.column]; data.selectionMode ¬ none; } ELSE IF input.rest.first = $EndCalendarSelect AND data.selectionMode = command THEN BEGIN data.startBoundX ¬ mouse.mouseX; data.startBoundY ¬ mouse.mouseY; start ¬ MouseToTimeWeekly[data]; [ht: ht, nRows: nRows] ¬ MarginOfWeekly[data]; ht ¬ AdjustHeightMargin[data].ht; SimpleFeedback.ClearHerald[$CalendarTool, $Error]; RemindEdit.SpecifyMeeting[NEW[Remind.MeetingRec ¬ [type: command, start: start, duration: CommandMeetingLength, explanation: "", uniqID: 0, public: data.name#NIL]], FALSE, data.name, data.outerViewer.column]; data.selectionMode ¬ none; END; }; $CalendarOpen => { data.x ¬ mouse.mouseX; data.y ¬ mouse.mouseY; [newTime, sel] ¬ data.view.MouseToSel[data]; SELECT data.view FROM yearly => newView ¬ monthly; monthly => newView ¬ daily; weekly => { IF sel = NIL THEN { SimpleFeedback.Append[$CalendarTool, oneLiner, $Error, "Click on top of meeting"]; SimpleFeedback.Blink[$CalendarTool, $Error]; RETURN; }; newView ¬ meetingly; }; daily => newView ¬ meetingly; meetingly => RETURN; ENDCASE => NULL; [] ¬ MakeCalendarTool[data.name, newTime, newView, data.outerViewer.column, NIL, sel]; }; ENDCASE => RETURN; ViewerOps.PaintViewer[ viewer: data.calenderViewer, hint: client, whatChanged: NEW[WhatChangedRec ¬ [newMouseLoc: TRUE]], clearClient: FALSE]; }; Midnight: PROC = { wait: INT; now, midnight: BasicTime.GMT; DO now ¬ BasicTime.Now[]; midnight ¬ TimeParse.Adjust[baseTime: now, days:1].time; WHILE TRUE DO wait ¬ BasicTime.Period[now, midnight]; wait ¬ wait + 60; Process.Pause[Process.SecondsToTicks[wait]]; now ¬ BasicTime.Now[]; IF BasicTime.Period[now, midnight] < 0 THEN EXIT; ENDLOOP; FOR vwL: List.LORA ¬ viewerList, vwL.rest UNTIL vwL = NIL DO ViewerOps.PaintViewer[viewer: NARROW[vwL.first], hint: client, clearClient: TRUE]; ENDLOOP; ENDLOOP; }; CTSetDebugging: Commander.CommandProc = { debugging ¬ IF Rope.Fetch[CommanderOps.ArgN[cmd, 1], 0] = 'T THEN TRUE ELSE FALSE; }; calendarIcon: Icons.IconFlavor ¬ Icons.NewIconFromFile["CalendarTool.icons", 0]; invertedCalendarIcon: Icons.IconFlavor ¬ Icons.NewIconFromFile["CalendarTool.icons", 1]; aSelPos: ViewerTools.SelPos ¬ NEW[ViewerTools.SelPosRec ¬ [0, 0, FALSE, before]]; debugging: BOOL ¬ FALSE; calendarClass: ViewerClasses.ViewerClass ¬ NEW[ ViewerClasses.ViewerClassRec ¬ [ paint: PaintCalendar, notify: NotifyCalendar, tipTable: TIPUser.InstantiateNewTIPTable["CalendarTool.tip"]]]; outerViewerClass: ViewerClasses.ViewerClass ¬ NEW[ ViewerClasses.ViewerClassRec ¬ [ adjust: AdjustOuter, icon: private, -- will call PaintIcon to paint icon paint: PaintIcon, destroy: DestroyDisplay]]; Commander.Register[ key: "calendartool", proc: MakeFirstCalendarTool, doc: "[CalendarName]\nBring up a viewer that displays meetings in the Remind database. Options are:\n -l left column\n -r right column\n -o open\n -c closed" ]; ViewerOps.RegisterViewerClass[$Calendar, calendarClass]; ViewerOps.RegisterViewerClass[$OuterCalendar, outerViewerClass]; Commander.Register[key: "CalendarToolReset", proc: CalendarToolReset, doc: "\nResets CalendarTool and recalculates reminder schedule" ]; Commander.Register[key: "ResetCalendarTool", proc: CalendarToolReset, doc: "\nResets CalendarTool and recalculates reminder schedule" ]; Commander.Register[key: "CTSetDebugging", proc: CTSetDebugging, doc: "\nUsage: CTSetDebugging T or CTSetDebugging F" ]; UserProfile.CallWhenProfileChanges[CacheProfileData]; TRUSTED {Process.Detach[FORK Midnight[]]}; END. END. ¦CalendarTool.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, September 18, 1992 11:40 am PDT Theimer, September 18, 1990 7:47 pm PDT Last tweaked by Mike Spreitzer August 27, 1993 9:22 am PDT Willie-s, May 6, 1992 2:44 pm PDT Chauser, January 13, 1993 3:04 pm PST paint the calendar turn on feedback selection for data.selDate computes margins of calendar use location of mouse click to update selDate and curSel convert a date to a selection. Roughly inverse of MouseToSel. repaint=TRUE if date is not visible This is the smallest duration that will show up on smaller CalendarTool viewer sizes. top row of calendar is row=0 Utility that convertd from LIST of Meeting to LORA Utilities that put an order onto Views Utilities for computing about dates Return date of the first of the month that contains this day Return date of the last day of the month that contains this day number of weeks in month containing day, that is, number of rows it takes to display it Return date of the sunday that begins the week containing this day. Set time to 12 am. given a (row, col) pair and a date, convert (row,col) to a new date in the same month. Row 0 is top row given a date, convert to (row, col) pair in appropriate month. Row 0 is top row given a date, convert to (row, col) pair in appropriate year. given a (row, col) pair and a date, convert (row,col) to a new date in the same year. Row 0 is top row XXX: shouldn't repaint unless viewing something that changed PROC [reason: UserProfile.ProfileChangeReason] PROC [cmd: Handle] RETURNS [result: REF _ NIL, msg: ROPE _ NIL]; PROC [cmd: Handle] RETURNS [result: REF _ NIL, msg: ROPE _ NIL]; ELSE name _ CommanderOps.ArgN[cmd, i]; add our command to the menu PROC [parent: Viewer, clientData: REF ANY _ NIL, mouseButton: MouseButton _ red, shift, control: BOOL _ FALSE]; PROC [parent: Viewer, clientData: REF ANY _ NIL, mouseButton: MouseButton _ red, shift, control: BOOL _ FALSE]; - Prevents looping forever if at last meeting (and no periodics) This is a utility for UpFunction. If yes is TRUE then show text, otherwise hide it PROC [parent: Viewer, clientData: REF ANY _ NIL, mouseButton: MouseButton _ red, shift, control: BOOL _ FALSE]; EditFunction Stuff PROC [parent: Viewer, clientData: REF ANY _ NIL, mouseButton: MouseButton _ red, shift, control: BOOL _ FALSE]; PROC [parent: Viewer, clientData: REF ANY _ NIL, mouseButton: MouseButton _ red, shift, control: BOOL _ FALSE]; PROC [parent: Viewer, clientData: REF ANY _ NIL, mouseButton: MouseButton _ red, shift, control: BOOL _ FALSE]; PROC [parent: Viewer, clientData: REF ANY _ NIL, mouseButton: MouseButton _ red, shift, control: BOOL _ FALSE]; PROC [parent: Viewer, clientData: REF ANY _ NIL, mouseButton: MouseButton _ red, shift, control: BOOL _ FALSE]; PROC [parent: Viewer, clientData: REF ANY _ NIL, mouseButton: MouseButton _ red, shift, control: BOOL _ FALSE]; PROC [parent: Viewer, clientData: REF ANY _ NIL, mouseButton: MouseButton _ red, shift, control: BOOL _ FALSE]; Drawing Routines this routines caches lists of meetings draw grid of dimensions wd, ht write days on top of a box of dimensions wd, ht PROC [ref1: REF ANY, ref2: REF ANY] RETURNS [Comparison] PROC [ref1: REF ANY, ref2: REF ANY] RETURNS [Comparison] draw the days of a month starting with date=first in a box of dimensions wd, ht. FirstRow = nRows if there are no blank lines at top of month use location of mouse click to update selDate and curSel MaskBoxOutline[context, leftMargin, bottomMargin + ht - rowHt*cnt, leftMargin + wd, bottomMargin + ht - rowHt*(cnt+1), [xor, null]]; doubles as SelectYearly draw meetings for weekly or monthly color: ImagerColorPrivate.ColorFromStipple[word: IF mt.repeat = once THEN grey ELSE lightGrey, function: [or, null]] XXX: the factor of 1/3 should be shared with DrawWeekly XXX: should I move forward looking for a day with a meeting in it? The reason for forking, is that I don't want the walnut lookup stuff inside a paint proc XXX: actually, if if there are no meetings for this day, then zoom should have been a no-op, instead of trying to paint a non-existent meeting IF meeting.more # NIL THEN { tContents _ GetMailMsg[meeting.more]; IF tContents = NIL THEN { SimpleFeedback.Append[$CalendarTool, oneLiner, $Error, Rope.Cat["Could not find message: ", meeting.more]]; SimpleFeedback.Blink[$CalendarTool, $Error]; ViewerTools.SetContents[data.textViewer, str]; RETURN; }; ViewerTools.SetTiogaContents[data.textViewer, tContents]; str _ Rope.Concat[str, "\n\t==========\n"]; TiogaOps.SaveSelA[]; ViewerTools.SetSelection[data.textViewer, aSelPos]; TiogaOps.InsertRope[str]; TiogaOps.Break[]; TiogaOps.RestoreSelA[]; } ELSE write Dates below x-axis write month(s) below x-axis write times on y-axis Put month, year at top of calendar Imager.DoSave[context, DrawMeetingsForMonth]; put year at top of calendar Draw in Months doubles as Yearly variant on MouseToSelWeekly that returns time, independent of whether any meeting is under the mouse to avoid DST problems, set time to 3:00 am round to units of "round" [self: ViewerClasses.Viewer, context: Imager.Context, whatChanged: REF ANY, clear: BOOL] RETURNS [quit: BOOL _ FALSE] Am I drawing a bounding box? Otherwise I asked for a repaint, and changed encodes special repaint instructions color: ImagerColorPrivate.ColorFromStipple[word: white, function: [and, null]]]; color: ImagerColorPrivate.ColorFromStipple[word: black, function: [or, null]]]; color: ImagerColorPrivate.ColorFromStipple[word: black, function: [or, null]]]; this routine causes the calendar to resize when the outer viewer is resized PROC [self: Viewer] RETURNS [adjusted: BOOL _ FALSE]; When viewer is destroyed, remove it from viewerList [self: ViewerClasses.Viewer] This isn't in the scope of DestroyDisplay because FORKed proccesses must be top level [self: Viewer, context: Imager.Context, whatChanged: REF, clear: BOOL] color: ImagerColorPrivate.ColorFromStipple[word: white, function: [and, null]]] color: ImagerColorPrivate.ColorFromStipple[word: black, function: [or, null]]]; [self: ViewerClasses.Viewer, input: LIST OF REF ANY] fix up coordinates so the box orientation is correct for later calculations KAP. September 18, 1992. Origin in lower left corner of viewer round to units of "round" IF NewDate[data].leftMargin THEN newView _ weekly ELSE newView _ daily; at midnight, change the date on icons wait extra minute to increase chance that its really past midnight wait _ MIN[wait, LAST[CARDINAL]]; Not needed now that Process.SecondsToTicks takes CARD32. XXX: there ought to be a way to invert this automatically ÊL“–(cedarcode) style•NewlineDelimiter ˜code– "Cedar" stylešœ™K– "Cedar" stylešœ Ïeœ7™BKšœ/™/K™1K™&K™0K™'K™:K™!K™%K™—šÏk ˜ Kšœžœ˜Kšœ ˜ Kšœ žœ!˜0Kšœ žœ˜#KšœžœF˜SKšœžœ)˜4Kšœžœ˜‹Kšœžœ˜,Kšœ žœ˜Kšœ žœ ˜Kšžœžœ4žœžœ˜OKšœžœžœ˜6Kšœžœ<˜GKšœ˜Kšœžœ ˜$Kšœžœ!˜.Kšœ˜Kšœ žœ˜"K˜ Kšœ˜Kšœ˜Kšœ ˜ Kšœžœ+˜8Kšœ žœ5˜FKšœžœ&˜2Kšœ˜Kšœ ˜ Kšœžœ˜#Kšœ ˜ —K˜šÐln œžœž˜Kšžœežœžœ£˜£—K˜Kšœž˜K˜– 3 in tabStopsšœžœžœ˜K– 3 in tabStopsšœ™K– 3 in tabStopsšÏnœžœ.˜8K– 3 in tabStopsšÏc+™+K– 3 in tabStopsš œžœ8žœžœ˜UK– 3 in tabStopsšœ™K– 3 in tabStopsš œžœžœQžœ˜K– 3 in tabStopsšœ8™8K– 3 in tabStopsš  œžœžœžœ˜SK– 3 in tabStopsšœGÏsœ™bK– 3 in tabStopsš   œžœ$žœžœžœ˜bK– 3 in tabStops˜K– 3 in tabStopsšœq˜qK– 3 in tabStopsšœY˜YK– 4 in tabStopsšœ^˜^K– 4 in tabStopsšœd˜dK– 4 in tabStopsšœ^˜^—K– 4 in tabStops˜K– 4 in tabStopsšžœžœžœ˜K– 4 in tabStopsšœ žœ˜K– 4 in tabStopsšœžœ˜-K– 4 in tabStops˜– 4 in tabStopsš œžœ˜K– 4 in tabStopsšœU™U—K– 4 in tabStops˜– 4 in tabStopsšœžœžœ˜Kšœ žœžœ¡T˜rKšœžœžœ¡˜7Kšœ žœž˜K˜—Kšœ žœžœ˜'K˜šœ žœžœ¡ ˜:Kšœ žœ¡˜*Kšœžœ¡U˜pKšœ˜—Kšœžœžœ ˜K˜Kšœžœžœ˜)– 4 in tabStopsšœžœžœ˜ Kšœžœžœ¡,˜?Kšœ$žœ¡"˜KKšœ'žœ¡*˜VKšœ#žœ˜'K˜Kšœžœ¡7˜BKšœžœ¡(˜@Kšœ™Kšœžœ˜Kšœ žœžœ¡˜8Kšœžœžœ¡˜;Kšœ+¡4˜_Kšœžœžœ¡˜4Kšœžœ¡"˜FKšœ žœ¡"˜9Kšœžœ˜Kšœžœž˜&K– 4 in tabStopsšœ˜K– 4 in tabStops˜—K– 4 in tabStopsšœžœžœ¡K˜oK– 4 in tabStops˜Kšœžœ¡˜*Kšœ žœ¡˜/Kšœ žœ¡˜-Kšœžœ¡*˜;Kšœžœžœ˜K˜K– 3 in tabStops˜NK– 3 in tabStops˜JK– 3 in tabStops˜NK– 3 in tabStops˜HK– 3 in tabStops˜MK– 3 in tabStops˜LK– 3 in tabStops˜IK– 3 in tabStops˜PK– 3 in tabStops˜Kšœžœ ˜Kšœžœ ˜Kšœ žœ ˜Kšœžœ ˜K˜K˜]K˜gK˜_K˜aK˜aK˜K˜Kšœ žœ ˜Kšœ žœ˜ K˜Kšœ žœ žœžœ~˜ŸK– 4 in tabStops˜– 3 in tabStopsšœ2™2– 3 in tabStopsš  œžœžœžœ žœ žœžœ˜Gšžœžœž˜Kšœžœ˜K˜ Kšžœ˜—Kšœ˜K– 3 in tabStops˜——– 3 in tabStopsšœ&™&– 3 in tabStopsš œžœžœ ˜+– 3 in tabStopsšžœžœž˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜K˜Kšžœ ˜—K– 3 in tabStops˜—– 3 in tabStopsš œžœžœ ˜+– 3 in tabStopsšžœžœž˜Kšœ˜K˜Kšœ˜Kšœ˜Kšžœ ˜—K– 3 in tabStops˜—– 3 in tabStopsš  œžœžœžœžœ˜HK– 3 in tabStops˜—K– 3 in tabStopsš  œžœžœžœžœ˜D—K– 3 in tabStops˜•StartOfExpansionL -- [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]šœ#™#K™K–L -- [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]šœ<™<š  œžœžœžœžœ˜MK˜K˜K˜K˜@Kšœ˜—K˜š  œžœžœžœžœ˜LK˜K˜K˜Kšœ1žœ-˜aKšœ˜—K˜Kšœ?™?š   œžœžœžœžœ˜HK˜K˜RK˜7Kšœ˜—K˜K–L -- [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]™Wš  œžœžœžœ žœ˜@Kšœžœ˜Kšœžœ¡"˜2K˜K˜Kšœžœ&˜0Kšžœ žœ ˜K˜EKšœ˜—K–L -- [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]™KšœW™Wš   œžœžœžœžœ˜HK˜K˜K˜šžœž˜K˜ —šž˜Kšœ0žœ˜H—Kšœ˜—K˜K–L -- [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]™hš  œžœ žœžœžœ žœ˜YKšœžœ¡"˜-K˜K˜K˜-Kšœžœ˜Kšžœ žœ ˜K˜!Kšžœ˜Kšœ˜K˜—KšœP™Pš  œžœžœžœ žœ˜JKšœžœ¡"˜-K˜K˜K˜Kšœžœ5˜?Kšžœ žœ ˜K˜Kšœžœ˜!Kšœ˜K˜—Kšœ=™=š  œžœžœžœ žœ˜IK˜K˜K˜Kšœžœ¡˜3Kšœžœ žœ˜Kšœ˜—K˜K–L -- [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]™gš  œžœ žœžœžœ žœ˜XK˜Kšœžœ˜ K˜K˜Kšœ¡˜,Kšœ žœ˜Kšœ ¡8˜EKšžœ˜Kšœ˜——š œžœžœ˜$Kšžœžœ˜7Kšœ˜—š œžœ žœ˜,Kšœ<™˜bKšžœžœžœžœ˜,K˜ZK˜K˜!K˜—Kšžœ˜—Kšžœžœžœ˜0šœ˜Kšœ˜Kšœ ˜ Kšœ žœ"žœ˜9Kšœ žœ˜—Kšœ˜—K˜š œžœžœžœ˜bKšœžœžœ˜ Kšœžœ˜K˜ Kšœžœ˜Kšœžœ˜ K˜K˜šžœžœž˜ K˜K˜*Kšžœžœ žœ,žœ˜Zš žœ žœžœžœž˜7Kšœžœ ˜Kšžœžœžœ˜!šžœžœž˜Kšžœ žœžœžœžœžœžœžœ˜W—Kšžœ˜—Kšœ.žœ žœžœ ˜NKšœ@™@Kšžœ žœžœ ˜!Kšžœ˜—Kšžœžœ˜ K˜—K˜Kšœ-¢œ"™Sš œž œžœ˜9šžœžœ˜ K–n[viewer: ViewerClasses.Viewer, x: INTEGER, y: INTEGER, w: INTEGER, h: INTEGER, paint: BOOL _ TRUE]šœœžœ˜£K–n[viewer: ViewerClasses.Viewer, x: INTEGER, y: INTEGER, w: INTEGER, h: INTEGER, paint: BOOL _ TRUE]šœ‚žœ˜‰Kšœ˜—šžœ˜K–n[viewer: ViewerClasses.Viewer, x: INTEGER, y: INTEGER, w: INTEGER, h: INTEGER, paint: BOOL _ TRUE]šœŠžœ˜‘K–n[viewer: ViewerClasses.Viewer, x: INTEGER, y: INTEGER, w: INTEGER, h: INTEGER, paint: BOOL _ TRUE]šœ”žœ˜›K˜—KšœNžœ˜T—Kšœ˜K˜š œ˜"Kš žœžœžœžœ4žœžœ™rKšœžœ ˜(K˜š žœžœžœžœžœ˜<šžœžœžœ˜!KšœS˜SKšœ,˜,Kšœ˜—šž˜Kšœžœ˜—K˜—šžœ˜KšœQ˜QKšœ,˜,K˜—Kšœ˜—K˜š  œžœžœ žœ˜DKšž˜Kšžœ,žœžœžœ˜BK˜yKš žœžœžœžœžœ˜"šžœ'žœ˜/Kšœžœ5˜HKš œžœžœžœžœ˜5Kšœ žœžœ ˜Kšœ žœžœ ˜Kšœ žœžœ˜Kšœžœžœ ˜0K˜oK˜sKšžœ2˜5K˜—Kšžœ˜—K˜šœ™K˜—š  œžœžœžœžœžœžœ˜CKšœžœ ˜(Kšœ˜K˜š žœžœžœžœžœ˜;KšœQ˜QKšœ,˜,Kšžœ˜K˜—K– 4 in tabStops˜!šžœžœ˜%Kšœ]˜]Kšœ,˜,Kšžœ˜K˜—šžœžœ¡ œž˜Kšœžœ˜?K˜K˜"Kšœ˜—K– 4 in tabStopsšœ#žœ&˜MK– 4 in tabStopsšœ˜K– 4 in tabStops˜—š  œ˜ Kš žœžœžœžœ4žœžœ™rJšœžœ˜K– 4 in tabStopsšœ˜K– 4 in tabStops˜—š  œ˜ Kš žœžœžœžœ4žœžœ™rJšœžœ˜K– 4 in tabStopsšœ˜—K˜š œ˜&Kš žœžœžœžœ4žœžœ™rKšœžœ ˜(K˜šžœžœ˜Kšœžœ˜K˜Kšœl˜lK˜—šžœ˜Kšœv˜vKšœ,˜,Kšœ˜—Kšœ˜—K˜š œ˜"Kš žœžœžœžœ4žœžœ™rKšœžœ ˜(K˜šžœžœ˜K˜Kšœ{˜{K˜—šžœ˜Kšœv˜vKšœ,˜,Kšœ˜—Kšœ˜—K˜š œ˜&Kš žœžœžœžœ4žœžœ™rKšœžœ ˜(K˜šžœžœ˜Kšœžœ˜K˜Kšœl˜lK˜—šžœ˜Kšœx˜xKšœ,˜,Kšœ˜—Kšœ˜—K˜š  œ˜ Kš žœžœžœžœ4žœžœ™rKšœžœ ˜(Kšœžœžœ ˜Kšœ žœ˜K˜ Kšœžœ˜Kšœžœ˜Kšœ žœ˜Kšœ˜K˜š  œžœžœžœžœ˜+Kšœžœ˜K˜šžœ,ž˜3K˜'K˜Kšžœ˜—Kšžœ˜ Kšœ˜—K˜šžœ ž˜Kšœ žœ˜˜ K˜>K˜K˜K˜—šœ ˜ K˜?K˜=K˜K˜—Kšžœžœ˜—Kšœ2žœ˜KK˜-KšœWžœ˜^Kšœ žœ˜K˜ šžœžœž˜Kšœžœ ˜šžœ1žœ˜9Kšœdžœ ˜qK˜ K˜—K˜UK˜ Kšžœ˜—˜(Kšœ ˜ K˜K˜ Kšœž˜ Kšœ˜—Kšœ˜K˜—š  œ˜Kš žœžœžœžœ4žœžœ™rKšœžœ ˜(K˜šžœžœ¡˜"šžœžœžœ˜Kšžœžœžœ˜4K˜šœ˜Kšœ˜Kšœ ˜ Kšœ žœ"žœ˜9Kšœ žœ˜—K˜—K˜—šžœ¡˜šžœžœžœ˜K˜Kšžœžœžœ˜3šœ˜Kšœ˜Kšœ ˜ Kšœ žœ"žœ˜9Kšœ žœ˜—K˜—K˜—Kšžœžœžœ˜0Kšœ˜—K˜šœ™K™Kšœ&™&K™š   œžœžœ#žœžœ˜hKšœžœžœ ˜Kšœžœ˜K˜šžœž˜˜K˜:Kšžœžœžœžœ˜Qšžœ˜˜Kšœ˜Kšœe˜eK˜Kšœ˜—K˜3K˜K˜K˜—K˜—˜ K˜Kšžœžœžœžœ˜Ošžœ˜˜Kšœ ˜ Kšœ`˜`Kšœžœ˜ K˜Kšœ˜—K˜K˜K˜K˜—K˜—Kšžœžœ˜—Kšœ˜—K˜š œžœ+žœ&˜hšœ˜K˜_—Kšœ@˜@Kšœ@˜@Kšœ@˜@Kšœ@˜@Kšœ˜K˜—š œžœ+žœ˜JK˜3Kšœ@˜@Kšœ@˜@Kšœ@˜@Kšœ@˜@Kšœ˜—K˜Kšœ™š œžœ˜Kšœ˜Kšœžœ˜Kšœžœ˜Kšœž˜ Kšœ˜K˜šžœžœžœž˜Kšœ‡˜‡Kšžœ˜—šžœžœžœž˜Kšœ‡˜‡Kšžœ˜—K˜—K˜Kšœ/™/š  œžœ<žœ žœ ˜hKšœ žœžœžœ5˜SKšœžœ˜ K˜Kšœ#˜#šžœžœžœ ž˜K˜=Kšœl˜lKšžœ žœ(˜9Kšœ'˜'Kšžœ žœ$˜5Kšžœ˜—Kšœ˜—K˜š  œ˜"Kš žœžœžœžœžœžœ ™8Kšœ˜Kšœžœ˜Kšœžœ˜Kšžœ=˜CKšœ˜K˜—š œ˜)Kš žœžœžœžœžœžœ ™8Kšœ˜Kšœžœ˜Kšœžœ˜Kšžœ=˜CKšœ˜—K˜KšœŽ™Žš œžœ+žœ)žœ žœ žœžœ˜™Kšœžœ˜K˜Kšœ˜Kšœžœ˜#Kšœžœ˜ Kšœžœ˜K˜K˜0K˜ K˜!K˜K˜K˜K˜/šžœž˜šžœžœžœ˜6K˜K˜/K˜—Kšœ žœ˜#Kšžœ žœ˜!K˜&šžœžœžœ žœ˜K˜'Kšžœžœ žœžœ˜RKšœ-˜-Kšœ˜K˜—K˜6K˜!Kšžœ˜—K˜—K˜Kšœ8™8š œžœžœžœ˜bKšœžœ˜Kšœ žœ˜Kšœžœ˜K˜K˜{K˜'K˜*K˜š žœžœžœžœž˜3šžœ ž˜K˜šœ ˜ K˜K—šœ ˜ Kšžœ žœ?ž˜Pšœžœ;žœ˜SK˜ K˜ K˜Kšœ˜—šž˜K˜3—K˜—K˜—K˜Nšžœžœžœž˜5K˜/—šžœžœžœ˜:K˜+šžœ4žœžœž˜Fšžœ žœž˜šœžœ˜!K˜%K˜$Kš œVžœ žœžœžœ˜šžœ ž˜Kšœhžœ˜o—šž˜Kšœežœžœ˜€Kšœ˜—šœžœ˜ K˜$˜#Kš œ*žœ žœžœžœ˜bKšœ˜—K˜—Kšžœžœ˜——Kšžœ˜—K˜—Kšœ-˜-šžœžœžœ™Kšœ&™&šžœ žœžœ™K–-[message: ROPE, clearFirst: BOOL _ FALSE]šœk™kKšœ,™,Kšœ.™.Kšžœ™Kšœ™—Kšœ9™9Kšœ+™+Kšœ™Kšœ3™3Kšœ™K™Kšœ™K™—šž™Kšœ.˜.—Kšœ.˜.K˜—K˜š   œžœžœžœžœ˜7Kšœžœ˜ K˜K˜!Kšžœ žœ˜Kšžœžœžœ žœ&˜PKšœ˜—K˜š  œžœ1˜@Kšœ žœ˜Kšœžœ˜"Kšœžœ˜Kšœžœ˜K˜ K˜Kšœ#˜#K˜eK˜>K˜-K˜3K˜K˜K˜%Kšœ)˜)Kšœ5˜5–³[from: GMT, start: Convert.TimePrecision _ years, end: Convert.TimePrecision _ minutes, includeDayOfWeek: BOOL _ FALSE, useAMPM: BOOL _ TRUE, includeZone: BOOL _ TRUE]šœ.˜.Kšœ˜Kšœ ˜ Kšœžœ˜Kšœ žœ˜Kšœ žœ˜Kšœ˜—Kšœ#˜#K˜š žœ žœžœžœž˜2Kšœžœ ˜Kšœ+˜+Kšœ.˜.Kšœ5˜5Kšœ)˜)K˜Kšžœ˜—K˜—K˜š  œžœ1˜AKšœ žœ žœžœ~˜ŸKšœ2žœ˜6Kšœžœ˜Kšœžœ˜ Kšœ žœ˜ Kšœžœ˜ Kšœ(žœ˜,Kšœžœ˜Kšœ žœ¡6˜DKšœžœ˜ Kšœžœ˜Kšœžœ˜K˜š œžœ˜Kšœ žœ˜K˜K˜.KšœA˜AKšœI˜IKšœ˜—K˜K˜cK˜+Kšœ˜Kšœs˜sK˜K˜Kšœ™Kšœ#˜#K˜!K˜6K˜@šžœžœžœ ž˜K˜;K˜#KšœZ˜ZKšžœ žœ5˜HKšœ5˜5Kšžœ žœ$˜7Kšžœ˜—K˜Kšœ@˜@K˜Kšœ™K˜<šžœ@žœ˜HKšœžœ!˜(Kšœžœžœžœ%˜XK˜3KšœJ˜JKšœ˜Kšœ˜—šžœ˜Kšœžœ!˜(Kšœžœžœžœ%˜XKšœD˜DKšœ˜Kšœžœ#˜*K˜EKšœ=˜=Kšœ(˜(K˜K˜—Kšœ™šžœžœ¡A˜YKšœ)˜)K˜,Kšœ˜—Kšžœ'˜+šžœžœžœž˜K˜2Kšœ=˜=Kšœžœžœ žœ˜;Kšœžœžœ ˜;Kšžœ˜—K˜,K˜—K˜š  œžœ1˜BKšœ2žœ˜6Kšœžœ˜Kšœžœ˜Kšœžœ˜Kšœ˜Kšœžœ˜Kšœžœ˜ K˜š œžœ˜Kšœ žœ˜Kšœžœ˜K˜K˜/K˜6K˜%Kšœ_˜_Kšœ_˜_Kšœ˜—K˜K˜«K˜&Kšœžœžœžœ˜+Kšœw˜wKšœ9˜9K˜Kšœ"™"K˜&Kš œžœžœžœ žœ$˜bKšœ&˜&K˜HK˜