-- ConversationImpl.mesa -- McGregor, February 2, 1983 12:38 pm -- Paul Rovner, March 21, 1983 2:46 pm -- Last Edited by: Greene, April 20, 1984 3:39:11 pm PST -- Last Edited by: Rumph, June 15, 1984 4:02:16 pm PDT -- Last Edited by: Nickell, June 26, 1984 2:10:13 pm PDT DIRECTORY DefaultRegistry USING [MakeRegistryExplicit], IO USING [GetLineRope], InputFocus USING [GetInputFocus], Labels USING [Set], Menus USING[MenuProc, CreateMenu], Process USING [Detach, Pause, SecondsToTicks], Rope USING[ROPE, Concat, Equal, Translate, Cat, Length, Substr], RopeReader USING [Backwards, Create, SetPosition, Ref], TalkerOpsRpcControl USING[InterfaceRecord, ImportNewInterface], TalkerPrivate USING[Handle, Conversant, ConversantObject, EnumerateConversations, GetLocalName], TextLooks USING [Looks], TEditLocks USING [LockDocAndTdd, UnlockDocAndTdd], TEditDocument USING [TEditDocumentData], TextNode USING [Location, LastLocWithin, NarrowToTextNode], TextEdit USING [GetRope, InsertRope], TiogaOps USING [CallWithLocks, Ref], TypeScript USING [WaitUntilCharsAvail], ViewerClasses USING [Viewer], ViewerOps USING [PaintViewer, BlinkIcon], ViewerTools USING [GetSelectionContents]; ConversationImpl: CEDAR MONITOR -- protects individual conversations LOCKS h.LOCK USING h: Handle IMPORTS DefaultRegistry, IO, Labels, Menus, Process, Rope, TalkerOpsRpcControl, TalkerPrivate, TypeScript, ViewerOps, ViewerTools, TextNode, RopeReader, TextEdit, TEditLocks, TiogaOps, InputFocus EXPORTS TalkerPrivate = BEGIN OPEN Rope, TalkerPrivate; ropeReader: RopeReader.Ref = RopeReader.Create[]; sentMark: ROPE _ " \t"; --Used to identify ropes that were received (space tab) -- ******* Processes -- FORK'd for each conversation; performs the only remote calls Puller: PUBLIC PROC[h: Handle] = { UNTIL PullerStopRequested[h] DO TypeScript.WaitUntilCharsAvail[h.ts ! ANY => EXIT]; IF PullerStopRequested[h] THEN EXIT; PullLine[h]; ENDLOOP; }; PullerStopRequested: ENTRY PROC[h: Handle] RETURNS[BOOL] = { ENABLE UNWIND => NULL; RETURN[h.pullerStopRequested]; }; -- end PullerStopRequested NewOutgoingLine: INTERNAL PROC[h: Handle, c: Conversant, line: ROPE] = { prev: LIST OF ROPE _ NIL; FOR x: LIST OF ROPE _ c.outGoingLines, x.rest UNTIL x = NIL DO prev _ x ENDLOOP; IF prev = NIL THEN c.outGoingLines _ CONS[line, NIL] ELSE prev.rest _ CONS[line, NIL]; NOTIFY c.newOutGoingLine; }; PullLine: PROC[h: Handle] = { proc: INTERNAL PROC[c: Conversant] RETURNS[stop: BOOL _ FALSE] = {NewOutgoingLine[h, c, line]}; line: ROPE _ h.tsInStream.GetLineRope[! ANY => GOTO return]; WHILE Length[line]>Length[sentMark] AND Equal[Substr[line, 0, Length[sentMark]], sentMark] DO line _ h.tsInStream.GetLineRope[! ANY => GOTO return]; ENDLOOP; IF line = NIL THEN RETURN; line _ Concat[line, "\n"]; [] _ EnumerateConversants[h, proc]; EXITS return => RETURN; }; -- FORKED for each conversant SendOutGoingLines: PUBLIC PROC[h: Handle, c: Conversant] = { DO { line: ROPE = WaitForLineToSend[h, c]; IF line = NIL THEN EXIT; -- kill this process TRUSTED {c.ir.PutMyLine[GetLocalName[], line ! ANY => GOTO disConnected]}; EXITS disConnected => {DestroyConversant[h, c]; RemakeStatusLabel[h]; EXIT} }; ENDLOOP; }; WaitForLineToSend: ENTRY PROC[h: Handle, c: Conversant] RETURNS[line: ROPE _ NIL] = { ENABLE UNWIND => NULL; UNTIL c.outGoingLines # NIL OR c.senderToStop DO WAIT c.newOutGoingLine ENDLOOP; IF c.senderToStop THEN RETURN; line _ c.outGoingLines.first; c.outGoingLines _ c.outGoingLines.rest; }; -- Blinker process: FORKED for this module; blinks talker icons that have new messages Blinker: PROC = { DO p: PROC[h: Handle] RETURNS[stop: BOOL _ FALSE] = {MaybeBlinkIt[h]}; [] _ EnumerateConversations[p]; TRUSTED {Process.Pause[Process.SecondsToTicks[6]]}; ENDLOOP }; MaybeBlinkIt: ENTRY PROC[h: Handle] = { ENABLE UNWIND => NULL; IF h.shouldBlink AND (NOT h.container.destroyed) AND h.container.iconic THEN ViewerOps.BlinkIcon[h.container]; }; BlinkOn: ENTRY PROC[h: Handle] = { ENABLE UNWIND => NULL; h.shouldBlink _ TRUE; }; BlinkOff: PUBLIC ENTRY PROC[h: Handle] = { ENABLE UNWIND => NULL; h.shouldBlink _ FALSE; }; -- ******* menu procedures ShowStatusHit: PUBLIC Menus.MenuProc = { -- [parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL] RemakeStatusLabel[NARROW[clientData, Handle]]; }; JoinConversationHit: PUBLIC Menus.MenuProc = { -- [parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL] OPEN D: DefaultRegistry; h: Handle _ NARROW[clientData, Handle]; conversantName: ROPE _ D.MakeRegistryExplicit[ ViewerTools.GetSelectionContents[], LIST[$Talk, $Default] ]; JoinConversation[h, conversantName]; RemakeStatusLabel[h]; RemoveConversantFromOtherConversations[h, conversantName]; }; -- end JoinConversationHit JoinConversation: PUBLIC ENTRY PROC[h: Handle, conversantName: ROPE] = { ENABLE UNWIND => NULL; ir: TalkerOpsRpcControl.InterfaceRecord _ NIL; IF InnerFindConversant[h, conversantName] # NIL THEN RETURN; -- redundant. No-op ir _ TalkerOpsRpcControl.ImportNewInterface [interfaceName: [instance: LowerRope[conversantName]] ! ANY => CONTINUE]; IF ir = NIL THEN RETURN; h.conversants _ CONS[NEW[ConversantObject _ [ir: ir, conversantName: conversantName]], h.conversants]; FOR cl: LIST OF Conversant _ h.conversants, cl.rest UNTIL cl = NIL DO TRUSTED{Process.Detach[FORK cl.first.ir.JoinWith[GetLocalName[], conversantName]]}; ENDLOOP; TRUSTED {Process.Detach[FORK SendOutGoingLines[h, h.conversants.first]]}; }; -- ******* destruction procedures KillConversation: PUBLIC ENTRY PROC[h: Handle] = { ENABLE UNWIND => NULL; h.pullerStopRequested _ TRUE; DestroyConversants[h]; FOR cl: LIST OF Conversant _ h.conversants, cl.rest UNTIL cl = NIL DO TRUSTED {cl.first.ir.DealMeOut[GetLocalName[] ! ANY => CONTINUE]} ENDLOOP; h.conversants _ NIL; }; RemoveConversantFromOtherConversations: PUBLIC PROC[h: Handle, conversantName: ROPE] = { p: PROC[otherH: Handle] RETURNS[stop: BOOL _ FALSE] = { c: Conversant; IF otherH = h THEN RETURN; c _ FindConversant[otherH, conversantName]; IF c # NIL THEN {DestroyConversant[otherH, c]; RETURN[TRUE]}; }; [] _ EnumerateConversations[p]; -- remove conversant from any other conversation }; EnumerateConversants: ENTRY PROC[h: Handle, proc: PROC[Conversant] RETURNS[stop: BOOL]] RETURNS[c: Conversant _ NIL] = { ENABLE UNWIND => NULL; FOR cl: LIST OF Conversant _ h.conversants, cl.rest UNTIL cl = NIL DO IF proc[cl.first] THEN RETURN[cl.first]; ENDLOOP; }; -- end EnumerateConversants DestroyConversants: INTERNAL PROC[h: Handle] = { FOR cl: LIST OF Conversant _ h.conversants, cl.rest UNTIL cl = NIL DO cl.first.senderToStop _ TRUE; NOTIFY cl.first.newOutGoingLine ENDLOOP; h.container.menu _ Menus.CreateMenu[lines: 1]; ViewerOps.PaintViewer[h.container, all ! ANY => CONTINUE] }; DestroyConversant: ENTRY PROC[h: Handle, c: Conversant] = { prev: LIST OF Conversant _ NIL; FOR cl: LIST OF Conversant _ h.conversants, cl.rest UNTIL cl = NIL DO IF cl.first = c THEN {IF prev = NIL THEN h.conversants _ cl.rest ELSE prev.rest _ cl.rest; EXIT}; prev _ cl; ENDLOOP; c.senderToStop _ TRUE; NOTIFY c.newOutGoingLine; IF h.conversants = NIL THEN DestroyConversants[h]; }; -- ******* viewer output PaintLine: PUBLIC PROC[h: Handle, line: ROPE] = BEGIN tdd: TEditDocument.TEditDocumentData _ NARROW[h.ts.data]; caret: BOOL = (InputFocus.GetInputFocus[].owner=h.ts); MakeEdit: PROC [root: TiogaOps.Ref] = BEGIN italic: TextLooks.Looks; endPos: TextNode.Location _ TextNode.LastLocWithin[tdd.text]; italic['i] _ TRUE; IF endPos.where#0 THEN BEGIN -- find the last CR RopeReader.SetPosition[ropeReader, TextEdit.GetRope[TextNode.NarrowToTextNode[endPos.node]], endPos.where]; WHILE endPos.where>0 AND RopeReader.Backwards[ropeReader] # 15C DO endPos.where _ endPos.where-1; ENDLOOP; END; [] _ TextEdit.InsertRope[root: tdd.text, dest: TextNode.NarrowToTextNode[endPos.node], inherit: FALSE, looks: italic, rope: line, destLoc: endPos.where]; IF caret THEN h.ts.class.notify[h.ts, LIST[$NextPlaceholder]]; -- hack; force caret to end END; line _ Concat[sentMark, line]; IF h.ts.destroyed THEN RETURN; IF caret THEN TiogaOps.CallWithLocks[MakeEdit] ELSE BEGIN [] _ TEditLocks.LockDocAndTdd[tdd, "Talker"]; MakeEdit[NIL]; TEditLocks.UnlockDocAndTdd[tdd]; END; IF h.container.iconic THEN h.shouldBlink _ TRUE; END; RemakeStatusLabel: PUBLIC ENTRY PROC[h: Handle] = { ENABLE UNWIND => NULL; Labels.Set[h.statusLabel, InnerComputeStatus[h]]; ViewerOps.PaintViewer[h.statusLabel, all]; }; -- end NotifyLineRead -- ******* information, search procs ComputeStatus: PUBLIC ENTRY PROC[h: Handle] RETURNS[ROPE] = { ENABLE UNWIND => NULL; RETURN[InnerComputeStatus[h]]; }; InnerComputeStatus: INTERNAL PROC[h: Handle] RETURNS[status: ROPE _ "STATUS: "] = { IF h.conversants = NIL THEN status _ Concat[status, "No Conversants"] ELSE {status _ Cat[status, "talking with "]; FOR cl: LIST OF Conversant _ h.conversants, cl.rest UNTIL cl = NIL DO status _ Cat[status, cl.first.conversantName, IF cl.first.outGoingLines # NIL THEN "(attempting to send line) " ELSE " "]; ENDLOOP}; }; IsViewer: PUBLIC ENTRY PROC[h: Handle, viewer: ViewerClasses.Viewer] RETURNS[BOOL] = { ENABLE UNWIND => NULL; RETURN[h.container = viewer]; }; FindConversant: PUBLIC ENTRY PROC[h: Handle, conversantName: ROPE] RETURNS[Conversant] = { ENABLE UNWIND => NULL; RETURN[InnerFindConversant[h, conversantName]]; }; InnerFindConversant: INTERNAL PROC[h: Handle, conversantName: ROPE] RETURNS[c: Conversant _ NIL] = { FOR cl: LIST OF Conversant _ h.conversants, cl.rest UNTIL cl = NIL DO IF Equal[conversantName, cl.first.conversantName, FALSE] THEN RETURN[cl.first]; ENDLOOP; }; -- ******* utility procedures LowerRope: PUBLIC PROC[r: ROPE] RETURNS[ROPE] = { trans: PROC[old: CHAR] RETURNS[new: CHAR] = {RETURN[IF old IN ['A..'Z] THEN old + ('a - 'A) ELSE old]}; RETURN[Translate[base: r, translator: trans]]; }; -- end LowerRope -- START HERE -- FORK the blinker TRUSTED{Process.Detach[FORK Blinker[]]}; END.