DIRECTORY
BasicTime USING [ GMT, Now, Period ],
Booting USING [ CheckpointProc, RollbackProc, RegisterProcs ],
Buttons USING [ButtonProc, Create ],
Commander USING [CommandProc, Handle, Register],
CommandTool USING [NextArgument],
Containers USING [ ChildXBound, ChildYBound, Container, Create ],
FinchSmarts USING [
AnswerCall, ConvDesc, DisconnectCall, Feep, FinchIsRunning, IdentifyVisitor, Poke, ReleaseVisitor, InitFinchSmarts, UninitFinchSmarts, unknown, us ],
FinchSynthesizer USING [ InitializeFinchSynthesizer, StopSpeech, TextToSpeech ],
FinchTool,
Icons USING [ IconFlavor, NewIconFromFile ],
IO,
Labels USING [Create],
MBQueue USING [ Create, CreateButton, CreateMenuEntry, Queue, QueueClientAction ],
Menus USING [AppendMenuEntry, CreateMenu, Menu, MenuProc ],
Nice USING [ View ],
Process USING [ Detach, Pause, MsecToTicks, SetTimeout],
Rope USING [ ActionType, Cat, Concat, Equal, Fetch, Find, Flatten, Length, Map, MakeRope, MaxLen, Replace, ROPE, Substr ],
Rules USING [ Create ],
Synthesizer USING [ SynthSpecBody, SynthSpec ],
TextEdit USING [ ReplaceByRope ], 
TextLooks USING [ RopeToLooks ], 
TextNode USING [ Ref, Root ], 
Thrush USING [ ActionReport, ConversationID, NB, notReallyInConv, nullConvID, Reason, StateInConv ],
TiogaButtons USING [ ChangeButtonLooks, CreateButton, CreateViewer, FindTiogaButton, SetStyleFromRope, TextNodeRef, TiogaButton, TiogaButtonProc, TiogaOpsRef],
TiogaOps USING [ CallWithLocks, 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, DestroyViewer, FetchProp, PaintViewer, SetOpenHeight],
ViewerSpecs USING [ openTopY, openLeftWidth, openRightWidth ],
ViewerTools USING [GetSelectionContents, GetContents, MakeNewTextViewer, SetContents, SetSelection ],
VoiceUtils USING [ CurrentRName, Report, ReportFR, RegisterWhereToReport, WhereProc ]
;

FinchToolImpl: CEDAR MONITOR					       
IMPORTS BasicTime, Booting, Buttons, Commander, CommandTool, Containers, FinchSmarts, FinchSynthesizer, FinchTool, Icons, IO, Labels, MBQueue, Menus, Nice, Rope, Rules, Process, TextEdit, TextLooks, TextNode, TiogaButtons, TiogaOps, TypeScript, UserProfile, VFonts, ViewerEvents, ViewerIO, ViewerLocks, ViewerOps, ViewerSpecs, ViewerTools, VoiceUtils
EXPORTS FinchTool = {
OPEN IO;

ConvDesc: TYPE = FinchSmarts.ConvDesc;
NB: TYPE = Thrush.NB;
Reason: TYPE = Thrush.Reason;
ROPE: TYPE = Rope.ROPE;
Viewer: TYPE = ViewerClasses.Viewer;

finchToolHandle: PUBLIC FinchTool.Handle;
cmdHandle: Commander.Handle_NIL;
serverInstance: Rope.ROPE  _ NIL;
us: INT = FinchSmarts.us;
unknown: INT = FinchSmarts.unknown;

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;

xFudge: INTEGER = 2;
entryHeight: INTEGER = 14;
defaultToolHeight: NAT _ ViewerSpecs.openTopY/5;

PhoneProc: Buttons.ButtonProc = { -- Place call to party identified by primary selection
calledPartyText: Rope.ROPE _ ViewerTools.GetSelectionContents[];
DoPhone[calledPartyText,
SELECT mouseButton FROM
red, yellow => FALSE, blue=> TRUE, ENDCASE=>ERROR];
};

CalledPartyProc: Buttons.ButtonProc = { -- Place call to Called Party field
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 = { -- Place call to Calling Party field
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 = { -- Commander Phone command
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 = { -- Phone home (ET) command
cmd.commandLine _ " home\n";
[result, msg] _ PhoneCmd[cmd];
};

RedialCmd: Commander.CommandProc = { -- Redial command
ENABLE UNWIND => cmdHandle _ NIL;
cmdHandle _ cmd;
IF CheckActive[finchToolHandle] THEN
DoPhone[ViewerTools.GetContents[finchToolHandle.calledPartyText], FALSE];
cmdHandle _ NIL;
};

RedialOfficeCProc: PROC [data: REF] = { -- Redial from TiogaButton in conversation log
RedialIt[cDesc: NARROW[data]]; };
RedialHomeCProc: PROC [data: REF] = { -- Redial from TiogaButton in conversation log
RedialIt[cDesc: NARROW[data], wantResidence: TRUE]; };
RedialIt: PROC [cDesc: ConvDesc, wantResidence: BOOL_FALSE] = {
which: INT _ SELECT cDesc.whoOriginated FROM
us =>  us+1, -- redial called party (choose randomly if more than 1 callee)
> us => cDesc.whoOriginated,  -- redial calling party
ENDCASE => unknown;
IF which#unknown THEN DoPhone[SimplifyName[cDesc, which], wantResidence];
};
DoPhone: PROC[calledPartyText: Rope.ROPE, wantResidence: BOOL_FALSE] = {
callee, newCalledPartyText: Rope.ROPE;
pauseNeededInMs: CARDINAL;
IF ~CheckActive[finchToolHandle] OR calledPartyText = NIL OR calledPartyText.Length[]=0
THEN RETURN;
[callee, newCalledPartyText, wantResidence] _ ParseCallee[calledPartyText, wantResidence];
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: wantResidence];
};

Answer: Menus.MenuProc = TRUSTED {  -- Answer from Answer button in Finch Tool
cDesc: ConvDesc = GetSelectedDesc[];
AnswerIt[cDesc: cDesc];
};

AnswerCProc: PROC [data: REF] = { -- Answer from TiogaButton in conversation log
AnswerIt[cDesc: NARROW[data]]; };

AnswerIt: PROC [cDesc: ConvDesc_NIL] = TRUSTED {
IF cDesc = NIL OR
(SELECT cDesc.situation.self.state FROM $notified, $ringing =>FALSE, ENDCASE=>TRUE)
THEN 
{ Report[" No conversation to join"]; RETURN; };
FinchSmarts.AnswerCall[convID: cDesc.situation.self.convID];
};

HangUpCmd: Commander.CommandProc = { -- HangUp command
ENABLE UNWIND => cmdHandle _ NIL;
cmdHandle _ cmd;
[] _ HangItUp[TRUE];
cmdHandle _ NIL;
};

Hangup: PUBLIC Menus.MenuProc = { -- Hang up from Disconnect button in Finch Tool
[]_HangItUp[TRUE]; };

HangupQuietly: PUBLIC Menus.MenuProc = { []_HangItUp[FALSE]; };
HangupCProc: PROC [data: REF] = { -- Hang up from TiogaButton in conversation log
[]_HangItUp[complain: TRUE, cDesc: NARROW[data]]; };
HangItUp: PROC[complain: BOOL, moreToCome: BOOL_FALSE, cDesc: ConvDesc_NIL] RETURNS [pauseNeededInMs: CARDINAL_0] = TRUSTED {
state: Thrush.StateInConv;
isTrunk: BOOL;
IF cDesc=NIL THEN cDesc _ GetSelectedDesc[];
state _ IF cDesc=NIL THEN idle ELSE cDesc.situation.self.state;
isTrunk _ IF cDesc#NIL THEN cDesc.partyInfo.conversationInfo.bilateralConv ELSE FALSE;
SELECT state FROM
idle => {
IF complain THEN Report[" No conversation to leave"]; RETURN };
reserved, parsing => IF moreToCome THEN RETURN;
ENDCASE;
IF isTrunk THEN pauseNeededInMs _ IF cDesc.situation.self.state=active THEN 2000 ELSE 500;
FinchSmarts.DisconnectCall[convID: cDesc.situation.self.convID];
};

ConversationMgmtProc: TiogaButtons.TiogaButtonProc = {
IF control THEN
MBQueue.QueueClientAction[q: finchQueue, proc: HangupCProc, data: clientData]
ELSE IF shift THEN
MBQueue.QueueClientAction[q: finchQueue, proc: AnswerCProc, data: clientData]
ELSE IF mouseButton=yellow THEN
MBQueue.QueueClientAction[q: finchQueue, proc: RedialOfficeCProc, data: clientData]
ELSE IF mouseButton=blue THEN
MBQueue.QueueClientAction[q: finchQueue, proc: RedialHomeCProc, data: clientData]
};

ParseCallee: PROC[fullCallee: ROPE, wantResidence: BOOL] RETURNS [callee: ROPE, fCallee: ROPE, residence: BOOL] = {
endCallee: INT;
[callee, residence] _
CheckForMouseButtonSpec[fullCallee, "left ", wantResidence, wantResidence];
[callee, residence] _ CheckForMouseButtonSpec[callee, "middle ", residence, residence];
[callee, residence] _ CheckForMouseButtonSpec[callee, "right ", residence, TRUE];
IF callee.Equal["home", FALSE] THEN {
callee _ VoiceUtils.CurrentRName[];
residence _ TRUE;
};
IF (endCallee_callee.Find[" at home", 0, FALSE]) >= 0 THEN {
callee _ callee.Substr[len: endCallee];
residence _ TRUE;
};
fCallee _ callee;
IF wantResidence THEN fCallee _ fCallee.Concat[" at home"];
};

CheckForMouseButtonSpec: PROC[
fullCallee, pattern: ROPE, wantedRes: BOOL, ifMatch: BOOL]
RETURNS [fCallee: ROPE, wantResidence: BOOL] = {
pLen: INT _ pattern.Length[];
fCallee _ fullCallee;
wantResidence _ wantedRes;
IF fullCallee.Length[] >= pLen AND pattern.Equal[fullCallee.Substr[len: pLen]] THEN {
fCallee _ fullCallee.Substr[start: pLen];
wantResidence _ ifMatch;
};
};
GetSelectedDesc: PUBLIC PROC[chosenButton: TiogaButtons.TiogaButton_NIL, checkActive: BOOL_TRUE] RETURNS [cDesc: ConvDesc_NIL] = {
viewer: Viewer = IF finchToolHandle#NIL THEN finchToolHandle.conversations ELSE NIL;
selected: TiogaButtons.TiogaButton _ IF viewer=NIL THEN NIL
ELSE NARROW[ViewerOps.FetchProp[viewer, $selectedEntry]];
IF chosenButton#NIL AND selected#chosenButton THEN RETURN;
IF selected = NIL OR (checkActive AND ~CheckActive[finchToolHandle, TRUE]) THEN RETURN;
cDesc _ NARROW[selected.clientData];
};
speakTextFeedbackLen: INT _ 40;
dectalkParaSep: Rope.ROPE _ " \033P;z.+\033\\";

SpeakSelectedProc: Buttons.ButtonProc = { 
queueIt: BOOL_TRUE;
IF ~CheckActive[finchToolHandle] THEN RETURN;
SELECT mouseButton FROM
red => NULL;
yellow =>  RETURN;
blue => queueIt _ FALSE;
ENDCASE => NULL;
SpeakTextNodesFromSelection[queueIt];
};

SpeakTextCmd: Commander.CommandProc = {
ENABLE UNWIND => cmdHandle _ NIL;
textToSpeak: Rope.ROPE _ cmd.commandLine.Substr[start: 1, len: cmd.commandLine.Length[]-2];
cmdHandle _ cmd;
IF CheckActive[finchToolHandle] AND textToSpeak.Length[]#0 THEN
[]_SpeakTextWithFeedback[TRUE, textToSpeak];
cmdHandle _ NIL;
};

SpeakTextWithFeedback: PROC [
queueIt: BOOL, text: ROPE_NIL, convID: Thrush.ConversationID_Thrush.nullConvID]
RETURNS [newConvID: Thrush.ConversationID] ~ {
nb: NB;
Status[
IF ~queueIt THEN "Flushing text. " ELSE NIL,
"Speaking text: \"", 
text.Substr[len: speakTextFeedbackLen],
IF text.Length[] > speakTextFeedbackLen THEN "...\"" ELSE "\""
];
[nb, newConvID] _ FinchSynthesizer.TextToSpeech[convID: convID,
 synthSpec: NEW[Synthesizer.SynthSpecBody_[textToSpeak: text]], queueIt: queueIt];
IF nb#$success THEN {
newConvID _ Thrush.nullConvID;
Report["Sorry, trouble with text to speech service (%g)", [atom[nb]]];
};
};

SpeakTextNodesFromSelection:PROC[queueIt: BOOL, nodeEnd: Rope.ROPE_dectalkParaSep] ~ {
start, end: TiogaOps.Location;
[start: start, end: end] _ TiogaOps.GetSelection[];
FOR node: TiogaOps.Ref _ start.node, TiogaOps.StepForward[node] DO
convID: Thrush.ConversationID _ Thrush.nullConvID;
tiogaopstext: Rope.ROPE _ TiogaOps.GetRope[node];
tiogaopstext _ SELECT TRUE FROM
node = start.node => Rope.Substr[
base: tiogaopstext,
start: start.where,
len: IF node=end.node THEN end.where-start.where+1 ELSE Rope.MaxLen
],
node = end.node => Rope.Substr[base: tiogaopstext, len: end.where+1],
ENDCASE => tiogaopstext;
IF node#end.node THEN tiogaopstext _ Rope.Cat[tiogaopstext, nodeEnd];
convID _ SpeakTextWithFeedback[queueIt, tiogaopstext, convID];
queueIt _ TRUE;
IF node=end.node THEN EXIT;
ENDLOOP;
};


StopSpeechProc: Buttons.ButtonProc = { StopSpeaking[]; };
StopSpeakingCmd: Commander.CommandProc = {
ENABLE UNWIND => cmdHandle _ NIL;
cmdHandle _ cmd;
StopSpeaking[];
cmdHandle _ NIL;
};

StopSpeaking: PROC = {
nb: NB;
IF ~CheckActive[finchToolHandle, TRUE] THEN RETURN;
nb_FinchSynthesizer.StopSpeech[];
IF nb#$success THEN
Report["Sorry, trouble with text to speech service; could not Stop. (%g)", [atom[nb]]];
};
FeepCmd: Commander.CommandProc = {
ENABLE UNWIND => cmdHandle _ NIL;
textToFeep: Rope.ROPE _ FeepValue[cmd.commandLine.Substr[start: 1, len: cmd.commandLine.Length[]-2]];
cDesc: ConvDesc;
cmdHandle _ cmd;
cDesc _ GetSelectedDesc[];
IF cDesc#NIL THEN FinchSmarts.Feep[cDesc.situation.self.convID, textToFeep]
ELSE Report["Sorry, was not able to `feep'."];
cmdHandle _ NIL;
};

FeepValue: PUBLIC PROC[text: ROPE] RETURNS [feepText: ROPE] = {
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 ];

VisitCmd: Commander.CommandProc = {
ENABLE UNWIND => cmdHandle _ NIL;
nb: NB;
visitor: Rope.ROPE _ cmd.commandLine.Substr[start: 1, len: cmd.commandLine.Length[]-2];
IF Rope.Length[visitor] = 0 THEN { -- collect the name now instead
IO.Put[cmd.out, IO.rope["Visitor name: "]];
visitor _ IO.GetLineRope[cmd.in];
};
cmdHandle _ cmd;
IF CheckActive[finchToolHandle] THEN
nb _ FinchSmarts.IdentifyVisitor[visitor: visitor, password: "", complain: FALSE];
IF nb=$passwordNotValid THEN {  -- password required, collect it
password: Rope.ROPE _ "";
IO.PutF[cmd.out, "password: %l", IO.rope["h"]];
password _ IO.GetLineRope[cmd.in];
IO.PutF[cmd.out, "%l", IO.rope["H"]];
[] _ FinchSmarts.IdentifyVisitor[visitor, password];
};
cmdHandle _ NIL;
};

UnvisitCmd: Commander.CommandProc = {
ENABLE UNWIND => cmdHandle _ NIL;
visitor: Rope.ROPE _ cmd.commandLine.Substr[start: 1, len: cmd.commandLine.Length[]-2];
password: Rope.ROPE _ "";
cmdHandle _ cmd;
IF CheckActive[finchToolHandle] THEN
FinchSmarts.ReleaseVisitor[visitor, password];
cmdHandle _ NIL;
};

TDirCmd: Commander.CommandProc = {
ENABLE UNWIND => cmdHandle _ NIL;
cmdHandle _ cmd;
FinchTool.BuildDirectory[directoryFile: CommandTool.NextArgument[cmd], newOK: TRUE];
cmdHandle _ NIL;
};



StartFinch: PUBLIC PROC[] = TRUSTED {
handle: FinchTool.Handle _ finchToolHandle;
enabled, connected: BOOL;
IF handle=NIL THEN {
MakeFinchTool[];
handle _ finchToolHandle;
IF handle=NIL THEN { Report["Can't create Finch viewer"]; RETURN; };
};
[enabled, connected] _ FinchSmarts.FinchIsRunning[];
SELECT TRUE FROM
connected => Report["Already running."];
enabled => { Report["Connecting . . ."]; FinchSmarts.Poke[]; };
ENDCASE => {
Report["Connecting . . ."];
FinchSmarts.InitFinchSmarts[serverInstance, ReportSystemState, ReportConversationState, ReportRequestState, FALSE];
FinchSynthesizer.InitializeFinchSynthesizer[];
finchToolHandle.finchEnabledAtCheckpoint _ FALSE;
};
};

FinchCmd: Commander.CommandProc = { 
ENABLE UNWIND => cmdHandle _ NIL;
cmdHandle _ cmd;
serverInstance _ CommandTool.NextArgument[cmd];
StartFinch[];
cmdHandle _ NIL;
};

ReFinchOnRollback: Booting.RollbackProc = {
IF finchToolHandle=NIL OR ~finchToolHandle.finchEnabledAtCheckpoint THEN RETURN;
finchToolHandle.finchEnabledAtCheckpoint _ FALSE;
Try[StartFinch, 0];
};

StopFinch: PUBLIC PROC[] = TRUSTED {
handle: FinchTool.Handle = finchToolHandle;
IF handle=NIL THEN RETURN;
Report["Disconnecting . . ."];
FinchSmarts.UninitFinchSmarts[];
};

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 = {
enabled: BOOL;
IF finchToolHandle=NIL THEN RETURN;
enabled _ FinchSmarts.FinchIsRunning[].finchIsEnabled;
finchToolHandle.finchEnabledAtCheckpoint _
finchToolHandle.finchEnabledAtCheckpoint OR enabled;
IF enabled THEN Try[StopFinch];
};

CheckActive: PUBLIC PROC[handle: FinchTool.Handle, mustAlreadyBeActive: BOOL_FALSE, complain: BOOL_TRUE] RETURNS[active: BOOL_FALSE] =  {
enabled, connected: BOOL;
IF handle=NIL THEN RETURN; -- This is essentially a bug.
IF ~FinchSmarts.FinchIsRunning[].finchIsEnabled AND ~mustAlreadyBeActive
THEN StartFinch[];
[enabled, connected] _ FinchSmarts.FinchIsRunning[];
active _ IF mustAlreadyBeActive THEN connected ELSE enabled;
IF complain AND ~active AND mustAlreadyBeActive THEN Report["Sorry, Finch has lost contact with the telephone server"];
};

UnfinchCmd: Commander.CommandProc = {
ENABLE UNWIND => cmdHandle _ NIL;
cmdHandle _ cmd;
StopFinch[];
cmdHandle _ NIL;
};

ReportSystemState: PROC[enabled, connected: BOOL] = {
cDesc: ConvDesc _ GetSelectedDesc[checkActive: FALSE];
IF finchToolHandle=NIL OR (connected=finchToolHandle.finchConnectedAtLastReport
AND enabled=finchToolHandle.finchEnabledAtLastReport) THEN RETURN;
finchToolHandle.finchConnectedAtLastReport_connected;
finchToolHandle.finchEnabledAtLastReport_enabled;
VoiceUtils.Report[where: $FinchOnly, remark: IF ~enabled THEN "Finch is not active." ELSE IO.PutFR["Finch is active%g connected to the telephone server.", rope[IF connected THEN " and" ELSE ", but is not"]]];
IF ~connected AND cDesc # NIL THEN {
cDesc.situation.self.state _ $idle;
ReportConversationState[$success, cDesc, "Server connection broken; state of call unknown"];
};
};

didOurBest: BOOL_FALSE;
defaultTime: CARDINAL _ 2500;

Try: PROC[p: PROC, msecsToWait: CARDINAL _ defaultTime] = TRUSTED {
didOurBest _ FALSE;
Process.Detach[FORK Try1[p]];
IF msecsToWait#0 THEN FOR i: NAT IN [0..100) DO
Process.Pause[Process.MsecToTicks[msecsToWait/100]];
IF didOurBest THEN EXIT;
ENDLOOP;
};

Try1: PROC[p: PROC] = {
p[];
didOurBest _ TRUE;
};

ReportConversationState: PROC[ nb: NB, cDesc: ConvDesc, remark: Rope.ROPE ] = {
s: IO.STREAM;
p: PROCESS;
difficulty: BOOL_FALSE;
button: TiogaButtons.TiogaButton;
state: Thrush.StateInConv;
SELECT nb FROM $success => NULL; ENDCASE => { Status[remark]; RETURN; };
IF cDesc = NIL THEN {Status["No state to report"]; RETURN;}; --Not much more can be done.
IF finchToolHandle=NIL THEN RETURN;
s _ IO.ROS[];
state_cDesc.situation.self.state;
IF ~cDesc.originatorRecorded AND cDesc.numParties>1 THEN
SELECT cDesc.whoOriginated FROM
us => SetContents[finchToolHandle.calledPartyText, cDesc, us+1];
> us => SetContents[finchToolHandle.callingPartyText, cDesc, cDesc.whoOriginated];
-- unknown => -- ENDCASE;
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, state=idle];
IF (state#reserved AND state#parsing) THEN {
s.PutRope["Call "];
IF cDesc.numParties>1 THEN {
IF cDesc.whoOriginated = us THEN
s.PutF[" to %s", rope[RepairIntelnet[OtherParty[cDesc, TRUE]]]]
ELSE IF cDesc.whoOriginated = unknown THEN 
s.PutF[" to/from %s", rope[RepairIntelnet[OtherParty[cDesc, TRUE]]]]
ELSE IF NOT Rope.Equal[cDesc.partyInfo[us].intendedName, cDesc.partyInfo[us].name] THEN 
s.PutF[" to %s from %s", rope[RepairIntelnet[OtherParty[cDesc, TRUE, us]]], rope[RepairIntelnet[OtherParty[cDesc, TRUE, cDesc.whoOriginated]]]]
ELSE s.PutF[" from %s", rope[RepairIntelnet[OtherParty[cDesc, TRUE, cDesc.whoOriginated]]]];
IF cDesc.numParties > 2 THEN s.Put[rope[" (conference)"]];
};
s.PutF[" at %t", time[cDesc.startTime] ];
IF ~finchToolHandle.keepTwiddling THEN
PaintFinchIcon[finchToolHandle, cDesc, state=idle];
};
IF state <= Thrush.notReallyInConv THEN {
s.PutF["%s%s, duration = %r",
rope[IF state#$idle THEN " was not successful"
ELSE IF cDesc.ultimateState>ringing THEN printLabel[$idle] ELSE " was abandoned"],
rope[IF state#$failed THEN ""
ELSE SELECT cDesc.situation.reason FROM
$busy => " -- busy",
$notFound => " -- no valid party found",
$error => " -- connection failed",
$noCircuits => " -- no circuits",
ENDCASE => " for some reason"
],
int[BasicTime.Period[cDesc.startTime, BasicTime.Now[]]]
];
}
ELSE s.PutRope[printLabel[state]];
IF cDesc.situation.comment#NIL THEN s.PutF[" (%s)", rope[cDesc.situation.comment]];
IF remark#NIL THEN s.PutF[" [%s]", rope[remark]];
button _ NARROW[cDesc.clientData];
IF button = NIL THEN
button _ AddConvDesc[ finchToolHandle.conversations, cDesc, s.RopeFromROS[], basicConvFormat ]
ELSE IF ~cDesc.reportComplete THEN
RelabelTiogaButton[ button: button, newRope: s.RopeFromROS[] ];
IF state <= Thrush.notReallyInConv THEN cDesc.reportComplete_TRUE;
SelectEntryInConversations[button, state >= $failed AND state#$notified];
};

ReportRequestState: PROC[cDesc: ConvDesc, actionReport: Thrush.ActionReport, actionRequest: REF] RETURNS[betterActionRequest: REF] = {
betterActionRequest _ actionRequest;
SELECT actionReport.actionClass FROM
$recording =>
Status[SELECT actionReport.actionType FROM
$started => "Please begin Speaking",
$finished => "Thank you",
$flushed => "Voice transmission stopped",
ENDCASE => NIL
];
$playback =>
IF actionReport.actionType = $flushed THEN Status["Voice transmission stopped"];
$synthesizer =>
IF actionReport.actionType = $finished THEN {
spoken: Rope.ROPE =
Rope.Substr[base: NARROW[actionRequest, Synthesizer.SynthSpec].textToSpeak, len: speakTextFeedbackLen];
Status["Finished speaking \"", spoken, "...\""];
};
ENDCASE;
};
OtherParty: PROC[cDesc: ConvDesc, indicateUnauthenticated: BOOL_FALSE, which: INT_1] RETURNS [otherParty: ROPE_NIL] = {
IF cDesc.numParties<2 OR cDesc.partyInfo=NIL OR cDesc.partyInfo.numParties<2
THEN RETURN;
otherParty _ cDesc.partyInfo[which].intendedName;
IF indicateUnauthenticated AND cDesc.partyInfo[which].type=$telephone THEN otherParty _ otherParty.Concat["?"];
};
SetContents: PROC[v: ViewerClasses.Viewer, cDesc: ConvDesc, which: INT] = {
ViewerTools.SetContents[v, SimplifyName[cDesc, which], TRUE];
cDesc.originatorRecorded _ TRUE;
};

SimplifyName: PROC [cDesc: ConvDesc, which: INT] RETURNS [name: ROPE] = {
i: INT;
name _ RepairIntelnet[OtherParty[cDesc, FALSE, which]];
i _ name.Find["(", 0, FALSE];
IF name=NIL OR name.Length[]=0 THEN RETURN;
IF i>0 THEN name _ name.Substr[len: i-1];
};

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.Replace[number, i, 2, "*"]; -- replace first pause with "*"
i_-1;
IF number.Map[0, number.Length[], M] THEN
number _ Rope.Replace[number, i, 5, ""]; -- replace second pause and authentication with "*"
};
};

PaintFinchIcon: ENTRY PROC [handle: FinchTool.Handle, cDesc: ConvDesc, useFinchIcon: BOOL] = {
ENABLE UNWIND => NULL;
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.whoOriginated FROM
us => Rope.Concat["to ",   -- this doesn't work right for "listen to this!`' mode
ViewerTools.GetContents[handle.calledPartyText]
],
> us => Rope.Concat["from ", ViewerTools.GetContents[handle.callingPartyText]],
ENDCASE => Rope.Concat["to/from ", RepairIntelnet[OtherParty[cDesc, FALSE]]];
};
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];
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;
};


lineHeight: INT _ 12;
tsHeight: INT _ 3*lineHeight;
convHeight: INT _ 5*lineHeight;
convStyle: ROPE _ "BeginStyle (Cedar) AttachStyle (basicConv) \"common defs for conversation log entries\" { default LooseLeading ExtraLead 0 topLeadingGlue 150 bp restIndent clearTabStops 60 bp flushLeft tabStop 135 bp flushLeft tabStop 200 bp flushLeft tabStop 255 bp flushLeft tabStop 475 bp flushLeft tabStop } StyleRule EndStyle";
basicConvFormat: ROPE _ "basicConv";

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];
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 _ TiogaButtons.CreateViewer[
info: [wy: bt.wy + 2, ww: v.ww,
wh: convHeight, parent: v, border: FALSE] ];
TiogaButtons.SetStyleFromRope[h.conversations, convStyle];
Containers.ChildXBound[v, h.conversations];
Containers.ChildYBound[v, 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 = {
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];
};

MakeFinchToolCmd: Commander.CommandProc = {
ENABLE UNWIND => cmdHandle _ NIL;
cmdHandle _ cmd;
MakeFinchTool[];
cmdHandle _ NIL;
};

AddConvDesc: PROC [cViewer: Viewer, cDesc: ConvDesc, bRope: ROPE_NIL, bFormat: ROPE_NIL, bLooks: ROPE_NIL] RETURNS [newButton: TiogaButtons.TiogaButton_NIL] = {
lastButton: TiogaButtons.TiogaButton;
IF finchToolHandle=NIL THEN RETURN;
newButton _ NIL;
lastButton _ NARROW[ViewerOps.FetchProp[finchToolHandle.conversations, $lastButton]];
IF lastButton#NIL THEN {
lastDesc: ConvDesc = NARROW[lastButton.clientData];
IF lastDesc#NIL AND lastDesc.reportComplete AND
(lastDesc.ultimateState=$reserved OR lastDesc.ultimateState=$parsing OR
  (lastDesc.ultimateState=$notified AND cDesc.situation.self.state=$notified AND
cDesc.partyInfo[cDesc.whoOriginated].type=$trunk AND lastDesc.partyInfo[cDesc.whoOriginated].type=$trunk AND
BasicTime.Period[from: lastDesc.startTime, to: cDesc.startTime] <= 12)  )
THEN newButton _ lastButton;
};
IF newButton#NIL THEN {
RelabelTiogaButton[button: newButton, newRope: bRope];
newButton.clientData _ cDesc;
}
ELSE
newButton _ TiogaButtons.CreateButton[viewer: cViewer, rope: bRope, format: bFormat, looks: bLooks, proc: ConversationMgmtProc, clientData: cDesc, fork: TRUE];
cDesc.clientData _ newButton;
ViewerOps.AddProp[cViewer, $lastButton, newButton];
};

SelectEntryInConversations: PROC[entryButton: TiogaButtons.TiogaButton, doSelect: BOOL_TRUE] = {
viewer: Viewer = IF finchToolHandle#NIL THEN finchToolHandle.conversations ELSE NIL;
prevSelected: TiogaButtons.TiogaButton = IF viewer=NIL THEN NIL
ELSE NARROW[ViewerOps.FetchProp[viewer, $selectedEntry]];
IF prevSelected # NIL AND NOT IsDestroyed[prevSelected, viewer] AND
((prevSelected=entryButton) # doSelect) THEN {
TiogaButtons.ChangeButtonLooks[button: prevSelected, removeLooks: "bs"];
ViewerOps.AddProp[viewer, $selectedEntry, NIL];
};
IF IsDestroyed[entryButton, viewer] THEN
{ Report["Conversation is no longer available."]; RETURN; }
ELSE IF NOT doSelect THEN RETURN;
TiogaButtons.ChangeButtonLooks[button: entryButton, addLooks: "bs"]; -- show it's selected
ViewerOps.AddProp[viewer, $selectedEntry, entryButton];
};

IsDestroyed: PROC [button: TiogaButtons.TiogaButton, viewer: Viewer] RETURNS [BOOL] = {
RETURN [TiogaButtons.FindTiogaButton[viewer, button.startLoc] = NIL];
}; 

MakeDirectory: Menus.MenuProc = {
FinchTool.BuildDirectories[];
}; 

RelabelTiogaButton: PROC [button: TiogaButtons.TiogaButton, newRope: ROPE_NIL,
looks: ROPE_NIL] = {
node, rootNode: TextNode.Ref;
LockedRelabel: PROC [root: TiogaOps.Ref] = {
len: INT_0;
[resultStart: button.startLoc.where, resultLen: len] _ TextEdit.ReplaceByRope[
root: rootNode, dest: node, rope: newRope, looks: TextLooks.RopeToLooks[looks]];
button.endLoc.where _ button.startLoc.where + len;
};
IF button#NIL THEN {
node _ TiogaButtons.TextNodeRef[button.startLoc.node];
rootNode _ TextNode.Root[node];
TiogaOps.CallWithLocks[LockedRelabel, TiogaButtons.TiogaOpsRef[rootNode]];
};
};

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];
};

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] = {
  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];
  };

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.ChildXBound[sib.parent, ts];
};

Report: PUBLIC PROC[msg: ROPE, a1, a2, a3: IO.Value _ [null[]]] = {
VoiceUtils.ReportFR[where: $Finch, remark: msg, a1: a1, a2: a2, a3: a3];
};

ReportRope: PUBLIC PROC[msg1: ROPE] = {
IF msg1#NIL THEN VoiceUtils.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: VoiceUtils.WhereProc = {
RETURN[IF cmdHandle#NIL THEN cmdHandle.out ELSE IF finchToolHandle=NIL THEN NIL
ELSE finchToolHandle.tsOut];
};
FinchOnlyWhereProc: VoiceUtils.WhereProc = {
RETURN[IF finchToolHandle=NIL THEN NIL ELSE finchToolHandle.tsOut];
};
FinchWhereCmdProc: VoiceUtils.WhereProc = {
RETURN[NIL];
};

ViewCmd: Commander.CommandProc = TRUSTED {
Nice.View[finchToolHandle, "FinchTool PD"];
};


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];

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"];
Commander.Register["Visit", VisitCmd, "Identify an office visitor"];
Commander.Register["Unvisit", UnvisitCmd, "Cancel visiting for specified visitor or self"];
Commander.Register["TDir", TDirCmd, "Create telephone directory for named file"];
VoiceUtils.RegisterWhereToReport[proc: FinchWhereProc, where: $Finch];
VoiceUtils.RegisterWhereToReport[proc: FinchOnlyWhereProc, where: $FinchOnly];
VoiceUtils.RegisterWhereToReport[proc: FinchWhereCmdProc, where: $FinchCmd];
printLabel[$active] _ " is in progress";
printLabel[$idle] _ " is completed";
printLabel[$ringing] _ " is ringing";
printLabel[$notified] _ NIL;
printLabel[$initiating] _ NIL;
printLabel[$ringback] _ " is ringing";
printLabel[$reserved] _ " Telephone set is off hook";
printLabel[$parsing] _ " Call is being dialed";
TRUSTED { Booting.RegisterProcs[c: UnFinchOnCheckpointOrBoot, r: ReFinchOnRollback, b: UnFinchOnCheckpointOrBoot]; };
Commander.Register["VuFinchTool", ViewCmd,
"Program Management variables for FinchTool"];
}.
��+Š��FinchToolImpl.mesa 
Copyright Ó 1985, 1987 by Xerox Corporation.  All rights reserved.
Last Edited by: Swinehart, July 19, 1987 10:13:43 pm PDT
Last Edited by: Pier, April 17, 1984 3:51:54 pm PST
Polle Zellweger (PTZ) July 30, 1987 5:02:33 pm PDT
List USING [Reverse],
Current assumption, subject to change as we develop the notion of multiple calls: Only one call is allowed, by Lark, to reach interesting states (>=failed, +notified).  We can select any call that does go beyond an interesting state, to indicate the "current" call; all actions that deal with ongoing call will refer to that one.  We will not, for the moment, allow any manual selection of conversation-log entries.  When there isn't an entry, it's OK to place calls without hanging up first.  So Lark and FinchTool/Directory know about the one-call philosophy; FinchSmarts does not, at present.

Current assumption:  all descriptions, including called/calling party fields and conversation logs, describe only the first other party; in a conference, they'll ignore late arrivals.

Types and Typeoids

Placing and Controlling Calls

[parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE]
[parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE]
[parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE]



No Conversation ID needed here because assumed not active in any now.
Refers to entries in the viewer finchToolHandle.conversations, not necessarily current one.
Refers to entries in the viewer finchToolHandle.conversations, not necessarily current one.
Called to disconnect a call in progress without logging a message.


PROC [button: TiogaButton, clientData: REF ANY _ NIL,
mouseButton: ViewerClasses.MouseButton _ red, shift, control: BOOL _ FALSE]
Button-invoked operations when applied directly to conversation-viewer log entries.
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.)


Speaking Text

We let FinchSynthesizer invent the actionID's, we don't track reports, but we do return the conversationID, so that subsequent adjacent requests can be reliably queued under the same conversation.

Other User Commands

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.
 a  b  c  d   e  f   g  h  i   j  k   l  m  n  o  p   q  r   s  t   u  v  w  x  y  z
try first time w/o password
IF Rope.Length[visitor] = 0 THEN {}; -- no name => self
No password for unvisiting, either.
Starting and Stopping
If not active, try to make it active, unless mustAlreadyBeActive.  If complain is FALSE, don't complain when not active, if you have any control over it.
Conversation Management

Don't use conversation buttons for failure reports
If we originated, called-party is already set better than we can get this way (home or office, nicknames, and so on.)  Else pick up calling party (incoming), or called-party (outgoing).  originatorRecorded is set TRUE whenever one of these fields is set.
Set called-party field (choose randomly if more than 1 callee)
Set calling-party field
Don't want to step on twiddling if state=ringing.  This call covers hanging up from non-ringing state or placing outgoing call.
If idle, elaborate.
We might receive reports about IntervalSpec requests or voice synthesizer requests.  The actionRequest will be REF that we supplied in the original request subject to change.  We may also have to add parameters (first, last) to this report. .

Describe the other party.

Other party descriptions can be of the form "xxx.pa (nnnn)", which confuses Phone commands.  Number in parens and lack-of-authentication question mark should be eliminated from number field being set here.
Other party description can contain Intelnet pause characters and authentication code.
Intelnet pause characters are all characters < '\040, followed by "P".  For the present, we assume that there's a P following the pause in one of these; other values are possible, but we don't expect them in phone numbers.
Should be repaired (pause => "*" and auth-code removed) wherever the user sees it.
IF cDesc.bluejayConnection THEN "Recording service"
ELSE IF cDesc.proseConnection THEN "Text-to-Speech service"
ELSE
Assumes that the ordinary Finch icon is unlabelled.
Viewer Construction and Management

Finch Viewer
Check how much space is left (if any) in the control window for a typescript.
Finch Tool Viewer
MakeOneMenu[finchMenu, "Drop Out", ButtonStopFinch];
Removed for now; use "Unfinch".  Should be replaced by toggle that also serves
to indicate current enabled/connected state.
Conversations Viewer

ViewersOps.AddProp[newButton, $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.
At present, this occurs only when one had dial-tone or was dialing and hung up.  Otherwise, busy calls would be lost and we some day hope to be able to use the information about busy calls in redialing. STCWN (Subject to change without notice.)
Incoming back door calls are retried once per ring if rejected because the called telephone is busy.  Combine them in the log.  This is a hack.
do it even if prevSelected = entryButton, because we may have relabeled the previous one inbetween, which will remove looks

Directory
Viewer and Button Utilities

Creates a text viewer, next right on the same line as sib
 sib must be a Viewer, not NIL
 Make an h-bit wide line after sib
Sib is sibling to create TS after
Containers.ChildYBound[sib.parent, ts];
Reporting and Logging

Registration, Initialization
Register a command with the UserExec that will create an instance of this tool
Debugging nonsense
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)
Swinehart, October 27, 1986 5:12:27 pm PST
Synthesis had been removed from development versions of Finch.  Put it back in, now using the service facilities provided by the FinchSynthesizer interface.
Polle Zellweger (PTZ) February 9, 1987 6:27:46 pm PST
changes to: FeepMap, VisitCmd, Commander, Commander
Polle Zellweger (PTZ) February 19, 1987 10:29:47 pm PST
changes to: VisitCmd, DIRECTORY
Polle Zellweger (PTZ) February 20, 1987 6:19:47 pm PST
changes to: VisitCmd
Polle Zellweger (PTZ) February 23, 1987 2:15:31 pm PST
changes to: StartFinch
Polle Zellweger (PTZ) February 25, 1987 5:08:58 pm PST
changes to: UnvisitCmd
Polle Zellweger (PTZ) February 26, 1987 11:30:32 am PST
changes to: Commander, VoiceUtils, DIRECTORY
Polle Zellweger (PTZ) February 27, 1987 2:35:40 pm PST
changes to: DIRECTORY, VisitCmd
Polle Zellweger (PTZ) July 27, 1987 11:24:07 pm PDT
Better party identification in ReportConversationState.
changes to: ReportConversationState, OtherParty, SetContents, DIRECTORY, serverInstance, us, unknown, PaintFinchIcon
Polle Zellweger (PTZ) July 28, 1987 8:34:12 pm PDT
Replace buttons with TiogaButtons in conversation log; no new functionality.
changes to: DIRECTORY, FinchToolImpl, PhoneProc, GetSelectedDesc, AddConvDesc, ConversationMgmtProc, HangupCProc, AnswerCProc, RedialOfficeCProc, RedialHomeCProc, Hangup, HangItUp, ReportConversationState, convStyle, basicConvFormat, MakeFinchTool, MakeMenus, RelabelTiogaButton, LockedRelabel (local of RelabelTiogaButton), SetOpenHeight, finchMenu, SelectEntryInConversations, IsDestroyed
Polle Zellweger (PTZ) July 30, 1987 3:00:51 pm PDT
Add ability to Redial from TiogaButtons in conversation log.
changes to: RedialOfficeCProc, RedialHomeCProc, RedialIt, Answer, AnswerCProc, AnswerIt, HangupCProc, HangItUp, ConversationMgmtProc, SetContents, SimplifyName
Polle Zellweger (PTZ) July 30, 1987 3:10:56 pm PDT
changes to: RedialOfficeCProc, RedialHomeCProc, RedialIt, DoPhone, Answer, AnswerCProc, AnswerIt, HangUpCmd, Hangup, HangupCProc, HangItUp, ConversationMgmtProc, ParseCallee, SetContents, SimplifyName
Polle Zellweger (PTZ) July 30, 1987 3:25:21 pm PDT
changes to: SimplifyName

�Ê-•��˜�–
"Cedar" stylešœ™Icode–
"Cedar" stylešœB™BJšœ8™8J™3Kšœ2™2—J˜�šÏk	˜	J˜%J˜>Jšœœ˜$Jšœ
œ!˜0Jšœœ˜!Jšœœ1˜Ašœœ˜Jšœ•˜•—Jšœœ:˜PJšœ
˜
Jšœœ!˜,J˜Jšœœ
˜Jšœœ™JšœœE˜RJšœœ0˜;Jšœœ
˜Jšœœ+˜8Jšœœaœ˜zJšœœ˜Jšœœ˜/Jšœ	œ˜"Jšœ
œ˜!Jšœ	œ˜Jšœœ!œ5˜dJšœ
œ˜ŸJšœ	œF˜TJšœœ
˜Jšœœ˜J˜#Jšœœ'˜:Jšœ
œ"˜4Jšœ	œ˜'Jšœœ˜)Jšœ
œP˜_J˜>JšœœT˜eJšœU˜UJšœ˜J˜�—šœ˜(Jšœsœâ˜ÞJšœ˜Jšœ˜J˜�—Idefault™ÓL™�L™·L™�™J™�Jšœ
œ˜&Jšœœ
œ˜Jšœœ˜Jšœœœ˜J˜$J˜�Jšœœ˜)Jšœœ˜ Jšœœœ˜!Jšœœ˜Jšœ	œ˜#J˜�Jš	œœœœœ˜MJšœœ"˜4J˜#J˜'J˜(J˜*J˜�Jšœ+˜+Jšœ/˜/Jšœ0˜0Jšœ2˜2J˜�J˜J˜�Jšœœ˜Jšœ
œ˜Jšœœ˜0J˜�—™J™�•StartOfExpansion‚ -- [parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE]šÏn	œÏc6˜XJšÐck~™~Jšœœ&˜@šœ˜šœ
˜Jš	œœ	œœœ˜3——J˜J˜�—–‚ -- [parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE]šžœŸ#˜KJš ~™~Jšœœ:˜Tšœ
˜JšœAœ˜FJšœ#œ˜*Jšœ!œ˜'Jšœœ˜—Jšœ˜J˜�—–‚ -- [parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE]šžœŸ$˜MJš ~™~Jšœœ;˜Všœ
˜JšœBœ˜GJšœ$œ˜+Jšœ"œ˜(Jšœœ˜—Jšœ˜J˜�—šžœŸ˜>Jšœœœ˜!JšœœE˜_J˜J˜Jšœœ˜J˜J˜�—šžœŸ˜BJšœ˜Jšœ˜J˜J˜�—šž	œŸ˜6Jšœœœ˜!J˜šœ˜$JšœBœ˜I—Jšœœ˜J˜J˜�—šžœœœŸ.˜VJšœœ˜!J™�—šžœœœŸ.˜TJšœœœ˜6J™�—šžœœ"œœ˜?šœœ˜,Jšœ
Ÿ>˜KJšœŸ˜5Jšœ˜—Kšœœ4˜IJšœ˜J™�—š
žœœœœœ˜HJšœ!œ˜&Jšœœ˜šœœœœ˜WJšœœ˜—JšœZ˜ZJšœ/˜/šœM˜SJšœM˜M—Jšœœ˜(šœœ5˜NJšŸ)˜)—šœK˜KJ™E—J˜J˜�—šžœœŸ*˜NJšœ[™[Jšœ$˜$Jšœ˜Jšœ˜J˜�—šžœœœŸ.˜PJšœœ˜!—J˜�šžœœœœ˜0Kšœ	œœœœœœœ˜ešœ˜Jšœœ$œ˜0—Jšœ<˜<Jšœ˜J˜�—šž	œŸ˜6Jšœœœ˜!J˜Jšœœ˜Jšœœ˜J˜J˜�—šžœœŸ/˜QJšœœ˜Jšœ[™[—J˜�šž
œœ œ˜?JšœB™BJ™�—šžœœœŸ/˜QJšœœ	œ˜4J™�—šžœœœœœœœœœ˜}Jšœ˜Jšœ	œ˜Kšœœœ˜,Jš	œœœœœ˜?Jšœ	œœœ0œœ˜Všœ˜šœ	˜	Jšœ
œ&œ˜?—Jšœœœœ˜/Jšœ˜—Jš
œ	œœ#œœ˜ZJšœ@˜@Jšœ˜J˜�—šžœ"˜6Kšœ#œœœ@œœ™J™Sšœ	˜JšœM˜M—šœœ˜JšœM˜M—šœœ˜JšœS˜S—šœœ˜JšœQ˜Q—Jšœ˜J˜�—šžœœ
œœœ
œœ
œ˜sJ™ßJšœœ˜šœ˜JšœK˜K—JšœW˜WJšœKœ˜Qšœœ˜%Jšœ#˜#Jšœœ˜J˜—šœ'œœ˜<Jšœ'˜'Jšœœ˜J˜—J˜Jšœœ&˜;J˜J˜�—šžœœ˜Jšœœ
œœ˜:Jšœœœ˜0Jšœœ˜J˜Jšœ˜šœœ-œ˜UJ˜)J˜J˜—J˜—J™�šžœœœ(œœœœœ˜‚Jšœœœœœœ˜Tšœ%œœœ˜;Jšœœ.˜9—Jš
œœœœœ˜:Jšœœœœœœœ˜WJšœœ˜$Jšœ˜—J™�—™
J™�Jšœœ˜Jšœœ˜/J˜�šžœ˜*Jšœ	œœ˜Jšœœœ˜-šœ
˜Kšœœ˜Jšœœ˜Jšœœ˜Kšœœ˜—K˜%Jšœ˜J˜�—šžœ˜'Jšœœœ˜!JšœœE˜[J˜šœœ˜?Jšœœ˜,—Jšœœ˜J˜J˜�—šžœœ˜Kšœ	œœœ2˜OKšœ'˜.K™ÄKšœœ˜šœ˜Kšœ
œœœ˜,Kšœ˜Kšœ'˜'Kšœ&œ	œ˜>Kšœ˜—šœ?˜?JšœœC˜R—šœ
œ˜Jšœ˜J˜FJ˜—Kšœ˜K˜�—šžœœ	œœ˜VJšœ˜Kšœ3˜3šœ=˜BK˜2Kšœœ˜1šœœœ˜šœ!˜!Kšœ˜Kšœ˜Kšœœœœ˜CKšœ˜—Kšœœ3˜E—Kšœ˜Kšœœ0˜EK˜>Kšœ
œ˜Kšœœœ˜Kšœ˜—K˜K˜�—J˜�Jšžœ+˜9šžœ˜*Jšœœœ˜!J˜Jšœ˜Jšœœ˜Jšœ˜J˜�—šžœœ˜Jšœœ˜Jšœœœœ˜3Jšœ!˜!šœ
˜J˜W—J˜J™�——™J™�šžœ˜"Jšœœœ˜!JšœœP˜eJ˜J˜J˜Jšœœœ:˜KJšœ*˜.Jšœœ˜J˜J˜�—š
ž	œœœœœ˜?J™‰J™@šž	œœœ	œœœ˜<Jšœœœ˜"šœœ˜Jšœ ˜"Jšœ˜Jšœ˜
—Jšœ˜—J–. -- [data: REF ANY, index: INT] RETURNS [CHAR]š
œœœœœ˜1JšœQ˜WJ˜J˜�—šœ	œœœ	œœ˜.J˜iJ™TJ˜�—šžœ˜#Jšœœœ˜!Jšœœ˜JšœœE˜WšœœŸ ˜BKšœœ˜+Kšœ
œ˜!K˜—J˜šœ˜$šœKœ˜RJšœ™——šœœŸ ˜@Kšœœ˜Jšœœ˜/Kšœœ˜"Kšœœ˜%Kšœ4˜4K˜—Jšœœ˜J˜J˜�—šž
œ˜%Jšœœœ˜!JšœœE˜WJšœœ˜KšœœŸ™7K™#J˜šœ˜$Jšœ.˜.—Jšœœ˜J˜J˜�—šžœ˜"Jšœœœ˜!J˜JšœNœ˜TJšœœ˜J˜J˜�—J˜�—™J˜�šž
œœœœ˜%Jšœ+˜+Jšœœ˜šœœœ˜J˜J˜Jšœœœ(œ˜DJšœ˜—Jšœ4˜4šœœ˜J˜(J˜?šœ˜Jšœ˜Jšœlœ˜sJ˜.Jšœ+œ˜1J˜——Jšœ˜—J˜�šžœ˜$Jšœœœ˜!J˜J˜/Jšœ
˜
Jšœœ˜Jšœ˜J˜�—šžœ˜+Jš
œœœ+œœ˜PJšœ+œ˜1Jšœ˜J˜J˜�—šž	œœœ˜$Jšœ+˜+Jšœœœœ˜Jšœ˜Jšœ ˜ J˜—J˜�Jšžœ)˜8J˜�šžœ˜,Jšœœœœ˜#Jšœ˜šœœœ˜+Jšœ/œ˜5Jšœ7˜7J˜—J˜Jšœœœ˜>Jšœœœ˜@Jšœœ˜Jšœ˜J˜�—šžœ˜5Jšœ	œ˜Jšœœœ˜#Jšœ6˜6šœ*˜*Jšœ)œ	˜4—Jšœ	œ˜J˜J˜�—šžœœœ0œœœœœ	œœ˜‰JšœRœB™™Jšœœ˜Jš	œœœœŸ˜8šœ.œ˜HJšœ˜—Jšœ4˜4Jšœ	œœœ	˜<Jšœ
œ	œœC˜wJ˜J˜�—šž
œ˜%Jšœœœ˜!J˜Jšœ˜Jšœœ˜Jšœ˜J˜�—šžœœœ˜5Jšœ/œ˜6šœœœ6˜OJšœ3œœ˜B—Jšœ5˜5Jšœ1˜1Jšœ-œ
œœœDœœœ˜Ðšœœ	œœ˜$J˜#J˜\J˜—J˜J˜�—Jšœœœ˜Jšœ
œ˜J˜�š
žœœœœœ˜CJšœ
œ˜Jšœœ
˜šœœœœœ
˜/Jšœ4˜4Jšœœœ˜Jšœ˜—J˜J˜�—šžœœœ˜J˜Jšœ
œ˜J˜—J˜�—™J™�šžœœœ œ˜OJšœœœ˜
Jšœœ˜Jšœœœ˜J˜!J˜š
œœ
œœœ˜HJšœ2™2—Jš	œ	œœ œŸ˜YJšœœœœ˜#Jšœœœ˜
J˜!šœœ˜8šœ˜JšœÕœ%™þšœ@˜@Jšœ>™>—šœR˜RJšœ™—JšŸ
œŸœœ˜——–[]šœœŸ%˜=šœœ˜(Jšœ œ˜'Kšœœ$˜DK˜—K˜—šœŸ$˜)šœ˜%Jšœ3˜3——šœœœ˜,Jšœ˜šœœ˜šœ˜ Kšœ7œ˜?—šœœœ˜+Kšœ<œ˜D—šœœœHœ˜XKšœ?œ/œ˜—Kšœ:œ˜\Kšœœ˜:K˜—Jšœ)˜)šœ˜&K™Jšœ3˜3—J˜—J™šœ!œ˜)˜šœœ
œ˜.Jšœœœœ˜R—šœœœ˜šœœ˜'J˜J˜(J˜"J˜!Jšœ˜Jšœ˜——J˜7J˜—J˜—Jšœ˜"Jšœœœ0˜SJšœœœ˜1Jšœœ˜"šœ
œ˜Jšœ^˜^—šœ˜"Jšœ?˜?—Jšœ!œœ˜BJšœ4œ˜IJšœ˜—J˜�š
žœœDœœœ˜†Jšœoœ*ÏtœU¡œ™ôJ˜$šœ˜$šœ
˜
šœœ˜*J˜$J˜J˜)Jšœ˜Jšœ˜——šœ˜Jšœ$œ&˜P—˜šœ%œ˜-šœ
œ˜JšœœO˜g—J˜0J˜——Jšœ˜—J˜J™�—J™šž
œœ+œœ	œœœœ˜wšœœœœ˜LJšœœ˜—Jšœ1˜1Jšœœ(œ%˜oJ˜—J™�J™Íšžœœ2œ˜KJšœ7œ˜=Jšœœ˜ J˜J˜�—š
žœœœœœ˜IJšœœ˜Jšœ(œ
˜7Jšœœ˜Jš
œœœœœ˜+Jšœœ˜)J˜J˜�—™VJ™ÞJ™R—š
žœœœœ	œ˜9Jšœœ˜
Jšžœœ˜,Jšœ
˜
šœ#œ˜+Jšœ*Ÿ˜IJ˜šœ#˜)Jšœ)Ÿ3˜\—J˜—J˜J˜�—šžœœœ;œ˜^Kšœœœ˜Kšœ.˜.Kšœœ˜Jšœ˜šœœ˜Jšœ˜Jšœœ˜J˜—šœ˜Kšœ-˜-šœœ˜4šœŸ6˜QKšœœ™3Kšœœ™;Kš™Kšœ/˜/Kšœ˜—JšœO˜OJšœ=œ˜M—J˜—šœœ˜BJšœ7˜7—K˜K˜�—šžœ
œ˜;Jšœœœ˜š
žœœœœœ˜-JšœA˜AJšœ˜Jšœ˜—šžœœœœœ
œ˜Wšœœ˜Jšœ˜šœ˜Jšœ7˜7—Jšœ˜J˜—Kšœ˜Jšœ˜J˜�—šœœ˜$JšœQ˜Q—šœF˜FJ™3—šœ˜šœ˜Jšœ˜—šœ˜šœœœ˜Jšœ:œœ˜HJšœ5œœ˜CJšœ9œœ˜GJšœ5œœ˜CJšœ˜—Jšœ2œœ˜@J˜—Jšœ˜—J˜—J˜�—™"J™�™J˜�Jšœœ˜Jšœ
œ˜Jšœœ˜Jšœœ$Ïb	œ“˜ÏJšœœ˜$J˜�šž
œœ˜Jšœœ˜Jšœ	œ˜J˜Jšœ˜J˜
šœœœ˜Jšœ3œ˜=—Jšœœ˜.J˜J˜&J˜šœ+Ðac˜=JšœŸ ˜/J˜JšœœŸ8˜IJšœŸ˜0JšœŸ˜.JšœŸ
˜,JšœœœŸ'˜>—J˜Jšœ
œœœ˜^šœ"˜"Jšœ;œ˜A—JšœY˜YJ˜HJšœsœ˜zJ˜AJ˜4J˜JšœM™MJ˜
šœ*œ˜2Jšœ1˜1Jšœœ#˜4J˜—J˜'Jšœ1œ˜DJ˜(šœ,˜,šœ˜Jšœ#œ˜,——Jšœ:˜:Jšœ+˜+Jšœ+˜+Jšœ"Ÿ˜9šœœ˜&JšœM˜M—šœœ˜(šœO˜SJ˜——Jšœ˜—J˜�šž	œœ˜J™šžœœœ˜Išœ˜Jšœ˜JšœG˜GJšœ˜—J˜—Jšœ˜Jšœ+˜+Jšœ)˜)Jšœ-˜-Jšœ7˜7Jšœ5˜5Jšœ3˜3šœ4™4Jš¡œ{¡™}—Jšœ˜J˜�—š¢œ˜+Jšœœœ˜!J˜Jšœ˜Jšœœ˜Jšœ˜——J˜�™J™�šžœœ*œœœœ
œœœ&œ˜ Jšœ%˜%Jšœœœœ˜#Jšœœ˜Jšœ
	œB˜Ušœœœ˜Jšœœ˜3šœ)œ™.JšœÜ™Ü—šœ
œœ˜/šœ"œ!˜GJ™ô—šœ$œ&˜Pšœ1œ5˜lšœI˜IJšœ€¡œ¡™‘———Jšœ˜—J˜—šœœœ˜Kšœ6˜6Kšœ˜K˜—š˜Kšœ™œ˜Ÿ—Jšœ˜Jšœ3˜3J˜J˜�—šžœœ2œœ˜`Jšœœœœœœ˜Tšœ)œœœ˜?Jšœœ.˜9—šœœœœ#œ)œ˜rJšœH˜HJšœ*œ˜/J˜—šœ"˜(Jšœ1œ˜;—Jš
œœœ
œœ˜!šœEŸ˜ZJšœ{™{—Jšœ7˜7J˜J˜�—šÐbnœœ4œœ˜WKšœ:œ˜EJšœ˜——J™�J™	˜�š¢
œ˜!J˜Jšœ˜J˜�——™J™�š
žœœ-œ	œœ˜cKšœ˜šž
œœ˜,Kšœœ˜KšœŸ˜ŸKšœ2˜2Kšœ˜—šœœœ˜Kšœ6˜6Kšœ˜KšœJ˜JK˜—Jšœ˜J˜�—šž
œœ-œ˜LJšžœœ3˜HJšœ8˜8Jšœ˜—J˜�šž
œœœœ˜EJšœ!œœ˜/šœ˜Jšœ:˜:Jš	œœœœœ˜=—J˜—J˜�š
žœœœœ$œ˜PJšœœœœœœœœœ˜bšœ˜Jšœ˜Jšœœ˜Jšœœ˜Jšœœœœœœœœ˜BJšœœœ"˜1JšœM˜MJš
œœœœœ˜%šœœŸ˜-JšœœœœŸ˜BJšœ	œœœ˜1Jšœ˜—šœŸ.˜3Jšœ˜——JšœG˜MJšœ˜—J˜�š
žœœœœ$œ˜MJš	œœœœœ˜8šœ˜JšœH˜HJšœ$˜$Jšœ	œŸ˜-JšœœœœŸ˜DJšœ	œœœ˜5Jšœ˜—šœŸ.˜3Jšœ˜—JšœP˜VJšœ˜—J˜�JšŸ>˜>šž
œœ˜Jšœœ(˜DJš
œœœœ
œœ	œ˜;Jš	œ	œœ3œœ˜SJšœ˜—JšœU˜Ušœ&˜&Jšœœœ˜"šœ	œŸ˜-JšœœœœŸ˜BJšœ	œœœ˜5Jšœ˜—šœŸ-˜2Jšœ˜—šœ˜JšœN˜N—Jšœ˜—J˜�šžœœ˜Jšœœ˜7Jš
œœœœ
œœ	œ˜KJšœ	œœ)˜=Jšœ˜šœ˜Jšœ?˜?Jšœœœœ˜>—Jšœœœ˜%šœ˜JšœN˜N—Jšœ˜J˜�—š
žœœœœœ˜<Jš
œœœœœ˜Jšœœœœ˜$Jšœœ˜
Jšœ˜J˜�—JšŸ9™9JšŸ™š
žœœœœœ˜RJšœœœ˜,šœ"˜"Jšœ@˜@Jšœ œ˜(—Jšœ˜—J˜�Jšž	œœœœ˜CJšœŸ ™"Jšœœœœ˜.Jšœ˜JšœP˜PJšœ(˜(Jšœœ˜J˜�JšŸ!™!šžœœœ˜:Jšœœ˜!Jšœœœ˜"šœ˜JšœDœ˜M—Jšœ'™'Jšœ'˜'Jšœ˜—J˜�——™™�š
žœœœœœ˜CJ˜HJšœ˜—J˜�šž
œœœœ˜'Jšœœœ0˜@Jšœ˜—J˜�š
žœœœœœ˜9Jšœœ$˜.J˜J˜—J˜�šžœ˜(šœœœœœœœœ˜OJšœ˜—Jšœ˜—šžœ˜,Jšœœœœœœ˜CJšœ˜—šžœ˜+Jšœœ˜Jšœ˜J˜�—šžœœ˜*Jšœ+˜+Jšœ˜J˜�———™J˜�Jšœ3œœ˜BJšœ7œœ˜FJšœ8œœ˜GJšœ<œœ˜KJšœ@œœ˜OJšœAœœ˜PJšœ:œœ˜IJšœCœœ˜RJšŸN™NJ˜�šœ<˜<Jšœ%˜%—JšœI˜IJšœL˜LJ˜2J˜QJ˜=J˜NJ˜HJ˜TJ˜>J˜9J˜DJ˜[J˜QJ˜FJ˜NJ˜LJ˜(Jšœ$˜$Jšœ%˜%Jšœœ˜Jšœœ˜Jšœ&˜&J˜5J˜/Jšœn˜uJ™šœ*˜*Jšœ.˜.——Jšœ˜™(K™KšœÏr'™3—šœ4™4Kšœ™Kšœ¥2™>—šœ4™4Kšœ¥A™M—šœ6™6Kšœš™šKšœ¥g™s—™,KšœF™FKšœ;™;K™CK™4K™8K™-K™ZK™•—™4K™IKšœ¥…™‘—™5K™‘Kšœ¥n™z—šœ5™5K™IKšœ¥1œ™C—™*K™œ—™5Kšœ¥'™3—™7Kšœ¥™—™6Kšœ¥™—™6Kšœ¥
™—™6Kšœ¥
™—™7Kšœ¥ ™,—™6Kšœ¥™—™3K™7Kšœ¥h™t—™2K™LKšœ¥™œ¥C™†—™2K™<Kšœ¥“™Ÿ—™2Kšœ¥¼™È—™2Kšœ¥™—K™�—�…—����™t��ò“��