-- /ivy/binding/calendar/calFormImplB.mesa
-- handling the selection of things on the calendar form and parsing of the calendar form
-- once the user says "DoIt"
-- Last edited by: Binding, August 5, 1984 10:01:22 am PDT
DIRECTORY
BasicTime USING [ Unpack, Now, Period, GMT, nullGMT, Unpacked, Pack],
Buttons USING [ Button, ReLabel],
Calendar USING [ Date, Years, Months, Days, Weekdays, ZoomLevel],
CalBrowser USING [ DisplayEvents],
CalForm USING [ RepetitionDescriptor, EventDescriptor, YearTable, MonthTable, DayTable, Row1, Row234, Row5, Row6, Row7, MenuTable, CalForm, CurSelection, calForm, curSelection, FillDayTable, Delta],
CalStorage USING [ protData],
CalSupport USING [ GetMonthInfo, DisplayMsg, GetRowCol, M2I, R2I, ClearButton, SetButton],
Hickory USING [ Group, GroupSet, RepetitionType, ProtectionType, EventTuple, EventList],
InputFocus USING [ SetInputFocus],
IO USING [ STREAM, RIS, EndOfStream, GetTokenRope, GetChar, SkipWhitespace, EndOf, BreakProc, NUL, SP],
Menus USING [ MouseButton],
Rope USING [ ROPE, Equal],
RopeSets USING [ InsertValueIntoSet, IsValueInSet, Direction, RopeSetEl, MoveRover, IsSetEmpty, DeleteValueFromSet],
Tempus USING [ Parse, Adjust, Unintelligible],
ViewerClasses USING [ Viewer],
ViewerOps USING [ PaintViewer, MoveViewer],
ViewerTools USING [ GetContents]
;
CalFormImplB: CEDAR MONITOR
LOCKS CalStorage.protData
IMPORTS BasicTime, Buttons, CalStorage, CalSupport, IO, Rope, Tempus, ViewerOps, ViewerTools, CalForm, CalBrowser, RopeSets, InputFocus
EXPORTS CalForm
SHARES CalSupport, CalForm, CalBrowser
= BEGIN OPEN CalStorage.protData, Calendar, CalForm;
Error used for parsing
SyntaxError: PUBLIC ERROR = CODE;
Global variables
curYear: Years ← BasicTime.Unpack[ BasicTime.Now[]].year;
Support stuff
UpdateButtons: PROCEDURE [ but1, but2: Buttons.Button] = BEGIN
to modify the hightlighting of those buttons...
IF but1 # NIL THEN CalSupport.ClearButton[ but1];
IF but2 # NIL THEN CalSupport.SetButton[ but2];
END; -- UpdateButtons
AdjustDay: PROCEDURE [ inDate: Date] RETURNS [ outDate: Date] = BEGIN
when we go from August 31 to September, we must adjust the day, since Sept only has
30 days...
nbrOfDays: Days ← CalSupport.GetMonthInfo[ inDate.Month, inDate.Year].nbrOfDays;
outDate ← inDate;
IF outDate.Day > nbrOfDays THEN outDate.Day ← nbrOfDays;
RETURN[ outDate];
END; -- AdjustDay
changing the selection: we have to enter monitor...
YearSelection: PUBLIC ENTRY PROCEDURE [ y: Years, mouseBut: Menus.MouseButton] = BEGIN
ENABLE UNWIND => NULL;
IF y # curSelection.date.Year THEN BEGIN
graphical feed back when selection changes
OPEN calForm.row1.yearTable;
oldRow, newRow: INT;
oldRow ← curSelection.date.Year - curYear + ( rows-1)/2;
newRow ← y - curYear + ( rows-1)/2;
UpdateButtons[ buttons[ oldRow], buttons[ newRow]];
curSelection.date.Year ← y;
curSelection.date ← AdjustDay[ curSelection.date];
FillDayTable[ calForm.row1.dayTable.table, curSelection.date];
IF mouseBut # yellow THEN ViewerOps.PaintViewer[ calForm.row1.dayTable.table, all];
END;
IF mouseBut = yellow THEN BrowseCmd[ Year];
END; -- YearSelection
MonthSelection: PUBLIC ENTRY PROCEDURE [ m: Months, mouseBut: Menus.MouseButton] = BEGIN
ENABLE UNWIND => NULL;
IF m # curSelection.date.Month THEN BEGIN
graphical feed back when selected month is changed
OPEN calForm.row1.monthTable;
oldRow, newRow: INT;
oldRow ← CalSupport.M2I[ curSelection.date.Month] + 1;
newRow ← CalSupport.M2I[ m] + 1;
UpdateButtons[ buttons[ oldRow], buttons[ newRow]];
curSelection.date.Month ← m;
curSelection.date ← AdjustDay[ curSelection.date];
FillDayTable[ calForm.row1.dayTable.table, curSelection.date];
IF mouseBut # yellow THEN ViewerOps.PaintViewer[ calForm.row1.dayTable.table, all];
END;
IF mouseBut = yellow THEN BrowseCmd[ Month];
END; -- MonthSelection
DaySelection: PUBLIC ENTRY PROCEDURE [ d: Days, mouseBut: Menus.MouseButton] = BEGIN
to change the selected day
ENABLE UNWIND => NULL;
IF d # curSelection.date.Day THEN BEGIN
OPEN calForm.row1.dayTable;
oldRow, oldCol, newRow, newCol: INT;
firstDay: Weekdays;
[ , firstDay] ← CalSupport.GetMonthInfo[ curSelection.date.Month, curSelection.date.Year];
[ oldRow, oldCol] ← CalSupport.GetRowCol[ curSelection.date.Day, firstDay];
[ newRow, newCol] ← CalSupport.GetRowCol[ d, firstDay];
UpdateButtons[ buttons[ oldRow] [oldCol], buttons[ newRow] [ newCol]];
curSelection.date.Day ← d;
END;
IF mouseBut = yellow THEN BrowseCmd[ Day];
END; -- DaySelection
BrowseCmd: INTERNAL PROCEDURE [ zoom: ZoomLevel] = BEGIN
when we enter into browsing mode
IF calForm.container # NIL THEN BEGIN
InputFocus.SetInputFocus[]; -- to nowhere
ViewerOps.MoveViewer[ viewer: calForm.container, x: calForm.container.wx+Delta, y: calForm.container.wy+Delta, w: calForm.container.ww, h: calForm.container.wh, paint: FALSE];
END;
CalBrowser.DisplayEvents[ zoom, curSelection.date];
END;
ChangeRepetitionSelection: PUBLIC ENTRY PROCEDURE [ repType: Hickory.RepetitionType] = BEGIN
repType # None
OPEN curSelection.repetition;
ENABLE UNWIND => NULL;
IF RepeatType # repType THEN BEGIN
OPEN calForm.row5.menuTable, CalSupport;
IF RepeatType = None THEN UpdateButtons[ NIL, buttons[ R2I[ repType]-1]]
ELSE UpdateButtons[ buttons[ R2I[ RepeatType]-1], buttons[ R2I[ repType]-1]];
RepeatType ← repType;
END
ELSE BEGIN -- undo selection
OPEN calForm.row5.menuTable, CalSupport;
UpdateButtons[ buttons[ R2I[ repType]-1], NIL];
RepeatType ← None;
END;
END; -- ChangeRepetitionSelection
ChangeGroupSelection: PUBLIC ENTRY PROCEDURE [ groupIndex: INT] = BEGIN
groupIndex is the index of the col in menu, starting with 0!
ENABLE UNWIND => NULL;
lookAhead: REF RopeSets.RopeSetEl ← NIL;
newGroup: Hickory.Group;
lookAhead ← RopeSets.MoveRover[ calForm.row6.groupHead, Right, groupIndex, TRUE];
IF lookAhead = NIL THEN RETURN;
newGroup ← lookAhead.Value;
IF NOT RopeSets.IsValueInSet[ newGroup, curSelection.groups] THEN BEGIN
here we must highlight more than one entry..
OPEN calForm.row6;
CalSupport.SetButton[ menuTable.buttons[ groupIndex]];
curSelection.groups ← RopeSets.InsertValueIntoSet[ newGroup, curSelection.groups];
END
ELSE BEGIN -- undo selection
OPEN calForm.row6;
CalSupport.ClearButton[ menuTable.buttons[ groupIndex]];
curSelection.groups ← RopeSets.DeleteValueFromSet[ newGroup, curSelection.groups];
END;
END; -- ChangeGroupSelection
ScrollGroupMenu: PUBLIC ENTRY PROCEDURE [ dir: RopeSets.Direction] = BEGIN
to scroll the groups left/right. For now, always scroll the full table, i.e. total nbr of cols...
OPEN calForm.row6;
ENABLE UNWIND => NULL;
gr: REF RopeSets.RopeSetEl;
IF RopeSets.IsSetEmpty[ groups] THEN RETURN;
groupHead ← RopeSets.MoveRover[ rover: groupHead, dir: dir, howFar: menuTable.cols, passLast: FALSE];
gr ← groupHead;
FOR i: INT IN [ 0..menuTable.cols) DO
IF gr # NIL THEN BEGIN
Buttons.ReLabel[ menuTable.buttons[ i], gr.Value, FALSE];
IF RopeSets.IsValueInSet[ gr.Value, curSelection.groups] THEN
CalSupport.SetButton[ menuTable.buttons[ i]]
ELSE CalSupport.ClearButton[ menuTable.buttons[ i]];
gr ← gr.Next;
END
ELSE BEGIN
Buttons.ReLabel[ menuTable.buttons[ i], " ", FALSE]; -- erase
CalSupport.ClearButton[ menuTable.buttons[ i]];
END;
ENDLOOP;
END; -- ScrollGroupMenu
Translating the selection as on the screen into a Form...
TranslateSelection: PUBLIC INTERNAL PROCEDURE [ editedEv: REF Hickory.EventTuple ← NIL] RETURNS [ evList: Hickory.EventList, groups: Hickory.GroupSet] = BEGIN -- scopes for EXITS and ERROR: a Pain...
here we look at the different selections and build up the calendar entry form
OPEN calForm;
ENABLE BEGIN
UNWIND => NULL;
END; -- Enable
BEGIN -- scope for exits
text, place: Rope.ROPE;
events: LIST OF EventDescriptor ← NIL;
repetition: RepetitionDescriptor;
keepUntil: BasicTime.GMT ← BasicTime.nullGMT;
first: BOOLEAN;
events ← GetEventTimes[ curSelection.date, row2.txtViewer];
text ← ViewerTools.GetContents[ row3.txtViewer];
IF Rope.Equal[ text, ""] THEN GOTO noText;
place ← ViewerTools.GetContents[ calForm.row4.txtViewer];
IF Rope.Equal[ place, ""] THEN place ← NIL;
repetition ← GetRepetition[ row5.txtViewer, row7.txtViewers[1], curSelection.repetition];
groups ← GetGroups[ row6.txtViewer, curSelection.groups];
keepUntil ← GetKeepUntil[ row7.txtViewers[2]];
evList ← NIL;
IF events = NIL THEN GOTO noEvents;
first ← TRUE;
FOR l: LIST OF EventDescriptor ← events, l.rest UNTIL l = NIL DO
ev: Hickory.EventTuple;
IF editedEv # NIL AND first THEN ev.Key ← editedEv.Key -- already in hickory
ELSE ev.Key ← NIL; -- it's a really new hickory event
first ← FALSE;
ev.EventTime ← l.first.EventTime;
ev.Duration ← l.first.Duration / 60; -- in minutes
ev.Text ← text;
ev.Place ← place;
ev.RepeatType ← repetition.RepeatType;
ev.RepeatTime ← repetition.RepeatTime;
ev.RepeatUntil ← repetition.RepeatUntil;
ev.KeepUntil ← keepUntil;
ev.Protection ← curSelection.protection;
ev.Remind ← curSelection.reminder;
evList ← CONS[ ev, evList];
ENDLOOP;
RETURN[ evList, groups];
EXITS
noEvents => BEGIN
CalSupport.DisplayMsg[ "No event times & durations were specified!"];
ERROR SyntaxError;
END;
noText => BEGIN
CalSupport.DisplayMsg[ "No text for event was specified!"];
ERROR SyntaxError;
END;
END; END; -- TranslateSelection
GetGroups: INTERNAL PROCEDURE [ txtViewer: ViewerClasses.Viewer, groups: Hickory.GroupSet] RETURNS [ moreGroups: Hickory.GroupSet] = BEGIN
scan the contents of the viewer and insert the groups into the list..
Groups are separated by ',, ': or ';. Spaces are part of group names...
st: IO.STREAMIO.RIS[ ViewerTools.GetContents[ txtViewer]];
g: Rope.ROPE;
ch: CHAR;
BreakProc: IO.BreakProc = BEGIN
RETURN[
SELECT char FROM
IN [ IO.NUL..IO.SP), ',, ';, ': => sepr,
ENDCASE => other];
END; -- BreakProc
moreGroups ← groups;
DO
ENABLE IO.EndOfStream => EXIT;
[] ← IO.SkipWhitespace[ st];
g ← IO.GetTokenRope[ stream: st, breakProc: BreakProc].token;
moreGroups ← RopeSets.InsertValueIntoSet[ g, moreGroups];
ch ← IO.GetChar[ st]; -- separator consumed
ENDLOOP;
RETURN[ moreGroups];
END; -- GetGroups
GetRepetition: INTERNAL PROCEDURE [ txt1, txt2: ViewerClasses.Viewer, rep: RepetitionDescriptor] RETURNS [ newRep: RepetitionDescriptor] = BEGIN BEGIN
repRope: Rope.ROPE ← ViewerTools.GetContents[ txt1];
newRep ← rep;
IF newRep.RepeatType # None THEN
IF NOT Rope.Equal[ repRope, ""] THEN CalSupport.DisplayMsg[ "Warning: repetition time in text viewer ignored!"]
ELSE NULL
ELSE IF NOT Rope.Equal[ repRope, ""] THEN BEGIN
newRep.RepeatType ← Complicated;
newRep.RepeatTime ← repRope;
END;
repRope ← ViewerTools.GetContents[ txt2]; -- repeatUntil..
IF repRope = NIL OR Rope.Equal[ repRope, ""] THEN newRep.RepeatUntil ← BasicTime.nullGMT
ELSE newRep.RepeatUntil ← LOOPHOLE[ Tempus.Parse[ repRope ! Tempus.Unintelligible => GOTO badTimeFormat].time];
RETURN[ newRep];
EXITS
badTimeFormat => BEGIN
CalSupport.DisplayMsg[ "Tempus could not parse repeat until specification!"];
ERROR SyntaxError;
END;
END; END; -- GetRepetition
GetKeepUntil: INTERNAL PROCEDURE[ txtViewer: ViewerClasses.Viewer] RETURNS [ time: BasicTime.GMT ← BasicTime.nullGMT] = BEGIN BEGIN
r: Rope.ROPE ← ViewerTools.GetContents[ txtViewer];
IF r = NIL OR Rope.Equal[ r, ""] THEN RETURN[ BasicTime.nullGMT];
time ← LOOPHOLE[ Tempus.Parse[ r ! Tempus.Unintelligible => GOTO badTimeFormat].time];
RETURN[ time];
EXITS
badTimeFormat => BEGIN
CalSupport.DisplayMsg[ "Tempus could not parse keep until specification!"];
ERROR SyntaxError;
END;
END; END; -- GetKeepUntil
GetEventTimes: INTERNAL PROCEDURE [ date: Date, txtViewer: ViewerClasses.Viewer] RETURNS [ eventList: LIST OF EventDescriptor ← NIL] = BEGIN BEGIN
here we parse the event start and end times. Use following grammar:
time [ '—' time] { '; time [ '—' time]} where time must be parsed by Tempus.Parse
r: Rope.ROPE ← ViewerTools.GetContents[ txtViewer];
st: IO.STREAMIO.RIS[ r];
startTime, endTime, baseTime: BasicTime.GMT;
ch: CHAR; -- lookahead
auxTime: BasicTime.Unpacked;
IF Rope.Equal[ r, ""] THEN GOTO noTimes;
BEGIN OPEN auxTime;
year ← date.Year; month ← date.Month; day ← date.Day;
hour ← 0; minute ← 0; second ← 0;
END; -- OPEN
baseTime ← BasicTime.Pack[ auxTime];
DO
EOF: BOOLEANFALSE;
[] ← IO.SkipWhitespace[ st];
[ startTime, ch] ← GetTime[ st, baseTime];
IF NOT EOF AND ch = '— THEN BEGIN
ch ← IO.GetChar[ st ! IO.EndOfStream => { EOF ← TRUE; CONTINUE} ];
[] ← IO.SkipWhitespace[ st];
[ endTime, ch] ← GetTime[ st, startTime];
END
ELSE endTime ← startTime;
IF NOT EOF THEN ch ← IO.GetChar[ st ! IO.EndOfStream => { EOFTRUE; CONTINUE}]; -- consume separator
eventList ← ExpandEvList[ startTime, endTime, eventList];
IF IO.EndOf[ st] OR EOF THEN EXIT;
ENDLOOP;
RETURN[ eventList];
EXITS
noTimes => BEGIN
CalSupport.DisplayMsg[ "No event start time specified!"];
ERROR SyntaxError;
END;
END; END; -- GetEventTimes
GetTime: INTERNAL PROCEDURE [ st: IO.STREAM, baseTime: BasicTime.GMT] RETURNS [ time: BasicTime.GMT, nextChar: CHAR]= BEGIN BEGIN
to parse the time from stream in conjunction with given baseTime. Uses Tempus.Parse
r: Rope.ROPE;
BreakProc: IO.BreakProc = BEGIN
RETURN[
SELECT char FROM
'—, '; => sepr,
ENDCASE => other];
END; -- BreakProc
r ← IO.GetTokenRope[ st, BreakProc ! IO.EndOfStream => CONTINUE].token;
nextChar ← IO.GetChar[ st ! IO.EndOfStream => CONTINUE];
time ← Tempus.Parse[ rope: r, baseTime: baseTime ! Tempus.Unintelligible => GOTO badTimeFormat].time;
RETURN[ time, nextChar];
EXITS
badTimeFormat => BEGIN
CalSupport.DisplayMsg[ "Tempus could not parse time!"];
ERROR SyntaxError;
END;
END; END; -- GetTime
ExpandEvList: INTERNAL PROCEDURE [ startTime, endTime: BasicTime.GMT, evl: LIST OF EventDescriptor] RETURNS [ newEvl: LIST OF EventDescriptor] = BEGIN BEGIN
here we split events that wrap over to next day into several events, spread out over
several days.
start, end, time: BasicTime.Unpacked;
evDescr: EventDescriptor;
IF BasicTime.Period[ startTime, endTime] = 0 THEN BEGIN
evDescr.Duration ← 0; evDescr.EventTime ← startTime;
newEvl ← CONS[ evDescr, NIL];
RETURN[ newEvl];
END
ELSE IF BasicTime.Period[ startTime, endTime] < 0 THEN GOTO badInterval;
start ← BasicTime.Unpack[ startTime];
end ← BasicTime.Unpack[ endTime];
newEvl ← evl;
WHILE start.day # end.day DO
evDescr.EventTime ← startTime;
time ← start;
time.hour ← 23; time.minute ← 59; time.second ← 59; -- end of start day
evDescr.Duration ← BasicTime.Period[ startTime, BasicTime.Pack[ time]];
newEvl ← CONS[ evDescr, newEvl];
start.hour ← 0; start.minute ← 0; start.second ← 0;
startTime ← Tempus.Adjust[ days: 1, baseTime: BasicTime.Pack[ start], precisionOfResult: minutes].time;
start ← BasicTime.Unpack[ startTime];
ENDLOOP;
now start.day = end.day
evDescr.EventTime ← startTime;
evDescr.Duration ← BasicTime.Period[ startTime, endTime];
IF evDescr.Duration > 0 THEN newEvl ← CONS[ evDescr, newEvl];
RETURN[ newEvl];
EXITS
badInterval => BEGIN
CalSupport.DisplayMsg[ "StartTime > EndTime ??"];
ERROR SyntaxError;
END;
END; END; -- ExpandEvList
END.