-- ConversationImpl.mesa
-- McGregor, February 2, 1983 12:38 pm
-- Paul Rovner, March 21, 1983 2:46 pm
DIRECTORY
TextLooks USING [Looks],
TEditLocks USING [LockDocAndTdd, UnlockDocAndTdd],
TEditDocument USING [TEditDocumentData],
TextNode USING [Location, LastLocWithin, NarrowToTextNode],
TextEdit USING [GetRope, InsertRope],
TiogaOps USING [CallWithLocks, Ref],
InputFocus USING [GetInputFocus],
Labels USING [Set],
IO USING [GetSequence, GetChar],
Menus USING[MenuProc, CreateMenu],
Process USING [Detach, Pause, SecondsToTicks],
Rope USING[ROPE, Concat, Find, Equal, Translate, Lower, Cat],
RopeReader USING [Backwards, Create, SetPosition, Ref],
TalkerOpsRpcControl USING[InterfaceRecord, ImportNewInterface],
TalkerPrivate USING[Handle, Conversant, ConversantObject, EnumerateConversations,
GetLocalName],
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 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[];
-- ******* 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.GetSequence[! ANY => GOTO return];
[] ← h.tsInStream.GetChar[! ANY => CONTINUE]; -- heave the CR
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]
h: Handle ← NARROW[clientData, Handle];
conversantName: ROPE ← ViewerTools.GetSelectionContents[];
IF Find[conversantName, "."] = -1 THEN conversantName ← Concat[conversantName, ".pa"];
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;
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[Lower[old]]};
RETURN[Translate[base: r, translator: trans]];
}; -- end LowerRope
-- START HERE
-- FORK the blinker
TRUSTED{Process.Detach[FORK Blinker[]]};
END.