GVPMain.mesa, start module for the GVPatch system
HGM, May 23, 1984 10:47:43 pm PDT
Steve Temple, September 30, 1982 12:19 pm
Schroeder, March 31, 1983 10:03 am
This module contains the start code for the GVPatch system and also the server mode
viewer code. The monitor lock which is shared with GVPEditor and GVPBrowser is here
as is the basic viewer framework.
DIRECTORY
Buttons USING [Button, ButtonProc, Create],
Commander USING [CommandProc, Register],
Containers USING [ChildXBound, ChildYBound, Container, Create],
GVPDefs,
IO USING [Flush, PutF, time],
Labels USING [Create, Set, SetDisplayStyle],
Process USING [MsecToTicks, Pause],
Rope USING [Fetch, Length],
ViewerEvents USING [EventProc, RegisterEventProc],
ViewerIO USING [CreateViewerStreams],
ViewerOps USING [CreateViewer, MoveViewer, PaintViewer, SaveViewer],
ViewerTools USING [GetContents, MakeNewTextViewer, SetSelection];
GVPMain: CEDAR MONITOR
LOCKS lock
IMPORTS
Buttons, Commander, Containers, GVPDefs, IO, Labels, Process, Rope, ViewerEvents, ViewerIO, ViewerOps, ViewerTools
EXPORTS GVPDefs =
BEGIN OPEN GVPDefs;
lock: PUBLIC MONITORLOCK;
h: GVPRef ← NIL;
message, focus, root: Viewer;
serverButtons, logText, serverInput, fileInput, cLineInput, lengthInput: Viewer;
MainInit makes a container for the various viewers which will be created, a label for
messages and a small viewer to put the current selection in while the mode is changing.
(If a viewer is taken off screen with the selection in it you can't get it (the selection) back)
It makes calls to create a viewer for each mode and then puts the server mode viewer on
the screen. The fields currentText and currentButtons in the data record (h^) hold the
typescript and buttons container for the mode currently on screen.
MainInit: PROC = BEGIN
root ← Containers.Create[[name: "GV-Patch", iconic: FALSE, scrollable: FALSE]];
h.root ← root; -- this is a fudge I don't really understand
focus ← ViewerTools.MakeNewTextViewer[[parent: root, wx: 0, wy: row5, ww: char,
wh: rowHeight, border: FALSE]];
message ← Labels.Create[[parent: root, wx: col1, wy: row5, ww: 90*char,
wh: rowHeight, border: FALSE]];
MakeEditorButtons[h];
MakeBrowserButtons[h];
MakeServerButtons[h];
ViewerOps.MoveViewer[viewer: serverButtons, x: 0, y: 0, w: 0, h: 4*rowSize, paint: FALSE];
ViewerOps.MoveViewer[viewer: logText, x: 0, y: row6, w: 0, h: 0, paint: FALSE];
Containers.ChildXBound[root, serverButtons];
Containers.ChildXBound[root, logText];
Containers.ChildYBound[root, logText];
ViewerOps.PaintViewer[root, all];
ToFocus[];
h.currentButtons ← serverButtons;
h.currentText ← logText
END;
DestroyProc is called whenever a viewer is destroyed (this is set up near the end of this
module). We filter out all but destroys of the outermost container (root) and when this occurs
free the data record and call each TidyUp procedure.
DestroyProc: ViewerEvents.EventProc = TRUSTED BEGIN
IF viewer#root THEN RETURN;
h.root ← NIL;
BrowserTidyUp[];
ProcsTidyUp[];
EditorTidyUp[];
DriverTidyUp[];
h ← NIL
END;
ToFocus: PUBLIC PROC = {ViewerTools.SetSelection[focus]}; -- selection put in "focus"
GetHandle: PUBLIC PROC RETURNS[handle: GVPRef] = {handle ← h}; -- return data record^
Set and Flash put a message in the message window (the label "message")
Flash also blinks the window to signify that this is an error message
Set: PUBLIC PROC[r: ROPENIL] = { Labels.Set[message, r, TRUE] };
Flash: PUBLIC PROC[r: ROPE] = BEGIN
Labels.Set[message, r, TRUE];
Labels.SetDisplayStyle[message, $WhiteOnBlack];
Process.Pause[Process.MsecToTicks[750]];
Labels.SetDisplayStyle[message, $BlackOnWhite]
END;
Failed takes a rope error message, Flashes it if non-NIL and returns a BOOLEAN error flag
Failed: PUBLIC PROC[r: ROPE] RETURNS [failed: BOOLEAN] = BEGIN
failed ← r#NIL;
IF failed THEN Flash[r] ELSE Set[]
END;
NumFromRope is a general purpose proc which extracts numbers in base 8 or 10 from a rope.
The "start" parameter is the position in the rope from which the first char of the number will
be taken. The proc returns the number and "end", the position after the last char of the
number or the last char of the rope
If the char / is found instead of a digit this counts as the number LAST[CARDINAL],
this value is also returned if the end of the rope is reached before a digit is found.
Any non-digit chars are valid as separators. Note that overflow is not checked for
and the max number obtainable is 65535
NumFromRope: PUBLIC PROC[octal: BOOLEAN, r: ROPE, start: CARDINAL]
RETURNS [num, end: CARDINAL] = BEGIN
i: CARDINAL ← start;
top: CHAR = IF octal THEN '7 ELSE '9;
base: CARDINAL = IF octal THEN 8 ELSE 10;
c: CHAR ← ' ;
len: CARDINAL = Rope.Length[r];
num ← 0;
end ← len;
UNTIL c IN ['0..top] OR c='/ DO
IF i=len THEN {num ← lastCard; RETURN};
c ← Rope.Fetch[r, i];
i ← i + 1
ENDLOOP;
IF c IN ['0..top]
THEN BEGIN
i ← i - 1;
UNTIL i = len OR NOT( c IN ['0..top] ) DO
c ← Rope.Fetch[r, i];
IF c IN ['0..top] THEN num ← num * base + c - '0;
i ← i + 1
ENDLOOP;
IF i#len THEN i ← i-1
END
ELSE num ← lastCard;
end ← i
END;
MakeServerButtons creates the server mode viewer, this consists of a typescript (which also
serves as the system log and so has a backing file) and a container to hold the buttons and
the four input viewers. All the buttons FORK their procedures and we rely on all the
button procedures being ENTRY procedures to ensure that no conflicts occur.
MakeServerButtons: PROC[h: GVPRef] = BEGIN
logText ← ViewerOps.CreateViewer[flavor: $Typescript,
info: [parent: h.root, file: "GVPatch.log", border: TRUE, iconic: FALSE, wy: offScreen]];
h.logStream ← ViewerIO.CreateViewerStreams[viewer: logText, name: NIL].out;
serverButtons ← Containers.Create[[name: NIL, parent: h.root, wx: 0, wy: offScreen,
wh: 4*rowSize, border: FALSE, scrollable: FALSE]];
serverInput ← ViewerTools.MakeNewTextViewer[[parent: serverButtons, wx: col5, wy: row1+2,
ww: 40*char, wh: rowHeight, scrollable: FALSE, border: FALSE, data: "???"]];
fileInput ← ViewerTools.MakeNewTextViewer[[parent: serverButtons, wx: col5, wy: row2+2,
ww: 40*char, wh: rowHeight, scrollable: FALSE, border: FALSE]];
lengthInput ← ViewerTools.MakeNewTextViewer[[parent: serverButtons, wx: col5, wy: row3+2,
ww: 40*char, wh: rowHeight, scrollable: FALSE, border: FALSE]];
cLineInput ← ViewerTools.MakeNewTextViewer[[parent: serverButtons, wx: col5, wy: row4+2,
ww: 80*char, wh: rowHeight, scrollable: FALSE, border: FALSE, data: "@Server"]];
[] ← Buttons.Create[info: [name: "BROWSER", wx: col1, wy: row3, wh: rowSize+rowHeight,
ww: buttonSize, parent: serverButtons], clientData: h, proc: BrowserButton];
[] ← Buttons.Create[info: [name: "EDITOR", wx: col1, wy: row1, wh: rowSize+rowHeight,
ww: buttonSize, parent: serverButtons], clientData: h, proc: EditorButton];
[] ← Buttons.Create[info: [name: "server >", wx: col4, wy: row1, wh: rowHeight,
ww: buttonSize, parent: serverButtons], clientData: h, proc: ServNameButton];
[] ← Buttons.Create[info: [name: "read heap", wx: col2, wy: row1, wh: rowHeight,
ww: buttonSize, parent: serverButtons], clientData: h,
guarded: TRUE, proc: ReadHeapButton];
[] ← Buttons.Create[info: [name: "write heap", wx: col3, wy: row1, wh: rowHeight,
ww: buttonSize, parent: serverButtons], clientData: h,
guarded: TRUE, proc: WriteHeapButton];
[] ← Buttons.Create[info: [name: "read file", wx: col2, wy: row2, wh: rowHeight,
ww: buttonSize, parent: serverButtons], clientData: h, proc: ReadFileButton];
[] ← Buttons.Create[info: [name: "write file", wx: col3, wy: row2, wh: rowHeight,
ww: buttonSize, parent: serverButtons], clientData: h, proc: WriteFileButton];
[] ← Buttons.Create[info: [name: "file >", wx: col4, wy: row2, wh: rowHeight,
ww: buttonSize, parent: serverButtons], clientData: h, proc: FileNameButton];
[] ← Buttons.Create[info: [name: "length >", wx: col4, wy: row3, wh: rowHeight,
ww: buttonSize, parent: serverButtons], clientData: h, proc: LengthButton];
[] ← Buttons.Create[info: [name: "set local", wx: col2, wy: row3, wh: rowHeight,
ww: buttonSize, parent: serverButtons], clientData: h, proc: SetLocalButton];
[] ← Buttons.Create[info: [name: "set server", wx: col3, wy: row3, wh: rowHeight,
ww: buttonSize, parent: serverButtons], clientData: h, proc: SetServerButton];
[] ← Buttons.Create[info: [name: "write log", wx: col2, wy: row4, wh: rowHeight,
ww: buttonSize, parent: serverButtons], clientData: h, proc: WriteLogButton];
[] ← Buttons.Create[info: [name: "RESTART", wx: col3, wy: row4, wh: rowHeight,
ww: buttonSize, parent: serverButtons], clientData: h,
guarded: TRUE, proc: RestartButton];
[] ← Buttons.Create[info: [name: "command >", wx: col4, wy: row4, wh: rowHeight,
ww: buttonSize, parent: serverButtons], clientData: h, proc: CLineButton];
END;
ServerButton will be called by clicking the SERVER button in either Editor or Browser
modes. It just moves the current viewer off the screen and puts the server mode viewer up
instead. Note that we put the selection in the "focus" viewer before attempting any of this.
ServerButton: PUBLIC ENTRY ButtonProc = TRUSTED BEGIN
h: GVPRef = NARROW[clientData];
ToFocus[];
ViewerOps.MoveViewer[viewer: h.currentButtons, x:0, y: offScreen, w: 0, h: 0, paint: FALSE];
ViewerOps.MoveViewer[viewer: serverButtons, x:0, y: 0, w: 0, h: 4*rowSize, paint: FALSE];
ViewerOps.MoveViewer[viewer: h.currentText, x:0, y: offScreen, w: 0, h: 0, paint: FALSE];
ViewerOps.MoveViewer[viewer: logText, x:0, y: row6, w: 0, h: 0, paint: FALSE];
Containers.ChildXBound[h.root, logText];
Containers.ChildXBound[h.root, serverButtons];
Containers.ChildYBound[h.root, logText];
ViewerOps.PaintViewer[h.root, all];
h.currentButtons ← serverButtons;
h.currentText ← logText
END;
GetServerName gives the server name to the proc in GVPDriver which opens the server
bytestream. The next four procs just put the selection in the appropriate input viewer.
GetServerName: PUBLIC PROC RETURNS[name: ROPE] = BEGIN
name ← ViewerTools.GetContents[serverInput]
END;
ServNameButton: ButtonProc = TRUSTED {ViewerTools.SetSelection[serverInput]};
CLineButton: ButtonProc = TRUSTED {ViewerTools.SetSelection[cLineInput]};
FileNameButton: ButtonProc = TRUSTED {ViewerTools.SetSelection[fileInput]};
LengthButton: ButtonProc = TRUSTED {ViewerTools.SetSelection[lengthInput]};
ReadHeap uses the proc in GVPDriver to do the work, as do ReadFile and WriteFile.
WriteHeap uses a proc in GVPProcs to do its work
ReadHeapButton: ENTRY ButtonProc = TRUSTED BEGIN
h: GVPRef = NARROW[clientData];
[] ← Failed[GetHeapFile[h]]
END;
WriteHeapButton: ENTRY ButtonProc = TRUSTED BEGIN
h: GVPRef = NARROW[clientData];
[] ← Failed[WriteHeap[h]]
END;
ReadFileButton: ENTRY ButtonProc = TRUSTED BEGIN
h: GVPRef = NARROW[clientData];
[] ← Failed[ReadFile[h, ViewerTools.GetContents[fileInput]]]
END;
WriteFileButton: ENTRY ButtonProc = TRUSTED BEGIN
h: GVPRef = NARROW[clientData];
[] ← Failed[WriteFile[h, ViewerTools.GetContents[fileInput]]]
END;
SetLocal and SetServer both call ParseLength to get the file length (pages and bytes)
and then call the appropriate proc in GVPDriver
SetLocalButton: ENTRY ButtonProc = TRUSTED BEGIN
h: GVPRef = NARROW[clientData];
pages, bytes: CARDINAL;
ok: BOOLEAN;
[pages, bytes, ok] ← ParseLength[];
IF NOT ok THEN RETURN;
[] ← Failed[SetLocalLength[h, ViewerTools.GetContents[fileInput], pages, bytes]]
END;
SetServerButton: ENTRY ButtonProc = TRUSTED BEGIN
h: GVPRef = NARROW[clientData];
pages, bytes: CARDINAL;
ok: BOOLEAN;
[pages, bytes, ok] ← ParseLength[];
IF NOT ok THEN RETURN;
[] ← Failed[SetServerLength[h, ViewerTools.GetContents[fileInput], pages, bytes]]
END;
Restart uses the proc in GVPDriver to do its work, a sensible default command line
was set up earlier in MakeServerButtons.
WriteLog flushes the typescript and puts it in the file GVPatch.log, the old file is lost.
RestartButton: ENTRY ButtonProc = TRUSTED BEGIN
h: GVPRef = NARROW[clientData];
[] ← Failed[RestartServer[h, ViewerTools.GetContents[cLineInput]]]
END;
WriteLogButton: ENTRY ButtonProc = TRUSTED BEGIN
Set["Writing log . . ."];
h.logStream.PutF["\nLog written on %t\n", IO.time[]];
h.logStream.Flush[];
ViewerOps.SaveViewer[logText];
Set["Log written to file GVPatch.log"]
END;
ParseLength tries to make a file length from the rope in the length input viewer.
A BOOLEAN says that we got something reasonable. A file length is two numbers, the
first is the number of completely full pages in the file, the second the number of bytes in
the last page of the file.
ParseLength: PROC RETURNS[pages, bytes: CARDINAL, ok: BOOLEAN] = BEGIN
r: ROPE = ViewerTools.GetContents[lengthInput];
pos: CARDINAL ← 0;
ok ← FALSE;
[pages, pos] ← NumFromRope[FALSE, r, 0];
[bytes, pos] ← NumFromRope[FALSE, r, pos];
IF pages=lastCard OR bytes>=bytesPerPage THEN {Flash["Bad file length"]; RETURN};
ok ← TRUE
END;
The command that is registered with the Commander only lets one instance of GVPatch be
alive at any one time. If the REF to the data record isn't NIL when we invoke the
command nothing happens!
StartPatch makes a new record and calls each Init proc to get things going
DoIt: Commander.CommandProc = TRUSTED {StartPatch[]};
StartPatch: ENTRY PROC = BEGIN
IF h#NIL THEN RETURN;
h ← NEW[GVPRec];
DriverInit[];
ProcsInit[];
EditorInit[];
MainInit[];
BrowserInit[]
END;
The remaining lines are the start code. We make sure DestroyProc is called each time a viewer
is destroyed, then register the command with the Commander and finally start the system going.
[] ← ViewerEvents.RegisterEventProc[DestroyProc, destroy];
Commander.Register["GVPatch", DoIt];
StartPatch[]
END.