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 ], Convert USING [ RopeFromInt ], FinchSmarts USING [ AnswerCall, ConvDesc, DisconnectCall, Feep, GetCurrentConvID, InitFinchSmarts, StopSpeech, TextToSpeech, UninitFinchSmarts ], 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, ROPE, Substr ], Rules USING [ Create ], Thrush USING [ ConversationHandle, IntervalSpec, IntervalSpecs, NB, nullConvHandle, nullHandle, ProseSpecs, ProseSpec, Reason, StateInConv, ThHandle ], 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, Convert, FinchSmarts, FinchTool, Icons, IO, Labels, MBQueue, Menus, Nice, Process, Rope, Rules, TiogaOps, TypeScript, UserProfile, VFonts, ViewerEvents, ViewerIO, ViewerLocks, ViewerOps, ViewerSpecs, ViewerTools, VoiceUtils EXPORTS FinchTool = { OPEN IO; 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 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 = { calledPartyText: Rope.ROPE _ ViewerTools.GetSelectionContents[]; DoPhone[ViewerTools.GetSelectionContents[], SELECT mouseButton FROM red, yellow => FALSE, blue=> TRUE, ENDCASE=>ERROR]; }; CalledPartyProc: Buttons.ButtonProc = { 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 = { 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 = { 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 = { cmd.commandLine _ " home\n"; [result, msg] _ PhoneCmd[cmd]; }; RedialCmd: Commander.CommandProc = { IF ~CheckActive[finchToolHandle] THEN RETURN; DoPhone[ViewerTools.GetContents[finchToolHandle.calledPartyText], FALSE]; }; DoPhone: PROC[calledPartyText: Rope.ROPE, wantResidence: BOOL_FALSE] = { callee, newCalledPartyText: Rope.ROPE; residence: BOOL; pauseNeededInMs: CARDINAL; IF ~CheckActive[finchToolHandle] OR calledPartyText = NIL OR calledPartyText.Length[]=0 THEN RETURN; [callee, newCalledPartyText, residence] _ ParseCallee[calledPartyText]; IF ~residence AND wantResidence THEN { residence _ TRUE; newCalledPartyText _ newCalledPartyText.Concat[" at home"]; }; 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: residence]; -- Launch the call. }; Answer: Menus.MenuProc = TRUSTED { viewer: Viewer = finchToolHandle.conversations; selected: Viewer _ IF viewer=NIL THEN NIL ELSE NARROW[ViewerOps.FetchProp[viewer, $selectedEntry]]; cDesc: ConvDesc = GetSelectedDesc[]; IF cDesc = NIL OR (SELECT cDesc.cState.state FROM pending, ringing =>FALSE, ENDCASE=>TRUE) THEN { Report[" No conversation to join"]; RETURN; }; FinchSmarts.AnswerCall[convID: cDesc.cState.credentials.convID]; }; ConversationMgmtProc: Buttons.ButtonProc = { viewer: Viewer = NARROW[parent]; IF ~CheckActive[finchToolHandle] THEN RETURN; []_ SelectEntryInConversations[viewer]; 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.cState.state; SELECT state FROM idle => { IF complain THEN Report[" No conversation to leave"]; RETURN }; reserved, parsing => IF moreToCome THEN RETURN; ENDCASE; pauseNeededInMs _ IF cDesc.cState.state=active THEN 2000 ELSE 500; FinchSmarts.DisconnectCall[convID: cDesc.cState.credentials.convID]; }; ParseCallee: PROC[fullCallee: ROPE] RETURNS [callee: ROPE, fCallee: ROPE, residence: BOOL_FALSE] = { endCallee: INT; fCallee _ CheckForMouseButtonSpec[fullCallee, "left ", ""]; fCallee _ CheckForMouseButtonSpec[fCallee, "middle ", ""]; fCallee _ CheckForMouseButtonSpec[fCallee, "right ", " at home"]; callee _ fCallee; IF fCallee.Equal["home", FALSE] THEN RETURN[VoiceUtils.CurrentRName[], VoiceUtils.CurrentRName[].Concat[" at home"], TRUE]; IF (endCallee_fCallee.Find[" at home", 0, FALSE]) >= 0 THEN RETURN[fCallee.Substr[len: endCallee], fCallee, TRUE]; }; CheckForMouseButtonSpec: PROC[fullCallee, pattern, replacement: ROPE] RETURNS [fCallee: ROPE] = { pLen: INT _ pattern.Length; fCallee _ fullCallee; IF fullCallee.Length[]>=pLen AND pattern.Equal[fullCallee.Substr[len: pLen]] THEN fCallee _ fullCallee.Substr[start: pLen].Concat[replacement]; }; 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]]; }; speakTextFeedbackLen: INT _ 40; dectalkParaSep: Rope.ROPE _ " \033P;z.+\033\\"; SpeakSelectedProc: Buttons.ButtonProc = { IF ~CheckActive[finchToolHandle] THEN RETURN; SELECT mouseButton FROM red => SpeakTextWithFeedback[TRUE]; yellow => NULL; blue => SpeakTextWithFeedback[FALSE ]; ENDCASE => NULL; }; SpeakTextCmd: Commander.CommandProc = { textToSpeak: Rope.ROPE _ cmd.commandLine.Substr[start: 1, len: cmd.commandLine.Length[]-2]; IF ~CheckActive[finchToolHandle] THEN RETURN; IF textToSpeak.Length[]#0 THEN SpeakTextWithFeedback[TRUE, textToSpeak]; }; SpeakTextWithFeedback: PROC [queueIt: BOOL, text: ROPE_NIL] ~ { IF text=NIL THEN text _ RopeWithNodesFromSelection[]; IF text = NIL THEN RETURN; Status[ IF ~queueIt THEN "Flushing text. " ELSE NIL, "Speaking text: \"", text.Substr[len: speakTextFeedbackLen], IF text.Length[] > speakTextFeedbackLen THEN "...\"" ELSE "\"" ]; [] _ FinchSmarts.TextToSpeech[text: text, queueIt: queueIt]; }; RopeWithNodesFromSelection: PROC [nodeEnd: Rope.ROPE _ dectalkParaSep] RETURNS [tiogaopstext: Rope.ROPE _ NIL] ~ { start, end: TiogaOps.Location; [start: start, end: end] _ TiogaOps.GetSelection[]; FOR node: TiogaOps.Ref _ start.node, TiogaOps.StepForward[node] DO IF node = start.node THEN IF node = end.node THEN { tiogaopstext _ Rope.Substr[base: TiogaOps.GetRope[node], start: start.where, len: end.where-start.where+1]; RETURN; } ELSE tiogaopstext _ Rope.Substr[base: TiogaOps.GetRope[node], start: start.where] ELSE IF node = end.node THEN { tiogaopstext _ Rope.Cat[tiogaopstext, nodeEnd, Rope.Substr[base: TiogaOps.GetRope[node], len: end.where+1]]; RETURN; } ELSE tiogaopstext _ Rope.Cat[tiogaopstext, nodeEnd, TiogaOps.GetRope[node]]; ENDLOOP; }; StopSpeechProc: Buttons.ButtonProc = { text: Rope.ROPE; text _ ViewerTools.GetSelectionContents[]; IF ~CheckActive[finchToolHandle] OR text = NIL THEN RETURN; Status["Flushing text."]; [] _ FinchSmarts.StopSpeech[]; }; StopSpeakingCmd: Commander.CommandProc = { IF ~CheckActive[finchToolHandle] THEN RETURN; []_FinchSmarts.StopSpeech[]; }; FeepCmd: Commander.CommandProc = { textToFeep: Rope.ROPE _ FeepValue[cmd.commandLine.Substr[start: 1, len: cmd.commandLine.Length[]-2]]; IF ~CheckActive[finchToolHandle] THEN RETURN; [] _ FinchSmarts.Feep[textToFeep]; }; FeepValue: PUBLIC PROC[text: ROPE] RETURNS [feepText: ROPE] = { 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 ]; StartFinch: PUBLIC PROC[] = TRUSTED { handle: FinchTool.Handle _ finchToolHandle; IF handle=NIL THEN { MakeFinchTool[]; handle _ finchToolHandle; IF handle=NIL THEN RETURN; -- complain?-- }; Report["Connecting . . ."]; FinchSmarts.InitFinchSmarts[ serverInstance, ReportSystemState, ReportConversationState]; 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] = TRUSTED { 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; intervalSpec: Thrush.IntervalSpec; proseSpec: Thrush.ProseSpec; currentConvID: ConversationHandle _ FinchSmarts.GetCurrentConvID[]; curConv: BOOL_FALSE; SELECT nb FROM -- should possibly interpret more success => NULL; ENDCASE => { Status[remark]; RETURN; }; IF cDesc = NIL THEN RETURN; -- not much more can be done. curConv _ currentConvID=nullConvHandle OR currentConvID=cDesc.cState.credentials.convID; 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.failed_FALSE; }; reserved, parsing => { cDesc.originator _ us; cDesc.conference _ FALSE; SELECT cDesc.cState.reason FROM busy, error, noCircuits => difficulty_TRUE; ENDCASE; }; ENDCASE => cDesc.failed_FALSE; IF ~cDesc.weOriginated AND state > parsing THEN { SetContents: PROC[v: ViewerClasses.Viewer, cDesc: ConvDesc] = { contents: ROPE _ RepairIntelnet[cDesc.otherPartyDesc]; i: INT_contents.Find["(", 0, FALSE]; IF i>0 THEN contents _ contents.Substr[len: i-1]; ViewerTools.SetContents[v, contents, TRUE]; cDesc.weOriginated _ TRUE; }; SELECT cDesc.originator FROM us => { SetContents[finchToolHandle.calledPartyText, cDesc]; }; them, unknown => { SetContents[finchToolHandle.callingPartyText, cDesc]; }; ENDCASE; }; IF curConv THEN { 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, difficulty OR state=idle]; }; 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_RepairIntelnet[cDesc.otherPartyDesc]], time[cDesc.startTime] ]; IF ~finchToolHandle.keepTwiddling AND curConv THEN PaintFinchIcon[finchToolHandle, cDesc, difficulty OR state=idle]; }; IF difficulty OR state=idle THEN { s.PutF["%s%s, duration = %r", rope[IF difficulty THEN " was not successful" ELSE IF cDesc.ultimateState>ringing 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; IF curConv THEN [] _ SelectEntryInConversations[button, state#idle]; FOR intervals: Thrush.IntervalSpecs _ cDesc.requestedIntervals, intervals.rest WHILE intervals#NIL DO intervalSpec _ intervals.first; IF intervalSpec.changeNoted OR intervalSpec.type=request THEN LOOP; intervalSpec.changeNoted_TRUE; SELECT intervalSpec.direction FROM record => SELECT intervalSpec.type FROM started => Status["Please begin speaking"]; finished => Status["Thank you"]; ENDCASE => Status[NIL]; play => IF intervalSpec.interval.length#0 OR intervalSpec.queueIt THEN Report[ SELECT intervalSpec.type FROM started => "Playing message", finished => "End of message", ENDCASE => NIL]; ENDCASE; ENDLOOP; FOR proses: Thrush.ProseSpecs _ cDesc.requestedProses, proses.rest WHILE proses#cDesc.pendingProses AND proses#NIL DO proseSpec _ proses.first; IF proseSpec.changeNoted THEN LOOP; proseSpec.changeNoted_TRUE; IF finchToolHandle.debug THEN Status[ SELECT proseSpec.type FROM request => "R", started => "S", finished => "F", ENDCASE => NIL, Convert.RopeFromInt[proseSpec.intID.stateID], ".", Convert.RopeFromInt[proseSpec.intID.reqID] ]; ENDLOOP; }; 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.Cat[number.Substr[len: i], "*", number.Substr[start:i+1]]; i_-1; IF number.Map[0, number.Length[], M] THEN number _ number.Substr[len:i]; }; }; PaintFinchIcon: ENTRY PROC [handle: FinchTool.Handle, cDesc: ConvDesc, useFinchIcon: BOOL] = { 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.originator FROM us => Rope.Concat["to ", -- this doesn't work right for "listen to this!`' mode IF cDesc.bluejayConnection THEN "Recording service" ELSE IF cDesc.proseConnection THEN "Text-to-Speech service" ELSE ViewerTools.GetContents[handle.calledPartyText] ], them => Rope.Concat["from ", ViewerTools.GetContents[handle.callingPartyText]], ENDCASE => Rope.Concat["to/from ", RepairIntelnet[cDesc.otherPartyDesc]]; }; 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]]; IF lastDesc#NIL AND lastDesc.ultimateState<=parsing 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, 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]; }; 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]; }; 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] = { 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"]; 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[pending] _ NIL; printLabel[initiating] _ NIL; printLabel[maybe] _ " 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"]; }. FinchToolImpl.mesa Copyright c 1985, 1986 by Xerox Corporation. All rights reserved. Last Edited by: Swinehart, May 19, 1986 9:14:22 am PDT Last Edited by: Pier, April 17, 1984 3:51:54 pm PST Polle Zellweger (PTZ) July 18, 1986 10:19:12 pm PDT List USING [Reverse], Types and Typeoids Placing and Controlling Calls [parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE] [parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE] [parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE] Called whenever the user clicks the answer button Refers to entries in the viewer finchToolHandle.conversations, not necessarily current one. Button-invoked operations when applied directly to conversation-viewer log entries. Called whenever the user clicks the hangup button Refers to entries in the viewer finchToolHandle.conversations, not necessarily current one. Called to disconnect a call in progress without logging a message. Parses single party-specification: phone number (leading character is not a letter), or named recipient. Names can be RNames or other text sequences that can be looked up in one's private (TDir-style) telephone directory. "XXX at home" is a request to call a residence rather than office number. "home" is equivalent to " 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.) Speaking Text nodeEnd = "" makes this function the same as ViewerTools.GetSelectionContents. Other User Commands Convert an arbitrary rope into characters found on a DTMF touchpad. '*, '#, and digits and all punctuation (often used to format phone numbers) map to themselves. Letters (either case) map to their standard telephone-dial locations. Also, 'Q->'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. Now people can dial .FAX or .HELP or 9(800)TheCard, or 9Fastell. Starting and Stopping If not active, try to make it active. Conversation Management Don't use conversation buttons for failure reports The first clause covers getting the idle report (the conversation is already gone). cDesc.otherPartyDesc can be of the form "xxx.pa (nnnn)", which confuses Phone commands. Number in parens should be eliminated from number field being set here. If we originated the call, the called-party field is already set, and contains better information than we can get this way (home or office, nicknames, and so on.) Here we pick up the calling party for incoming calls, or called-party for calls initiated from the telset. weOriginated is assumed FALSE, set TRUE by FinchSmarts when initiating a call. Set called-party field Set calling-party field Don't want to step on twiddling if state=ringing. This call covers hanging up from non-ringing state or placing outgoing call. If idle, elaborate. Now report on any interval changes. <> IF proseSpec.prose.Length[]#0 OR proseSpec.queueIt THEN cDesc.otherPartyDesc can contain Intelnet pause characters and authentication code. Intelnet pause characters are all characters < '\040 Should be repaired (pause => "*" and auth-code removed) wherever the user sees it. Assumes that the ordinary Finch icon is unlabelled. Viewer Construction and Management Finch Viewer Check how much space is left (if any) in the control window for a typescript. Finch Tool Viewer Conversation Viewer Conversations Viewer ViewersOps.AddProp[newV, $convDesc, NIL]; 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. Future: can use $BlackOnGrey for held conversations, $WhiteOnBlack for current (or vice versa -- $WhiteOnBlack is more legible, so may be better for held convs). Directory Viewer and Button Utilities Creates a text viewer, next right on the same line as sib sib must be a Viewer, not NIL Make an h-bit wide line after sib Sib is sibling to create TS after Containers.ChildYBound[sib.parent, ts]; Reporting and Logging Registration, Initialization Register a command with the UserExec that will create an instance of this tool Debugging nonsense Swinehart, August 7, 1985 8:56:34 am PDT Merge PTZ TextSpeech stuff changes to: DIRECTORY, MakeMenus, SpeakSelectedProc Polle Zellweger (PTZ) August 19, 1985 5:00:46 pm PDT Handle Prose flushing. changes to: MakeMenus, ReportConversationState, StopSpeechProc Polle Zellweger (PTZ) August 19, 1985 5:54:32 pm PDT changes to: DIRECTORY, FinchToolImpl, ReportConversationState, StopSpeechProc Polle Zellweger (PTZ) September 3, 1985 5:57:21 pm PDT Curtail intermediate Prose reports if ~finchToolHandle.debug; allow user to specify SpeakText queueIt via red|blue button; some cosmetic feedback changes. changes to: ReportConversationState, SpeakSelectedProc, speakTextFeedbackLen, SpeakTextWithFeedback, StopSpeechProc Swinehart, September 6, 1985 10:03:45 am PDT Don't OPEN FinchTool.bcd, eliminate (status line, extra Phone button). Eliminate dual-menu, demote Drop-Out. Merge Phone, Redial. PhoneSelection becomes CommandTool feature, with user profile help. Add names->feepNum features (can dial 9(800)TheCard) Add feep command (for talking to your bank or whatever). Add HangUp, SpeakText, StopSpeaking commands. Pick up incoming call idents, outgoing calls originated at telset, in called-party fields. Automatically connect to Server when command is issued; don't delete conversation viewer when disconnecting any more. Reorganize order of procedures. Polle Zellweger (PTZ) October 4, 1985 6:58:47 pm PDT Add labelled icons, used to display caller when Finch tool etc is closed. changes to: labelledFinchIcon, labelledLeftFinchIcon, labelledRightFinchIcon, labelledConversationIcon, TwiddleFinchIcon, (module initialization) Polle Zellweger (PTZ) October 16, 1985 4:38:58 pm PDT Change Finch telephone icon to labelled conversation icon while conversation in progress. Added monitor to protect icon twiddling and restoring. changes to: FinchToolImpl, ConversationHandle, ReportConversationState, PaintFinchIcon, TwiddleFinchIcon, NewIcon, MsPause Polle Zellweger (PTZ) October 18, 1985 4:08:31 pm PDT Add ability to recognize node boundaries in selection for text-to-speech. changes to: SpeakTextWithFeedback, RopeWithNodesFromSelection (new) Polle Zellweger (PTZ) December 10, 1985 4:13:33 pm PST changes to: ReportConversationState fix callingParty, calledParty fields, AddConvDesc test ultimateState<=parsing rather than pending (for incoming calls while call in progress) Polle Zellweger (PTZ) December 13, 1985 2:55:28 pm PST Fix selection in conv log and icon mgmt for incoming calls while call in progress. changes to: DIRECTORY, ReportConversationState Swinehart, May 19, 1986 9:14:07 am PDT Cedar 6.1 changes to: DIRECTORY, FinchToolImpl, ParseCallee, Report, ReportRope, FinchWhereProc, FinchWhereCmdProc, VoiceUtils, VoiceUtils Polle Zellweger (PTZ) July 3, 1986 12:36:25 pm PDT minor DECtalk changes changes to: dectalkParaSep, RopeWithNodesFromSelection Polle Zellweger (PTZ) July 18, 1986 5:22:50 pm PDT changes to: dectalkParaSep Polle Zellweger (PTZ) July 18, 1986 10:19:12 pm PDT changes to: dectalkParaSep Κ&œ˜– "Cedar" stylešœ™Icode– "Cedar" stylešœ Οmœ7™BJšœ6™6J™3Kšœ3™3—J˜šΟk ˜ J˜%J˜>Jšœžœ1˜>Jšœ žœ!˜0Jšœ žœ˜Jšœ žœ1˜AJšœžœ˜šœ žœ˜Jšœ}˜}—Jšœ ˜ Jšœžœ!˜,J˜Jšœžœ ˜Jšœžœ ™Jšœžœ2˜?Jšœžœ0˜;Jšœžœ ˜Jšœžœ+˜8JšœžœPžœ ˜iJšœžœ ˜Jšœžœ‹˜—Jšœ žœ7˜EJšœ žœ ˜Jšœ žœ ˜J˜#Jšœžœ'˜:Jšœ žœ"˜4Jšœ žœ˜'Jšœ žœ˜)Jšœ žœ^˜mJ˜>Jšœ žœT˜eJšœ žœ;˜KJšœ˜J˜—šœž˜(Jšžœjžœ΅˜¨Jšžœ˜Jšžœ˜J˜—™J™šœžœ˜5J˜;—Jšœ žœ˜&Jšžœžœ žœ˜Jšœžœ˜Jšžœžœžœ˜˜$J˜0—J˜Jšœžœ˜)Jšœžœ˜ Jšœžœžœ˜!J˜Jš œ žœžœžœžœ˜MJšœ žœ"˜4J˜#J˜'J˜(J˜*J˜Jšœ+˜+Jšœ/˜/Jšœ0˜0Jšœ2˜2J˜J˜J˜J˜Jšœžœ˜Jšœ žœ˜Jšœžœ˜0J˜—™J™•StartOfExpansion‚ -- [parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE]šΟn œ˜!JšΠck~™~Jšœžœ&˜@šœ+˜+šžœ ž˜Jš œžœ žœžœžœ˜3——J˜J˜—–‚ -- [parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE]šŸœ˜'Jš ~™~Jšœžœ:˜Tšžœ ž˜JšœAžœ˜FJšœ#žœ˜*Jšœ!žœ˜'Jšžœžœ˜—Jšœ˜J˜—–‚ -- [parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE]šŸœ˜(Jš ~™~Jšœžœ;˜Všžœ ž˜JšœBžœ˜GJšœ$žœ˜+Jšœ"žœ˜(Jšžœžœ˜—Jšœ˜J˜—šŸœ˜#Jšžœžœžœ˜!JšœžœE˜_J˜J˜Jšœ žœ˜J˜J˜—šŸ œ˜'Jšœ˜Jšœ˜J˜J˜—šŸ œ˜$Jšžœžœžœ˜-JšœBžœ˜IJ˜J˜—š Ÿœžœžœžœžœ˜HJšœ!žœ˜&Jšœ žœ˜Jšœžœ˜šžœžœžœžœ˜WJšžœžœ˜ —JšœG˜Gšžœ žœžœ˜&Jšœ žœ˜Jšœ;˜;J˜—Jšœ/˜/šžœMž˜SJšœM˜M—Jšœž œ˜(šžœžœ5˜NJšΟc(˜(—JšœH‘˜[J˜J˜—šŸœžœ˜"Jšœ1™1Jšœ[™[Jšœ/˜/šœžœžœžœž˜)Jšžœžœ.˜9—Jšœ$˜$š žœ žœžœžœž˜1š œžœžœžœžœ˜/Jšœ$žœ˜.——Jšœ@˜@Jšœ˜J˜—šŸœ˜,J™SJšœžœ ˜ Jšžœžœžœ˜-J˜'Jšžœ žœ8˜GJšžœžœžœ9˜UJšœ˜J˜—šŸœžœ žœ˜7Jšœ1™1Jšœ[™[—J˜šŸ œ˜$Jšžœžœžœ˜-Jšœžœ˜J˜J˜—šŸ œžœ žœ˜?JšœB™BJ™—šŸœžœ žœžœžœžœžœžœ˜hJ˜$Jš œžœžœžœžœ˜Kšžœž˜šœ ˜ Jšžœ žœ&žœ˜?—Jšœžœ žœžœ˜/Jšžœ˜—Jšœžœžœžœ˜BJšœD˜DJšœ˜J˜—šŸ œžœ žœžœ žœ žœ žœžœ˜dJ™ίJšœ žœ˜Jšœ;˜;Jšœ:˜:JšœA˜AJ˜šžœžœž˜$JšžœJžœ˜V—šžœ(žœž˜;Jšžœ*žœ˜6—J˜J˜—š Ÿœžœ#žœžœ žœ˜aJšœžœ˜J˜šžœžœ-ž˜QJ˜=—J˜—J™šŸœžœžœžœ˜7Jšœ/˜/šœžœžœžœž˜)Jšžœžœ.˜9—Jš žœžœ žœžœžœ˜?Jšœžœ+˜9Jšœ˜—J™—™ J™Jšœžœ˜Jšœžœ˜/J˜šŸœ˜*Jšžœžœžœ˜-šžœ ž˜Kšœžœ˜#Jšœ žœ˜Jšœžœ˜&Kšžœžœ˜—Jšœ˜J˜—šŸ œ˜'JšœžœE˜[Jšžœžœžœ˜-Jšžœžœžœ˜HJ˜J˜—š Ÿœžœ žœžœžœ˜?Jšžœžœžœ%˜5Kšžœžœžœžœ˜šœ˜Kšžœ žœžœžœ˜,Kšœ˜Kšœ'˜'Kšžœ&žœ žœ˜>Kšœ˜—Jšœ<˜Jšžœžœžœ˜@Jšœžœ˜Jšœ˜J˜—šŸœ˜5Jšžœžœžœž˜#šœ)˜)Jšœ(žœ˜G—Jšžœžœ˜3J˜J˜—šŸ œžœžœžœ žœžœžœ˜ZJ™%Jš žœžœžœžœžœžœ˜7J˜ Jšžœ˜J˜J˜—šŸ œ+˜5J˜—šŸœžœžœžœ˜-Jšžœžœžœžœ˜#J˜!Jšžœ<žœžœ˜JJšœ;˜;Jšœ3˜3J˜J˜—Jšœ žœžœ˜Jšœ žœ˜J˜šŸœžœžœžœ˜Jšœ žœ˜Jšœžœ ˜šžœžœžœ ž˜J˜0Jšžœ žœžœ˜Jšžœ˜—J˜J˜—šŸœžœžœ˜J˜Jšœ žœ˜J˜—J˜—™J™šŸœžœžœ žœ˜OJšœžœžœ˜ Jšœžœ˜ Jšœ žœžœ˜J˜J˜J˜"Jšœ˜JšœC˜CJšœ žœžœ˜J™šžœžœ‘!˜0Jšœ žœ˜šžœžœ˜'Jšœ2™2——Jš žœ žœžœžœ‘˜9šœ'žœ/˜XJ™S—Jšœžœ˜"Jšžœ žœžœ;˜PJšœžœžœ˜ J˜šžœž˜šœ ˜ Jšœ žœ˜Jšœ˜—šœ˜Jšœ˜Jšœžœ˜Jšžœžœ'žœžœ˜TJ˜—Jšžœžœ˜—šžœžœžœ˜1J™ šŸ œžœ.˜?Jšœ žœ(˜6Jšœžœžœ˜$Jšžœžœ&˜1Jšœ%žœ˜+Jšœžœ˜J˜—šžœž˜Jšœ¨žœžœ'™ήšœ˜J™J˜4J˜—˜J™Jšœ5˜5J˜—Jšžœ˜—J˜—–[]šžœ žœ˜šžœžœ‘%˜=šžœžœ˜(Jšœ žœ˜'Kšžœžœ$˜DK˜—K˜—šžœ‘$˜)šžœž˜%Jšœ2žœ ˜A——J˜—šžœžœžœ žœ˜:šœ˜Jšœžœžœžœ ˜PJšœ@˜@Jšœ˜J˜—šžœ žœ ž˜2K™Jšœ2žœ ˜A—J˜—J™šžœ žœ žœ˜"˜šœžœ žœ˜-Jšžœžœžœžœ˜R—šœžœ žœ˜šžœžœž˜$J˜J˜!J˜ Jšžœ˜Jšœ˜——J˜7J˜—J˜—Jšžœ˜"Jšžœžœžœ-˜MJšžœžœžœ˜1šžœžœ)žœ˜CJšœ˜—Jšžœ žœžœ˜%šžœ ž˜Jšœ4˜4—J™UšžœK˜Nšžœ žœž˜J˜Jšžœžœžœžœ˜CJšœžœ˜šžœž˜"˜ šžœž˜Jšœ+˜+Jšœ ˜ Jšžœ žœ˜——šœžœ žœžœ˜Nšžœž˜J˜J˜Jšžœžœ˜——Jšžœ˜——Jšžœ˜—šžœ?žœžœž˜uJšœ˜Jšžœžœžœ˜#Jšœžœ˜Jšžœžœžœ™8šžœžœ˜šœ˜šžœž˜Jšœ˜Jšœ˜Jšœ˜Jšžœžœ˜—Jšœ-˜-Jšœ˜Jšœ*˜*Jšœ˜——Jšžœ˜—Jšœ˜—J˜™SJ™4J™R—š Ÿœžœžœžœ žœ˜9Jšœžœ˜ JšŸœžœ ˜,Jšœ ˜ šžœ#žœ˜+JšœH˜HJ˜Jšžœ#žœ˜HJ˜—J˜J˜—šŸœžœžœ;žœ˜^Kšœ.˜.Kšœžœ˜Jšžœ˜šžœžœ˜Jšœ˜Jšœžœ˜J˜—šžœ˜Kšœ-˜-šœžœž˜1šœ‘6˜QKšžœžœ˜3Kšžœžœ˜;Kšžœ/˜4Kšœ˜—JšœO˜OJšžœB˜I—J˜—šžœžœž˜BJšœ7˜7—K˜K˜—šŸœž œ˜;Jšžœžœžœ˜š Ÿœžœžœžœžœ˜-JšœA˜AJšžœ˜Jšœ˜—š Ÿœžœžœžœžœ žœ˜Wšžœžœ˜Jšœ˜šžœž˜Jšœ7˜7—Jšœ ˜ J˜—Kšžœ˜Jšœ˜J˜—šœ žœ˜$JšœQ˜Q—šœF˜FJ™3—šžœž˜šžœž˜Jšœ ˜ —šžœ˜šžœžœžœž˜Jšžœ:žœžœ˜HJšžœ5žœžœ˜CJšžœ9žœžœ˜GJšžœ5žœžœ˜CJšžœ˜—Jšžœ2žœžœ˜@J˜—Jšžœ˜—J˜—J˜—™"J™™ J˜Jšœ žœ˜Jšœ žœ˜Jšœ žœ˜J˜šŸ œžœž˜Jšœ žœ˜Jšœ žœ˜J˜ Jšœ˜J˜ šžœžœžœ˜Jšœ3žœ˜=—Jšœžœ˜.J˜J˜&J˜ šœ+Πac˜=Jšœ‘ ˜/J˜Jšœžœ‘8˜IJšœ‘˜0Jšœ‘˜.Jšœ‘ ˜,Jšœ žœžœ‘'˜>—J˜ Jšœ žœžœžœ˜^šœ"˜"Jšœ;žœ˜A—JšœY˜YJ˜HJšœsžœ˜zJ˜AJ˜4J˜JšœM™MJ˜ šžœ*žœ˜2Jšœ1˜1Jšžœ žœ#˜4J˜—J˜'Jšœ1žœ˜DJ˜(šœ)˜)Jšœžœ˜ šœ˜Jšœ#žœ˜,——Jšœ1˜1Jšœ1˜1Jšœ"‘˜9šžœžœ˜&JšœM˜M—šžœžœ˜(šœOž˜SJ˜——Jšžœ˜—J˜šŸ œžœ˜™šŸ œžœžœ˜Išœ˜Jšœ ˜ JšœG˜GJšœ˜—J˜—Jšœ˜Jšœ+˜+Jšœ)˜)Jšœ-˜-Jšœ7˜7Jšœ5˜5Jšœ3˜3Jšœ4˜4J˜—™Jšœ#˜#Jšœ-˜-Jšœ1˜1—Jšœ˜J˜—JšΟbœ/˜?—J˜™J™šŸ œžœ#žœžœ˜Ršœ˜JšžœB˜H—šŸ œžœ˜šžœžœž˜šœ˜Jšœžœ;˜S——šžœ˜JšœžœEžœ˜c—J˜—šžœ žœžœ˜Jšœžœ-˜Hšœ$žœ™)JšœΪ™Ϊ—Jšžœ žœžœ!žœ˜KJ˜—šžœžœž˜Jšžœžœ ˜*Jšžœ4˜8—Jšœ˜Jšœ*˜*Jšœ.˜.J˜J˜—š Ÿœžœ žœžœžœžœ˜jJšœžœ:˜WJš žœžœ žœžœžœ˜=šžœžœžœžœ˜8Jšœ5˜5J˜)J˜—Jšžœžœ/˜LJšžœžœ žœ7žœ˜Qšžœ˜šœ4‘˜JJšœ‘™‘—Jšœ(˜(JšœC˜CJ˜—Jšžœžœ˜J˜——J™J™ ˜š£ œ˜!J˜Jšœ˜J˜——™J™šŸ œžœ-žœ˜LJšŸœžœ3˜HJšœ8˜8Jšœ˜—J˜šŸ œžœžœžœ˜EJšžœ!žœžœ˜/šœ˜Jšœ:˜:Jš œžœžœžœžœ˜=—J˜—J˜š Ÿœžœžœžœ$žœ˜PJšœ žœžœžœžœ žœžœ žœžœ˜bšžœ ˜Jšœ˜Jšœžœ˜Jšœžœ˜Jšžœžœžœžœžœžœžœžœ˜BJšžœžœžœ"˜1JšœM˜MJš žœ žœžœžœžœ˜%šžœžœ‘˜-Jšœžœžœžœ‘ ˜BJšœ žœžœžœ˜1Jšœ˜—šžœ‘.˜3Jšœ˜——JšžœG˜MJšœ˜—J˜š Ÿ œžœžœžœ$žœ˜MJš œžœžœ žœžœ˜8šžœ ˜JšœH˜HJšœ$˜$Jšžœ žœ‘˜-Jšœžœžœžœ‘ ˜DJšœ žœžœžœ˜5Jšœ˜—šžœ‘.˜3Jšœ˜—JšžœP˜VJšœ˜—J˜Jš‘>˜>šŸ œž œ˜Jšœžœ(˜DJš œžœžœžœ žœžœ žœ˜;Jš œ žœžœ3žœžœ˜SJšžœ˜—JšœU˜Ušœ&˜&Jšžœžœžœ˜"šžœ žœ‘˜-Jšœžœžœžœ‘ ˜BJšœ žœžœžœ˜5Jšœ˜—šžœ‘-˜2Jšœ˜—šžœ˜JšœN˜N—Jšœ˜—J˜šŸ œž œ˜Jšœžœ˜7Jš œžœžœžœ žœžœ žœ˜KJšœ žœžœ)˜=Jšžœ˜šœ˜Jšœ?˜?Jšœžœžœžœ˜>—Jšžœžœžœ˜%šœ˜JšœN˜N—Jšœ˜J˜—š Ÿ œžœžœžœžœ˜J˜9J˜FJ˜LJ˜'Jšœ#˜#Jšœ$˜$Jšœžœ˜Jšœžœ˜Jšœ"˜"J˜4J˜.Jšžœn˜uJ™šœ*˜*Jšœ.˜.——Jšœ˜™(K™Kšœ Οr'™3—šœ4™4Kšœ™Kšœ €2™>—šœ4™4Kšœ €A™M—šœ6™6Kšœš™šKšœ €g™s—™,KšœF™FKšœ;™;K™CK™4K™8K™-K™ZK™•—™4K™IKšœ €…™‘—™5K™‘Kšœ €n™z—šœ5™5K™IKšœ €1œ™C—™6Kšœ €œ%€ œ\™±—™6KšœR™RKšœ €"™.—™&K™ Kšœ €t™€—™2K™Kšœ €*™6—™2Kšœ €™—™3Kšœ €™—K™—…—}ˆΒ8