<> <> <> <> <> 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 ], FinchSmarts USING [ AnswerCall, ConvDesc, DisconnectCall, Feep, IdentifyVisitor, ReleaseVisitor, InitFinchSmarts, RegisterForReports, UninitFinchSmarts ], FinchSynthesizer USING [ InitializeFinchSynthesizer, StopSpeech, TextToSpeech ], FinchTool, Icons USING [ IconFlavor, NewIconFromFile ], IO, Labels USING [Create], <> MBQueue USING [ Create, CreateButton, CreateMenuEntry, Queue ], 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 ], Thrush USING [ ActionReport, ConversationID, NB, notReallyInConv, nullConvID, Reason, StateInConv ], 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 ], VoiceUtils USING [ CurrentRName, Report, RegisterWhereToReport, WhereProc ] ; FinchToolImpl: CEDAR MONITOR IMPORTS BasicTime, Booting, Buttons, Commander, CommandTool, Containers, FinchSmarts, FinchSynthesizer, FinchTool, Icons, IO, Labels, MBQueue, Menus, Nice, Rope, Rules, Process, TiogaOps, TypeScript, UserProfile, VFonts, ViewerEvents, ViewerIO, ViewerLocks, ViewerOps, ViewerSpecs, ViewerTools, VoiceUtils EXPORTS FinchTool = { OPEN IO; <=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.>> <<>> <> <<>> <> <<>> 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; 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; <> <<>> PhoneProc: Buttons.ButtonProc = { -- Place call to party identified by primary selection <<[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 = { -- Place call to Called Party field <<[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 = { -- Place call to Calling Party field <<[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 = { -- 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 IF ~CheckActive[finchToolHandle] THEN RETURN; DoPhone[ViewerTools.GetContents[finchToolHandle.calledPartyText], FALSE]; }; 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 { <> <> cDesc: ConvDesc = GetSelectedDesc[]; 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]; }; ConversationMgmtProc: Buttons.ButtonProc = { <> viewer: Viewer = NARROW[parent]; cDesc: ConvDesc = GetSelectedDesc[viewer]; <> IF ~CheckActive[finchToolHandle] OR cDesc=NIL THEN RETURN; 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]; }; <> <> HangUpCmd: Commander.CommandProc = { IF ~CheckActive[finchToolHandle] THEN RETURN; [] _ HangItUp[TRUE]; }; HangupQuietly: PUBLIC Menus.MenuProc = { []_HangItUp[FALSE]; }; <> <<>> HangItUp: PROC[complain: BOOL, moreToCome: BOOL_FALSE] RETURNS [pauseNeededInMs: CARDINAL_0] = TRUSTED { cDesc: ConvDesc = GetSelectedDesc[]; state: Thrush.StateInConv _ IF cDesc=NIL THEN idle ELSE cDesc.situation.self.state; isTrunk: BOOL_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]; }; ParseCallee: PROC[fullCallee: ROPE, wantResidence: BOOL] RETURNS [callee: ROPE, fCallee: ROPE, residence: BOOL] = { < 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 " says you want to call the specified number, and the recipient is XXX (not implemented yet.)>> 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: Viewer_NIL] RETURNS [cDesc: ConvDesc_NIL] = { viewer: Viewer = finchToolHandle.conversations; selected: Viewer _ IF viewer=NIL THEN NIL ELSE NARROW[ViewerOps.FetchProp[viewer, $selectedEntry]]; IF chosenButton#NIL AND selected#chosenButton THEN RETURN; IF ~CheckActive[finchToolHandle] OR selected = NIL THEN RETURN; cDesc _ NARROW[ViewerOps.FetchProp[selected, $convDesc]]; }; <<>> <> <<>> 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 = { textToSpeak: Rope.ROPE _ cmd.commandLine.Substr[start: 1, len: cmd.commandLine.Length[]-2]; IF ~CheckActive[finchToolHandle] OR textToSpeak.Length[]=0 THEN RETURN; []_SpeakTextWithFeedback[TRUE, textToSpeak]; }; 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; }; 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 = { StopSpeaking[]; }; StopSpeaking: PROC = { IF ~CheckActive[finchToolHandle] THEN RETURN; []_FinchSynthesizer.StopSpeech[]; }; <<>> <> <<>> FeepCmd: Commander.CommandProc = { ENABLE UNWIND => cmdHandle _ NIL; textToFeep: Rope.ROPE _ FeepValue[cmd.commandLine.Substr[start: 1, len: cmd.commandLine.Length[]-2]]; cmdHandle _ cmd; IF CheckActive[finchToolHandle] THEN { cDesc: ConvDesc = 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] = { <'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.>> <> 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 ]; << 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>> 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 _ ""; < self>> <> cmdHandle _ cmd; IF CheckActive[finchToolHandle] THEN FinchSmarts.ReleaseVisitor[visitor, password]; cmdHandle _ NIL; }; <> StartFinch: PUBLIC PROC[] = TRUSTED { handle: FinchTool.Handle _ finchToolHandle; IF handle=NIL THEN { MakeFinchTool[]; handle _ finchToolHandle; IF handle=NIL THEN RETURN; -- complain?-- }; Report["Connecting . . ."]; FinchSmarts.RegisterForReports[ReportSystemState, ReportConversationState, ReportRequestState, FALSE]; -- Can go last. FinchSmarts.InitFinchSmarts[serverInstance]; FinchSynthesizer.InitializeFinchSynthesizer[]; 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 handle#NIL AND handle.finchActive THEN RETURN[TRUE]; StartFinch[]; RETURN[handle.finchActive]; }; UnfinchCmd: Commander.CommandProc = { StopFinch[]; }; ReportSystemState: PROC[on: BOOL] = { IF finchToolHandle=NIL THEN RETURN; finchToolHandle.finchActive _ on; IF finchToolHandle.finchWasActive=finchToolHandle.finchActive THEN RETURN; finchToolHandle.finchWasActive_finchToolHandle.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; }; <> <<>> ReportConversationState: PROC[ nb: NB, cDesc: ConvDesc, remark: Rope.ROPE ] = { s: IO.STREAM; p: PROCESS; difficulty: BOOL_FALSE; button: Viewer; 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. button _ NARROW[cDesc.clientData]; IF button = NIL THEN button _ AddConvDesc[finchToolHandle.conversations, cDesc]; 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]; -- Set called-party field them => SetContents[finchToolHandle.callingPartyText, cDesc]; --Set calling-party field -- 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 s.PutF[" %s %s", rope[SELECT cDesc.whoOriginated FROM $us => "to", $them=>"from", ENDCASE=>"to/from"], rope[RepairIntelnet[OtherParty[cDesc, TRUE]]] ]; 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]]; IF ~cDesc.reportComplete THEN Buttons.ReLabel[ button: button, paint: TRUE, newName: 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] RETURNS [otherParty: ROPE_NIL] = { IF cDesc.numParties<2 OR cDesc.partyInfo=NIL OR cDesc.partyInfo.numParties<2 THEN RETURN; otherParty _ cDesc.partyInfo[1].name; IF indicateUnauthenticated AND cDesc.partyInfo[1].type=$telephone THEN otherParty _ otherParty.Concat["?"]; }; <<>> <> SetContents: PROC[v: ViewerClasses.Viewer, cDesc: ConvDesc] = { contents: ROPE _ RepairIntelnet[OtherParty[cDesc, FALSE]]; i: INT_contents.Find["(", 0, FALSE]; IF contents=NIL OR contents.Length[]=0 THEN RETURN; IF i>0 THEN contents _ contents.Substr[len: i-1]; ViewerTools.SetContents[v, contents, TRUE]; cDesc.originatorRecorded _ TRUE; }; <> <> < "*" 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.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] ], $them => 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; 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 _ 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 = { <> 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]; <> finchConvMenu _ Menus.CreateMenu[]; MakeOneMenu[finchConvMenu, "Answer", Answer]; MakeOneMenu[finchConvMenu, "Disconnect", Hangup]; }; MakeFinchToolCmd: Commander.CommandProc = { MakeFinchTool[]; }; <> <<>> 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]]; <> <<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.reportComplete AND (lastDesc.ultimateState=$reserved OR lastDesc.ultimateState=$parsing OR <> (lastDesc.ultimateState=$notified AND cDesc.situation.self.state=$notified AND cDesc.partyInfo[1].type=$trunk AND lastDesc.partyInfo[1].type=$trunk AND BasicTime.Period[from: lastDesc.startTime, to: cDesc.startTime] <= 10) ) <> 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, doSelect: BOOL_TRUE] = { prevSelected: Viewer = NARROW[ViewerOps.FetchProp[entryButton.parent, $selectedEntry]]; IF prevSelected # NIL AND ~prevSelected.destroyed AND ((prevSelected=entryButton) # doSelect) THEN { Buttons.SetDisplayStyle[prevSelected, $BlackOnWhite]; ViewerOps.PaintViewer[prevSelected, all]; ViewerOps.AddProp[entryButton.parent, $selectedEntry, NIL]; }; IF entryButton.destroyed THEN { Report["Conversation is no longer available."];RETURN; } ELSE IF ~doSelect OR prevSelected = entryButton THEN RETURN; Buttons.SetDisplayStyle[entryButton, $BlackOnGrey]; -- show it is selected ViewerOps.PaintViewer[entryButton, all]; ViewerOps.AddProp[entryButton.parent, $selectedEntry, entryButton]; }; <<>> <> MakeDirectory: Menus.MenuProc = { FinchTool.BuildDirectories[]; }; <> <<>> 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]; }; <> << 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]; }; <> 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[msg1, msg2, msg3, msg4: ROPE_NIL] = { VoiceUtils.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 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]; }; 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"]; VoiceUtils.RegisterWhereToReport[proc: FinchWhereProc, where: $Finch]; 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"]; }. <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <feepNum features (can dial 9(800)TheCard)>> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <<>> <> <> <> <> <> <> <> <> <<>> <> <> <<>>