<> <> <> <> <> <> <> <> <> <> <<>> 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; <<- Prevents looping forever if at last meeting (and no periodics)>> 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 = { <<[self: ViewerClasses.Viewer, context: Imager.Context, whatChanged: REF ANY, clear: BOOL] RETURNS [quit: BOOL _ FALSE]>> 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 = { <<[self: ViewerClasses.Viewer]>> TRUSTED {Process.Detach[FORK Unlink[self]]}; }; <> Unlink: ENTRY PROC[vw: ViewerClasses.Viewer] = { ENABLE UNWIND => NULL; viewerList ¬ List.DRemove[vw, viewerList]; }; PaintIcon: ViewerClasses.PaintProc = { <<[self: Viewer, context: Imager.Context, whatChanged: REF, clear: BOOL]>> 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 = { <<[self: ViewerClasses.Viewer, input: LIST OF REF ANY]>> 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.