FinchToolImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Last Edited by: Swinehart, October 28, 1985 5:54:19 pm PST
Last Edited by: Pier, April 17, 1984 3:51:54 pm PST
Polle Zellweger (PTZ) December 13, 1985 5:14:40 pm PST
DIRECTORY
BasicTime USING [ GMT, Now, Period ],
Booting USING [ CheckpointProc, RollbackProc, RegisterProcs ],
Buttons USING [ButtonProc, Create, ReLabel, SetDisplayStyle ],
Commander USING [CommandProc, Handle, Register],
CommandTool USING [ArgN],
Containers USING [ ChildXBound, ChildYBound, Container, Create ],
Convert USING [ RopeFromInt ],
FinchSmarts
USING [
AnswerCall, ConvDesc, DisconnectCall, Feep, GetCurrentConvID, InitFinchSmarts, StopSpeech, TextToSpeech, UninitFinchSmarts ],
FinchTool,
Icons USING [ IconFlavor, NewIconFromFile ],
IO,
Labels USING [Create],
List USING [Reverse],
Log USING [ Report, RegisterWhereToReport, WhereProc ],
MBQueue USING [ Create, CreateButton, CreateMenuEntry, Queue ],
Menus USING [AppendMenuEntry, CreateMenu, Menu, MenuProc ],
Names USING [ CurrentRName ],
Nice USING [ View ],
Process USING [ Detach, Pause, MsecToTicks, SetTimeout],
Rope USING [ ActionType, Cat, Concat, Equal, Fetch, Find, Flatten, Length, Map, MakeRope, ROPE, Substr ],
Rules USING [ Create ],
Thrush USING [ ConversationHandle, IntervalSpec, IntervalSpecs, NB, nullConvHandle, nullHandle, ProseSpecs, ProseSpec, Reason, StateInConv, ThHandle ],
TiogaOps USING [ GetRope, GetSelection, Location, Ref, StepForward ],
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, SetOpenHeight],
ViewerSpecs USING [ openTopY, openLeftWidth, openRightWidth ],
ViewerTools USING [GetSelectionContents, GetContents, MakeNewTextViewer, SetContents, SetSelection ]
;
FinchToolImpl:
CEDAR MONITOR
IMPORTS BasicTime, Booting, Buttons, Commander, CommandTool, Containers, Convert, FinchSmarts, FinchTool, Icons, IO, Labels, MBQueue, Menus, Names, Nice, Rope, Rules, Log, Process, TypeScript, TiogaOps, UserProfile, VFonts, ViewerEvents, ViewerIO, ViewerLocks, ViewerOps, ViewerSpecs, ViewerTools
EXPORTS FinchTool = {
OPEN IO;
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 FinchTool.Handle;
cmdHandle: Commander.Handle←NIL;
serverInstance: Rope.ROPE ← NIL;
printLabel: ARRAY Thrush.StateInConv OF Rope.ROPE←ALL["does not make sense"];
finchQueue: PUBLIC MBQueue.Queue ← MBQueue.Create[];
finchIcon: Icons.IconFlavor ← tool;
leftFinchIcon: Icons.IconFlavor ← tool;
rightFinchIcon: Icons.IconFlavor ← tool;
conversationIcon: Icons.IconFlavor ← tool;
labelledFinchIcon: Icons.IconFlavor ← tool;
labelledLeftFinchIcon: Icons.IconFlavor ← tool;
labelledRightFinchIcon: Icons.IconFlavor ← tool;
labelledConversationIcon: Icons.IconFlavor ← tool;
finchMenu: Menus.Menu;
finchConvMenu: Menus.Menu;
xFudge: INTEGER = 2;
entryHeight: INTEGER = 14;
defaultToolHeight: NAT ← ViewerSpecs.openTopY/5;
Placing and Controlling Calls
PhoneProc: Buttons.ButtonProc = {
[parent: REF ANY, clientData: REF ANY ← NIL, mouseButton: Menus.MouseButton ← red, shift: BOOL ← FALSE, control: BOOL ← FALSE]
calledPartyText: Rope.ROPE ← ViewerTools.GetSelectionContents[];
DoPhone[ViewerTools.GetSelectionContents[],
SELECT mouseButton
FROM
red, yellow => FALSE, blue=> TRUE, ENDCASE=>ERROR];
};
CalledPartyProc: Buttons.ButtonProc = {
[parent: REF ANY, clientData: REF ANY ← NIL, mouseButton: Menus.MouseButton ← red, shift: BOOL ← FALSE, control: BOOL ← FALSE]
calledPartyText: Rope.ROPE←ViewerTools.GetContents[finchToolHandle.calledPartyText];
SELECT mouseButton
FROM
red => ViewerTools.SetSelection[finchToolHandle.calledPartyText, NIL];
yellow => DoPhone[calledPartyText, FALSE];
blue => DoPhone[calledPartyText, TRUE];
ENDCASE => ERROR;
};
CallingPartyProc: Buttons.ButtonProc = {
[parent: REF ANY, clientData: REF ANY ← NIL, mouseButton: Menus.MouseButton ← red, shift: BOOL ← FALSE, control: BOOL ← FALSE]
callingPartyText: Rope.ROPE←ViewerTools.GetContents[finchToolHandle.callingPartyText];
SELECT mouseButton
FROM
red => ViewerTools.SetSelection[finchToolHandle.callingPartyText, NIL];
yellow => DoPhone[callingPartyText, FALSE];
blue => DoPhone[callingPartyText, TRUE];
ENDCASE => ERROR;
};
PhoneCmd: Commander.CommandProc = {
ENABLE UNWIND => cmdHandle ← NIL;
calledPartyText: Rope.ROPE ← cmd.commandLine.Substr[start: 1, len: cmd.commandLine.Length[]-2];
cmdHandle ← cmd;
DoPhone[calledPartyText];
cmdHandle ← NIL;
};
PhoneHomeCmd: Commander.CommandProc = {
cmd.commandLine ← " home\n";
[result, msg] ← PhoneCmd[cmd];
};
RedialCmd: Commander.CommandProc = {
IF ~CheckActive[finchToolHandle] THEN RETURN;
DoPhone[ViewerTools.GetContents[finchToolHandle.calledPartyText], FALSE];
};
DoPhone:
PROC[calledPartyText: Rope.
ROPE, wantResidence:
BOOL←
FALSE] = {
callee, newCalledPartyText: Rope.ROPE;
residence: BOOL;
pauseNeededInMs: CARDINAL;
IF ~CheckActive[finchToolHandle]
OR calledPartyText =
NIL
OR calledPartyText.Length[]=0
THEN RETURN;
[callee, newCalledPartyText, residence] ← ParseCallee[calledPartyText];
IF ~residence
AND wantResidence
THEN {
residence ← TRUE;
newCalledPartyText ← newCalledPartyText.Concat[" at home"];
};
Status["Placing call to ", newCalledPartyText];
IF newCalledPartyText#ViewerTools.GetContents[finchToolHandle.calledPartyText]
THEN
ViewerTools.SetContents[finchToolHandle.calledPartyText, newCalledPartyText];
pauseNeededInMs ← HangItUp[FALSE, TRUE];
IF pauseNeededInMs#0
THEN Process.Pause[Process.MsecToTicks[pauseNeededInMs]];
--maybe a status check wait loop here ??
FinchTool.CallByDescription[description: callee, residence: residence]; -- Launch the call.
};
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];
};
ConversationMgmtProc: Buttons.ButtonProc = {
Button-invoked operations when applied directly to conversation-viewer log entries.
viewer: Viewer = NARROW[parent];
IF ~CheckActive[finchToolHandle] THEN RETURN;
[]← SelectEntryInConversations[viewer];
IF control THEN Hangup[parent: viewer.parent, mouseButton: mouseButton]
ELSE IF mouseButton#red THEN Answer[parent: viewer.parent, mouseButton: mouseButton];
};
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.
HangUpCmd: Commander.CommandProc = {
IF ~CheckActive[finchToolHandle] THEN RETURN;
[] ← HangItUp[TRUE];
};
HangupQuietly:
PUBLIC Menus.MenuProc = { []←HangItUp[
FALSE]; };
Called to disconnect a call in progress without logging a message.
HangItUp:
PROC[complain:
BOOL, moreToCome:
BOOL←
FALSE]
RETURNS [pauseNeededInMs:
CARDINAL𡤀] =
TRUSTED {
cDesc: ConvDesc = GetSelectedDesc[];
state: Thrush.StateInConv ← IF cDesc=NIL THEN idle ELSE cDesc.cState.state;
SELECT state
FROM
idle => {
IF complain THEN Report[" No conversation to leave"]; RETURN };
reserved, parsing => IF moreToCome THEN RETURN;
ENDCASE;
pauseNeededInMs ← IF cDesc.cState.state=active THEN 2000 ELSE 500;
FinchSmarts.DisconnectCall[convID: cDesc.cState.credentials.convID];
};
ParseCallee:
PROC[fullCallee:
ROPE]
RETURNS [callee:
ROPE, fCallee:
ROPE, residence:
BOOL←
FALSE] = {
Parses single party-specification: phone number (leading character is not a letter), or named recipient. Names can be RNames or other text sequences that can be looked up in one's private (TDir-style) telephone directory. "XXX at home" is a request to call a residence rather than office number. "home" is equivalent to "<logged-in user> at home". "left XXX" and "middle XXX" are equivalent to "XXX". "right XXX" is the same as "XXX at home". This is to accommodate the strange Command Tool. "XXX at <number>" says you want to call the specified number, and the recipient is XXX (not implemented yet.)
endCallee: INT;
fCallee ← CheckForMouseButtonSpec[fullCallee, "left ", ""];
fCallee ← CheckForMouseButtonSpec[fCallee, "middle ", ""];
fCallee ← CheckForMouseButtonSpec[fCallee, "right ", " at home"];
callee ← fCallee;
IF fCallee.Equal["home",
FALSE]
THEN
RETURN[Names.CurrentRName[], Names.CurrentRName[].Concat[" at home"], TRUE];
IF (endCalleellee.Find[" at home", 0,
FALSE]) >= 0
THEN
RETURN[fCallee.Substr[len: endCallee], fCallee, TRUE];
};
CheckForMouseButtonSpec:
PROC[fullCallee, pattern, replacement:
ROPE]
RETURNS [fCallee:
ROPE] = {
pLen: INT ← pattern.Length;
fCallee ← fullCallee;
IF fullCallee.Length[]>=pLen
AND pattern.Equal[fullCallee.Substr[len: pLen]]
THEN
fCallee ← fullCallee.Substr[start: pLen].Concat[replacement];
};
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]];
};
Speaking Text
SpeakSelectedProc: Buttons.ButtonProc = {
IF ~CheckActive[finchToolHandle] THEN RETURN;
SELECT mouseButton
FROM
red => SpeakTextWithFeedback[TRUE];
yellow => NULL;
blue => SpeakTextWithFeedback[FALSE ];
ENDCASE => NULL;
};
SpeakTextCmd: Commander.CommandProc = {
textToSpeak: Rope.ROPE ← cmd.commandLine.Substr[start: 1, len: cmd.commandLine.Length[]-2];
IF ~CheckActive[finchToolHandle] THEN RETURN;
IF textToSpeak.Length[]#0 THEN SpeakTextWithFeedback[TRUE, textToSpeak];
};
speakTextFeedbackLen: INT ← 40;
SpeakTextWithFeedback:
PROC [queueIt:
BOOL, text:
ROPE←
NIL] ~ {
IF text=NIL THEN text ← RopeWithNodesFromSelection[];
IF text = NIL THEN RETURN;
Status[
IF ~queueIt THEN "Flushing text. " ELSE NIL,
"Speaking text: \"",
text.Substr[len: speakTextFeedbackLen],
IF text.Length[] > speakTextFeedbackLen THEN "...\"" ELSE "\""
];
[] ← FinchSmarts.TextToSpeech[text: text, queueIt: queueIt];
};
RopeWithNodesFromSelection:
PROC [nodeEnd: Rope.
ROPE ← ".. "]
RETURNS [tiogaopstext: Rope.
ROPE ←
NIL] ~ {
nodeEnd = "" makes this function the same as ViewerTools.GetSelectionContents.
start, end: TiogaOps.Location;
[start: start, end: end] ← TiogaOps.GetSelection[];
FOR node: TiogaOps.Ref ← start.node, TiogaOps.StepForward[node]
DO
IF node = start.node
THEN
IF node = end.node
THEN {
tiogaopstext ← Rope.Substr[base: TiogaOps.GetRope[node], start: start.where, len: end.where-start.where+1];
RETURN;
}
ELSE
tiogaopstext ← Rope.Substr[base: TiogaOps.GetRope[node], start: start.where]
ELSE
IF node = end.node
THEN {
tiogaopstext ← Rope.Cat[tiogaopstext, nodeEnd,
Rope.Substr[base: TiogaOps.GetRope[node], len: end.where+1]];
RETURN;
}
ELSE
tiogaopstext ← Rope.Cat[tiogaopstext, nodeEnd, TiogaOps.GetRope[node]];
ENDLOOP;
};
StopSpeechProc: Buttons.ButtonProc = {
text: Rope.ROPE;
text ← ViewerTools.GetSelectionContents[];
IF ~CheckActive[finchToolHandle] OR text = NIL THEN RETURN;
Status["Flushing text."];
[] ← FinchSmarts.StopSpeech[];
};
StopSpeakingCmd: Commander.CommandProc = {
IF ~CheckActive[finchToolHandle] THEN RETURN;
[]𡤏inchSmarts.StopSpeech[];
};
Other User Commands
FeepCmd: Commander.CommandProc = {
textToFeep: Rope.ROPE ← FeepValue[cmd.commandLine.Substr[start: 1, len: cmd.commandLine.Length[]-2]];
IF ~CheckActive[finchToolHandle] THEN RETURN;
[] ← FinchSmarts.Feep[textToFeep];
};
FeepValue:
PUBLIC PROC[text:
ROPE]
RETURNS [feepText:
ROPE] = {
Convert an arbitrary rope into characters found on a DTMF touchpad. '*, '#, and digits and all punctuation (often used to format phone numbers) map to themselves. Letters (either case) map to their standard telephone-dial locations. Also, 'Q->'7 and 'Z->'9. One can put a leading '. or something in front of an address to force higher levels to interpret it as a "number" instead of a name.
Now people can dial .FAX or .HELP or 9(800)TheCard, or 9Fastell.
FeepFetch:
PROC[data:
REF, index:
INT]
RETURNS [c:
CHAR] = {
c←NARROW[data, ROPE].Fetch[index];
c ←
SELECT c
FROM
IN ['A..'Z] => FeepMap[c+('a-'A)],
IN ['a..'z] => FeepMap[c],
ENDCASE => c;
};
IF text=NIL OR text.Length[]=0 THEN RETURN[text];
RETURN[Rope.Flatten[Rope.MakeRope[base: text, size: text.Length[], fetch: FeepFetch]]];
};
FeepMap:
PACKED
ARRAY
CHAR['a..'z]
OF
CHAR = [
'2, '2, '2, '3, '3, '3, '4, '4, '4, '5, '5, '5, '6, '6, '6, '7, '7, '7, '7, '8, '8, '8, '9, '9, '9, '9 ];
Starting and Stopping
StartFinch:
PUBLIC PROC[] =
TRUSTED {
handle: FinchTool.Handle ← finchToolHandle;
IF handle=
NIL
THEN {
MakeFinchTool[];
handle ← finchToolHandle;
IF handle=NIL THEN RETURN; -- complain?--
};
Report["Connecting . . ."];
FinchSmarts.InitFinchSmarts[
serverInstance, ReportSystemState, ReportConversationState];
Report[
IF handle.finchActive
THEN "Finch is connected"
ELSE "Could not connect",
" to telephone server"];
finchToolHandle.finchActiveAtCheckpoint ← FALSE;
};
FinchCmd: Commander.CommandProc = {
serverInstance ← CommandTool.ArgN[cmd,1];
StartFinch[];
};
ReFinchOnRollback: Booting.RollbackProc = {
IF finchToolHandle=NIL OR ~finchToolHandle.finchActiveAtCheckpoint THEN RETURN;
finchToolHandle.finchActiveAtCheckpoint ← FALSE;
Try[StartFinch];
};
StopFinch:
PUBLIC PROC[] =
TRUSTED {
handle: FinchTool.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"];
};
ButtonStopFinch: Buttons.ButtonProc = { StopFinch[]; };
UnfinchOnDestroy: ViewerEvents.EventProc = {
IF finchToolHandle=NIL THEN RETURN;
StopFinch[];
IF finchToolHandle.conversations#
NIL
THEN {
finchToolHandle.conversations.inhibitDestroy ← FALSE;
ViewerOps.DestroyViewer[finchToolHandle.conversations];
};
FinchTool.DestroyDirectories[];
IF finchToolHandle.tsIn#NIL THEN finchToolHandle.tsIn.Close[];
IF finchToolHandle.tsOut#NIL THEN finchToolHandle.tsOut.Close[];
finchToolHandle ← NIL;
};
UnFinchOnCheckpointOrBoot: Booting.CheckpointProc = {
IF finchToolHandle=NIL THEN RETURN;
finchToolHandle.finchActiveAtCheckpoint ←
finchToolHandle.finchActiveAtCheckpoint OR finchToolHandle.finchActive;
IF finchToolHandle.finchActive THEN Try[StopFinch];
};
CheckActive:
PUBLIC
PROC[handle: FinchTool.Handle]
RETURNS[active:
BOOL←
FALSE] =
TRUSTED {
If not active, try to make it active.
IF handle#NIL AND handle.finchActive THEN RETURN[TRUE];
StartFinch[];
RETURN[handle.finchActive];
};
UnfinchCmd: Commander.CommandProc = { StopFinch[]; };
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.PaintViewer[finchToolHandle.outer, menu];
};
didOurBest: BOOL←FALSE;
maxTime: CARDINAL ← 2500;
Try:
PROC[p:
PROC] =
TRUSTED {
didOurBest ← FALSE;
Process.Detach[FORK Try1[p]];
FOR i:
NAT
IN [0..100)
DO
Process.Pause[Process.MsecToTicks[maxTime/100]];
IF didOurBest THEN EXIT;
ENDLOOP;
};
Try1:
PROC[p:
PROC] = {
p[];
didOurBest ← TRUE;
};
Conversation Management
ReportConversationState:
PROC[ nb:
NB, cDesc: ConvDesc, remark: Rope.
ROPE ] = {
s: IO.STREAM;
p: PROCESS;
difficulty: BOOL←FALSE;
button: Viewer;
state: Thrush.StateInConv;
intervalSpec: Thrush.IntervalSpec;
proseSpec: Thrush.ProseSpec;
currentConvID: ConversationHandle ← FinchSmarts.GetCurrentConvID[];
curConv: BOOL←FALSE;
SELECT nb
FROM
-- should possibly interpret more
success => NULL;
ENDCASE => { Status[remark];
RETURN; };
Don't use conversation buttons for failure reports
IF cDesc = NIL THEN RETURN; -- not much more can be done.
curConv ← currentConvID=nullConvHandle
OR currentConvID=cDesc.cState.credentials.convID;
The first clause covers getting the idle report (the conversation is already gone).
button ← NARROW[cDesc.clientData];
IF button = NIL THEN button ← AddConvDesc[finchToolHandle.conversations, cDesc];
s ← IO.ROS[];
statesc.cState.state;
SELECT state
FROM
active => {
cDesc.failed←FALSE;
};
reserved, parsing => {
cDesc.originator ← us;
cDesc.conference ← FALSE;
SELECT cDesc.cState.reason FROM busy, error, noCircuits => difficulty←TRUE; ENDCASE;
};
ENDCASE => cDesc.failed←FALSE;
IF ~cDesc.weOriginated
AND state > parsing
THEN {
cDesc.otherPartyDesc can be of the form "xxx.pa (nnnn)", which confuses Phone commands. Number in parens should be eliminated from number field being set here.
SetContents:
PROC[v: ViewerClasses.Viewer, cDesc: ConvDesc] = {
contents: ROPE ← RepairIntelnet[cDesc.otherPartyDesc];
i: INT𡤌ontents.Find["(", 0, FALSE];
IF i>0 THEN contents ← contents.Substr[len: i-1];
ViewerTools.SetContents[v, contents, TRUE];
cDesc.weOriginated ← TRUE;
};
SELECT cDesc.originator
FROM
If we originated the call, the called-party field is already set, and contains better information than we can get this way (home or office, nicknames, and so on.) Here we pick up the calling party for incoming calls, or called-party for calls initiated from the telset. weOriginated is assumed FALSE, set TRUE by FinchSmarts when initiating a call.
us => {
Set called-party field
SetContents[finchToolHandle.calledPartyText, cDesc];
};
them, unknown => {
Set calling-party field
SetContents[finchToolHandle.callingPartyText, cDesc];
};
ENDCASE;
};
IF curConv
THEN {
IF state=ringing
THEN {
-- start twiddling if haven't already
IF ~finchToolHandle.keepTwiddling
THEN {
finchToolHandle.keepTwiddling ← TRUE;
TRUSTED {Process.Detach[p ← FORK TwiddleFinchIcon[finchToolHandle]]}
};
}
ELSE
-- stop twiddling if haven't already
IF finchToolHandle.keepTwiddling
THEN
PaintFinchIcon[finchToolHandle, cDesc, difficulty OR state=idle];
};
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←RepairIntelnet[cDesc.otherPartyDesc]],
time[cDesc.startTime]
];
IF ~finchToolHandle.keepTwiddling
AND curConv
THEN
Don't want to step on twiddling if state=ringing. This call covers hanging up from non-ringing state or placing outgoing call.
PaintFinchIcon[finchToolHandle, cDesc, difficulty OR state=idle];
};
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.ultimateState>ringing 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;
IF curConv
THEN
[] ← SelectEntryInConversations[button, state#idle];
Now report on any interval changes. <<May need to change type codes to deactivate?>>
FOR intervals: Thrush.IntervalSpecs ← cDesc.requestedIntervals, intervals.rest
WHILE intervals#
NIL
DO
intervalSpec ← intervals.first;
IF intervalSpec.changeNoted OR intervalSpec.type=request THEN LOOP;
intervalSpec.changeNoted←TRUE;
SELECT intervalSpec.direction
FROM
record =>
SELECT intervalSpec.type
FROM
started => Status["Please begin speaking"];
finished => Status["Thank you"];
ENDCASE => Status[NIL];
play =>
IF intervalSpec.interval.length#0
OR intervalSpec.queueIt
THEN Report[
SELECT intervalSpec.type
FROM
started => "Playing message",
finished => "End of message",
ENDCASE => NIL];
ENDCASE;
ENDLOOP;
FOR proses: Thrush.ProseSpecs ← cDesc.requestedProses, proses.rest
WHILE proses#cDesc.pendingProses
AND proses#
NIL DO
proseSpec ← proses.first;
IF proseSpec.changeNoted THEN LOOP;
proseSpec.changeNoted←TRUE;
IF proseSpec.prose.Length[]#0 OR proseSpec.queueIt THEN
IF finchToolHandle.debug
THEN
Status[
SELECT proseSpec.type
FROM
request => "R",
started => "S",
finished => "F",
ENDCASE => NIL,
Convert.RopeFromInt[proseSpec.intID.stateID],
".",
Convert.RopeFromInt[proseSpec.intID.reqID]
];
ENDLOOP;
};
cDesc.otherPartyDesc can contain Intelnet pause characters and authentication code.
Intelnet pause characters are all characters < '\040
Should be repaired (pause => "*" and auth-code removed) wherever the user sees it.
RepairIntelnet:
PROC[num:
ROPE]
RETURNS[number:
ROPE] = {
i: INT←-1;
M: Rope.ActionType={i←i+1; RETURN[c<'\040]};
number ← num;
IF number.Map[0, number.Length[], M]
THEN {
number ← Rope.Cat[number.Substr[len: i], "*", number.Substr[start:i+1]];
i←-1;
IF number.Map[0, number.Length[], M] THEN number ← number.Substr[len:i];
};
};
PaintFinchIcon:
ENTRY
PROC [handle: FinchTool.Handle, cDesc: ConvDesc, useFinchIcon:
BOOL] = {
oldIcon: Icons.IconFlavor ← handle.outer.icon;
handle.keepTwiddling ← FALSE;
NOTIFY handle.twiddleWait;
IF useFinchIcon
THEN {
handle.outer.icon ← finchIcon;
handle.outer.label ← NIL;
}
ELSE {
handle.outer.icon ← labelledConversationIcon;
handle.outer.label ←
SELECT cDesc.originator
FROM
us => Rope.Concat["to ",
-- this doesn't work right for "listen to this!`' mode
IF cDesc.bluejayConnection THEN "Recording service"
ELSE IF cDesc.proseConnection THEN "Text-to-Speech service"
ELSE ViewerTools.GetContents[handle.calledPartyText]
],
them => Rope.Concat["from ", ViewerTools.GetContents[handle.callingPartyText]],
ENDCASE => Rope.Concat["to/from ", RepairIntelnet[cDesc.otherPartyDesc]];
};
IF finchToolHandle.outer.iconic
AND oldIcon#handle.outer.icon
THEN
ViewerOps.PaintViewer[viewer: handle.outer, hint: all];
};
TwiddleFinchIcon:
ENTRY PROC [handle: FinchTool.Handle] = {
ENABLE UNWIND => NULL;
MsPause:
INTERNAL
PROC [ms:
INT] =
TRUSTED {
Process.SetTimeout[@handle.twiddleWait, Process.MsecToTicks[ms]];
WAIT handle.twiddleWait;
};
NewIcon:
INTERNAL
PROC [icon: Icons.IconFlavor, ms:
INT]
RETURNS [keepGoing:
BOOL] = {
IF ms # 0
THEN {
handle.outer.icon ← icon;
IF handle.outer.iconic
THEN
ViewerOps.PaintViewer[viewer: handle.outer, hint: all];
MsPause[ms];
};
RETURN [handle.keepTwiddling];
};
pauseReps:
INT ← handle.pauseTimeOn/
((handle.pauseTimeTilted + handle.pauseTimeMiddle+handle.pauseTimeDrawFactor)*2);
handle.outer.label ← ViewerTools.GetContents[handle.callingPartyText];
Assumes that the ordinary Finch icon is unlabelled.
WHILE handle.keepTwiddling
DO
IF ~handle.outer.iconic
THEN
MsPause[250]
ELSE {
FOR i:
INT
IN [0..pauseReps)
DO
IF ~NewIcon[labelledRightFinchIcon, handle.pauseTimeTilted] THEN RETURN;
IF ~NewIcon[labelledFinchIcon, handle.pauseTimeMiddle] THEN RETURN;
IF ~NewIcon[labelledLeftFinchIcon, handle.pauseTimeTilted] THEN RETURN;
IF ~NewIcon[labelledFinchIcon, handle.pauseTimeMiddle] THEN RETURN;
ENDLOOP;
IF ~NewIcon[labelledFinchIcon, handle.pauseTimeOff] THEN RETURN;
};
ENDLOOP;
};
Viewer Construction and Management
Finch Viewer
lineHeight: INT ← 12;
tsHeight: INT ← 3*lineHeight;
convHeight: INT ← 5*lineHeight;
MakeFinchTool:
PROC =
BEGIN
finchWidth: INTEGER;
dif, wH: INTEGER;
bt: Viewer;
h: FinchTool.Handle;
v: Viewer;
IF finchToolHandle#
NIL
THEN {
ViewerOps.PaintViewer[finchToolHandle.outer, all]; RETURN; };
finchToolHandle ← NEW[FinchTool.FinchToolRec];
h ← finchToolHandle;
h.finchToolHeight ← defaultToolHeight;
MakeMenus[];
finchToolHandle.outer ← Containers.Create[[
-- outer container
name: "Finch", -- name displayed in the caption
icon: finchIcon,
iconic: FALSE, -- so tool will not be iconic (small) when first created
column: left, -- initially in the left column
menu: finchMenu,-- displaying our menu command
openHeight: h.finchToolHeight, -- See Walnut
scrollable: FALSE ]]; -- inhibit user from scrolling contents
v ← h.outer;
finchWidth ← IF v.column=right THEN ViewerSpecs.openRightWidth ELSE ViewerSpecs.openLeftWidth;
[]←ViewerEvents.RegisterEventProc[
proc: UnfinchOnDestroy, event: destroy, filter: v, before: TRUE];
bt ← FirstButton[q: finchQueue, name: "Called Party:", proc: CalledPartyProc, parent: v];
h.calledPartyText ← NextRightTextViewer[sib: bt, w: finchWidth/2-bt.ww];
bt ← AnotherButton[q: finchQueue, name: "Calling Party:", proc: CallingPartyProc, sib: h.calledPartyText, newLine: FALSE];
h.callingPartyText ← NextRightTextViewer[sib: bt, w: finchWidth];
Containers.ChildXBound[h.outer, h.callingPartyText];
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)) < tsHeight+convHeight
THEN {
SetOpenHeight[v, wH + (tsHeight+convHeight-dif)];
IF ~v.iconic THEN ViewerOps.ComputeColumn[v.column];
};
h.typescript ← MakeTypescript[sib: bt];
[h.tsIn, h.tsOut] ← ViewerIO.CreateViewerStreams[NIL, h.typescript];
bt ← MakeRuler[sib: h.typescript, h: 2];
h.conversations ← ViewerOps.CreateViewer[
flavor: $Container, paint: TRUE,
info: [wy: bt.wy + 2, ww: v.ww,
wh: convHeight, parent: v, border: FALSE] ];
Containers.ChildXBound[h.outer, h.conversations];
Containers.ChildYBound[h.outer, h.conversations];
ViewerOps.PaintViewer[v, all]; -- reflect above change
IF Rope.Equal[s1: "TRUE", case:
FALSE,
s2: UserProfile.Token[key: "Finch.InitialDirectoriesLeft", default: "FALSE"]]
OR Rope.Equal[s1: "TRUE", case:
FALSE,
s2: UserProfile.Token[key: "Finch.InitialDirectoriesRight", default: "FALSE"]]
THEN
FinchTool.BuildDirectories[];
END;
MakeMenus:
PROC = {
Finch Tool Viewer
MakeOneMenu:
PROC[menu: Menus.Menu, name:
ROPE, proc: Menus.MenuProc] = {
Menus.AppendMenuEntry[
menu: menu,
entry: MBQueue.CreateMenuEntry[finchQueue, name, proc, finchToolHandle]
];
};
finchMenu ← Menus.CreateMenu[];
MakeOneMenu[finchMenu, "Phone", PhoneProc];
MakeOneMenu[finchMenu, "Answer", Answer];
MakeOneMenu[finchMenu, "Disconnect", Hangup];
MakeOneMenu[finchMenu, "SpeakText", SpeakSelectedProc];
MakeOneMenu[finchMenu, "StopSpeech", StopSpeechProc];
MakeOneMenu[finchMenu, "Directory", MakeDirectory];
MakeOneMenu[finchMenu, "Drop Out", ButtonStopFinch];
Conversation Viewer
finchConvMenu ← Menus.CreateMenu[];
MakeOneMenu[finchConvMenu, "Answer", Answer];
MakeOneMenu[finchConvMenu, "Disconnect", Hangup];
};
MakeFinchToolCmd: Commander.CommandProc = { MakeFinchTool[]; };
Conversations Viewer
AddConvDesc:
PROC[cViewer: Viewer, cDesc: ConvDesc]
RETURNS [newV: Viewer←
NIL] = {
lastButton: Viewer =
NARROW[ViewerOps.FetchProp[finchToolHandle.conversations, $lastButton]];
BuildLine:
PROC = {
IF lastButton =
NIL
THEN
newV ← FirstButton[
q: finchQueue, name: NIL, proc: ConversationMgmtProc, width: 1024, parent: cViewer]
ELSE newV ← AnotherButton[
q: finchQueue, name: NIL, proc: ConversationMgmtProc, width: 1024, sib: lastButton, newLine: TRUE];
};
IF lastButton#
NIL
THEN {
lastDesc: ConvDesc = NARROW[ViewerOps.FetchProp[lastButton, $convDesc]];
ViewersOps.AddProp[newV, $convDesc, NIL];
Above could be used to forget about any conversation but the latest one. Once we're dealing with multiple converations, that won't be good enough. Perhaps a better place to do that is where we detect them going idle.
IF lastDesc#NIL AND lastDesc.ultimateState<=parsing THEN newV ← lastButton;
};
IF newV=
NIL
THEN
IF cViewer.parent.iconic THEN BuildLine[]
ELSE ViewerLocks.CallUnderWriteLock[BuildLine, cViewer];
cDesc.clientData ← newV;
ViewerOps.AddProp[newV, $convDesc, cDesc];
ViewerOps.AddProp[cViewer, $lastButton, newV];
};
SelectEntryInConversations:
PROC[entryButton: Viewer, reselect:
BOOL←
TRUE]
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
Future: can use $BlackOnGrey for held conversations, $WhiteOnBlack for current (or vice versa -- $WhiteOnBlack is more legible, so may be better for held convs).
ViewerOps.PaintViewer[entryButton, all];
ViewerOps.AddProp[entryButton.parent, $selectedEntry, entryButton];
};
RETURN[FALSE];
};
Directory
MakeDirectory: Menus.MenuProc = {
FinchTool.BuildDirectories[];
};
Viewer and Button Utilities
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←NIL, parent: Viewer←NIL, fork: BOOL← TRUE, guarded: BOOL← FALSE, newLine: BOOL← FALSE]
RETURNS[Viewer] = {
info: ViewerClasses.ViewerRec;
wy: INTEGER ← 1;
sibWh: INTEGER ← 0;
IF parent=NIL THEN IF sib#NIL THEN parent ← sib.parent ELSE ERROR;
IF sib#NIL THEN { wy ← sib.wy; sibWh ← sib.wh; };
info ← [name: name, wy: wy, wh: entryHeight, parent: parent, border: border];
IF (~newLine) AND sib=NIL THEN ERROR;
IF newLine
THEN {
-- first button on new line
info.wy← info.wy + sibWh + (IF border THEN 1 ELSE 0); -- extra bit
info.wx← IF 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: BOOL← FALSE, newLine: BOOL← FALSE]
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 ANY← NIL, border: BOOL← FALSE, width: INTEGER← 0,
guarded: BOOL← FALSE, font: VFonts.Font ← VFonts.defaultFont, newLine: BOOL← FALSE]
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 ANY← NIL, border: BOOL← FALSE, width: INTEGER← 0,
guarded: BOOL← FALSE, 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: tsHeight, border: FALSE] ];
Containers.ChildYBound[sib.parent, ts];
Containers.ChildXBound[sib.parent, ts];
};
Reporting and Logging
Report:
PUBLIC
PROC[msg1, msg2, msg3, msg4:
ROPE←
NIL] = {
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:
ROPE←
NIL] = {
what: ROPE = Rope.Cat[msg1, msg2, msg3, msg4];
ReportRope[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];
};
ViewCmd: Commander.CommandProc =
TRUSTED {
Nice.View[finchToolHandle, "FinchTool PD"];
};
Registration, Initialization
finchIcon ← Icons.NewIconFromFile["Finch.Icons", 0!ANY=>CONTINUE];
leftFinchIcon ← Icons.NewIconFromFile["Finch.Icons", 4!ANY=>CONTINUE];
rightFinchIcon ← Icons.NewIconFromFile["Finch.Icons", 3!ANY=>CONTINUE];
labelledFinchIcon ← Icons.NewIconFromFile["Finch.Icons", 11!ANY=>CONTINUE];
labelledLeftFinchIcon ← Icons.NewIconFromFile["Finch.Icons", 12!ANY=>CONTINUE];
labelledRightFinchIcon ← Icons.NewIconFromFile["Finch.Icons", 10!ANY=>CONTINUE];
conversationIcon ← Icons.NewIconFromFile["Finch.Icons", 5!ANY=>CONTINUE];
labelledConversationIcon ← Icons.NewIconFromFile["Finch.Icons", 14!ANY=>CONTINUE];
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", PhoneCmd, "Place telephone call to specified party"];
Commander.Register["ET", PhoneHomeCmd, "Phonnne hommmmmmme"];
Commander.Register["Redial", RedialCmd, "Hang up and try current call again"];
Commander.Register["HangUp", HangUpCmd, "Hang up current conversation"];
Commander.Register["SpeakText", SpeakTextCmd, "Utter the remainder of the command"];
Commander.Register["STOP!", StopSpeakingCmd, "Stop speaking"];
Commander.Register["Feep", FeepCmd, "Issue touch-tones"];
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: UnFinchOnCheckpointOrBoot, r: ReFinchOnRollback, b: UnFinchOnCheckpointOrBoot]; };
Debugging nonsense
Commander.Register["VuFinchTool", ViewCmd,
"Program Management variables for FinchTool"];
}.
Swinehart, August 7, 1985 8:56:34 am PDT
Merge PTZ TextSpeech stuff
changes to: DIRECTORY, MakeMenus, SpeakSelectedProc
Polle Zellweger (PTZ) August 19, 1985 5:00:46 pm PDT
Handle Prose flushing.
changes to: MakeMenus, ReportConversationState, StopSpeechProc
Polle Zellweger (PTZ) August 19, 1985 5:54:32 pm PDT
changes to: DIRECTORY, FinchToolImpl, ReportConversationState, StopSpeechProc
Polle Zellweger (PTZ) September 3, 1985 5:57:21 pm PDT
Curtail intermediate Prose reports if ~finchToolHandle.debug; allow user to specify SpeakText queueIt via red|blue button; some cosmetic feedback changes.
changes to: ReportConversationState, SpeakSelectedProc, speakTextFeedbackLen, SpeakTextWithFeedback, StopSpeechProc
Swinehart, September 6, 1985 10:03:45 am PDT
Don't OPEN FinchTool.bcd, eliminate (status line, extra Phone button).
Eliminate dual-menu, demote Drop-Out. Merge Phone, Redial.
PhoneSelection becomes CommandTool feature, with user profile help.
Add names->feepNum features (can dial 9(800)TheCard)
Add feep command (for talking to your bank or whatever).
Add HangUp, SpeakText, StopSpeaking commands.
Pick up incoming call idents, outgoing calls originated at telset, in called-party fields.
Automatically connect to Server when command is issued; don't delete conversation viewer when disconnecting any more. Reorganize order of procedures.
Polle Zellweger (PTZ) October 4, 1985 6:58:47 pm PDT
Add labelled icons, used to display caller when Finch tool etc is closed.
changes to: labelledFinchIcon, labelledLeftFinchIcon, labelledRightFinchIcon, labelledConversationIcon, TwiddleFinchIcon, (module initialization)
Polle Zellweger (PTZ) October 16, 1985 4:38:58 pm PDT
Change Finch telephone icon to labelled conversation icon while conversation in progress. Added monitor to protect icon twiddling and restoring.
changes to: FinchToolImpl, ConversationHandle, ReportConversationState, PaintFinchIcon, TwiddleFinchIcon, NewIcon, MsPause
Polle Zellweger (PTZ) October 18, 1985 4:08:31 pm PDT
Add ability to recognize node boundaries in selection for text-to-speech.
changes to: SpeakTextWithFeedback, RopeWithNodesFromSelection (new)
Polle Zellweger (PTZ) December 10, 1985 4:13:33 pm PST
changes to: ReportConversationState fix callingParty, calledParty fields, AddConvDesc test ultimateState<=parsing rather than pending (for incoming calls while call in progress)
Polle Zellweger (PTZ) December 13, 1985 2:55:28 pm PST
Fix selection in conv log and icon mgmt for incoming calls while call in progress.
changes to: DIRECTORY, ReportConversationState