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;
}
};
calendarIcon: Icons.IconFlavor ¬ Icons.NewIconFromFile["CalendarTool.icons", 0];
invertedCalendarIcon: Icons.IconFlavor ¬ Icons.NewIconFromFile["CalendarTool.icons", 1];
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" ];