FinchToolImpl.mesa
Last Edited by: Swinehart, November 27, 1983 3:28 pm
DIRECTORY
BasicTime USING [ GMT, Now, Period ],
Booting USING [ CheckpointProc, RollbackProc, RegisterProcs ],
Buttons USING [ButtonProc, Create, ReLabel, SetDisplayStyle ],
Commander USING [CommandProc, Handle, Register],
Containers USING [ ChildXBound, ChildYBound, Container, Create ],
FinchSmarts USING [
AnswerCall, ConvDesc, DisconnectCall, InitFinchSmarts, UninitFinchSmarts ],
FinchTool,
Icons USING [ IconFlavor ],
IO,
Labels USING [Create, Label],
MBQueue USING [ Create, CreateButton, CreateMenuEntry, Queue ],
Menus USING [AppendMenuEntry, CreateMenu, Menu, MenuProc ],
Names USING [ CurrentRName ],
Rope USING [ Cat, Equal, Find, Length, ROPE, Substr ],
Rules USING [ Create ],
Log USING [ Report, RegisterWhereToReport, WhereProc ],
Thrush USING [ ConversationHandle, IntervalSpec, NB, nullConvHandle, nullHandle, Reason, StateInConv, ThHandle ],
TypeScript USING [ Create ],
UserProfile USING [ Token ],
VFonts USING [ Font, defaultFont ],
ViewerClasses USING [ Viewer, ViewerClassRec, ViewerRec ],
ViewerEvents USING [ EventProc, RegisterEventProc ],
ViewerIO USING [ CreateViewerStreams ],
ViewerLocks USING [ CallUnderWriteLock ],
ViewerOps USING [AddProp, ComputeColumn, CreateViewer, DestroyViewer, FetchProp, PaintViewer, SetMenu, SetOpenHeight],
ViewerSpecs USING [ openLeftTopY, openLeftWidth, openRightWidth ],
ViewerTools USING [GetContents, MakeNewTextViewer, SetContents, SetSelection ]
;
FinchToolImpl: CEDAR PROGRAM     
IMPORTS BasicTime, Booting, Buttons, Commander, Containers, FinchSmarts, FinchTool, IO, Labels, MBQueue, Menus, Names, Rope, Rules, Log, TypeScript, UserProfile, VFonts, ViewerEvents, ViewerIO, ViewerLocks, ViewerOps, ViewerSpecs, ViewerTools
EXPORTS FinchTool = {
OPEN IO, FinchTool;
Types and Typeoids
ConversationHandle: TYPE = Thrush.ConversationHandle;
nullConvHandle: ConversationHandle = Thrush.nullConvHandle;
ConvDesc: TYPE = FinchSmarts.ConvDesc;
NB: TYPE = Thrush.NB;
Reason: TYPE = Thrush.Reason;
ROPE: TYPE = Rope.ROPE;
Viewer: TYPE = ViewerClasses.Viewer;
nullHandle: Thrush.ThHandle = Thrush.nullHandle;
finchToolHandle: PUBLIC Handle;
cmdHandle: Commander.Handle←NIL;
printLabel: ARRAY Thrush.StateInConv OF Rope.ROPEALL["does not make sense"];
finchQueue: PUBLIC MBQueue.Queue ← MBQueue.Create[];
finchIcon: Icons.IconFlavor ← tool;
finchMenu: Menus.Menu;
finchStartMenu: Menus.Menu;
finchConvMenu: Menus.Menu;
xFudge: INTEGER = 4;
entryHeight: INTEGER = 14;
Viewer Construction and Operation
Finch Viewer
MakeFinchTool: PROC = BEGIN
finchWidth: INTEGER;
dif, wH: INTEGER;
bt: Viewer;
h: Handle;
v: Viewer;
IF finchToolHandle#NIL THEN {
ViewerOps.PaintViewer[finchToolHandle.outer, all]; RETURN; };
finchToolHandle ← NEW[FinchToolRec];
h ← finchToolHandle;
MakeMenus[];
finchToolHandle.outer ← Containers.Create[[-- outer container
name: "Finch", -- name displayed in the caption
iconic: FALSE,   -- so tool will not be iconic (small) when first created
column: left,    -- initially in the left column
menu: finchStartMenu,-- displaying our menu command
openHeight: ViewerSpecs.openLeftTopY/4, -- See Walnut
scrollable: FALSE ]];  -- inhibit user from scrolling contentsLabel
v ← h.outer;
finchWidth ← IF v.column=right THEN ViewerSpecs.openRightWidth ELSE ViewerSpecs.openLeftWidth;
[]←ViewerEvents.RegisterEventProc[
proc: UnfinchOnDestroy, event: destroy, filter: v, before: TRUE];
h.status ← FirstLabel[
name: " ",
parent: v];
bt ← QueuedButton[name: "Phone", proc: CallProc,
border: TRUE, sib: finchToolHandle.status, newLine: TRUE];
bt ← ImmediateButton[name: "Party:", proc: SetCalledPartyProc, border: FALSE, sib: bt];
h.calledPartyText ← NextRightTextViewer[sib: bt, w: finchWidth];
Containers.ChildXBound[h.outer, h.calledPartyText];
bt ← MakeRuler[sib: bt, h: 2];
Check how much space is left (if any) in the control window for a typescript.
wH ← v.ch;
IF (dif ← (wH-bt.cy)) < 64 THEN {
SetOpenHeight[v, wH + (64-dif)];
IF ~v.iconic THEN ViewerOps.ComputeColumn[v.column];
};
h.typescript ← MakeTypescript[sib: bt];
[h.tsIn, h.tsOut] ← ViewerIO.CreateViewerStreams[NIL, h.typescript];
ViewerOps.PaintViewer[v, all];    -- reflect above change
END;
MakeMenus: PROC = TRUSTED {
Before connecting to server
finchStartMenu ← Menus.CreateMenu[];
Menus.AppendMenuEntry[ 
menu: finchStartMenu,
entry: MBQueue.CreateMenuEntry[finchQueue, "Participate", ButtonStartFinch, finchToolHandle]
];
Menus.AppendMenuEntry[ 
menu: finchStartMenu,
entry: MBQueue.CreateMenuEntry[finchQueue, "Directory", MakeDirectory, finchToolHandle]
];
After connecting to server
finchMenu ← Menus.CreateMenu[];
Menus.AppendMenuEntry[ 
menu: finchMenu,
entry: MBQueue.CreateMenuEntry[finchQueue, "Drop Out", ButtonStopFinch, finchToolHandle]
];
Menus.AppendMenuEntry[ 
menu: finchMenu,
entry: MBQueue.CreateMenuEntry[finchQueue, "Answer", Answer, finchToolHandle]
];
Menus.AppendMenuEntry[ 
menu: finchMenu,
entry: MBQueue.CreateMenuEntry[finchQueue, "Disconnect", Hangup, finchToolHandle ]  
];
Menus.AppendMenuEntry[ 
menu: finchMenu,
entry: MBQueue.CreateMenuEntry[finchQueue, "Directory", MakeDirectory, finchToolHandle ]
];
Conversation window
finchConvMenu ← Menus.CreateMenu[];
Menus.AppendMenuEntry[ 
menu: finchConvMenu,
entry: MBQueue.CreateMenuEntry[finchQueue, "Answer", Answer, finchToolHandle]
];
Menus.AppendMenuEntry[ 
menu: finchConvMenu,
entry: MBQueue.CreateMenuEntry[finchQueue, "Disconnect", Hangup, finchToolHandle]
];
};
Conversations Viewer
BuildConversationsWindow: PROC = {
IF finchToolHandle.conversations#NIL AND ~finchToolHandle.conversations.destroyed
THEN RETURN;
finchToolHandle.conversations ← ViewerOps.CreateViewer[
flavor: $Container, paint: TRUE,
info: [name: "Conversations", column: left, menu: finchConvMenu,
openHeight: ViewerSpecs.openLeftTopY/8,
iconic: FALSE, icon: finchIcon, inhibitDestroy: TRUE]];
ViewerOps.PaintViewer[finchToolHandle.outer, all];
ViewerOps.PaintViewer[finchToolHandle.conversations, all];
};
AddConvDesc: PROC[cViewer: Viewer, cDesc: ConvDesc] RETURNS [newV: Viewer] = {
lastButton: Viewer;
prevDesc: ConvDesc←NIL;
isIconic: BOOL;
BuildLine: PROC = {
IF lastButton.parent = NIL THEN
newV ← FirstButton[
q: finchQueue, name: NIL, proc: ConversationMgmtProc, width: 1024, parent: lastButton]
ELSE newV ← AnotherButton[
q: finchQueue, name: NIL, proc: ConversationMgmtProc, width: 1024, sib: lastButton, newLine: TRUE];
};
lastButton ← NARROW[ViewerOps.FetchProp[cViewer, $lastButton]];
IF lastButton#NIL THEN
prevDesc ← NARROW[ViewerOps.FetchProp[lastButton, $convDesc]]
ELSE lastButton← cViewer;
isIconic ← IF lastButton.parent = NIL THEN lastButton.iconic ELSE lastButton.parent.iconic;
IF isIconic THEN BuildLine[] ELSE ViewerLocks.CallUnderWriteLock[BuildLine,lastButton];
cDesc.clientData ← newV;
ViewerOps.AddProp[newV, $convDesc, cDesc];
lastButton ← newV;
ViewerOps.AddProp[cViewer, $lastButton, lastButton];
};
SelectEntryInConversations: PROC[entryButton: Viewer, reselect: BOOLTRUE] RETURNS[sameAsBefore: BOOL] = {
prevSelected: Viewer = NARROW[ViewerOps.FetchProp[entryButton.parent, $selectedEntry]];
IF prevSelected = entryButton AND reselect THEN RETURN[TRUE];
IF prevSelected # NIL AND ~prevSelected.destroyed THEN {
Buttons.SetDisplayStyle[prevSelected, $BlackOnWhite];
ViewerOps.PaintViewer[prevSelected, all];
};
IF entryButton.destroyed THEN Report["Conversation is no longer available."]
ELSE IF ~reselect THEN ViewerOps.AddProp[entryButton.parent, $selectedEntry, NIL]
ELSE {
Buttons.SetDisplayStyle[entryButton, $BlackOnGrey]; -- show it is selected
ViewerOps.PaintViewer[entryButton, all];
ViewerOps.AddProp[entryButton.parent, $selectedEntry, entryButton];
};
RETURN[FALSE];
};
Starting and Stopping
ReportSystemState: PROC[on: BOOL] = TRUSTED {
IF finchToolHandle=NIL THEN RETURN;
finchToolHandle.finchActive ← on;
IF finchToolHandle.finchWasActive=finchToolHandle.finchActive THEN RETURN;
finchToolHandle.finchWasActive𡤏inchToolHandle.finchActive;
ViewerOps.SetMenu[finchToolHandle.outer,
IF finchToolHandle.finchActive THEN finchMenu ELSE finchStartMenu];
ViewerOps.PaintViewer[finchToolHandle.outer, menu];
IF finchToolHandle.finchActive THEN BuildConversationsWindow[]
ELSE {
IF finchToolHandle.conversations#NIL THEN {
finchToolHandle.conversations.inhibitDestroy ← FALSE;
ViewerOps.DestroyViewer[finchToolHandle.conversations];
};
finchToolHandle.conversations ← NIL;
};
};
StartFinch: PUBLIC PROC[] = TRUSTED {
handle: Handle ← finchToolHandle;
IF handle=NIL THEN {
MakeFinchTool[];
handle ← finchToolHandle;
IF handle=NIL THEN RETURN; -- complain?--
};
Report["Connecting . . ."];
FinchSmarts.InitFinchSmarts[
ReportSystemState, ReportConversationState];
Report[IF handle.finchActive THEN "Finch is connected" ELSE "Could not connect",
" to telephone server"];
};
StopFinch: PUBLIC PROC[] = TRUSTED {
handle: Handle = finchToolHandle;
IF handle=NIL THEN RETURN;
Report["Disconnecting . . ."];
FinchSmarts.UninitFinchSmarts[];
Report[IF handle.finchActive THEN "Could not disconnect" ELSE "Finch is disconnected",
" from telephone server"];
};
CheckActive: PUBLIC PROC[handle: Handle] RETURNS[active: BOOLFALSE] = TRUSTED {
IF handle#NIL AND handle.finchActive THEN RETURN[TRUE];
Status["Finch not connected to telephone system"];
};
Conversation Management
ReportConversationState: PROC[ nb: NB, cDesc: ConvDesc, remark: Rope.ROPE ] = TRUSTED {
s: IO.STREAM;
difficulty: BOOLFALSE;
button: Viewer;
state: Thrush.StateInConv;
intervalSpec: Thrush.IntervalSpec;
SELECT nb FROM -- should possibly interpret more
success => NULL;
ENDCASE => { Status[remark]; RETURN; };
Don't use conversation buttons for failure reports
IF finchToolHandle.conversations=NIL OR finchToolHandle.conversations.destroyed
THEN BuildConversationsWindow[];
IF cDesc = NIL THEN RETURN; -- not much more can be done.
button ← NARROW[cDesc.clientData];
IF button = NIL THEN button ← AddConvDesc[finchToolHandle.conversations, cDesc];
s ← IO.ROS[];
state�sc.cState.state;
SELECT state FROM
active => { cDesc.completed ← TRUE; cDesc.failed←FALSE; };
reserved, parsing => {
cDesc.originator ← us;
cDesc.completed ← FALSE;
cDesc.conference ← FALSE;
SELECT cDesc.cState.reason FROM busy, error, noCircuits => difficulty←TRUE; ENDCASE;
};
ENDCASE => cDesc.failed←FALSE;
IF (state#reserved AND state#parsing) OR difficulty THEN s.PutF["Call %s %s at %t",
rope[SELECT cDesc.originator FROM us => "to", them=>"from", ENDCASE=>"to/from"],
rope[cDesc.otherPartyDesc],
time[cDesc.startTime]
];
If idle, elaborate.
IF difficulty OR state=idle THEN {
s.PutF["%s%s, duration = %r",
rope[IF difficulty THEN " was not successful"
ELSE IF cDesc.completed THEN printLabel[state] ELSE " was abandoned"],
rope[IF ~difficulty THEN ""
ELSE SELECT cDesc.cState.reason FROM
busy => " -- busy",
error => " -- connection failed",
noCircuits => " -- no circuits",
ENDCASE => " for some reason"
],
int[BasicTime.Period[cDesc.startTime, BasicTime.Now[]]]
];
}
ELSE s.PutRope[printLabel[state]];
IF cDesc.cState.comment#NIL THEN s.PutF[" (%s)", rope[cDesc.cState.comment]];
IF remark#NIL THEN s.PutF[" [%s]", rope[remark]];
IF ~cDesc.failed THEN Buttons.ReLabel[ button: button, paint: TRUE,
newName: s.RopeFromROS[] ];
IF difficulty THEN cDesc.failed←TRUE;
[] ← SelectEntryInConversations[button, state#idle];
Now report on any interval changes. <<May need to change type codes to deactivate?>>
FOR intervals: LIST OF Thrush.IntervalSpec ← cDesc.newIntervals, intervals.rest
WHILE intervals#NIL DO
intervalSpec ← intervals.first;
IF intervalSpec.type=request THEN LOOP;
SELECT intervalSpec.direction FROM
record =>
SELECT intervalSpec.type FROM
started => Status["Please begin speaking"];
finished => Status["Thank you"];
ENDCASE => Status[NIL];
play => Report[
SELECT intervalSpec.type FROM
started => "Playing message",
finished => "End of message",
ENDCASE => NIL];
ENDCASE;
ENDLOOP;
};
ParseCallee: PROC[fullCallee: ROPE] RETURNS [callee: ROPE, residence: BOOLTRUE] = {
endCallee: INT;
callee𡤏ullCallee;
IF fullCallee.Equal["home", FALSE] THEN callee←Names.CurrentRName[]
ELSE IF (endCallee𡤏ullCallee.Find[" at home", 0, FALSE]) >= 0 THEN
callee𡤏ullCallee.Substr[len: endCallee]
ELSE residence←FALSE;
};
HangItUp: PROC[complain: BOOL] = TRUSTED {
cDesc: ConvDesc = GetSelectedDesc[];
IF cDesc=NIL OR cDesc.cState.state=idle THEN {Report[" No conversation to leave"]; RETURN};
FinchSmarts.DisconnectCall[convID: cDesc.cState.credentials.convID];
};
GetSelectedDesc: PROC RETURNS [cDesc: ConvDesc←NIL] = {
viewer: Viewer = finchToolHandle.conversations;
selected: Viewer ← IF viewer=NIL THEN NIL
ELSE NARROW[ViewerOps.FetchProp[viewer, $selectedEntry]];
IF ~CheckActive[finchToolHandle] OR selected = NIL THEN RETURN;
cDesc ← NARROW[ViewerOps.FetchProp[selected, $convDesc]];
};
Button Procs
Finch Viewer
ButtonStartFinch: Buttons.ButtonProc = { StartFinch[]; };
ButtonStopFinch: Buttons.ButtonProc = { StopFinch[]; };
CallProc: Buttons.ButtonProc = {
callee: Rope.ROPE;
residence: BOOL;
callee ← ViewerTools.GetContents[finchToolHandle.calledPartyText];
IF ~CheckActive[finchToolHandle] OR callee = NIL THEN RETURN;
Status["Placing call to ", callee];
[callee, residence] ← ParseCallee[callee];
FinchTool.CallByDescription[description: callee, residence: residence]; -- Launch the call.
};
SetCalledPartyProc: Buttons.ButtonProc = { ViewerTools.SetSelection[finchToolHandle.calledPartyText, NIL]; };
Conversations Viewer
ConversationMgmtProc: Buttons.ButtonProc = {
viewer: Viewer = NARROW[parent];
[]← SelectEntryInConversations[viewer];
IF control THEN Hangup[parent: viewer.parent, mouseButton: mouseButton]
ELSE IF mouseButton#red THEN Answer[parent: viewer.parent, mouseButton: mouseButton];
};
Menu Procs
Hangup: PUBLIC Menus.MenuProc = { HangItUp[TRUE]; };
Called whenever the user clicks the hangup button
Refers to entries in the viewer finchToolHandle.conversations, not necessarily current one.
HangupQuietly: PUBLIC Menus.MenuProc = { HangItUp[FALSE]; };
Called to disconnect a call in progress without logging a message.
Answer: Menus.MenuProc = TRUSTED {
Called whenever the user clicks the answer button
Refers to entries in the viewer finchToolHandle.conversations, not necessarily current one.
viewer: Viewer = finchToolHandle.conversations;
selected: Viewer ← IF viewer=NIL THEN NIL
ELSE NARROW[ViewerOps.FetchProp[viewer, $selectedEntry]];
cDesc: ConvDesc = GetSelectedDesc[];
IF cDesc = NIL OR (SELECT cDesc.cState.state FROM
pending, ringing =>FALSE, ENDCASE=>TRUE) THEN {
Report[" No conversation to join"]; RETURN; };
FinchSmarts.AnswerCall[convID: cDesc.cState.credentials.convID];
};
MakeDirectory: Menus.MenuProc = {
directoryName: Rope.ROPE;
defaultName: Rope.ROPE="User.TDir";
IF finchToolHandle.directory#NIL THEN
ViewerOps.DestroyViewer[finchToolHandle.directory];
<< Be prepared to catch a "doesn't exist" in the above, and to ignore it. >>
directoryName ← UserProfile.Token[key: "FinchTelephoneDirectory",
default: defaultName];
finchToolHandle.directory ← FinchTool.BuildDirectoryDisplayer[
directoryName: directoryName,
viewerName: IF directoryName=defaultName THEN "Personal" ELSE directoryName
];
FinchTool.DisplayDirectoryInViewer[
directoryName: directoryName, msViewer: finchToolHandle.directory ];
};
Executive Commands
MakeFinchToolCmd: Commander.CommandProc = { MakeFinchTool[]; };
FinchCmd: Commander.CommandProc = { StartFinch[]; };
UnfinchCmd: Commander.CommandProc = { StopFinch[]; };
CallCmd: Commander.CommandProc = { {
ENABLE ANY => GOTO Quit; << When you know what comes up, deal directly with it. >>
callee: Rope.ROPE ← cmd.commandLine.Substr[start: 1, len: cmd.commandLine.Length[]-2];
residence: BOOL;
cmdHandle ← cmd;
IF CheckActive[finchToolHandle] AND callee # NIL AND callee.Length[]#0 THEN {
Status["Placing call to ", callee];
ViewerTools.SetContents[finchToolHandle.calledPartyText, callee];
[callee, residence] ← ParseCallee[callee];
FinchTool.CallByDescription[description: callee, residence: residence]; -- Launch the call.
};
};
cmdHandle ← NIL;
};
CallHomeCmd: Commander.CommandProc = {
cmd.commandLine ← " home\n";
[result, msg] ← CallCmd[cmd];
};
Event-Triggered Activities
UnfinchOnDestroy: ViewerEvents.EventProc = {
IF finchToolHandle=NIL THEN RETURN;
StopFinch[];
IF finchToolHandle.directory#NIL THEN {
ViewerOps.DestroyViewer[finchToolHandle.directory];
finchToolHandle.directory←NIL;
};
IF finchToolHandle.tsIn#NIL THEN finchToolHandle.tsIn.Close[];
IF finchToolHandle.tsOut#NIL THEN finchToolHandle.tsOut.Close[];
finchToolHandle ← NIL;
};
UnFinchOnCheckpoint: Booting.CheckpointProc = {
IF finchToolHandle=NIL THEN RETURN;
finchToolHandle.finchActiveAtCheckpoint ← finchToolHandle.finchActive;
IF finchToolHandle.finchActive THEN StopFinch[];
};
ReFinchOnRollback: Booting.RollbackProc = {
IF finchToolHandle=NIL OR ~finchToolHandle.finchActiveAtCheckpoint THEN RETURN;
StartFinch[];
};
Utilities
Viewers and Buttons
SetOpenHeight: PROC[viewer: ViewerClasses.Viewer, clientHeight: INTEGER] = {
LockedSetHeight: PROC = {ViewerOps.SetOpenHeight[viewer, clientHeight]};
ViewerLocks.CallUnderWriteLock[LockedSetHeight, viewer];
};
FirstLabel: PROC[name: ROPE, parent: Viewer] RETURNS [nV: Viewer] = {
IF ~FinchTool.CheckAborted[parent] THEN RETURN;
nV← Labels.Create[
info: [name: name, parent: parent, wh: entryHeight, wy: 1,
wx: IF parent.scrollable THEN 0 ELSE xFudge, border: FALSE]];
};
ImmediateButton: PUBLIC PROC[name: ROPE, proc: Buttons.ButtonProc, border: BOOL,
sib: Viewer, fork: BOOLTRUE, guarded: BOOLFALSE, newLine: BOOLFALSE]
RETURNS[Viewer] = {
info: ViewerClasses.ViewerRec← [name: name, wy: sib.wy, wh: entryHeight,
parent: sib.parent, border: border];
IF newLine THEN -- first button on new line
{ info.wy← sib.wy + sib.wh + (IF border THEN 1 ELSE 0); -- extra bit
info.wx← IF sib.parent.scrollable THEN 0 ELSE xFudge;
}
ELSE -- next button right on same line as previous
info.wx← sib.wx+sib.ww+xFudge;
RETURN[Buttons.Create[info: info, proc: proc, fork: fork, guarded: guarded]];
};
QueuedButton: PUBLIC PROC[name: ROPE, proc: Buttons.ButtonProc, border: BOOL,
sib: Viewer, guarded: BOOLFALSE, newLine: BOOLFALSE]
RETURNS[Viewer] = {
info: ViewerClasses.ViewerRec← [name: name, wy: sib.wy, wh: entryHeight,
parent: sib.parent, border: border];
IF newLine THEN -- first button on new line
{ info.wy← sib.wy + sib.wh + (IF border THEN 1 ELSE 0); -- extra bit
info.wx← IF sib.parent.scrollable THEN 0 ELSE xFudge;
}
ELSE -- next button right on same line as previous
info.wx← sib.wx+sib.ww+xFudge;
RETURN[MBQueue.CreateButton[q: finchQueue, info: info, proc: proc, guarded: guarded]];
};
-- sib is a viewer to the left or above the button to be made
AnotherButton: PUBLIC PROC[
q: MBQueue.Queue, name: ROPE, proc: Buttons.ButtonProc, sib: Viewer,
data: REF ANYNIL, border: BOOLFALSE, width: INTEGER← 0,
guarded: BOOLFALSE, font: VFonts.Font ← VFonts.defaultFont, newLine: BOOLFALSE]
RETURNS [nV: Viewer] = {
info: ViewerClasses.ViewerRec← [name: name, wy: sib.wy, ww: width, wh: entryHeight,
parent: sib.parent, border: border];
IF ~CheckAborted[sib] THEN RETURN;
IF newLine THEN { -- first button on new line
info.wy← sib.wy + sib.wh + (IF border THEN 1 ELSE 0); -- extra bit
info.wx← IF sib.parent.scrollable THEN 0 ELSE xFudge;
}
ELSE -- next button right on same line as previous
info.wx← sib.wx+sib.ww+xFudge;
RETURN[MBQueue.CreateButton[
q: q, info: info, proc: proc, clientData: data, font: font, guarded: guarded]]
};
FirstButton: PUBLIC PROC[
q: MBQueue.Queue, name: ROPE, proc: Buttons.ButtonProc,
parent: Viewer, data: REF ANYNIL, border: BOOLFALSE, width: INTEGER← 0,
guarded: BOOLFALSE, font: VFonts.Font ← VFonts.defaultFont]
RETURNS [nV: Viewer] = {
info: ViewerClasses.ViewerRec←
[name: name, parent: parent, wh: entryHeight, wy: 1, ww: width,
wx: IF parent.scrollable THEN 0 ELSE xFudge, border: border];
IF ~CheckAborted[parent] THEN RETURN;
nV← MBQueue.CreateButton[
q: q, info: info, proc: proc, clientData: data, font: font, guarded: guarded];
};
CheckAborted: PUBLIC PROC[sib: Viewer] RETURNS[ok: BOOL] = {
IF sib = NIL THEN RETURN[TRUE];
IF sib.destroyed THEN RETURN[FALSE];
RETURN[TRUE];
};
Creates a text viewer, next right on the same line as sib
sib must be a Viewer, not NIL
NextRightTextViewer: PUBLIC PROC[sib: Viewer, w: INTEGER] RETURNS [nV: Viewer] = {
IF ~FinchTool.CheckAborted[sib] THEN RETURN;
nV← ViewerTools.MakeNewTextViewer[
info: [parent: sib.parent, wx: sib.wx+sib.ww+xFudge, wy: sib.wy,
ww: w, wh: entryHeight, border: FALSE]];
};
MakeRuler: PROC[sib: Viewer, h: INTEGER← 1] RETURNS [r: Viewer] = {
Make an h-bit wide line after sib
IF ~FinchTool.CheckAborted[sib] THEN RETURN;
r← Rules.Create[
info: [parent: sib.parent, wy: sib.wy+sib.wh+1, ww: sib.parent.ww, wh: h]];
Containers.ChildXBound[sib.parent, r];
};
Sib is sibling to create TS after
MakeTypescript: PROC[sib: Viewer] RETURNS [ts: Viewer] = {
y: INTEGER← sib.wy+sib.wh+xFudge;
IF ~CheckAborted[sib] THEN RETURN;
ts← TypeScript.Create[
info: [parent: sib.parent, ww: sib.cw, wy: y, wh: sib.parent.ch - y, border: FALSE] ];
Containers.ChildYBound[sib.parent, ts];
Containers.ChildXBound[sib.parent, ts];
};
Reporting and Logging
Report: PUBLIC PROC[msg1, msg2, msg3, msg4: ROPENIL] = {
Log.Report[where: $Finch, remark:
IO.PutFR["%s%s%s%s",
rope[msg1], rope[msg2], rope[msg3], rope[msg4]]];
};
ReportRope: PUBLIC PROC[msg1: ROPE] = {
IF msg1#NIL THEN Log.Report[where: $Finch, remark: msg1];
};
Status: PUBLIC PROC[msg1, msg2, msg3, msg4: ROPENIL] = {
what: ROPE = Rope.Cat[msg1, msg2, msg3, msg4];
ReportRope[what];
IF finchToolHandle#NIL AND finchToolHandle.finchActive THEN
Labels.Set[finchToolHandle.status, what];
};
FinchWhereProc: Log.WhereProc = {
RETURN[IF cmdHandle#NIL THEN cmdHandle.out ELSE IF finchToolHandle=NIL THEN NIL
ELSE finchToolHandle.tsOut];
};
FinchWhereCmdProc: Log.WhereProc = {
RETURN[NIL];
};
Registration, Initialization
Register a command with the UserExec that will create an instance of this tool
Commander.Register[key: "FinchTool", proc: MakeFinchToolCmd,
doc: "Create a Finch viewers tool" ];
Commander.Register["Finch", FinchCmd, "Start Finch (connect to server)"];
Commander.Register["Unfinch", UnfinchCmd, "Stop Finch (disconnect server)"];
-- initialize text for print form of connect state
Commander.Register["Phone", CallCmd, "Place telephone call to specified party"];
Commander.Register["ET", CallHomeCmd, "Phonnne hommmmmmme"];
Log.RegisterWhereToReport[proc: FinchWhereProc, where: $Finch];
Log.RegisterWhereToReport[proc: FinchWhereCmdProc, where: $FinchCmd];
printLabel[active] ← " is in progress";
printLabel[idle] ← " is completed";
printLabel[ringing] ← " is ringing";
printLabel[pending] ← NIL;
printLabel[initiating] ← NIL;
printLabel[maybe] ← " is ringing";
printLabel[reserved] ← " Telephone set is off hook";
printLabel[parsing] ← " Call is being dialed";
TRUSTED { Booting.RegisterProcs[c: UnFinchOnCheckpoint, r: ReFinchOnRollback]; };
}.