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],
List USING [Reverse],
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 ],
Process USING [ Pause, SecondsToTicks],
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 [GetSelectionContents, GetContents, MakeNewTextViewer, SetContents, SetSelection ]
;
FinchToolImpl:
CEDAR PROGRAM
IMPORTS BasicTime, Booting, Buttons, Commander, Containers, FinchSmarts, FinchTool, IO, Labels, MBQueue, Menus, Names, Rope, Rules, Log, Process, 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.ROPE←ALL["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;
Conversation Management
ReportConversationState:
PROC[ nb:
NB, cDesc: ConvDesc, remark: Rope.
ROPE ] =
TRUSTED {
s: IO.STREAM;
difficulty: BOOL←FALSE;
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[];
statesc.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:
BOOL←
TRUE] = {
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]];
};
MenuProc: TYPE = PROC [parent: REF ANY, clientData: REF ANY ← NIL,
mouseButton: MouseButton ← red, shift, control: BOOL ← FALSE]
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 = {
directoryFiles: Rope.ROPE;
directoryFilesList: LIST OF Rope.ROPE;
defaultFile: Rope.ROPE="User.TDir";
IF finchToolHandle.directory#
NIL
THEN
ViewerOps.DestroyViewer[finchToolHandle.directory!ANY=>CONTINUE];
directoryFiles ← UserProfile.Token[key: "FinchTelephoneDirectory",
default: defaultFile];
{
nextDirectoryFile: Rope.ROPE;
stream: IO.STREAM = IO.RIS[directoryFiles];
UNTIL
IO.EndOf[stream]
DO
nextDirectoryFile ← IO.GetLineRope[stream];
IF NOT Rope.Equal[nextDirectoryFile,""] THEN directoryFilesList ← CONS[nextDirectoryFile, directoryFilesList];
ENDLOOP;
};
directoryFilesList ← List.Reverse[directoryFilesList]; --convenience; forget for now
finchToolHandle.directory ← FinchTool.BuildDirectoryDisplayer[
directoryFiles: directoryFilesList,
viewerName: IF directoryFilesList.first=defaultFile THEN "Personal" ELSE directoryFilesList.first
];
FinchTool.DisplayDirectoryInViewer[
directoryFiles: directoryFilesList, msViewer: finchToolHandle.directory ];
};
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]; };