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
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[
paint the calendar
Draw: PROC[context: Imager.Context, data: CalendarData],
turn on feedback selection for data.selDate
Select: PROC[context: Imager.Context, data: CalendarData, unselect: BOOLEAN ¬ FALSE],
computes margins of calendar
Margins: PROC[data: CalendarData] RETURNS [leftMargin, bottomMargin, rightMargin, topMargin, wd, ht, nRows, nCols, rowHt: INT],
use location of mouse click to update selDate and curSel
MouseToSel: PROC[data: CalendarData] RETURNS [time: BasicTime.GMT, curSel: CurSel],
convert a date to a selection. Roughly inverse of MouseToSel. repaint=TRUE if date is not visible
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;
This is the smallest duration that will show up on smaller CalendarTool viewer sizes.
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
top row of calendar is row=0
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"];
Utility that convertd from LIST of Meeting to LORA
ListToLora: PROC[ls: LIST OF Meeting] RETURNS [lr: List.LORA ¬ NIL] = {
WHILE ls # NIL DO
lr ¬ CONS[ls.first, lr];
ls ¬ ls.rest;
ENDLOOP;
};
Utilities that put an order onto Views
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]};
Utilities for computing about dates
Return date of the first of the month that contains this day
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;
};
Return date of the last day of the month that contains this day
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;
};
number of weeks in month containing day, that is, number of rows it takes to display it
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;
};
Return date of the sunday that begins the week containing this day. Set time to 12 am.
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
};
given a (row, col) pair and a date, convert (row,col) to a new date in the same month. Row 0 is top row
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]];
};
given a date, convert to (row, col) pair in appropriate month. Row 0 is top row
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;
};
given a date, convert to (row, col) pair in appropriate year.
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;
};
given a (row, col) pair and a date, convert (row,col) to a new date in the same year. Row 0 is top row
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] = {
XXX: shouldn't repaint unless viewing something that changed
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 = {
PROC [reason: UserProfile.ProfileChangeReason]
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 = {
PROC [cmd: Handle] RETURNS [result: REFNIL, msg: ROPENIL];
RemindReset.Reset[NIL];
};
MakeFirstCalendarTool: Commander.CommandProc = {
PROC [cmd: Handle] RETURNS [result: REFNIL, msg: ROPENIL];
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 ← CommanderOps.ArgN[cmd, i];
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];
add our command to the menu
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 = {
PROC [parent: Viewer,
clientData: REF ANYNIL,
mouseButton: MouseButton ← red,
shift, control: BOOLFALSE];
data: CalendarData ¬ NARROW[clientData];
ViewerOps.PaintViewer[
viewer: data.calenderViewer,
hint: client,
whatChanged: NEW[WhatChangedRec ¬ [setToday: TRUE]],
clearClient: FALSE];
};
NextFunction: Menus.MenuProc = {
PROC [parent: Viewer,
clientData: REF ANYNIL,
mouseButton: MouseButton ← red,
shift, control: BOOLFALSE];
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];
};
This is a utility for UpFunction. If yes is TRUE then show text, otherwise hide it
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 = {
PROC [parent: Viewer,
clientData: REF ANYNIL,
mouseButton: MouseButton ← red,
shift, control: BOOLFALSE];
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;
EditFunction Stuff
CopyOrEdit: PROC [clientData: REF ANYNIL, 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 = {
PROC [parent: Viewer,
clientData: REF ANYNIL,
mouseButton: MouseButton ← red,
shift, control: BOOLFALSE];
CopyOrEdit[clientData, TRUE];
};
CopyFunction: Menus.MenuProc = {
PROC [parent: Viewer,
clientData: REF ANYNIL,
mouseButton: MouseButton ← red,
shift, control: BOOLFALSE];
CopyOrEdit[clientData, FALSE];
};
NewMeetingFunction: Menus.MenuProc = {
PROC [parent: Viewer,
clientData: REF ANYNIL,
mouseButton: MouseButton ← red,
shift, control: BOOLFALSE];
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 = {
PROC [parent: Viewer,
clientData: REF ANYNIL,
mouseButton: MouseButton ← red,
shift, control: BOOLFALSE];
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 = {
PROC [parent: Viewer,
clientData: REF ANYNIL,
mouseButton: MouseButton ← red,
shift, control: BOOLFALSE];
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 = {
PROC [parent: Viewer,
clientData: REF ANYNIL,
mouseButton: MouseButton ← red,
shift, control: BOOLFALSE];
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 = {
PROC [parent: Viewer,
clientData: REF ANYNIL,
mouseButton: MouseButton ← red,
shift, control: BOOLFALSE];
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;
};
Drawing Routines
this routines caches lists of meetings
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]];
};
draw grid of dimensions wd, ht
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;
};
write days on top of a box of dimensions wd, ht
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 = {
PROC [ref1: REF ANY, ref2: REF ANY] RETURNS [Comparison]
m1, m2: Meeting;
m1 ¬ NARROW[ref1];
m2 ¬ NARROW[ref2];
RETURN[Basics.CompareInt[0, BasicTime.Period[m1.start, m2.start]]];
};
CompareStartReverse: List.CompareProc = {
PROC [ref1: REF ANY, ref2: REF ANY] RETURNS [Comparison]
m1, m2: Meeting;
m1 ¬ NARROW[ref1];
m2 ¬ NARROW[ref2];
RETURN[Basics.CompareInt[0, BasicTime.Period[m2.start, m1.start]]];
};
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
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;
};
use location of mouse click to update selDate and curSel
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];
MaskBoxOutline[context, leftMargin, bottomMargin + ht - rowHt*cnt, leftMargin + wd, bottomMargin + ht - rowHt*(cnt+1), [xor, null]];
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];
};
doubles as SelectYearly
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];
};
draw meetings for weekly or monthly
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: ImagerColorPrivate.ColorFromStipple[word: IF mt.repeat = once THEN grey ELSE lightGrey, function: [or, null]]
color: IF mt.repeat = once THEN greyOR ELSE lightGreyOR
];
XXX: the factor of 1/3 should be shared with DrawWeekly
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;
XXX: should I move forward looking for a day with a meeting in it?
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] = {
The reason for forking, is that I don't want the walnut lookup stuff inside a paint proc
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;
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 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];
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
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];
write Dates below x-axis
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];
write month(s) below x-axis
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]];
};
write times on y-axis
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];
Put month, year at top of calendar
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];
Imager.DoSave[context, DrawMeetingsForMonth];
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];
put year at top of calendar
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];
Draw in Months
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;
}
};
doubles as Yearly
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;
}
};
variant on MouseToSelWeekly that returns time, independent of whether any meeting is under the mouse
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;
to avoid DST problems, set time to 3:00 am
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
};
round to units of "round"
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: BOOLFALSE]
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];
Am I drawing a bounding box?
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;
};
Otherwise I asked for a repaint, and changed encodes special repaint instructions
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: ImagerColorPrivate.ColorFromStipple[word: white, function: [and, null]]];
color: whiteAND];
Imager.MaskRectangleI[context, self.wx, self.wy, self.ww, self.wh];
Imager.SetColor[
context: context,
color: ImagerColorPrivate.ColorFromStipple[word: black, function: [or, null]]];
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: ImagerColorPrivate.ColorFromStipple[word: black, function: [or, null]]];
color: blackOR];
data.view.Select[context, data]
};
}
EXITS
Finished => NULL;
};
this routine causes the calendar to resize when the outer viewer is resized
AdjustOuter: ViewerClasses.AdjustProc = {
PROC [self: Viewer] RETURNS [adjusted: BOOLFALSE];
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];
};
When viewer is destroyed, remove it from viewerList
DestroyDisplay: ViewerClasses.DestroyProc = {
[self: ViewerClasses.Viewer]
TRUSTED {Process.Detach[FORK Unlink[self]]};
};
This isn't in the scope of DestroyDisplay because FORKed proccesses must be top level
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: ImagerColorPrivate.ColorFromStipple[word: white, function: [and, null]]]
color: whiteAND]
ELSE
Imager.SetColor[
context: context,
color: ImagerColorPrivate.ColorFromStipple[word: black, function: [or, null]]];
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 {
fix up coordinates so the box orientation is correct for later calculations
KAP. September 18, 1992. Origin in lower left corner of viewer
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;
round to units of "round"
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 =>
IF NewDate[data].leftMargin THEN newView ← weekly ELSE newView ← daily;
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];
};
at midnight, change the date on icons
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 extra minute to increase chance that its really past midnight
wait ¬ wait + 60;
wait ← MIN[wait, LAST[CARDINAL]];
Not needed now that Process.SecondsToTicks takes CARD32.
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];
XXX: there ought to be a way to invert this automatically
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.