<> <> <> DIRECTORY BasicTime USING [ GMT, Now, Period ], Booting USING [ CheckpointProc, RollbackProc, RegisterProcs ], Buttons USING [ButtonProc, Create, ReLabel, SetDisplayStyle ], Commander USING [CommandProc, Handle, Register], Containers USING [ ChildXBound, ChildYBound, Container, Create ], FinchSmarts USING [ AnswerCall, ConvDesc, DisconnectCall, InitFinchSmarts, UninitFinchSmarts ], FinchTool, Icons USING [ IconFlavor ], IO, Labels USING [Create, Label], <> MBQueue USING [ Create, CreateButton, CreateMenuEntry, Queue ], Menus USING [AppendMenuEntry, CreateMenu, Menu, MenuProc ], Names USING [ CurrentRName ], Rope USING [ Cat, Equal, Find, Length, ROPE, Substr ], Rules USING [ Create ], Log USING [ Report, RegisterWhereToReport, WhereProc ], Process USING [ Pause, SecondsToTicks], Thrush USING [ ConversationHandle, IntervalSpec, NB, nullConvHandle, nullHandle, Reason, StateInConv, ThHandle ], TypeScript USING [ Create ], UserProfile USING [ Token ], VFonts USING [ Font, defaultFont ], ViewerClasses USING [ Viewer, ViewerClassRec, ViewerRec ], ViewerEvents USING [ EventProc, RegisterEventProc ], ViewerIO USING [ CreateViewerStreams ], ViewerLocks USING [ CallUnderWriteLock ], ViewerOps USING [AddProp, ComputeColumn, CreateViewer, DestroyViewer, FetchProp, PaintViewer, SetMenu, SetOpenHeight], ViewerSpecs USING [ openLeftTopY, openLeftWidth, openRightWidth ], ViewerTools USING [GetSelectionContents, GetContents, MakeNewTextViewer, SetContents, SetSelection ] ; FinchToolImpl: CEDAR PROGRAM IMPORTS BasicTime, Booting, Buttons, Commander, Containers, FinchSmarts, FinchTool, IO, Labels, MBQueue, Menus, Names, Rope, Rules, Log, Process, TypeScript, UserProfile, VFonts, ViewerEvents, ViewerIO, ViewerLocks, ViewerOps, ViewerSpecs, ViewerTools EXPORTS FinchTool = { OPEN IO, FinchTool; <> <<>> ConversationHandle: TYPE = Thrush.ConversationHandle; nullConvHandle: ConversationHandle = Thrush.nullConvHandle; ConvDesc: TYPE = FinchSmarts.ConvDesc; NB: TYPE = Thrush.NB; Reason: TYPE = Thrush.Reason; ROPE: TYPE = Rope.ROPE; Viewer: TYPE = ViewerClasses.Viewer; nullHandle: Thrush.ThHandle = Thrush.nullHandle; finchToolHandle: PUBLIC Handle; cmdHandle: Commander.Handle_NIL; printLabel: ARRAY Thrush.StateInConv OF Rope.ROPE_ALL["does not make sense"]; finchQueue: PUBLIC MBQueue.Queue _ MBQueue.Create[]; finchIcon: Icons.IconFlavor _ tool; finchMenu: Menus.Menu; finchStartMenu: Menus.Menu; finchConvMenu: Menus.Menu; xFudge: INTEGER = 4; entryHeight: INTEGER = 14; <> <<>> <> MakeFinchTool: PROC = BEGIN finchWidth: INTEGER; dif, wH: INTEGER; bt: Viewer; h: Handle; v: Viewer; IF finchToolHandle#NIL THEN { ViewerOps.PaintViewer[finchToolHandle.outer, all]; RETURN; }; finchToolHandle _ NEW[FinchToolRec]; h _ finchToolHandle; MakeMenus[]; finchToolHandle.outer _ Containers.Create[[-- outer container name: "Finch", -- name displayed in the caption iconic: FALSE, -- so tool will not be iconic (small) when first created column: left, -- initially in the left column menu: finchStartMenu,-- displaying our menu command openHeight: ViewerSpecs.openLeftTopY/4, -- See Walnut scrollable: FALSE ]]; -- inhibit user from scrolling contentsLabel v _ h.outer; finchWidth _ IF v.column=right THEN ViewerSpecs.openRightWidth ELSE ViewerSpecs.openLeftWidth; []_ViewerEvents.RegisterEventProc[ proc: UnfinchOnDestroy, event: destroy, filter: v, before: TRUE]; h.status _ FirstLabel[ name: " ", parent: v]; bt _ QueuedButton[name: "Phone", proc: CallProc, border: TRUE, sib: finchToolHandle.status, newLine: TRUE]; bt _ ImmediateButton[name: "Party:", proc: SetCalledPartyProc, border: FALSE, sib: bt]; h.calledPartyText _ NextRightTextViewer[sib: bt, w: finchWidth]; Containers.ChildXBound[h.outer, h.calledPartyText]; bt _ MakeRuler[sib: bt, h: 2]; <> wH _ v.ch; IF (dif _ (wH-bt.cy)) < 64 THEN { SetOpenHeight[v, wH + (64-dif)]; IF ~v.iconic THEN ViewerOps.ComputeColumn[v.column]; }; h.typescript _ MakeTypescript[sib: bt]; [h.tsIn, h.tsOut] _ ViewerIO.CreateViewerStreams[NIL, h.typescript]; ViewerOps.PaintViewer[v, all]; -- reflect above change END; MakeMenus: PROC = TRUSTED { <> finchStartMenu _ Menus.CreateMenu[]; Menus.AppendMenuEntry[ menu: finchStartMenu, entry: MBQueue.CreateMenuEntry[finchQueue, "Participate", ButtonStartFinch, finchToolHandle] ]; Menus.AppendMenuEntry[ menu: finchStartMenu, entry: MBQueue.CreateMenuEntry[finchQueue, "Directory", MakeDirectory, finchToolHandle] ]; <> finchMenu _ Menus.CreateMenu[]; Menus.AppendMenuEntry[ menu: finchMenu, entry: MBQueue.CreateMenuEntry[finchQueue, "Drop Out", ButtonStopFinch, finchToolHandle] ]; Menus.AppendMenuEntry[ menu: finchMenu, entry: MBQueue.CreateMenuEntry[finchQueue, "Answer", Answer, finchToolHandle] ]; Menus.AppendMenuEntry[ menu: finchMenu, entry: MBQueue.CreateMenuEntry[finchQueue, "Directory", MakeDirectory, finchToolHandle ] ]; Menus.AppendMenuEntry[ menu: finchMenu, entry: MBQueue.CreateMenuEntry[finchQueue, "Phone", CallProc, finchToolHandle ] ]; Menus.AppendMenuEntry[ menu: finchMenu, entry: MBQueue.CreateMenuEntry[finchQueue, "PhoneSelection", CallSelectedProc, finchToolHandle ] ]; Menus.AppendMenuEntry[ menu: finchMenu, entry: MBQueue.CreateMenuEntry[finchQueue, "Redial", ReDial, finchToolHandle ] ]; Menus.AppendMenuEntry[ menu: finchMenu, entry: MBQueue.CreateMenuEntry[finchQueue, "Disconnect", Hangup, finchToolHandle ] ]; <> finchConvMenu _ Menus.CreateMenu[]; Menus.AppendMenuEntry[ menu: finchConvMenu, entry: MBQueue.CreateMenuEntry[finchQueue, "Answer", Answer, finchToolHandle] ]; Menus.AppendMenuEntry[ menu: finchConvMenu, entry: MBQueue.CreateMenuEntry[finchQueue, "Disconnect", Hangup, finchToolHandle] ]; }; <> <<>> BuildConversationsWindow: PROC = { IF finchToolHandle.conversations#NIL AND ~finchToolHandle.conversations.destroyed THEN RETURN; finchToolHandle.conversations _ ViewerOps.CreateViewer[ flavor: $Container, paint: TRUE, info: [name: "Conversations", column: left, menu: finchConvMenu, openHeight: ViewerSpecs.openLeftTopY/8, iconic: FALSE, icon: finchIcon, inhibitDestroy: TRUE]]; ViewerOps.PaintViewer[finchToolHandle.outer, all]; ViewerOps.PaintViewer[finchToolHandle.conversations, all]; }; AddConvDesc: PROC[cViewer: Viewer, cDesc: ConvDesc] RETURNS [newV: Viewer] = { lastButton: Viewer; prevDesc: ConvDesc_NIL; isIconic: BOOL; BuildLine: PROC = { IF lastButton.parent = NIL THEN newV _ FirstButton[ q: finchQueue, name: NIL, proc: ConversationMgmtProc, width: 1024, parent: lastButton] ELSE newV _ AnotherButton[ q: finchQueue, name: NIL, proc: ConversationMgmtProc, width: 1024, sib: lastButton, newLine: TRUE]; }; lastButton _ NARROW[ViewerOps.FetchProp[cViewer, $lastButton]]; IF lastButton#NIL THEN prevDesc _ NARROW[ViewerOps.FetchProp[lastButton, $convDesc]] ELSE lastButton_ cViewer; isIconic _ IF lastButton.parent = NIL THEN lastButton.iconic ELSE lastButton.parent.iconic; IF isIconic THEN BuildLine[] ELSE ViewerLocks.CallUnderWriteLock[BuildLine,lastButton]; cDesc.clientData _ newV; ViewerOps.AddProp[newV, $convDesc, cDesc]; lastButton _ newV; ViewerOps.AddProp[cViewer, $lastButton, lastButton]; }; SelectEntryInConversations: PROC[entryButton: Viewer, reselect: BOOL_TRUE] RETURNS[sameAsBefore: BOOL] = { prevSelected: Viewer = NARROW[ViewerOps.FetchProp[entryButton.parent, $selectedEntry]]; IF prevSelected = entryButton AND reselect THEN RETURN[TRUE]; IF prevSelected # NIL AND ~prevSelected.destroyed THEN { Buttons.SetDisplayStyle[prevSelected, $BlackOnWhite]; ViewerOps.PaintViewer[prevSelected, all]; }; IF entryButton.destroyed THEN Report["Conversation is no longer available."] ELSE IF ~reselect THEN ViewerOps.AddProp[entryButton.parent, $selectedEntry, NIL] ELSE { Buttons.SetDisplayStyle[entryButton, $BlackOnGrey]; -- show it is selected ViewerOps.PaintViewer[entryButton, all]; ViewerOps.AddProp[entryButton.parent, $selectedEntry, entryButton]; }; RETURN[FALSE]; }; <> <<>> ReportSystemState: PROC[on: BOOL] = TRUSTED { IF finchToolHandle=NIL THEN RETURN; finchToolHandle.finchActive _ on; IF finchToolHandle.finchWasActive=finchToolHandle.finchActive THEN RETURN; finchToolHandle.finchWasActive_finchToolHandle.finchActive; ViewerOps.SetMenu[finchToolHandle.outer, IF finchToolHandle.finchActive THEN finchMenu ELSE finchStartMenu]; ViewerOps.PaintViewer[finchToolHandle.outer, menu]; IF finchToolHandle.finchActive THEN BuildConversationsWindow[] ELSE { IF finchToolHandle.conversations#NIL THEN { finchToolHandle.conversations.inhibitDestroy _ FALSE; ViewerOps.DestroyViewer[finchToolHandle.conversations]; }; finchToolHandle.conversations _ NIL; }; }; StartFinch: PUBLIC PROC[] = TRUSTED { handle: Handle _ finchToolHandle; IF handle=NIL THEN { MakeFinchTool[]; handle _ finchToolHandle; IF handle=NIL THEN RETURN; -- complain?-- }; Report["Connecting . . ."]; FinchSmarts.InitFinchSmarts[ ReportSystemState, ReportConversationState]; Report[IF handle.finchActive THEN "Finch is connected" ELSE "Could not connect", " to telephone server"]; }; StopFinch: PUBLIC PROC[] = TRUSTED { handle: 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"]; }; CheckActive: PUBLIC PROC[handle: Handle] RETURNS[active: BOOL_FALSE] = TRUSTED { IF handle#NIL AND handle.finchActive THEN RETURN[TRUE]; Status["Finch not connected to telephone system"]; }; <> <<>> ReportConversationState: PROC[ nb: NB, cDesc: ConvDesc, remark: Rope.ROPE ] = TRUSTED { s: IO.STREAM; difficulty: BOOL_FALSE; button: Viewer; state: Thrush.StateInConv; intervalSpec: Thrush.IntervalSpec; SELECT nb FROM -- should possibly interpret more success => NULL; ENDCASE => { Status[remark]; RETURN; }; <> IF finchToolHandle.conversations=NIL OR finchToolHandle.conversations.destroyed THEN BuildConversationsWindow[]; IF cDesc = NIL THEN RETURN; -- not much more can be done. button _ NARROW[cDesc.clientData]; IF button = NIL THEN button _ AddConvDesc[finchToolHandle.conversations, cDesc]; s _ IO.ROS[]; state_cDesc.cState.state; SELECT state FROM active => { cDesc.completed _ TRUE; cDesc.failed_FALSE; }; reserved, parsing => { cDesc.originator _ us; cDesc.completed _ FALSE; cDesc.conference _ FALSE; SELECT cDesc.cState.reason FROM busy, error, noCircuits => difficulty_TRUE; ENDCASE; }; ENDCASE => cDesc.failed_FALSE; IF (state#reserved AND state#parsing) OR difficulty THEN s.PutF["Call %s %s at %t", rope[SELECT cDesc.originator FROM us => "to", them=>"from", ENDCASE=>"to/from"], rope[cDesc.otherPartyDesc], time[cDesc.startTime] ]; <> IF difficulty OR state=idle THEN { s.PutF["%s%s, duration = %r", rope[IF difficulty THEN " was not successful" ELSE IF cDesc.completed THEN printLabel[state] ELSE " was abandoned"], rope[IF ~difficulty THEN "" ELSE SELECT cDesc.cState.reason FROM busy => " -- busy", error => " -- connection failed", noCircuits => " -- no circuits", ENDCASE => " for some reason" ], int[BasicTime.Period[cDesc.startTime, BasicTime.Now[]]] ]; } ELSE s.PutRope[printLabel[state]]; IF cDesc.cState.comment#NIL THEN s.PutF[" (%s)", rope[cDesc.cState.comment]]; IF remark#NIL THEN s.PutF[" [%s]", rope[remark]]; IF ~cDesc.failed THEN Buttons.ReLabel[ button: button, paint: TRUE, newName: s.RopeFromROS[] ]; IF difficulty THEN cDesc.failed_TRUE; [] _ SelectEntryInConversations[button, state#idle]; <>>> FOR intervals: LIST OF Thrush.IntervalSpec _ cDesc.newIntervals, intervals.rest WHILE intervals#NIL DO intervalSpec _ intervals.first; IF intervalSpec.type=request THEN LOOP; SELECT intervalSpec.direction FROM record => SELECT intervalSpec.type FROM started => Status["Please begin speaking"]; finished => Status["Thank you"]; ENDCASE => Status[NIL]; play => Report[ SELECT intervalSpec.type FROM started => "Playing message", finished => "End of message", ENDCASE => NIL]; ENDCASE; ENDLOOP; }; ParseCallee: PROC[fullCallee: ROPE] RETURNS [callee: ROPE, residence: BOOL_TRUE] = { endCallee: INT; callee_fullCallee; IF fullCallee.Equal["home", FALSE] THEN callee_Names.CurrentRName[] ELSE IF (endCallee_fullCallee.Find[" at home", 0, FALSE]) >= 0 THEN callee_fullCallee.Substr[len: endCallee] ELSE residence_FALSE; }; HangItUp: PROC[complain: BOOL] = TRUSTED { cDesc: ConvDesc = GetSelectedDesc[]; IF cDesc=NIL OR cDesc.cState.state=idle THEN {Report[" No conversation to leave"]; RETURN}; FinchSmarts.DisconnectCall[convID: cDesc.cState.credentials.convID]; }; GetSelectedDesc: PROC RETURNS [cDesc: ConvDesc_NIL] = { viewer: Viewer = finchToolHandle.conversations; selected: Viewer _ IF viewer=NIL THEN NIL ELSE NARROW[ViewerOps.FetchProp[viewer, $selectedEntry]]; IF ~CheckActive[finchToolHandle] OR selected = NIL THEN RETURN; cDesc _ NARROW[ViewerOps.FetchProp[selected, $convDesc]]; }; <