<> <> <> <> <> <<>> DIRECTORY BasicTime USING [ GMT, Now, OutOfRange, Period ], Booting USING [ CheckpointProc, RollbackProc, RegisterProcs ], Buttons USING [ ButtonProc, Create ], Commander USING [ CommandProc, Handle, Register ], CommandTool USING [ NextArgument ], Containers USING [ ChildXBound, ChildYBound, Container, Create ], Convert USING [ IntFromRope, RopeFromTime, RopeFromTimeRFC822 ], FinchSmarts USING [ AnswerCall, ConvDesc, CurrentConversations, CurrentRName, DisconnectCall, Feep, FetchAttribute, FinchIsRunning, IdentifyVisitor, Join, Poke, ReleaseVisitor, InitFinchSmarts, UninitFinchSmarts ], FinchSynthesizer USING [ InitializeFinchSynthesizer, StopSpeech, TextToSpeech ], FinchTool, FS USING [ ComponentPositions, ExpandName ], Icons USING [ IconFlavor, NewIconFromFile ], IO, Labels USING [Create], <> MBQueue USING [ Create, CreateButton, CreateMenuEntry, Queue, QueueClientAction ], Menus USING [AppendMenuEntry, CreateEntry, CreateMenu, FindEntry, Menu, MenuEntry, MenuProc, MouseButton, ReplaceMenuEntry ], <> Process USING [ Detach, Pause, MsecToTicks, SecondsToTicks, SetTimeout], Rope USING [ ActionType, Cat, Concat, Equal, Fetch, Find, Flatten, Length, Map, MakeRope, MaxLen, Replace, ROPE, Substr ], Rules USING [ Create ], Synthesizer USING [ SynthSpecBody, SynthSpec ], TEditScrolling USING [ AutoScroll ], TextEdit USING [ MaxLen, ReplaceByRope ], TextLooks USING [ RopeToLooks ], TextNode USING [ Ref, Root ], ThParty USING [ nullIx, PartyInfo ], Thrush USING [ ActionReport, ConvEventBody, ConversationID, NB, notReallyInConv, nullConvID, Reason, StateInConv ], TiogaButtons USING [ ChangeButtonLooks, CreateButton, CreateViewer, DeleteButton, FindTiogaButton, GetRope, SetStyleFromRope, TextNodeRef, TiogaButton, TiogaButtonProc, TiogaOpsRef, WrongViewerClass], TiogaOps USING [ CallWithLocks, CancelSelection, GetRope, GetSelection, Location, Ref, SelectionError, SelectionGrain, SetSelection, StepForward ], TypeScript USING [ Create ], UserProfile USING [ Boolean, CallWhenProfileChanges, ProfileChangedProc, Token ], VFonts USING [ Font, defaultFont ], ViewerClasses USING [ Viewer, ViewerRec ], ViewerEvents USING [ EventProc, RegisterEventProc, UnRegisterEventProc ], 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, Convert, FinchSmarts, FinchSynthesizer, FinchTool, FS, Icons, IO, Labels, MBQueue, Menus, Rope, Rules, Process, TEditScrolling, TextEdit, TextLooks, TextNode, TiogaButtons, 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[]; fadedIcon: Icons.IconFlavor _ tool; fadingIcon: Icons.IconFlavor _ tool; finchIcon: Icons.IconFlavor _ tool; leftFinchIcon: Icons.IconFlavor _ tool; rightFinchIcon: Icons.IconFlavor _ tool; outgoingFinchIcon: Icons.IconFlavor _ tool; outgoingConvIcon: Icons.IconFlavor _ tool; incomingConvIcon: Icons.IconFlavor _ tool; labelledFinchIcon: Icons.IconFlavor _ tool; labelledLeftFinchIcon: Icons.IconFlavor _ tool; labelledRightFinchIcon: Icons.IconFlavor _ tool; finchMenu: Menus.Menu; commentEntry, endCommentEntry: Menus.MenuEntry; xFudge: INTEGER = 2; entryHeight: INTEGER = 14; defaultToolHeight: NAT _ ViewerSpecs.openTopY/5; finchEnabledAtCheckpoint: BOOL _ FALSE; deleteButtonEnabled: BOOL _ FALSE; Which: TYPE = { self, other, originator, moderator }; <> <<>> 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[calledPartyText, 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 = { -- CommandTool 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]; }; <<>> 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[complain: FALSE, moreToCome: TRUE]; IF pauseNeededInMs#0 THEN Process.Pause[Process.MsecToTicks[pauseNeededInMs]]; -- Maybe a status check wait loop here ?? FinchTool.CallByDescription[description: callee, residence: wantResidence]; <> }; RedialCmd: Commander.CommandProc = { -- CommandTool 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 cDesc: ConvDesc _ NARROW[data]; IF cDesc#NIL THEN ShowRetriedCall[cDesc]; RedialIt[cDesc: cDesc]; }; <<>> RedialHomeCProc: PROC [data: REF] = { -- Redial from TiogaButton in conversation log cDesc: ConvDesc _ NARROW[data]; IF cDesc#NIL THEN ShowRetriedCall[cDesc]; RedialIt[cDesc: cDesc, wantResidence: TRUE]; }; <<>> RedialIt: PROC [cDesc: ConvDesc, wantResidence: BOOL_FALSE] = { activeCDesc: FinchSmarts.ConvDesc = GetSelectedDesc[]; IF activeCDesc#NIL AND activeCDesc.situation.self.state > $parsing THEN { Report["Call already in progress"]; RETURN; }; IF cDesc.partyInfo=NIL OR cDesc.partyInfo.ixOriginator = ThParty.nullIx THEN Report["Unknown caller/callee"] ELSE DoPhone[SimplifyName[cDesc, $other], wantResidence]; }; JoinCmd: Commander.CommandProc = { [] _ FinchSmarts.Join[CommandTool.NextArgument[cmd]]; }; AnswerCmd: Commander.CommandProc = { -- CommandTool Answer command <> ENABLE UNWIND => cmdHandle _ NIL; cDesc: ConvDesc = GetSelectedDesc[]; cmdHandle _ cmd; AnswerIt[cDesc: cDesc]; cmdHandle _ NIL; }; 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; action: Menus.MouseButton _ $red; IF CheckForMouseButtonSpec[cmd.commandLine, " middle", FALSE, TRUE].isMatch THEN action _ $yellow; IF CheckForMouseButtonSpec[cmd.commandLine, " right", FALSE, TRUE].isMatch THEN action _ $blue; cmdHandle _ cmd; [] _ HangItUp[complain: TRUE, action: action]; cmdHandle _ NIL; }; Hangup: PUBLIC Menus.MenuProc = { -- Hang up from Disconnect button in Finch Tool []_HangItUp[complain: TRUE, action: mouseButton]; }; <> HangupQuietly: PUBLIC Menus.MenuProc = { []_HangItUp[complain: FALSE, action: mouseButton]; }; <> <<>> HangupCProc: PROC [data: REF] = { -- Hang up from TiogaButton in conversation log []_HangItUp[complain: TRUE, cDesc: NARROW[data]]; }; <<>> ToggleActiveState: PROC [data: REF] = { -- Hang up from TiogaButton in conversation log newState: Thrush.StateInConv; cDesc: ConvDesc _ NARROW[data]; IF cDesc=NIL THEN { Report["No active or held conversation"]; RETURN; }; SELECT cDesc.situation.self.state FROM $active => newState _ $inactive; $inactive => newState _ $active; ENDCASE => { Report["Conversation not active or held"]; RETURN; }; FinchSmarts.DisconnectCall[ -- Function now misnamed convID: cDesc.situation.self.convID, newState: newState]; }; <<>> HangItUp: PROC[complain: BOOL_TRUE, moreToCome: BOOL_FALSE, cDesc: ConvDesc_NIL, action: Menus.MouseButton_$red] RETURNS [pauseNeededInMs: CARDINAL_0] = TRUSTED { state: Thrush.StateInConv; isTrunk: BOOL; previousSituation: Thrush.ConvEventBody_[]; 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 }; $inactive, -- flashing for a clear line; not needed $reserved, $parsing => IF moreToCome THEN RETURN; ENDCASE; IF isTrunk THEN pauseNeededInMs _ IF cDesc.situation.self.state=active THEN 2000 ELSE 500; IF action = $yellow THEN previousSituation _ cDesc.situation; FinchSmarts.DisconnectCall[ convID: cDesc.situation.self.convID, newState: SELECT action FROM $red => $idle, $yellow => $inactive, ENDCASE => $active]; IF action = $yellow THEN { previousSituation.time _ cDesc.situation.time; cDesc.previousSituation _ previousSituation; }; }; ConversationMgmtProc: TiogaButtons.TiogaButtonProc = { <> <> IF deleteButtonEnabled AND control AND shift AND mouseButton=blue THEN { <> TiogaButtons.DeleteButton[button]; RETURN; }; 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] ELSE MBQueue.QueueClientAction[q: finchQueue, proc: ToggleActiveState, data: clientData] }; 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[ target, prefix: ROPE, trueAlready: BOOL, ifMatch: BOOL] RETURNS [remainingTarget: ROPE, isMatch: BOOL] = { pLen: INT _ prefix.Length[]; remainingTarget _ target; isMatch _ trueAlready; IF target.Length[] >= pLen AND prefix.Equal[target.Substr[len: pLen]] THEN { remainingTarget _ target.Substr[start: pLen]; isMatch _ 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]; }; MakeListener: Buttons.ButtonProc = { <<[parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE]>> Menus.ReplaceMenuEntry[finchToolHandle.outer.menu, endCommentEntry, commentEntry]; ViewerOps.PaintViewer[finchToolHandle.outer, menu]; Status["Listening only enabled"]; }; MakeCommentator: Buttons.ButtonProc = { <<[parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE]>> Menus.ReplaceMenuEntry[finchToolHandle.outer.menu, commentEntry, endCommentEntry]; ViewerOps.PaintViewer[finchToolHandle.outer, menu]; Status["Permission to comment granted"]; }; <<>> <<>> <> <<>> 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[].nb; 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] = { <'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; }; TDirCmd: Commander.CommandProc = { ENABLE UNWIND => cmdHandle _ NIL; fileName: ROPE _ CommandTool.NextArgument[cmd]; cP: FS.ComponentPositions; IF fileName = NIL THEN RETURN[$Failed, "No Directory name Supplied"]; [fileName, cP] _ FS.ExpandName[fileName]; IF cP.ext.length=0 THEN fileName _ fileName.Substr[len: cP.ext.start] .Concat[".TelephoneDirectory"] .Concat[fileName.Substr[start: cP.ver.start, len: cP.ver.length]]; cmdHandle _ cmd; FinchTool.BuildDirectory[directoryFile: fileName, 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, $FinchTool, ReportSystemState, ReportConversationState, ReportRequestState]; 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 ENTRY PROC[disable: BOOL_TRUE] = TRUSTED { handle: FinchTool.Handle = finchToolHandle; IF handle=NIL THEN RETURN; handle.keepTwiddling _ FALSE; NOTIFY handle.twiddleWait; Process.SetTimeout[@handle.twiddleWait, 1]; WAIT handle.twiddleWait; Report["Disconnecting . . ."]; FinchSmarts.UninitFinchSmarts[disable]; }; ButtonStopFinch: Buttons.ButtonProc = { StopFinch[]; }; UnfinchCmd: Commander.CommandProc = { ENABLE UNWIND => cmdHandle _ NIL; cmdHandle _ cmd; StopFinch[]; cmdHandle _ NIL; }; 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[]; ViewerEvents.UnRegisterEventProc[proc: finchToolHandle.scrollEvent, event: open]; 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[StopF]; }; StopF: PROC = { IF finchToolHandle#NIL THEN ViewerOps.DestroyViewer[finchToolHandle.outer]; }; 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"]; }; ReportSystemState: PROC[enabled, connected, voicePath: BOOL] = { cDesc: ConvDesc _ GetSelectedDesc[checkActive: FALSE]; UpdateIconE[finchToolHandle]; 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; }; <> <<>> joinReason: Thrush.Reason _ NIL; -- or $join; here to allow testing ReportConversationState: ENTRY PROC[ nb: NB, cDesc: ConvDesc, remark: Rope.ROPE ] = { ENABLE UNWIND => NULL; s: IO.STREAM; difficulty: BOOL_FALSE; 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; state _ cDesc.situation.self.state; IF state=initiating THEN RETURN; -- wait until we know more about this conv IF ~cDesc.originatorRecorded AND cDesc.numParties>1 AND cDesc.partyInfo#NIL THEN SELECT cDesc.partyInfo.ixOriginator FROM <> cDesc.partyInfo.ixSelf => SetContents[finchToolHandle.calledPartyText, cDesc, $other]; ThParty.nullIx => NULL; ENDCASE => SetContents[finchToolHandle.callingPartyText, cDesc, $originator]; UpdateIcon[finchToolHandle]; s _ IO.ROS[]; IF state=reserved OR state=parsing THEN s.PutRope[printLabel[state]] ELSE { IF cDesc.firstReport THEN { TRUSTED {Process.Detach[ FORK DurationTimer[cDesc] ]}; -- increments every second cDesc.firstReport _ FALSE; }; s.PutF["%g\t%g\t%g\t%g", rope[CallDateAndTime[cDesc]], rope[CallStatus[cDesc]], rope[CallDuration[cDesc]], rope[CallerAndOrCallee[cDesc]]]; IF cDesc.numParties>1 AND cDesc.subject#NIL THEN s.Put[rope["\t"], rope[cDesc.subject]]; }; IF state = $failed THEN -- if failed, elaborate. s.PutF["%s", rope[ SELECT cDesc.situation.reason FROM $busy => "", -- reported above in status field $notFound => " -- no valid party found", $error => " -- connection failed", $noCircuits => " -- no circuits", ENDCASE => " -- unexplained failure" ] ]; IF cDesc.situation.comment#NIL THEN s.PutF[" (%s)", rope[cDesc.situation.comment]]; IF remark#NIL THEN s.PutF[" [%s]", rope[remark]]; <> IF finchToolHandle.backgrounding THEN SELECT state FROM $notified => { selectedDesc: ConvDesc _ GetSelectedDesc[checkActive: FALSE]; acceptNew: BOOL_TRUE; IF selectedDesc#NIL THEN SELECT selectedDesc.situation.self.state FROM $idle, $inactive, $neverWas => NULL; -- OK to accept new $active => IF ConvPriority[cDesc] > ConvPriority[selectedDesc] THEN [] _ HangItUp[cDesc: selectedDesc, action: $yellow] -- Put active call on hold. ELSE acceptNew _ FALSE; ENDCASE => acceptNew _ FALSE; FinchSmarts.DisconnectCall[ convID: cDesc.situation.self.convID, newState: IF acceptNew THEN $ringing ELSE $idle, reason: IF acceptNew THEN NIL ELSE $busy ]; <> }; $idle, $inactive => { bestCDesc: ConvDesc _ NIL; convs: LIST OF ConvDesc _ FinchSmarts.CurrentConversations[]; FOR convs1: LIST OF ConvDesc _ convs, convs1.rest WHILE convs1#NIL DO <> pSitBest: Thrush.ConvEventBody_[]; pSit1: Thrush.ConvEventBody_[]; cDesc1: ConvDesc _ convs1.first; IF cDesc1#NIL THEN pSit1 _ cDesc1.previousSituation; IF cDesc1 = cDesc OR cDesc1.situation.self.state # $inactive OR pSit1.self.state # $active OR ConvPriority[cDesc1] >= 500 THEN LOOP; -- don't make evergreen connections IF bestCDesc = NIL OR ConvPriority[cDesc1] > ConvPriority[bestCDesc] OR (ConvPriority[cDesc1] = ConvPriority[bestCDesc] AND BasicTime.Period[ from: pSitBest.time, to: pSit1.time] > 0) THEN { bestCDesc _ cDesc1; pSitBest _ pSit1; }; ENDLOOP; IF bestCDesc#NIL THEN [] _ HangItUp[cDesc: bestCDesc, action: $blue]; -- reactivate some background call. }; ENDCASE => NULL; -- no special action <> <