DIRECTORY Convert USING [RopeFromInt, IntFromRope], Menus USING [MenuProc, MenuEntry, FindEntry, CreateEntry, AppendMenuEntry, ReplaceMenuEntry, Menu, MenuLine, GetNumberOfLines, GetLine, SetLine, InsertMenuEntry], MBQueue USING [Queue, Create, CreateMenuEntry], Rope USING [ROPE, Concat, Equal, Substr, Find], ViewerBLT USING [ChangeNumberOfLines], ViewerOps USING [FetchProp, PaintViewer], MessageWindow USING [Append, Blink], ViewerClasses USING [Viewer], SourceMarkerTracking USING [RemoveParentPointersTo, RegisterViewer], TiogaAccess USING [Reader, TiogaChar, FromSelection, EndOf, Get, FromViewer], TiogaOps USING [CallWithLocks, Ref, CommandProc, RegisterCommand, GetSelection, NoSelection, ViewerDoc, StepForward, SelectionGrain, SetSelection, Root, SaveSelA, SelectDocument, RestoreSelA], TiogaOpsDefs USING [Location], TiogaButtons USING [TextNodeRef], TextNode USING [Ref], TextEdit USING [PutCharProp, GetCharProp, PutProp, Size], IO USING [STREAM, PutF, PutFR, int], Atom USING [PropList], VoiceMarkers USING [RopeFromTextList], VoiceRecord USING [AddVoiceProc, StopRecording, DictationMachine], VoiceRope USING [Open, Handle, Stop], Commander USING [CommandProc, Register], TiogaMenuOps USING [tiogaMenu], VoiceViewers USING [BuildVoiceViewer, VoiceViewerInfo, SetParentViewer, RemoveParentViewer], VoicePlayBack USING [CancelPlayBack, PlayRopeWithoutCue, PlayBackMenuProc], VoiceInText; VoiceInTextImpl: CEDAR PROGRAM IMPORTS Convert, Menus, MBQueue, Rope, ViewerBLT, ViewerOps, MessageWindow, TiogaAccess, SourceMarkerTracking, TiogaOps, TiogaButtons, TextEdit, IO, Commander, TiogaMenuOps, VoiceViewers, VoicePlayBack, VoiceMarkers, VoiceRecord, VoiceRope EXPORTS VoiceInText SHARES Menus = BEGIN VoiceMenu: Menus.MenuProc = { ChangeMenu[NARROW[parent], voiceMenu] }; ChangeMenu: PUBLIC PROC [viewer: ViewerClasses.Viewer, subMenu: Menus.MenuEntry] = { menu: Menus.Menu _ viewer.menu; found: BOOL _ FALSE; numLines: Menus.MenuLine = Menus.GetNumberOfLines[menu]; newLines: Menus.MenuLine _ numLines; FOR i: Menus.MenuLine IN [1..numLines) DO IF Rope.Equal[Menus.GetLine[menu,i].name, subMenu.name] THEN { -- yes, so remove it FOR j: Menus.MenuLine IN (i..numLines) DO Menus.SetLine[menu, j-1, Menus.GetLine[menu, j]]; ENDLOOP; newLines _ newLines-1; found _ TRUE; EXIT }; ENDLOOP; IF ~found THEN { GoesBefore: PROC [m1, m2: Menus.MenuEntry] RETURNS [BOOL] = { Priority: PROC [m: Menus.MenuEntry] RETURNS [INTEGER] = { RETURN [SELECT TRUE FROM Rope.Equal[m.name, "Find"] => 1, Rope.Equal[m.name, "FirstLevelOnly"] => 0, ENDCASE => -1 -- unknown menu goes at bottom -- ] }; RETURN [Priority[m1] > Priority[m2]] }; newLast: Menus.MenuLine = MIN[numLines, LAST[Menus.MenuLine]]; newLines _ newLines+1; FOR i: Menus.MenuLine IN [1..numLines) DO IF GoesBefore[subMenu, Menus.GetLine[menu, i]] THEN { FOR j: Menus.MenuLine DECREASING IN (i..newLast] DO Menus.SetLine[menu, j, Menus.GetLine[menu, j-1]]; ENDLOOP; Menus.SetLine[menu, i, subMenu]; found _ TRUE; EXIT }; ENDLOOP; IF ~found THEN Menus.SetLine[menu, newLast, subMenu]; }; ViewerBLT.ChangeNumberOfLines[viewer, newLines]; }; ApplyToCharsInPrimarySelection: PUBLIC PROC [ActionProc: PROC [TiogaOpsDefs.Location]] = { ScanLocked: PROC [root: TiogaOps.Ref] = { current, end: TiogaOpsDefs.Location; [start: current, end: end] _ TiogaOps.GetSelection[]; IF current.node = end.node THEN FOR i: INT IN [current.where..end.where] DO ActionProc[[current.node, i]] ENDLOOP ELSE { FOR i: INT IN [current.where..TextEdit.Size[TiogaButtons.TextNodeRef[current.node]]) DO ActionProc[[current.node, i]] ENDLOOP; DO current.node _ TiogaOps.StepForward[current.node]; IF current.node = end.node THEN { FOR i: INT IN [0..end.where) DO ActionProc[[current.node, i]] ENDLOOP; RETURN } ELSE FOR i: INT IN [0..TextEdit.Size[TiogaButtons.TextNodeRef[current.node]]) DO ActionProc[[current.node, i]] ENDLOOP ENDLOOP } }; TiogaOps.CallWithLocks[ScanLocked] }; ApplyToLockedChars: PUBLIC PROC [ActionProc: PROC [TiogaOpsDefs.Location]] = { current, end: TiogaOpsDefs.Location; [start: current, end: end] _ TiogaOps.GetSelection[]; IF current.node = end.node THEN FOR i: INT IN [current.where..end.where] DO ActionProc[[current.node, i]] ENDLOOP ELSE { FOR i: INT IN [current.where..TextEdit.Size[TiogaButtons.TextNodeRef[current.node]]) DO ActionProc[[current.node, i]] ENDLOOP; DO current.node _ TiogaOps.StepForward[current.node]; IF current.node = end.node THEN { FOR i: INT IN [0..end.where) DO ActionProc[[current.node, i]] ENDLOOP; RETURN } ELSE FOR i: INT IN [0..TextEdit.Size[TiogaButtons.TextNodeRef[current.node]]) DO ActionProc[[current.node, i]] ENDLOOP ENDLOOP } }; StoreVoiceAtSelection: PUBLIC PROC [voiceViewerInfo: VoiceViewers.VoiceViewerInfo] RETURNS [succeeded: BOOLEAN _ FALSE] = { selectedViewer: ViewerClasses.Viewer; suitableViewer: BOOLEAN; alreadyVoiceThere: BOOLEAN; SuitableViewer: PROC RETURNS [BOOLEAN] = { RETURN [selectedViewer.class.flavor = $Text AND ViewerOps.FetchProp[selectedViewer, $voiceViewerInfo] = NIL] }; AddVoiceMarkerAtPrimarySelection: PROC [root: TiogaOps.Ref] = { startChar, endChar, targetChar: TiogaOpsDefs.Location; node: TextNode.Ref; caretBefore: BOOLEAN; pendingDelete: BOOLEAN; level: TiogaOps.SelectionGrain; [viewer: selectedViewer, start: startChar, end: endChar, caretBefore: caretBefore, pendingDelete: pendingDelete, level: level] _ TiogaOps.GetSelection[]; suitableViewer _ SuitableViewer[]; IF pendingDelete AND suitableViewer THEN { ApplyToLockedChars[DeleteVoiceFromChar]; TiogaOps.SetSelection[viewer: selectedViewer, start: startChar, end: endChar, level: level, caretBefore: caretBefore, pendingDelete: FALSE, which: primary] -- simply makes not pending delete }; IF suitableViewer THEN { targetChar _ IF caretBefore THEN startChar ELSE endChar; node _ TiogaButtons.TextNodeRef[targetChar.node]; -- just a type convertor alreadyVoiceThere _ TextEdit.GetCharProp[node, targetChar.where, $voice] # NIL; IF alreadyVoiceThere THEN RETURN; TextEdit.PutCharProp[node, targetChar.where, $voice, voiceViewerInfo.ropeInterval.ropeID]; TextEdit.PutCharProp[node, targetChar.where, $textInVoice, VoiceMarkers.RopeFromTextList[voiceViewerInfo.textMarkList]]; TextEdit.PutCharProp[node, targetChar.where, $Artwork, NARROW["TalksBubble", Rope.ROPE]]; -- the NARROW prevents a REF TEXT being created TextEdit.PutCharProp[node, targetChar.where, $voiceWindow, Rope.Concat["Sound Viewer #", Convert.RopeFromInt[voiceViewerInfo.viewerNumber]]]; voiceViewerInfo.parentViewer _ selectedViewer; voiceViewerInfo.positionInParent _ targetChar; SourceMarkerTracking.RegisterViewer[selectedViewer] } }; IF voiceViewerInfo.ropeInterval.ropeID = NIL THEN RETURN; TiogaOps.CallWithLocks[AddVoiceMarkerAtPrimarySelection ! TiogaOps.NoSelection => {suitableViewer _ FALSE; CONTINUE}]; IF NOT suitableViewer THEN { MessageWindow.Append["Make a selection in a tioga viewer first", TRUE]; MessageWindow.Blink[] } ELSE { IF alreadyVoiceThere THEN { MessageWindow.Append["Cannot add sound on top of another sound", TRUE]; MessageWindow.Blink[] } ELSE succeeded _ TRUE } }; DeleteVoiceProc: Menus.MenuProc = { ApplyToCharsInPrimarySelection[DeleteVoiceFromChar] }; DeleteVoiceFromChar: PUBLIC PROC [position: TiogaOpsDefs.Location] = { node: TextNode.Ref _ TiogaButtons.TextNodeRef[position.node]; offset: INT _ position.where; voiceWindowRope: Rope.ROPE; TextEdit.PutCharProp[node, offset, $Artwork, NIL]; TextEdit.PutCharProp[node, offset, $voice, NIL]; TextEdit.PutCharProp[node, offset, $textInVoice, NIL]; voiceWindowRope _ NARROW[TextEdit.GetCharProp[node, offset, $voiceWindow], Rope.ROPE]; IF voiceWindowRope # NIL THEN { TextEdit.PutCharProp[node, offset, $voiceWindow, NIL]; VoiceViewers.RemoveParentViewer[Convert.IntFromRope[ voiceWindowRope.Substr[voiceWindowRope.Find["#"]+1]]] } }; PlaySelection: PUBLIC PROC = { selectStream: TiogaAccess.Reader _ TiogaAccess.FromSelection[]; charsInSelection: INT _ 0; soundsInSelection: INT _ 0; heavyChar: TiogaAccess.TiogaChar; props: Atom.PropList; IF NOT TiogaAccess.EndOf[selectStream] THEN DO heavyChar _ TiogaAccess.Get[selectStream]; IF TiogaAccess.EndOf[selectStream] THEN EXIT; charsInSelection _ charsInSelection + 1; FOR props _ heavyChar.propList, props.rest WHILE props # NIL DO IF props.first.key = $voice THEN { VoicePlayBack.PlayRopeWithoutCue[NARROW[props.first.val, Rope.ROPE]]; soundsInSelection _ soundsInSelection + 1 }; ENDLOOP ENDLOOP; IF soundsInSelection = 0 THEN MessageWindow.Append["No sounds in selection", TRUE]; DebugRope[IO.PutFR["%d characters in selection\n", IO.int[charsInSelection]]] }; CancelProc: PUBLIC Menus.MenuProc = { VoiceRope.Stop[thrushHandle]; VoicePlayBack.CancelPlayBack[]; VoiceRecord.StopRecording[] }; EditVoiceProc: Menus.MenuProc = { voiceList: LIST OF Rope.ROPE _ NIL; textInVoiceList: LIST OF Rope.ROPE _ NIL; voiceViewerInfoList: LIST OF VoiceViewers.VoiceViewerInfo _ NIL; voiceViewerNumberList: LIST OF INT _ NIL; AppendRope: PROC [list: LIST OF Rope.ROPE, entry: Rope.ROPE] RETURNS [LIST OF Rope.ROPE] = { oneElementList: LIST OF Rope.ROPE _ CONS[entry, NIL]; hangOffPoint: LIST OF Rope.ROPE _ list; IF list = NIL THEN RETURN [oneElementList]; WHILE hangOffPoint.rest # NIL DO hangOffPoint _ hangOffPoint.rest ENDLOOP; hangOffPoint.rest _ oneElementList; RETURN [list] }; AppendViewerInfo: PROC [list: LIST OF VoiceViewers.VoiceViewerInfo, entry: VoiceViewers.VoiceViewerInfo] RETURNS [LIST OF VoiceViewers.VoiceViewerInfo] = { oneElementList: LIST OF VoiceViewers.VoiceViewerInfo _ CONS[entry, NIL]; hangOffPoint: LIST OF VoiceViewers.VoiceViewerInfo _ list; IF list = NIL THEN RETURN [oneElementList]; WHILE hangOffPoint.rest # NIL DO hangOffPoint _ hangOffPoint.rest ENDLOOP; hangOffPoint.rest _ oneElementList; RETURN [list] }; AppendInt: PROC [list: LIST OF INT, entry: INT] RETURNS [LIST OF INT] = { oneElementList: LIST OF INT _ CONS[entry, NIL]; hangOffPoint: LIST OF INT _ list; IF list = NIL THEN RETURN [oneElementList]; WHILE hangOffPoint.rest # NIL DO hangOffPoint _ hangOffPoint.rest ENDLOOP; hangOffPoint.rest _ oneElementList; RETURN [list] }; SearchForVoice: PROC [targetChar: TiogaOpsDefs.Location] = { node: TextNode.Ref _ TiogaButtons.TextNodeRef[targetChar.node]; voiceRopeID: Rope.ROPE _ NARROW[TextEdit.GetCharProp[node, targetChar.where, $voice], Rope.ROPE]; IF voiceRopeID # NIL THEN { IF TextEdit.GetCharProp[node, targetChar.where, $voiceWindow] = NIL THEN { voiceList _ AppendRope[voiceList, voiceRopeID]; textInVoiceList _ AppendRope[textInVoiceList, NARROW[TextEdit.GetCharProp[node, targetChar.where, $textInVoice], Rope.ROPE]] } ELSE DebugRope["Voice already has an associated viewer\n"] } }; AddVoiceWindowProps: PROC [targetChar: TiogaOpsDefs.Location] = { node: TextNode.Ref _ TiogaButtons.TextNodeRef[targetChar.node]; voiceRopeID: Rope.ROPE _ NARROW[TextEdit.GetCharProp[node, targetChar.where, $voice], Rope.ROPE]; IF voiceList = NIL THEN RETURN; IF voiceRopeID = NIL THEN RETURN; IF TextEdit.GetCharProp[node, targetChar.where, $voiceWindow] # NIL THEN RETURN; IF voiceRopeID.Equal[voiceList.first] THEN { parentViewer: ViewerClasses.Viewer _ TiogaOps.GetSelection[].viewer; alreadyEdited: BOOLEAN _ parentViewer.newVersion; TextEdit.PutCharProp[node, targetChar.where, $voiceWindow, Rope.Concat["Sound Viewer #", Convert.RopeFromInt[voiceViewerNumberList.first]]]; VoiceViewers.SetParentViewer[voiceViewerInfoList.first, parentViewer, targetChar]; IF ~alreadyEdited THEN { parentViewer.newVersion _ FALSE; ViewerOps.PaintViewer[parentViewer, caption, FALSE] } }; voiceList _ voiceList.rest; voiceViewerInfoList _ voiceViewerInfoList.rest; voiceViewerNumberList _ voiceViewerNumberList.rest }; ApplyToCharsInPrimarySelection[SearchForVoice ! TiogaOps.NoSelection => GOTO Quit]; IF voiceList = NIL THEN { MessageWindow.Append["No undisplayed sounds in selection", TRUE]; RETURN }; FOR l: LIST OF Rope.ROPE _ voiceList, l.rest WHILE l # NIL DO newInfo: VoiceViewers.VoiceViewerInfo; newNumber: INT; [viewerInfo: newInfo, viewerNumber: newNumber] _ VoiceViewers.BuildVoiceViewer[voiceID: l.first, textInVoice: textInVoiceList.first, youngVoice: FALSE]; voiceViewerInfoList _ AppendViewerInfo[voiceViewerInfoList, newInfo]; voiceViewerNumberList _ AppendInt[voiceViewerNumberList, newNumber]; textInVoiceList _ textInVoiceList.rest ENDLOOP; ApplyToCharsInPrimarySelection[AddVoiceWindowProps ! TiogaOps.NoSelection => CONTINUE] EXITS Quit => NULL }; DeleteSourceMarker: PUBLIC PROC [viewer: ViewerClasses.Viewer, positionInParent: TiogaOpsDefs.Location, voiceViewerNumber: INT] = { DoIt: PROC [root: TiogaOps.Ref] = { node: TextNode.Ref _ TiogaButtons.TextNodeRef[positionInParent.node]; voiceWindowRope: Rope.ROPE _ Rope.Concat["Sound Viewer #", Convert.RopeFromInt[voiceViewerNumber]]; IF TiogaOps.ViewerDoc[viewer] # TiogaOps.Root[positionInParent.node] THEN { DebugRope["source marker's viewer and node do not match!!\n"]; RETURN }; IF voiceWindowRope.Equal[NARROW[TextEdit.GetCharProp[node, positionInParent.where, $voiceWindow], Rope.ROPE]] THEN { -- see comments about 'edited' status at head of procedure AddVoiceWindowProps alreadyEdited: BOOLEAN _ viewer.newVersion; TextEdit.PutCharProp[node, positionInParent.where, $voiceWindow, NIL]; IF ~alreadyEdited THEN { viewer.newVersion _ FALSE; ViewerOps.PaintViewer[viewer, caption, FALSE] } } ELSE DebugRope["source marker not found at expected position!!\n"] }; TiogaOps.CallWithLocks[DoIt, TiogaOps.ViewerDoc[viewer]]; }; SaveRopeAtSourceMarker: PUBLIC PROC [viewer: ViewerClasses.Viewer, positionInParent: TiogaOpsDefs.Location, voiceViewerNumber: INT, voiceRopeID: Rope.ROPE, textInVoice: Rope.ROPE] RETURNS [succeeded: BOOLEAN _ FALSE] = { DoIt: PROC [root: TiogaOps.Ref] = { node: TextNode.Ref _ TiogaButtons.TextNodeRef[positionInParent.node]; voiceWindowRope: Rope.ROPE _ Rope.Concat["Sound Viewer #", Convert.RopeFromInt[voiceViewerNumber]]; IF TiogaOps.ViewerDoc[viewer] # TiogaOps.Root[positionInParent.node] THEN { DebugRope["source marker's viewer and node do not match!!\n"]; RETURN }; IF voiceWindowRope.Equal[NARROW[TextEdit.GetCharProp[node, positionInParent.where, $voiceWindow], Rope.ROPE]] THEN { TextEdit.PutCharProp[node, positionInParent.where, $voice, voiceRopeID]; TextEdit.PutCharProp[node, positionInParent.where, $textInVoice, textInVoice]; succeeded _ TRUE } ELSE DebugRope["source marker not found at expected position!!\n"] }; TiogaOps.CallWithLocks[DoIt, TiogaOps.ViewerDoc[viewer]]; }; ScanForVoice: TiogaOps.CommandProc = { IF viewer.newFile OR viewer.newVersion THEN RecordVoiceInstancesAtRoot[viewer] ELSE MessageWindow.Append["File is not altered", TRUE] }; DeleteLinks: Menus.MenuProc = { viewer: ViewerClasses.Viewer _ NARROW[parent]; RemoveAnySourceMarker: PROC [position: TiogaOpsDefs.Location] = { node: TextNode.Ref _ TiogaButtons.TextNodeRef[position.node]; IF TextEdit.GetCharProp[node, position.where, $voiceWindow] # NIL THEN { -- see comments about 'edited' status at head of procedure AddVoiceWindowProps alreadyEdited: BOOLEAN _ viewer.newVersion; TextEdit.PutCharProp[node, position.where, $voiceWindow, NIL]; IF ~alreadyEdited THEN { viewer.newVersion _ FALSE; ViewerOps.PaintViewer[viewer, caption, FALSE] } } }; TiogaOps.SaveSelA[]; TiogaOps.SelectDocument[viewer: viewer, level: branch]; ApplyToCharsInPrimarySelection[RemoveAnySourceMarker]; TiogaOps.RestoreSelA[]; SourceMarkerTracking.RemoveParentPointersTo[viewer]; }; RecordVoiceInstancesAtRoot: PROC [viewer: ViewerClasses.Viewer] = { wholeFile: TiogaAccess.Reader _ TiogaAccess.FromViewer[viewer]; heavyChar: TiogaAccess.TiogaChar; props: Atom.PropList; soundsInDocument: INT _ 0; rootNode: TextNode.Ref; voiceList: Rope.ROPE _ NIL; DebugRope["Voice messages in document:"]; IF NOT TiogaAccess.EndOf[wholeFile] THEN DO heavyChar _ TiogaAccess.Get[wholeFile]; IF TiogaAccess.EndOf[wholeFile] THEN EXIT; FOR props _ heavyChar.propList, props.rest WHILE props # NIL DO IF props.first.key = $voice THEN { DebugRope["\n"]; DebugRope[NARROW[props.first.val, Rope.ROPE]]; soundsInDocument _ soundsInDocument + 1; voiceList _ voiceList.Concat["&"]; -- just used here as a separator: a character not found in the IDs voiceList _ voiceList.Concat[NARROW[props.first.val, Rope.ROPE]] } ENDLOOP ENDLOOP; rootNode _ TiogaButtons.TextNodeRef[TiogaOps.ViewerDoc[viewer]]; -- TextNodeRef is just a type convertor - replace with a more cautious implementation?? [several references throughout TiogaVoice] TextEdit.PutProp[rootNode, $voicelist, voiceList]; IF soundsInDocument = 0 THEN DebugRope[" none\n"] ELSE DebugRope["\n"] }; InstallMenuButton: PROC [name: Rope.ROPE, proc: Menus.MenuProc] = { old: Menus.MenuEntry = Menus.FindEntry[menu: TiogaMenuOps.tiogaMenu, entryName: name]; new: Menus.MenuEntry = Menus.CreateEntry[name: name, proc: proc]; IF old = NIL THEN Menus.AppendMenuEntry[TiogaMenuOps.tiogaMenu, new] ELSE Menus.ReplaceMenuEntry[TiogaMenuOps.tiogaMenu, old, new]; }; thrushHandle: PUBLIC VoiceRope.Handle _ VoiceRope.Open[]; voiceMenu: Menus.MenuEntry; voiceButtonQueue: PUBLIC MBQueue.Queue _ MBQueue.Create[]; debugStream: IO.STREAM _ NIL; DebugRope: PUBLIC PROC [rope: Rope.ROPE] = { IF debugStream # NIL THEN debugStream.PutF[rope] }; DebugStreamInit: Commander.CommandProc = { debugStream _ cmd.out }; Commander.Register[key: "VoiceInfo", proc: DebugStreamInit, doc: "VoiceInfo: registers an output stream for debugging messages"]; Menus.InsertMenuEntry[TiogaMenuOps.tiogaMenu, MBQueue.CreateMenuEntry[voiceButtonQueue, "DeleteLinks", DeleteLinks], 1]; Menus.InsertMenuEntry[TiogaMenuOps.tiogaMenu, MBQueue.CreateMenuEntry[voiceButtonQueue, "DictationMachine", VoiceRecord.DictationMachine], 1]; Menus.InsertMenuEntry[TiogaMenuOps.tiogaMenu, MBQueue.CreateMenuEntry[voiceButtonQueue, "DeleteVoice", DeleteVoiceProc], 1]; Menus.InsertMenuEntry[TiogaMenuOps.tiogaMenu, MBQueue.CreateMenuEntry[voiceButtonQueue, "EditVoice", EditVoiceProc], 1]; Menus.InsertMenuEntry[TiogaMenuOps.tiogaMenu, MBQueue.CreateMenuEntry[voiceButtonQueue, "STOP", CancelProc], 1]; Menus.InsertMenuEntry[TiogaMenuOps.tiogaMenu, MBQueue.CreateMenuEntry[voiceButtonQueue, "PlayVoice", VoicePlayBack.PlayBackMenuProc], 1]; Menus.InsertMenuEntry[TiogaMenuOps.tiogaMenu, MBQueue.CreateMenuEntry[voiceButtonQueue, "AddVoice", VoiceRecord.AddVoiceProc], 1]; voiceMenu _ Menus.GetLine[TiogaMenuOps.tiogaMenu, 1]; InstallMenuButton["Voice", VoiceMenu]; Menus.SetLine[TiogaMenuOps.tiogaMenu, 1, NIL]; TiogaOps.RegisterCommand[name: $RedSave, proc: ScanForVoice]; TiogaOps.RegisterCommand[name: $YellowSave, proc: ScanForVoice]; TiogaOps.RegisterCommand[name: $BlueSave, proc: ScanForVoice]; TiogaOps.RegisterCommand[name: $RedStore, proc: ScanForVoice]; TiogaOps.RegisterCommand[name: $YellowStore, proc: ScanForVoice]; TiogaOps.RegisterCommand[name: $BlueStore, proc: ScanForVoice]; END. VoiceInTextImpl.mesa: basic code to add voice to textual tioga documents and to replay that voice Ades, April 28, 1986 10:57:39 am PDT this just toggles the voice submenu on and off the screen see comments in interface about this see if already showing the submenu add it. do insertion sort to get it in the right place higher priority means goes above in series of submenus looks at rope for first item to identify the subMenu. put it here a convenience: looks after all the tree walking and calls the given ActionProc once for each character in the primary selection this is the same as above, except that it should be called when the primary selection is already locked procedure to put voice from a voice viewer into a text viewer next line places a 'talks bubble' on the selected character - see TalksBubbleImpl test for failure conditions and report them to the user after releasing the viewer lock this procedure is in three parts: first look in the primary selection for characters with the property $voice but not $voiceWindow; next open up a window for each: then add $voiceWindow to the relevant characters. If the expected characters don't occur in the expected order then voice window(s) may be created without association to the parent viewer. these routines are simply append procedures for the three list types above [alas] because we are under a tioga lock, it is safe to muck with the 'edited' status of the viewer philosophically, putting a source marker in a voice viewer does not constitute editing it, so keep the 'edited' status of the viewer constant through this operation if the ropes didn't match then you've changed the selection: I'll try feebly to match some of the entries on voiceList this procedure severs any parent pointers that voice viewers might have back to this viewer and removes the source marker artworks. [It can also be used to get rid of source marker artworks embarassingly left in a file by bad tracking etc.] all buttons registered using MBQueue.CreateMenuEntry are serialised on this one queue: all the buttons in this voice system, or having to do with voice [e.g. Finch buttons], ought to be put on this queue, except for those which simply toggle menus on and off the display **** voiceButtonQueue should be deleted, or used for ALL button activities Êÿ˜šœ™J™KJ™%J™šÏk ˜ Jšœœ˜)Jšœœ—˜¢Jšœœ"˜/Jšœœœ˜/Jšœ œ˜&Jšœ œ˜)Jšœœ˜$Jšœœ ˜Jšœœ*˜DJšœ œ=˜NJšœ œ²˜ÀJšœ œ ˜Jšœ œ˜!Jšœ œ˜Jšœ œ+˜9Jšœœœ˜$Jšœœ ˜Jšœ œ˜&Jšœ œ1˜BJšœ œ˜%Jšœ œ˜(Jšœ œ ˜Jšœ œJ˜\Jšœœ8˜KJšœ ˜ J˜JšÏnœœœœŠœ]œ œ ˜·J˜——šœž œ œ˜GJ™9J˜—šœž œœœ=˜UIcode™&K˜Kšœœœ˜K˜8K˜$šœœ˜)Kšœ"™"šœ6œÏc˜Sšœœ˜)K˜1Kšœ˜—K˜Kšœœœ˜K˜—Kšœ˜—šœœ˜Kšœ7™7šž œœœœ˜=šžœœœœ˜9Kšœ6™6Kšœ5™5šœœœ˜K˜ K˜*KšœŸ!œ˜2—K˜—Kšœ˜%K˜—Kšœœ œ˜>K˜šœœ˜)šœ-œ˜5Kšœ ™ šœ œœ˜3Kšœ2œ˜:—K˜ Kšœœœ˜K˜—Kšœ˜—Kšœœ'˜5K˜—K˜0K˜K˜š žœœœž œœ˜ZK™šž œœ˜)Kšœ$˜$Kšœ5˜5Kšœ˜Kš œœœœœ˜VKš˜š œœœœHœœ˜š˜Kšœ2˜2Kšœ˜š œœœœœœ˜IKš˜—K˜Kš œœœœ<œ˜v—Kš˜—K˜—K˜K˜Kšœ"˜"—K˜K˜Kš žœœœž œœ˜N™gKšœ$˜$Kšœ5˜5Kšœ˜Kš œœœœœ˜VKš˜š œœœœHœœ˜š˜Kšœ2˜2Kšœ˜š œœœœœœ˜IKš˜—K˜Kš œœœœ<œ˜v—Kš˜—K˜—K˜K˜J™Jš žœœœ1œ œœ˜{™=Jšœ%˜%Jšœœ˜Jšœœ˜—˜šžœœœœ˜+Jšœ&œ9œ˜o—J˜šž œœ˜?Jšœ6˜6Jšœ˜Jšœ œ˜Jšœœ˜Jšœ˜J˜Jšœ¼˜¼J˜Jšœœœ˜)šœ+˜+Jšœ…œŸ"˜¾—˜J˜—Jšœ˜šœœ œ œ ˜;Jšœ2Ÿ˜JJšœKœ˜OJšœœœ˜!JšœZ˜ZJšœx˜xJ™QJšœ7œœŸ/˜‰Jšœ˜Jšœ.˜.Jšœ.˜.Jšœ3˜3—Jšœ˜—J˜—˜Jšœ™šœ'œœœ˜9J˜—Jšœdœœ˜vJ™WJšœœ˜šœDœ˜JJšœ˜—J˜Jšœ˜šœœ˜šœCœ˜IJšœ˜—J˜Jšœ ˜—J˜—J˜J˜J˜šžœ˜#Jšœ6˜6J˜—J˜šžœœœ&˜FJšœ=˜=Jšœœ˜Jšœœ˜J˜Jšœ-œ˜2Jšœ+œ˜0Jšœ1œ˜6Jšœœ8œ˜VJšœœœ˜šœ4œ˜9Jšœj˜j—J˜—˜J˜—J˜šž œœœ˜Jšœ?˜?Jšœœ˜Jšœœ˜Jšœ!˜!Jšœ˜—˜šœœ!œ˜.Jšœ*˜*Jšœ!œœ˜-J˜(šœ(œ œ˜?Jšœ˜ šœ$œœ˜HJšœ)˜)—J˜—Jš˜—Jšœ˜J˜Jšœœ0œ˜SJšœ œ'œ˜M—J˜J˜šž œœ˜&Jšœ˜Jšœ˜Jšœ˜—Jšœ˜J˜šž œ˜!J™áJš œ œœœœ˜#Jš œœœœœ˜)Jšœœœ œ˜@š œœœœœ˜)J™—J™Qšž œœœœœœœœœœ˜\Jš œœœœœœ˜5Jšœœœœ˜'Jšœœœœ˜+Jšœœœ"œ˜JJ˜#Jšœ˜ —J˜šžœœœœDœœœ"˜›Jš œœœ œœ˜HJšœœœ%˜:Jšœœœœ˜+Jšœœœ"œ˜JJ˜#Jšœ˜ —J˜šž œœœœœ œœœœœ˜IJš œœœœœœ˜/Jšœœœœ˜!Jšœœœœ˜+Jšœœœ"œ˜JJ˜#Jšœ˜ —J˜J˜šžœœ(˜˜FJšœ˜šœ2˜2Jšœ.œBœ˜|—J˜Jšœ6˜:—J˜—J˜J˜šžœœ(˜AJ™\J™¤Jšœ?˜?Jšœœœ<œ˜aJšœ œœœ˜Jšœœœœ˜"šœ>œœœ˜PJ˜—Jšœ$œ˜+šœG˜GJšœœ˜1JšœŒ˜ŒJšœR˜RJšœ˜šœœ˜#Jšœ-œ˜3—J˜—J˜J™vJ˜Jšœ˜Jšœ/˜/Jšœ3˜3—J˜J˜JšœHœ˜SJ˜Jšœ œœ˜šœ=œ˜CJš˜—J˜J˜š œœœœœœ˜=Jšœ&˜&Jšœ œ˜Jšœ˜˜˜JšœE˜EJšœD˜DJ˜&—Jšœ˜J˜JšœMœ˜VJ˜š˜Jšœ˜ ——J˜J˜šžœœœ\œ˜ƒšžœœ˜#JšœE˜EJšœœI˜cJ˜JšœB˜DJš˜˜AJš˜—J˜J˜JšœœHœ˜nJšœ˜šœŸN˜QJšœœ˜+JšœAœ˜FJšœ˜šœœ˜Jšœ'œ˜-—J˜—J˜Jšœ>˜B—˜J˜—Jšœ9˜9—J˜J˜šžœœœ\œœœœ œœ˜Üšžœœ˜#JšœE˜EJšœœI˜cJ˜JšœB˜DJš˜˜AJš˜—J˜J˜JšœœHœ˜nJšœ˜šœJ˜JJšœN˜NJšœ ˜—J˜Jšœ>˜B—˜J˜—Jšœ9˜9—J˜J˜šž œ˜&Jš œœœ$œ-œ˜…—J˜J˜šž œ˜J™ðJšœœ ˜.J˜šžœœ&˜AJšœ=˜=Jšœ<œ˜FšœŸN˜PJšœœ˜+Jšœ9œ˜>Jšœ˜šœœ˜Jšœ'œ˜-—J˜—J˜—J˜J˜Jšœ˜Jšœ7˜7Jšœ6˜6Jšœ˜J˜Jšœ4˜4—šœ˜J™—J˜šžœœ#˜CJšœ?˜?Jšœ!˜!Jšœ˜Jšœœ˜Jšœ˜Jšœœœ˜J˜Jšœ)˜)J˜šœœœ˜+Jšœ'˜'Jšœœœ˜*šœ(œ œ˜?Jšœ˜ šœ˜Jšœ œœ˜.Jšœ(˜(Jšœ#ŸB˜eJšœœœ˜@—J˜—Jš˜—Jšœ˜J˜JšœAŸƒ˜ÄJšœ2˜2J˜Jšœœœ˜G—˜J˜—šžœœ œ˜Cšœ˜Kšœ?˜?—šœ˜Kšœ*˜*—šœ˜ Kšœ3˜7Kšœ:˜>—Kšœ˜—Kšœœ%˜9Kšœ˜Kšœœ"˜:Kšœ™K˜Kšœ œœœ˜šž œœœ œ˜,Kšœœœ˜0—K˜Kšžœ4˜CKšœ˜K˜KšœJ™JKšœx˜xKšœŽ˜ŽKšœ|˜|Kšœx˜xKšœp˜pKšœ‰˜‰Kšœ‚˜‚Kšœ6˜6Kšœ&˜&Kšœ)œ˜.K˜K˜=K˜@K˜>K˜>K˜AK˜?K˜Kšœ˜——…—I²dÅ