DIRECTORY Atom USING [GetPName, MakeAtom], Buttons USING [Button], Commander USING [CommandProc, Register], Containers USING [ChildXBound, ChildYBound, Container, Create], Convert USING [RopeFromInt], Icons USING [IconFlavor, NewIconFromFile], IO USING [PutFR, PutFR1, int, rope], MBQueue USING [QueueClientAction], Menus USING [MenuProc, MouseButton], MessageWindow USING [Append, Blink, Clear], PopUpButtons USING [Class, Instantiate, MakeClass, nullChoice, PopUpButtonProc], Rope USING [ROPE, Concat, FromChar, FromRefText, Substr], Rules USING [Create, Rule], TEditDocument USING [SelectionId], TEditInput USING [CommandProc, editState, InterpInput, RecordRef, Register, UnRegister], TEditInputBackdoor USING [editObject, pdelState, pDel, sel, selState, mouseColor, editMessage], TEditInputOps USING [Delete, Copy, CopyFormat, CopyLooks, SetStyleName, Transpose, TransposeLooks, TransposeFormat], TEditSelection USING [pSel, sSel, MakeSelection], TextEdit USING [ChangeLooks, Size], TextLooks USING [allLooks, RopeToLooks], TiogaOps USING [CallWithLocks, CancelSelection, CommandProc, FirstChild, GetRope, GetSelection, Location, RegisterCommand, Root, SelectPoint, ViewerDoc], TiogaOpsDefs USING [Location, Ref, SelectionGrain, WhichSelection], TiogaVoicePrivate USING [ AddCharMark, AddVoiceProc, AdjustSilences, AgeAllViewers, BackSpace, BackWord, CopyTextMarkerToTextViewer, CopyTextViewerToTextMarker, DebugRope, DeleteCharMarks, DeleteSourceMarkers, DisplayCharMarks, EditAges, EditCharMarks, EditTextMarks, ExtractCharMarks, ExtractSoundList, ExtractTextMarks, oldest, PlayBackMenuProc, PlayFromSelection, ReColorViewer, RedrawTextMarkers, RedrawViewer, RegisterViewer, RemoveViewerReferences, ReplaceSoundList, ResumeFromEnd, ResumeFromSelection, RopeFromTextList, SaveRopeAtSourceMarkers, Selection, SelectionRec, Sound, SoundChars, SoundInterval, SoundIntervalRec, SoundList, SoundListFromIntervalSpecs, soundRopeCharDivisions, soundRopeCharLength, soundRopeResolution, StoreVoiceAtSelection, TextInput, TextListFromRope, thrushHandle, TiogaInterpret, TrackDeletes, voiceButtonQueue, VoiceViewerInfo, VoiceViewerInfoList, VoiceViewerInfoRec, youngest ], TIPUser USING [InstantiateNewTIPTable, TIPTable], ViewerClasses USING [Viewer, NotifyProc, ViewerClass], ViewerEvents USING [EventRegistration, RegisterEventProc, UnRegisterEventProc, EventProc], ViewerLocks USING [CallUnderWriteLock], ViewerOps USING [AddProp, CreateViewer, DestroyViewer, FetchProp, FetchViewerClass, OpenIcon, PaintViewer], ViewerSpecs USING [menuBarHeight, menuHeight], ViewerTools USING [TiogaContents, TiogaContentsRec, SetTiogaContents], VoiceRope USING [ DescribeRope, IntervalSpecs, Length, Replace, VoiceRopeInterval ], WindowManager USING [colorDisplayOn] ; VoiceViewersImpl: CEDAR MONITOR IMPORTS Atom, Commander, Containers, Convert, Icons, IO, MBQueue, MessageWindow, PopUpButtons, Rope, Rules, TEditInput, TEditInputBackdoor, TEditInputOps, TEditSelection, TextEdit, TextLooks, TiogaOps, TiogaVoicePrivate, TIPUser, ViewerEvents, ViewerLocks, ViewerOps, ViewerSpecs, ViewerTools, VoiceRope, WindowManager EXPORTS TiogaVoicePrivate = { addClass, playClass, stopClass, dictationOpsClass, markClass, storeClass, saveClass, adjustSilencesClass: PopUpButtons.Class; VoiceViewerInfo: TYPE = TiogaVoicePrivate.VoiceViewerInfo; VoiceViewerInfoList: TYPE = TiogaVoicePrivate.VoiceViewerInfoList; VoiceViewerInfoRec: TYPE = TiogaVoicePrivate.VoiceViewerInfoRec; voiceViewerInfoList: VoiceViewerInfoList ¬ NIL; nextVoiceViewerNumber: INT ¬ 1; voiceTIPTable: TIPUser.TIPTable ¬ NIL; soundViewerIcon: Icons.IconFlavor ¬ unInit; dirtySoundViewerIcon: Icons.IconFlavor ¬ unInit; BuildVoiceViewer: PUBLIC PROC [ voiceID: Rope.ROPE, textInVoice: Rope.ROPE, youngVoice: BOOLEAN] RETURNS [viewer: ViewerClasses.Viewer, viewerInfo: VoiceViewerInfo, viewerNumber: INT] = { container: ViewerClasses.Viewer; curIndent: INTEGER ¬ 0; buttonSpacing: INTEGER = 9; button: Buttons.Button; rule: Rules.Rule; viewerInfo ¬ InsertVoiceViewerInfo[]; container ¬ Containers.Create[ info: [name: Rope.Concat["Sound Viewer #", Convert.RopeFromInt[viewerInfo.viewerNumber]], column: IF WindowManager.colorDisplayOn THEN color ELSE left, inhibitDestroy: FALSE, scrollable: FALSE, icon: soundViewerIcon, iconic: TRUE ], paint: FALSE]; button ¬ addClass.Instantiate[viewerInfo: [ name: "Add", wx: curIndent, wy: -1, border: FALSE, parent: container], paint: FALSE]; curIndent ¬ button.wx + button.ww + buttonSpacing; button ¬ playClass.Instantiate[viewerInfo: [ name: "Play", wx: curIndent, wy: -1, border: FALSE, parent: container], paint: FALSE]; curIndent ¬ button.wx + button.ww + buttonSpacing; button ¬ stopClass.Instantiate[viewerInfo: [ name: "STOP", wx: curIndent, wy: -1, border: FALSE, parent: container], paint: FALSE]; curIndent ¬ button.wx + button.ww + buttonSpacing; button ¬ dictationOpsClass.Instantiate[viewerInfo: [ name: "DictationOps", wx: curIndent, wy: -1, border: FALSE, parent: container], paint: FALSE]; curIndent ¬ button.wx + button.ww + buttonSpacing; button ¬ markClass.Instantiate[viewerInfo: [ name: "Mark", wx: curIndent, wy: -1, border: FALSE, parent: container], paint: FALSE]; curIndent ¬ button.wx + button.ww + buttonSpacing; button ¬ storeClass.Instantiate[viewerInfo: [ name: "Store", wx: curIndent, wy: -1, border: FALSE, parent: container], paint: FALSE]; curIndent ¬ button.wx + button.ww + buttonSpacing; button ¬ saveClass.Instantiate[viewerInfo: [ name: "Save", wx: curIndent, wy: -1, border: FALSE, parent: container], paint: FALSE]; curIndent ¬ button.wx + button.ww + buttonSpacing; button ¬ adjustSilencesClass.Instantiate[viewerInfo: [ name: "AdjustSilences", wx: curIndent, wy: -1, border: FALSE, parent: container], paint: FALSE]; rule ¬ Rules.Create[ info: [ parent: container, wx: 0, wy: ViewerSpecs.menuHeight, wh: ViewerSpecs.menuBarHeight ], paint: FALSE]; Containers.ChildXBound[container, rule]; viewer ¬ ViewerOps.CreateViewer[ -- text viewer is first child of parent container flavor: $Text, info: [parent: container, wx: 0, wy: ViewerSpecs.menuHeight+ViewerSpecs.menuBarHeight, border: FALSE, scrollable: TRUE ], paint: FALSE]; Containers.ChildXBound[container, viewer]; Containers.ChildYBound[container, viewer]; viewer.tipTable ¬ voiceTIPTable; viewerNumber ¬ viewerInfo.viewerNumber; ViewerOps.AddProp[viewer, $voiceViewerInfo, viewerInfo]; viewerInfo.viewer ¬ viewer; viewerInfo.color ¬ viewer.column = color; viewerInfo.destroyEvent ¬ ViewerEvents.RegisterEventProc[ proc: DestroyViewerEvent, event: destroy, filter: container, before: TRUE]; viewerInfo.changeColumnEvent ¬ ViewerEvents.RegisterEventProc[ proc: ChangeColumnEvent, event: changeColumn, filter: container, before: FALSE]; SetViewerContents[viewer, viewerInfo, voiceID, textInVoice, youngVoice]; MakeVoiceNotEdited[viewer]; ViewerOps.OpenIcon[icon: container]; }; CreateVoiceViewerPopUpButtons: PROC ~ { addClass ¬ PopUpButtons.MakeClass[[ proc: AddProc, headMenu: FALSE, choices: LIST[ [$AddAtSelection, "Start recording at voice selection"], [$DictationViewer, "Open new viewer & start recording"] ], doc: "Add: recording operations" ]]; playClass ¬ PopUpButtons.MakeClass[[ proc: PlayProc, headMenu: FALSE, choices: LIST[ [$PlayViewer, "Play back entire viewer"], [$PlaySelection, "Play back contents of voice selection"] ], doc: "Play: playback operations" ]]; stopClass ¬ PopUpButtons.MakeClass[[ proc: StopProc, headMenu: FALSE, choices: LIST[ [$STOP, "Stop recording or playback"] -- stop all | stop one? ], doc: "STOP: stop recording or playback" ]]; dictationOpsClass ¬ PopUpButtons.MakeClass[[ proc: DictationOpsProc, headMenu: FALSE, choices: LIST[ [$PlayFromSelection, "Play back from voice selection to end of fresh voice"], [$ReplaceFromSelection, "Delete from voice selection to end & start recording"], [$AddAtEnd, "Start recording at end of fresh voice"] ], doc: "DictationOps: specialized dictation machine operations" ]]; markClass ¬ PopUpButtons.MakeClass[[ proc: MarkProc, headMenu: FALSE, choices: LIST[ [$MarkAtPlayback, "Mark current playback location"], [$MarkAtSelection, "Mark beginning of voice selection"], PopUpButtons.nullChoice, PopUpButtons.nullChoice, PopUpButtons.nullChoice, PopUpButtons.nullChoice, [$DeleteViewerMarks, "Delete all marks in viewer"], [$DeleteSelectionMarks, "Delete all marks in voice selection"] ], doc: "Mark: operations on temporary markers" ]]; storeClass ¬ PopUpButtons.MakeClass[[ proc: StoreProc, guarded: TRUE, headMenu: FALSE, choices: LIST[ [$Store, "Store viewer at Tioga selection"] ], doc: "Store: store viewer at Tioga selection" ]]; saveClass ¬ PopUpButtons.MakeClass[[ proc: SaveProc, headMenu: FALSE, choices: LIST[ [$Save, "Save viewer at all matching open voice balloons"] ], doc: "Save: save viewer at all matching open voice balloons" ]]; adjustSilencesClass ¬ PopUpButtons.MakeClass[[ proc: AdjustSilencesProc, headMenu: FALSE, choices: LIST[ [$AdjustSilences, "Reduce all silences in viewer to set length"] ], doc: "AdjustSilences: reduce lengths of silences" ]]; }; QProcRef: TYPE ~ REF QProcBody; QProcBody: TYPE ~ RECORD [ proc: Menus.MenuProc, parent: ViewerClasses.Viewer, clientData: REF ANY ¬ NIL, mouseButton: Menus.MouseButton ¬ $red, shift, control: BOOL ¬ FALSE ]; QProc: PROC [ref: REF] ~ { q: QProcRef ¬ NARROW [ref]; q.proc[q.parent, q.clientData, q.mouseButton, q.shift, q.control]; }; AddProc: PopUpButtons.PopUpButtonProc ~ { viewer: ViewerClasses.Viewer ¬ NARROW[view]; MBQueue.QueueClientAction[TiogaVoicePrivate.voiceButtonQueue, QProc, NEW [QProcBody ¬ [proc: TiogaVoicePrivate.TiogaInterpret, clientData: Atom.MakeAtom[Rope.Concat["TV", Atom.GetPName[NARROW[key]]]], parent: viewer.parent.child]] ]; }; PlayProc: PopUpButtons.PopUpButtonProc ~ { viewer: ViewerClasses.Viewer ¬ NARROW[view]; menuData: QProcRef ¬ NEW [QProcBody ¬ [proc: TiogaVoicePrivate.TiogaInterpret, clientData: Atom.MakeAtom[Rope.Concat["TV", Atom.GetPName[NARROW[key]]]], parent: viewer.parent.child]]; SELECT key FROM $PlayViewer => menuData.mouseButton ¬ $blue; $PlaySelection => menuData.mouseButton ¬ $red; ENDCASE; MBQueue.QueueClientAction[TiogaVoicePrivate.voiceButtonQueue, QProc, menuData]; }; StopProc: PopUpButtons.PopUpButtonProc ~ { viewer: ViewerClasses.Viewer ¬ NARROW[view]; MBQueue.QueueClientAction[TiogaVoicePrivate.voiceButtonQueue, QProc, NEW [QProcBody ¬ [proc: TiogaVoicePrivate.TiogaInterpret, clientData: $TVSTOP, parent: viewer.parent.child]] ]; }; SaveProc: PopUpButtons.PopUpButtonProc ~ { viewer: ViewerClasses.Viewer ¬ NARROW[view]; MBQueue.QueueClientAction[TiogaVoicePrivate.voiceButtonQueue, QProc, NEW [QProcBody ¬ [proc: TiogaVoicePrivate.TiogaInterpret, clientData: $TVSaveVoice, parent: viewer.parent.child]] ]; }; StoreProc: PopUpButtons.PopUpButtonProc ~ { viewer: ViewerClasses.Viewer ¬ NARROW[view]; MBQueue.QueueClientAction[TiogaVoicePrivate.voiceButtonQueue, QProc, NEW [QProcBody ¬ [proc: TiogaVoicePrivate.TiogaInterpret, clientData: $TVStoreVoice, parent: viewer.parent.child]] ]; }; MarkProc: PopUpButtons.PopUpButtonProc ~ { viewer: ViewerClasses.Viewer ¬ NARROW[view]; menuData: QProcRef ¬ NEW [QProcBody ¬ [proc: TiogaVoicePrivate.TiogaInterpret, clientData: Atom.MakeAtom[Rope.Concat["TV", Atom.GetPName[NARROW[key]]]], parent: viewer.parent.child]]; SELECT key FROM $MarkAtSelection => menuData.mouseButton ¬ $red; $MarkAtPlayback => menuData.mouseButton ¬ $yellow; $DeleteSelectionMarks => menuData.mouseButton ¬ $red; $DeleteViewerMarks => menuData.mouseButton ¬ $blue; ENDCASE; MBQueue.QueueClientAction[TiogaVoicePrivate.voiceButtonQueue, QProc, menuData]; }; DictationOpsProc: PopUpButtons.PopUpButtonProc ~ { viewer: ViewerClasses.Viewer ¬ NARROW[view]; MBQueue.QueueClientAction[TiogaVoicePrivate.voiceButtonQueue, QProc, NEW [QProcBody ¬ [proc: TiogaVoicePrivate.TiogaInterpret, clientData: Atom.MakeAtom[Rope.Concat["TV", Atom.GetPName[NARROW[key]]]], parent: viewer.parent.child]] ]; }; AdjustSilencesProc: PopUpButtons.PopUpButtonProc ~ { viewer: ViewerClasses.Viewer ¬ NARROW[view]; MBQueue.QueueClientAction[TiogaVoicePrivate.voiceButtonQueue, QProc, NEW [QProcBody ¬ [proc: TiogaVoicePrivate.TiogaInterpret, clientData: $TVAdjustSilences, parent: viewer.parent.child]] ]; }; RegisterButtonBehaviors: PROC ~ { TiogaOps.RegisterCommand[name: $TVAddAtEnd, proc: TiogaVoicePrivate.ResumeFromEnd]; TiogaOps.RegisterCommand[name: $TVAddAtSelection, proc: TiogaVoicePrivate.AddVoiceProc]; TiogaOps.RegisterCommand[name: $TVAdjustSilences, proc: TiogaVoicePrivate.AdjustSilences]; TiogaOps.RegisterCommand[name: $TVDeleteSelectionMarks, proc: TiogaVoicePrivate.DeleteCharMarks]; TiogaOps.RegisterCommand[name: $TVDeleteViewerMarks, proc: TiogaVoicePrivate.DeleteCharMarks]; TiogaOps.RegisterCommand[name: $TVMarkAtPlayback, proc: TiogaVoicePrivate.AddCharMark]; TiogaOps.RegisterCommand[name: $TVMarkAtSelection, proc: TiogaVoicePrivate.AddCharMark]; TiogaOps.RegisterCommand[name: $TVPlayFromSelection, proc: TiogaVoicePrivate.PlayFromSelection]; TiogaOps.RegisterCommand[name: $TVPlaySelection, proc: TiogaVoicePrivate.PlayBackMenuProc]; TiogaOps.RegisterCommand[name: $TVPlayViewer, proc: TiogaVoicePrivate.PlayBackMenuProc]; TiogaOps.RegisterCommand[name: $TVReplaceFromSelection, proc: TiogaVoicePrivate.ResumeFromSelection]; TiogaOps.RegisterCommand[name: $TVSaveVoice, proc: SaveVoice]; TiogaOps.RegisterCommand[name: $TVStoreVoice, proc: StoreVoice]; }; SetVoiceRopeInfo: PROC [viewerInfo: VoiceViewerInfo, voiceID: Rope.ROPE, textInVoice: Rope.ROPE, youngVoice: BOOLEAN] RETURNS [soundRope: Rope.ROPE] ~ { intervals: VoiceRope.IntervalSpecs; viewerInfo.soundList ¬ NIL; IF voiceID # NIL THEN { viewerInfo.ropeInterval ¬ [voiceID, 0, VoiceRope.Length[TiogaVoicePrivate.thrushHandle, NEW [VoiceRope.VoiceRopeInterval ¬ [voiceID, 0, 0]]]]; intervals ¬ VoiceRope.DescribeRope[TiogaVoicePrivate.thrushHandle, NEW [VoiceRope.VoiceRopeInterval ¬ viewerInfo.ropeInterval]]; viewerInfo.soundList ¬ TiogaVoicePrivate.SoundListFromIntervalSpecs[intervals, viewerInfo.ropeInterval.length]; [soundRope: soundRope, remnant: viewerInfo.remnant] ¬ TiogaVoicePrivate.SoundChars[viewerInfo]; viewerInfo.textMarkList ¬ TiogaVoicePrivate.TextListFromRope[textInVoice]; viewerInfo.ageList ¬ CONS [[0, IF youngVoice THEN TiogaVoicePrivate.youngest-1 ELSE TiogaVoicePrivate.oldest], NIL]; } ELSE { soundRope ¬ " "; viewerInfo.ropeInterval ¬ [NIL, 0, 0]; }; }; SetViewerContents: PUBLIC PROC [viewer: ViewerClasses.Viewer, viewerInfo: VoiceViewerInfo, voiceID: Rope.ROPE, textInVoice: Rope.ROPE, youngVoice: BOOLEAN] = { SetViewerDoIt: PROC ~ { soundRope: Rope.ROPE ¬ SetVoiceRopeInfo[viewerInfo, voiceID, textInVoice, youngVoice]; [] ¬ SetTiogaViewerParams[viewer, soundRope]; IF voiceID # NIL AND viewerInfo.ageList.first.age = TiogaVoicePrivate.youngest-1 THEN TiogaVoicePrivate.AgeAllViewers[viewer] ELSE TiogaVoicePrivate.ReColorViewer[viewer, viewerInfo]; viewerInfo.editInProgress ¬ (voiceID = NIL) -- can do this because the viewer is created with editInProgress = TRUE, so nobody else can have grabbed the lock }; ViewerLocks.CallUnderWriteLock[SetViewerDoIt, viewer]; }; SetTiogaViewerParams: PUBLIC PROC [viewer: ViewerClasses.Viewer, newcontents: Rope.ROPE] RETURNS [voiceNode: TiogaOpsDefs.Ref] = { SetTiogaViewerDoIt: PROC ~ { rootNode: TiogaOpsDefs.Ref; viewercontents: ViewerTools.TiogaContents ¬ NEW[ViewerTools.TiogaContentsRec ¬ [contents: newcontents, formatting: NIL]]; ViewerTools.SetTiogaContents[viewer: viewer, contents: viewercontents, paint: FALSE]; rootNode ¬ TiogaOps.ViewerDoc[viewer]; TEditInputOps.SetStyleName["voiceProfile", rootNode]; voiceNode ¬ TiogaOps.FirstChild[rootNode]; TiogaVoicePrivate.RedrawTextMarkers[viewer, voiceNode]; }; ViewerLocks.CallUnderWriteLock[SetTiogaViewerDoIt, viewer]; }; SetVoiceLooks: PUBLIC PROC [node: TiogaOpsDefs.Ref, start, len: INT, looks: Rope.ROPE] ~ { DoIt: PROC [root: TiogaOpsDefs.Ref] ~ { TextEdit.ChangeLooks[root: root, text: node, remove: TextLooks.allLooks, add: TextLooks.RopeToLooks[looks], start: start, len: len, event: NIL] }; TiogaOps.CallWithLocks[DoIt, TiogaOps.Root[node]]; }; MakeVoiceEdited: PUBLIC PROC [viewer: ViewerClasses.Viewer] = { viewerInfo: VoiceViewerInfo; container: ViewerClasses.Viewer; DoIt: PROC = { viewerInfo.edited ¬ container.newVersion ¬ TRUE; container.icon ¬ dirtySoundViewerIcon; ViewerOps.PaintViewer[container, caption, FALSE]; }; IF viewer=NIL THEN RETURN; viewerInfo ¬ NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], VoiceViewerInfo]; IF viewerInfo=NIL THEN RETURN; -- not really a voice viewer container ¬ viewer.parent; IF NOT viewerInfo.edited THEN ViewerLocks.CallUnderWriteLock[DoIt, container]; }; MakeVoiceNotEdited: PROC [viewer: ViewerClasses.Viewer] = { viewerInfo: VoiceViewerInfo; container: ViewerClasses.Viewer; DoIt: PROC = { viewerInfo.edited ¬ container.newVersion ¬ container.newFile ¬ FALSE; container.icon ¬ soundViewerIcon; ViewerOps.PaintViewer[container, caption, FALSE]; }; IF viewer=NIL THEN RETURN; viewerInfo ¬ NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], VoiceViewerInfo]; IF viewerInfo=NIL THEN RETURN; -- not really a voice viewer container ¬ viewer.parent; ViewerLocks.CallUnderWriteLock[DoIt, container]; }; SetVoiceViewerEditStatus: PUBLIC PROC [viewer: ViewerClasses.Viewer] = { viewerInfo: VoiceViewerInfo; container: ViewerClasses.Viewer; DoIt: PROC = { IF viewerInfo.edited # container.newVersion THEN { container.newVersion ¬ viewerInfo.edited; IF ~viewerInfo.edited THEN { container.newFile ¬ FALSE; container.icon ¬ soundViewerIcon; } ELSE container.icon ¬ dirtySoundViewerIcon; ViewerOps.PaintViewer[container, caption, FALSE]; } }; IF viewer=NIL THEN RETURN; viewerInfo ¬ NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], VoiceViewerInfo]; IF viewerInfo=NIL THEN RETURN; -- not really a voice viewer container ¬ viewer.parent; ViewerLocks.CallUnderWriteLock[DoIt, container] }; ViewerContainsVoice: PUBLIC PROC [v: ViewerClasses.Viewer] RETURNS [BOOL] = { IF v#NIL AND ViewerOps.FetchProp[v, $voiceViewerInfo]#NIL THEN { voiceNode: TiogaOpsDefs.Ref¬ TiogaOps.FirstChild[TiogaOps.ViewerDoc[v]]; IF TextEdit.Size[voiceNode] # 0 THEN RETURN[TRUE]; }; RETURN[FALSE]; }; SameViewerDoc: PUBLIC PROC [v1, v2: ViewerClasses.Viewer] RETURNS [BOOL] = { FOR v: ViewerClasses.Viewer ¬ v1, v1.link DO IF v=v2 THEN RETURN[TRUE]; IF v=v1 THEN EXIT; ENDLOOP; RETURN[FALSE]; }; SetParentViewer: PUBLIC PROC [viewerInfo: VoiceViewerInfo, parentViewer: ViewerClasses.Viewer, positionInParent: TiogaOpsDefs.Location] = { viewerInfo.parentViewer ¬ parentViewer; viewerInfo.positionInParent ¬ positionInParent; TiogaVoicePrivate.RegisterViewer[parentViewer]; }; FindAttachedVoiceViewers: PUBLIC PROC [viewer: ViewerClasses.Viewer] RETURNS [viewerInfoList: VoiceViewerInfoList] = { FOR vList: VoiceViewerInfoList ¬ voiceViewerInfoList, vList.rest WHILE vList # NIL DO IF vList.first.parentViewer = viewer THEN viewerInfoList ¬ CONS[vList.first, viewerInfoList] ENDLOOP; }; RemoveParentPointersTo: PUBLIC PROC [viewer: ViewerClasses.Viewer, findSplits: BOOLEAN] = { FOR vList: VoiceViewerInfoList ¬ voiceViewerInfoList, vList.rest WHILE vList # NIL DO info: VoiceViewerInfo ¬ vList.first; IF info.parentViewer = viewer THEN { splitLink: ViewerClasses.Viewer ¬ info.parentViewer.link; IF findSplits AND splitLink # NIL THEN { info.parentViewer ¬ splitLink; TiogaVoicePrivate.RegisterViewer[splitLink]; } ELSE { info.parentViewer ¬ NIL; MessageWindow.Append[IO.PutFR1["Detaching parent link for voice viewer %d\n", IO.int[info.viewerNumber]], TRUE] -- was DebugRope; may be too verbose }; } ENDLOOP }; RemoveParentViewer: PUBLIC PROC [viewerInfo: VoiceViewerInfo] = { viewerInfo.parentViewer ¬ NIL; }; GetVoiceViewerInfo: PUBLIC PROC [n: INT] RETURNS [viewerInfo: VoiceViewerInfo] = { FOR vList: VoiceViewerInfoList ¬ voiceViewerInfoList, vList.rest WHILE vList#NIL DO IF vList.first.viewerNumber=n THEN RETURN[vList.first]; ENDLOOP; }; GetVoiceViewerInfoList: PUBLIC PROC RETURNS [vList: VoiceViewerInfoList] = { RETURN[voiceViewerInfoList]; }; InsertVoiceViewerInfo: PROC RETURNS [newViewerInfo: VoiceViewerInfo] = { newViewerInfo ¬ NEW[VoiceViewerInfoRec]; newViewerInfo.viewerNumber ¬ nextVoiceViewerNumber; nextVoiceViewerNumber ¬ nextVoiceViewerNumber + 1; voiceViewerInfoList ¬ CONS[newViewerInfo, voiceViewerInfoList]; }; StoreVoice: TiogaOps.CommandProc = { viewerInfo: VoiceViewerInfo ¬ NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], VoiceViewerInfo]; oldParentViewer: ViewerClasses.Viewer ¬ viewerInfo.parentViewer; IF NOT ViewerContainsVoice[viewer] THEN { MessageWindow.Append["Voice viewer is empty", TRUE]; MessageWindow.Blink[]; RETURN }; IF TiogaVoicePrivate.StoreVoiceAtSelection[viewerInfo] THEN { MakeVoiceNotEdited[viewer]; IF oldParentViewer # NIL THEN TiogaVoicePrivate.DeleteSourceMarkers[oldParentViewer, viewerInfo.viewerNumber, viewerInfo.positionInParent]; }; }; SaveVoice: TiogaOps.CommandProc = { viewerInfo: VoiceViewerInfo ¬ NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], VoiceViewerInfo]; IF ~viewerInfo.edited THEN { MessageWindow.Append["Voice viewer is not edited", TRUE]; RETURN }; IF NOT ViewerContainsVoice[viewer] THEN { MessageWindow.Append["Voice viewer is empty", TRUE]; MessageWindow.Blink[]; RETURN }; IF viewerInfo.parentViewer = NIL THEN { MessageWindow.Append["Voice viewer has no parent text viewer", TRUE]; MessageWindow.Blink[]; RETURN }; IF ~TiogaVoicePrivate.SaveRopeAtSourceMarkers[viewerInfo.parentViewer, viewerInfo.viewerNumber, viewerInfo.ropeInterval.ropeID, TiogaVoicePrivate.RopeFromTextList[viewerInfo.textMarkList]] THEN { MessageWindow.Append["Cannot find source marker for voice window", TRUE]; MessageWindow.Blink[]; RETURN }; MakeVoiceNotEdited[viewer] }; RemoveVoiceViewerInfo: PROC [viewerInfo: VoiceViewerInfo] = { IF voiceViewerInfoList # NIL THEN { IF voiceViewerInfoList.first = viewerInfo THEN {voiceViewerInfoList ¬ voiceViewerInfoList.rest; RETURN}; FOR vList: VoiceViewerInfoList ¬ voiceViewerInfoList, vList.rest WHILE vList#NIL DO IF vList.rest.first = viewerInfo THEN {vList.rest ¬ vList.rest.rest; RETURN} ENDLOOP; }; ERROR; -- not in list! }; DestroyViewerEvent: ViewerEvents.EventProc = { RETURN [~DestroyVoiceInfo[viewer.child]] }; DestroyVoiceInfo: PROC [ viewer: ViewerClasses.Viewer] RETURNS [didIt: BOOLEAN ¬ TRUE] = { viewerInfo: VoiceViewerInfo ¬ NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], VoiceViewerInfo]; IF ~TiogaVoicePrivate.RemoveViewerReferences[viewer] THEN { MessageWindow.Append["Wait until voice cue disappears before destroying viewer", TRUE]; MessageWindow.Blink[]; RETURN [FALSE] }; IF viewerInfo.editInProgress THEN { MessageWindow.Append["Finish edit in progress before destroying viewer", TRUE]; MessageWindow.Blink[]; RETURN [FALSE] }; ViewerEvents.UnRegisterEventProc[viewerInfo.destroyEvent, destroy]; ViewerEvents.UnRegisterEventProc[viewerInfo.changeColumnEvent, changeColumn]; IF viewerInfo.parentViewer = NIL THEN TiogaVoicePrivate.DebugRope[IO.PutFR1["Voice viewer %d had no parent\n", IO.int[viewerInfo.viewerNumber]]] ELSE TiogaVoicePrivate.DeleteSourceMarkers[viewerInfo.parentViewer, viewerInfo.viewerNumber, [NIL, -1]]; RemoveVoiceViewerInfo[viewerInfo]; }; RedrawViewerMenuProc: Menus.MenuProc = { viewer: ViewerClasses.Viewer ¬ parent; viewerInfo: VoiceViewerInfo ¬ NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], VoiceViewerInfo]; IF GetVoiceLock[viewerInfo] THEN { trueContents: Rope.ROPE ¬ TiogaVoicePrivate.SoundChars[viewerInfo].soundRope; [] ¬ TiogaVoicePrivate.RedrawViewer[viewer, trueContents, 0, 0, 0, viewerInfo.remnant, FALSE, unAltered]; SetVoiceViewerEditStatus[viewer]; viewerInfo.editInProgress ¬ FALSE } }; ChangeColumnEvent: ViewerEvents.EventProc = { viewerInfo: VoiceViewerInfo; viewer ¬ viewer.child; -- want to get at the $Text viewer inside the outer container viewerInfo ¬ NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], VoiceViewerInfo]; IF (viewer.column = color) # viewerInfo.color THEN { viewerInfo.color ¬ viewer.column = color; IF GetVoiceLock[viewerInfo] THEN { trueContents: Rope.ROPE ¬ TiogaVoicePrivate.SoundChars[viewerInfo].soundRope; [] ¬ TiogaVoicePrivate.RedrawViewer[viewer, trueContents, 0, 0, 0, viewerInfo.remnant, FALSE, unAltered]; SetVoiceViewerEditStatus[viewer]; viewerInfo.editInProgress ¬ FALSE } ELSE { MessageWindow.Append["Cannot repaint viewer during edit: close & open viewer when changing between color and monochrome if the wrong state persists", TRUE]; MessageWindow.Blink[] } } }; GetVoiceLock: PUBLIC ENTRY PROC [ info: VoiceViewerInfo] RETURNS [gotIt: BOOLEAN] = { IF info = NIL THEN RETURN[FALSE]; gotIt ¬ NOT info.editInProgress; IF info.editInProgress THEN MessageWindow.Append["Try again when the viewer's contents are stable", TRUE] ELSE info.editInProgress ¬ TRUE; }; InterceptAllInput: ViewerClasses.NotifyProc = { IF ViewerOps.FetchProp[self, $voiceViewerInfo] = NIL THEN TEditInput.InterpInput[self, input] -- just what the real one does ELSE { passOnList, endOfPassOnList: LIST OF REF ANY ¬ NIL; FOR currItem: LIST OF REF ANY ¬ input, currItem.rest WHILE currItem # NIL DO WITH currItem.first SELECT FROM z: REF CHAR => { IF passOnList # NIL THEN { TEditInput.InterpInput[self, passOnList]; passOnList ¬ NIL }; TiogaVoicePrivate.TextInput[self, Rope.FromChar[z­]] }; z: Rope.ROPE => { IF passOnList # NIL THEN { TEditInput.InterpInput[self, passOnList]; passOnList ¬ NIL }; TiogaVoicePrivate.TextInput[self, z] }; z: REF TEXT => { IF passOnList # NIL THEN { TEditInput.InterpInput[self, passOnList]; passOnList ¬ NIL }; TiogaVoicePrivate.TextInput[self, Rope.FromRefText[z]] }; ENDCASE => IF passOnList = NIL THEN { passOnList ¬ CONS[currItem.first, NIL]; endOfPassOnList ¬ passOnList } ELSE { endOfPassOnList.rest ¬ CONS[currItem.first, NIL]; endOfPassOnList ¬ endOfPassOnList.rest } ENDLOOP; IF passOnList # NIL THEN TEditInput.InterpInput[self, passOnList] } }; InterceptBackSpace: TEditInput.CommandProc = { IF TestPrimary[] = normal THEN RETURN; TiogaVoicePrivate.BackSpace[viewer]; RETURN [quit: TRUE] }; InterceptBackWord: TEditInput.CommandProc = { IF TestPrimary[] = normal THEN RETURN; TiogaVoicePrivate.BackWord[viewer]; RETURN [quit: TRUE] }; InterceptDelete: TEditInput.CommandProc = { IF TestPrimary[] = normal THEN TiogaVoicePrivate.TrackDeletes[] ELSE { Delete[]; RETURN [quit: TRUE] }; }; InterceptEdit: TEditInput.CommandProc = { quit ¬ TRUE; IF TEditSelection.pSel.viewer = NIL THEN { IF TEditSelection.sSel.viewer # NIL THEN TEditSelection.MakeSelection[NIL,secondary]; -- get rid of secondary recordAtom ¬ FALSE } ELSE IF TEditInput.editState = tolimbo THEN { recordAtom ¬ FALSE; TEditInput.RecordRef[$Delete]; CheckDelete[TRUE] } ELSE IF TEditSelection.sSel.viewer = NIL THEN recordAtom ¬ FALSE ELSE { RecordEditObject: PROC = { TEditInput.RecordRef[SELECT TEditInputBackdoor.editObject FROM text => $EditText, looks => $EditLooks, format => $EditFormat, ENDCASE => ERROR] }; recordAtom ¬ TRUE; SELECT TEditInput.editState FROM reset, abort => recordAtom ¬ FALSE; toprimary => { TEditInput.RecordRef[$GetSecondary]; TEditInput.RecordRef[$ToPrimary]; RecordEditObject[]; SELECT TEditInputBackdoor.editObject FROM text => { IF TEditSelection.pSel.pendingDelete THEN TEditInput.RecordRef[$MakePDel]; CheckCopy[primary] }; looks => CheckCopyLooks[primary]; format => CheckCopyFormat[primary]; ENDCASE => ERROR }; tosecondary => { TEditInput.RecordRef[$GetSecondary]; TEditInput.RecordRef[$ToSecondary]; RecordEditObject[]; SELECT TEditInputBackdoor.editObject FROM text => { IF TEditSelection.pSel.pendingDelete THEN TEditInput.RecordRef[$MakePDel]; CheckCopy[secondary] }; looks => CheckCopyLooks[secondary]; format => CheckCopyFormat[secondary]; ENDCASE => ERROR }; toboth => { TEditInput.RecordRef[$GetSecondary]; TEditInput.RecordRef[$ToBoth]; RecordEditObject[]; SELECT TEditInputBackdoor.editObject FROM text => CheckTranspose[]; looks => CheckTransposeLooks[]; format => CheckTransposeFormat[]; ENDCASE => ERROR }; ENDCASE => ERROR }; TEditInput.editState ¬ reset; TEditInputBackdoor.editObject ¬ text; TEditInputBackdoor.pdelState ¬ reset; TEditInputBackdoor.pDel ¬ FALSE; TEditInputBackdoor.selState ¬ reset; TEditInputBackdoor.sel ¬ primary; TEditInputBackdoor.mouseColor ¬ red; -- put these back to normal IF TEditInputBackdoor.editMessage # NIL THEN MessageWindow.Clear[]; TEditInputBackdoor.editMessage ¬ NIL }; selectionTypes: TYPE = {bothNormal, primaryVoice, secondaryVoice, bothVoice}; TestSelections: PROC RETURNS [types: selectionTypes] = { types ¬ IF ViewerOps.FetchProp[TiogaOps.GetSelection[primary].viewer, $voiceViewerInfo] = NIL THEN ( IF ViewerOps.FetchProp[TiogaOps.GetSelection[secondary].viewer, $voiceViewerInfo] = NIL THEN bothNormal ELSE secondaryVoice ) ELSE ( IF ViewerOps.FetchProp[TiogaOps.GetSelection[secondary].viewer, $voiceViewerInfo] = NIL THEN primaryVoice ELSE bothVoice ) }; primaryType: TYPE = {normal, voice}; TestPrimary: PROC RETURNS [type: primaryType] = { type ¬ IF ViewerOps.FetchProp[TiogaOps.GetSelection[primary].viewer, $voiceViewerInfo] = NIL THEN normal ELSE voice }; CheckDelete: PROC [saveForPaste: BOOL ¬ TRUE] = { SELECT TestPrimary[] FROM normal => { TiogaVoicePrivate.TrackDeletes[]; TEditInputOps.Delete[saveForPaste] }; voice => Delete[] ENDCASE }; CheckCopy: PROC [target: TEditDocument.SelectionId ¬ primary] = { SELECT TestSelections[] FROM bothNormal => { IF VoiceInSourceDocument[target] THEN TrackTextCopy[target]; TEditInputOps.Copy[target]; }; bothVoice => Copy[target]; primaryVoice => IF target=primary THEN TiogaVoicePrivate.CopyTextViewerToTextMarker[primary] ELSE TiogaVoicePrivate.CopyTextMarkerToTextViewer[secondary]; secondaryVoice => IF target=secondary THEN TiogaVoicePrivate.CopyTextViewerToTextMarker[secondary] ELSE TiogaVoicePrivate.CopyTextMarkerToTextViewer[primary]; ENDCASE; }; VoiceInSourceDocument: PROC [target: TEditDocument.SelectionId ¬ primary] RETURNS [BOOLEAN ¬ FALSE] = { }; TrackTextCopy: PROC [target: TEditDocument.SelectionId ¬ primary] = { }; CheckCopyFormat: PROC [target: TEditDocument.SelectionId ¬ primary] = { SELECT TestSelections[] FROM bothNormal => TEditInputOps.CopyFormat[target]; bothVoice => Mumble["Copy format", "two voice"] ENDCASE => Mumble["Copy format", "voice and text"] }; CheckCopyLooks: PROC [target: TEditDocument.SelectionId ¬ primary] = { SELECT TestSelections[] FROM bothNormal => TEditInputOps.CopyLooks[target]; bothVoice => Mumble["Copy looks", "two voice"] ENDCASE => Mumble["Copy looks", "voice and text"] }; CheckTranspose: PROC [target: TEditDocument.SelectionId ¬ primary] = { SELECT TestSelections[] FROM bothNormal => TEditInputOps.Transpose[target]; bothVoice => Transpose[target]; ENDCASE => Mumble["Transpose", "voice and text"] }; CheckTransposeFormat: PROC [target: TEditDocument.SelectionId ¬ primary] = { SELECT TestSelections[] FROM bothNormal => TEditInputOps.TransposeFormat[target]; bothVoice => Mumble["Transpose format", "two voice"] ENDCASE => Mumble["Transpose format", "voice and text"] }; CheckTransposeLooks: PROC [target: TEditDocument.SelectionId ¬ primary] = { SELECT TestSelections[] FROM bothNormal => TEditInputOps.TransposeLooks[target]; bothVoice => Mumble["Transpose looks", "two voice"] ENDCASE => Mumble["Transpose looks", "voice and text"] }; textClassDescriptor: ViewerClasses.ViewerClass ¬ ViewerOps.FetchViewerClass[$Text]; realNotifyProc: ViewerClasses.NotifyProc ¬ textClassDescriptor.notify; HereWeGoAgain: Commander.CommandProc = { textClassDescriptor: ViewerClasses.ViewerClass ¬ ViewerOps.FetchViewerClass[$Text]; textClassDescriptor.notify ¬ realNotifyProc; TEditInput.UnRegister[$BackSpace, InterceptBackSpace]; TEditInput.UnRegister[$BackWord, InterceptBackWord]; TEditInput.UnRegister[$Delete, InterceptDelete]; TEditInput.UnRegister[$DoEdit, InterceptEdit]; }; BadVoiceViewerDisplay: ERROR [viewer: ViewerClasses.Viewer]; Mumble: PUBLIC PROC [opAttempted: Rope.ROPE, viewerTypes: Rope.ROPE] = { MessageWindow.Append[IO.PutFR["%g is not a valid operation between %g viewers", IO.rope[opAttempted], IO.rope[viewerTypes]], TRUE]; MessageWindow.Blink[] }; DescribeSelection: PUBLIC PROC [ which: TiogaOpsDefs.WhichSelection, forceDelete: BOOLEAN, returnSoundInterval: BOOLEAN, viewerAlreadyLocked: ViewerClasses.Viewer ¬ NIL] RETURNS [failed: BOOLEAN ¬ FALSE, selection: TiogaVoicePrivate.Selection, soundInterval: TiogaVoicePrivate.SoundInterval ¬ NIL] = { selStart, selEnd: TiogaOps.Location; caretBefore, pendingDelete: BOOL; level: TiogaOpsDefs.SelectionGrain; selection ¬ NEW[TiogaVoicePrivate.SelectionRec]; [viewer: selection.viewer, start: selStart, end: selEnd, level: level, caretBefore: caretBefore, pendingDelete: pendingDelete] ¬ TiogaOps.GetSelection[which]; selection.voiceViewerInfo ¬ NARROW[ViewerOps.FetchProp[selection.viewer, $voiceViewerInfo], VoiceViewerInfo]; IF ~( viewerAlreadyLocked = selection.viewer OR GetVoiceLock[selection.voiceViewerInfo] ) THEN {failed ¬ TRUE; RETURN}; IF selStart.node # selEnd.node THEN ERROR BadVoiceViewerDisplay[selection.viewer]; selection.displayNode ¬ selStart.node; selection.ropeInterval ¬ [selection.voiceViewerInfo.ropeInterval.ropeID, selStart.where*TiogaVoicePrivate.soundRopeCharLength, (selEnd.where+(IF level=point THEN 0 ELSE 1))*TiogaVoicePrivate.soundRopeCharLength -selStart.where*TiogaVoicePrivate.soundRopeCharLength]; IF selection.ropeInterval.start + selection.ropeInterval.length > selection.voiceViewerInfo.ropeInterval.length THEN ERROR BadVoiceViewerDisplay[selection.viewer]; IF returnSoundInterval THEN { soundInterval ¬ NEW [TiogaVoicePrivate.SoundIntervalRec ¬ [ropeInterval: selection.ropeInterval]]; TiogaVoicePrivate.ExtractSoundList[selection.voiceViewerInfo, soundInterval]; TiogaVoicePrivate.ExtractCharMarks[selection.voiceViewerInfo, soundInterval]; TiogaVoicePrivate.ExtractTextMarks[selection.voiceViewerInfo, soundInterval] -- the width/displayChars fields in this will not necessarily be valid at the end of the list }; IF ~(pendingDelete OR forceDelete) THEN { IF ~caretBefore THEN selection.ropeInterval.start ¬ selection.ropeInterval.start + selection.ropeInterval.length; selection.ropeInterval.length ¬ 0 } }; ReplaceSelectionWithSavedInterval: PUBLIC PROC [ selection: TiogaVoicePrivate.Selection, soundInterval: TiogaVoicePrivate.SoundInterval, leaveSelected: BOOL, destroyOK: BOOL ¬ TRUE] RETURNS [viewerDeleted: BOOLEAN¬FALSE, insertChars, positionAfterInsert: INT¬0] = { newViewerContents: Rope.ROPE; displayCutPoint, deleteChars: INT; IF selection.ropeInterval.length = 0 AND soundInterval = NIL THEN RETURN; TiogaVoicePrivate.ReplaceSoundList[voiceViewerInfo: selection.voiceViewerInfo, cutStart: selection.ropeInterval.start, cutLength: selection.ropeInterval.length, replacement: IF soundInterval = NIL THEN NIL ELSE soundInterval.soundList]; displayCutPoint ¬ selection.ropeInterval.start/TiogaVoicePrivate.soundRopeCharLength; deleteChars ¬ selection.ropeInterval.length/TiogaVoicePrivate.soundRopeCharLength; insertChars ¬ IF soundInterval = NIL THEN 0 ELSE soundInterval.ropeInterval.length/TiogaVoicePrivate.soundRopeCharLength; positionAfterInsert ¬ displayCutPoint + insertChars + 1; TiogaVoicePrivate.EditCharMarks[selection.voiceViewerInfo, displayCutPoint, deleteChars, insertChars, soundInterval]; TiogaVoicePrivate.EditTextMarks[selection.voiceViewerInfo, displayCutPoint, deleteChars, insertChars, soundInterval]; TiogaVoicePrivate.EditAges[selection.voiceViewerInfo, displayCutPoint, deleteChars, insertChars]; [newViewerContents, selection.voiceViewerInfo.remnant] ¬ TiogaVoicePrivate.SoundChars[selection.voiceViewerInfo, displayCutPoint]; newViewerContents ¬ Rope.Concat[(TiogaOps.GetRope[selection.displayNode]).Substr[0, displayCutPoint], newViewerContents]; selection.displayNode ¬ TiogaVoicePrivate.RedrawViewer[selection.viewer, newViewerContents, displayCutPoint, deleteChars, insertChars, selection.voiceViewerInfo.remnant, TRUE, IF leaveSelected THEN primaryOnInsert ELSE deSelected]; viewerDeleted ¬ FALSE; -- to ease voice editing, do not automatically destroy voice viewers when empty IF viewerDeleted THEN { MessageWindow.Append["Voice viewer now completely empty - destroying", TRUE]; selection.voiceViewerInfo.editInProgress ¬ FALSE; ViewerOps.DestroyViewer[selection.viewer.parent]; } ELSE { selection.voiceViewerInfo.ropeInterval ¬ VoiceRope.Replace[handle: TiogaVoicePrivate.thrushHandle, vr: NEW [VoiceRope.VoiceRopeInterval ¬ selection.voiceViewerInfo.ropeInterval], start: selection.ropeInterval.start, len: selection.ropeInterval.length, with: IF soundInterval = NIL THEN NIL ELSE NEW [VoiceRope.VoiceRopeInterval ¬ soundInterval.ropeInterval]]­ } }; Delete: PUBLIC PROC = { charAfterDelete: INT; viewerDeleted: BOOLEAN; selectionLocked: BOOLEAN; selection: TiogaVoicePrivate.Selection; [selection: selection, failed: selectionLocked] ¬ DescribeSelection[which: primary, forceDelete: TRUE, returnSoundInterval: FALSE]; IF selectionLocked THEN RETURN; MakeVoiceEdited[selection.viewer]; [positionAfterInsert: charAfterDelete, viewerDeleted: viewerDeleted] ¬ ReplaceSelectionWithSavedInterval[selection: selection, soundInterval: NIL, leaveSelected: TRUE]; IF ~viewerDeleted THEN { TiogaOps.SelectPoint[viewer: selection.viewer, caret: [selection.displayNode, MIN [charAfterDelete, TextEdit.Size[selection.displayNode]-1]], which: primary]; selection.voiceViewerInfo.editInProgress ¬ FALSE } }; Copy: PUBLIC PROC [target: TEditDocument.SelectionId ¬ primary] = { sourceSelection, destSelection: TiogaVoicePrivate.Selection; sourceInterval: TiogaVoicePrivate.SoundInterval; selectionLocked: BOOLEAN; destMovesForward: INT ¬ 0; [selection: sourceSelection, soundInterval: sourceInterval, failed: selectionLocked] ¬ DescribeSelection[which: (IF target=primary THEN secondary ELSE primary), forceDelete: FALSE, returnSoundInterval: TRUE]; IF ~selectionLocked THEN { [selection: destSelection, failed: selectionLocked] ¬ DescribeSelection[which: (IF target=primary THEN primary ELSE secondary), forceDelete: FALSE, returnSoundInterval: FALSE, viewerAlreadyLocked: sourceSelection.viewer]; IF selectionLocked THEN sourceSelection.voiceViewerInfo.editInProgress ¬ FALSE }; TiogaOps.CancelSelection[primary]; TiogaOps.CancelSelection[secondary]; IF selectionLocked THEN RETURN; MakeVoiceEdited[destSelection.viewer]; IF sourceSelection.ropeInterval.length # 0 THEN { MakeVoiceEdited[sourceSelection.viewer]; IF sourceSelection.viewer = destSelection.viewer THEN { -- special case: same viewer IF (sourceSelection.ropeInterval.start< destSelection.ropeInterval.start+destSelection.ropeInterval.length) = (destSelection.ropeInterval.start< sourceSelection.ropeInterval.start+sourceSelection.ropeInterval.length) THEN { firstStart: INT ¬ destSelection.ropeInterval.start; firstLength: INT ¬ MAX [sourceSelection.ropeInterval.start -destSelection.ropeInterval.start, 0]; secondStart: INT ¬ sourceSelection.ropeInterval.start + sourceSelection.ropeInterval.length; secondLength: INT ¬ MAX [(destSelection.ropeInterval.start+destSelection.ropeInterval.length) - (sourceSelection.ropeInterval.start+sourceSelection.ropeInterval.length), 0]; sourceSelection.ropeInterval.start ¬ firstStart; sourceSelection.ropeInterval.length ¬ firstLength; destSelection.ropeInterval.start ¬ secondStart; destSelection.ropeInterval.length ¬ secondLength; sourceInterval ¬ NIL }; IF sourceSelection.ropeInterval.start lastSoundEnd THEN soundList ¬ AppendSound[soundList, [lengthOfRopeInterval-lastSoundEnd, 0]] }; ExtractSoundList: PUBLIC PROC [ voiceViewerInfo: VoiceViewerInfo, soundInterval: TiogaVoicePrivate.SoundInterval] = { soughtStart: INT ¬ soundInterval.ropeInterval.start; soughtEnd: INT ¬ soughtStart + soundInterval.ropeInterval.length; currStart, currEnd: INT ¬ 0; thisSilence, thisSound: INT; FOR l: TiogaVoicePrivate.SoundList ¬ voiceViewerInfo.soundList, l.rest WHILE l # NIL AND soughtEnd > currEnd DO currStart ¬ currEnd; thisSilence ¬ l.first.silence; thisSound ¬ l.first.sound; currEnd ¬ currStart + thisSilence + thisSound; IF soughtStart < currEnd THEN { IF soughtStart > currStart THEN { thisSound ¬ thisSound - MAX [soughtStart-currStart-thisSilence, 0]; thisSilence ¬ thisSilence - MIN [soughtStart-currStart, thisSilence] }; IF soughtEnd < currEnd THEN { thisSilence ¬ thisSilence - MAX [currEnd-soughtEnd-thisSound, 0]; thisSound ¬ thisSound - MIN [ currEnd-soughtEnd, thisSound] }; AppendSoundToSoundInterval[soundInterval, [thisSilence, thisSound]] } ENDLOOP }; ReplaceSoundList: PUBLIC PROC [ voiceViewerInfo: VoiceViewerInfo, cutStart: INT, cutLength: INT, replacement: TiogaVoicePrivate.SoundList] = { tail, workingPtr: TiogaVoicePrivate.SoundList; endCurrSound, startCurrSound: INT ¬ 0; IF cutStart = 0 THEN { tail ¬ voiceViewerInfo.soundList; voiceViewerInfo.soundList ¬ NIL } ELSE { FOR workingPtr ¬ voiceViewerInfo.soundList, workingPtr.rest DO endCurrSound ¬ endCurrSound + workingPtr.first.silence + workingPtr.first.sound; IF endCurrSound >= cutStart THEN EXIT ENDLOOP; tail ¬ workingPtr.rest; workingPtr.rest ¬ NIL; IF endCurrSound > cutStart THEN -- Have to 'move sound from workingPtr to tail' { silenceToMove: INT ¬ MAX [endCurrSound-cutStart-workingPtr.first.sound, 0]; soundToMove: INT ¬ MIN [endCurrSound-cutStart, workingPtr.first.sound]; tail ¬ CONS[[silenceToMove, soundToMove], tail]; workingPtr.first.silence ¬ workingPtr.first.silence - silenceToMove; workingPtr.first.sound ¬ workingPtr.first.sound - soundToMove } }; endCurrSound ¬ 0; DO startCurrSound ¬ endCurrSound; IF startCurrSound = cutLength THEN EXIT; endCurrSound ¬ endCurrSound + tail.first.silence + tail.first.sound; IF endCurrSound > cutLength THEN EXIT; tail ¬ tail.rest; ENDLOOP; IF startCurrSound < cutLength THEN -- delete some of the sound in the head of the list { silenceToSave: INT ¬ MAX [endCurrSound-cutLength-tail.first.sound, 0]; soundToSave: INT ¬ MIN [endCurrSound-cutLength, tail.first.sound]; tail.first.silence ¬ silenceToSave; tail.first.sound ¬ soundToSave }; IF replacement = NIL THEN replacement ¬ tail ELSE AppendSoundListToSoundList[replacement, tail]; IF voiceViewerInfo.soundList = NIL THEN voiceViewerInfo.soundList ¬ replacement ELSE AppendSoundListToSoundList[voiceViewerInfo.soundList, replacement] }; SoundChars: PUBLIC PROC [ viewerInfo: VoiceViewerInfo, skipChars: INT ¬ 0] RETURNS [soundRope: Rope.ROPE ¬ NIL, remnant: INT] = { soundList: TiogaVoicePrivate.SoundList ¬ viewerInfo.soundList; partiallyFilled: BOOLEAN ¬ FALSE; partsFilled: [0..TiogaVoicePrivate.soundRopeCharDivisions] ¬ 0; soundMajority: ARRAY [0..TiogaVoicePrivate.soundRopeCharDivisions) OF BOOLEAN; samplesAccounted, soundComponents: [0..TiogaVoicePrivate.soundRopeResolution] ¬ 0; skipping: BOOLEAN ¬ skipChars>0; skipSamples: INT ¬ skipChars*TiogaVoicePrivate.soundRopeCharLength; AddChars: PROC [length: INT, sound: BOOLEAN] = { AddToPartChar: PROC [availableSamples: INT, sound: BOOLEAN] RETURNS [usedSamples: [0..TiogaVoicePrivate.soundRopeResolution]] = { partiallyFilled ¬ TRUE; usedSamples ¬ MIN[availableSamples, TiogaVoicePrivate.soundRopeResolution-samplesAccounted]; samplesAccounted ¬ samplesAccounted + usedSamples; IF sound THEN soundComponents ¬ soundComponents + usedSamples; IF samplesAccounted = TiogaVoicePrivate.soundRopeResolution THEN { soundMajority[partsFilled] ¬ soundComponents >= TiogaVoicePrivate.soundRopeResolution/2; samplesAccounted ¬ 0; soundComponents ¬ 0; partsFilled ¬ partsFilled + 1; IF partsFilled = TiogaVoicePrivate.soundRopeCharDivisions THEN { binaryOfChar: INT ¬ 0; partsFilled ¬ 0; partiallyFilled ¬ FALSE; FOR i: INT IN [0..TiogaVoicePrivate.soundRopeCharDivisions) DO binaryOfChar ¬ binaryOfChar*2 + (IF soundMajority[i] THEN 1 ELSE 0) ENDLOOP; soundRope ¬ soundRope.Concat[Rope.FromChar[IF binaryOfChar # 0 THEN ('A + binaryOfChar) ELSE '!]] -- 'A is the base type of the sound font [i.e. all segments clear] but the 'all clear' character is actually ! so as to make 'word selections' work in normal tioga terms } } }; WHILE partiallyFilled AND length>0 DO taken: INT ¬ AddToPartChar[length, sound]; length ¬ length - taken ENDLOOP; WHILE length >= TiogaVoicePrivate.soundRopeCharLength DO soundRope ¬ soundRope.Concat[Rope.FromChar[IF sound THEN ('A + 15) ELSE '!]]; length ¬ length - TiogaVoicePrivate.soundRopeCharLength ENDLOOP; WHILE length>0 DO taken: INT ¬ AddToPartChar[length, sound]; length ¬ length - taken ENDLOOP }; FOR l: TiogaVoicePrivate.SoundList ¬ soundList, l.rest WHILE l # NIL DO silence: INT ¬ l.first.silence; sound: INT ¬ l.first.sound; IF skipping THEN { IF silence >= skipSamples THEN { silence ¬ silence - skipSamples; skipping ¬ FALSE; AddChars[length: silence, sound: FALSE]; AddChars[length: sound, sound: TRUE] } ELSE { skipSamples ¬ skipSamples - silence; IF sound >= skipSamples THEN { sound ¬ sound - skipSamples; skipping ¬ FALSE; AddChars[length: sound, sound: TRUE] } ELSE skipSamples ¬ skipSamples - sound; } } ELSE { AddChars[length: silence, sound: FALSE]; AddChars[length: sound, sound: TRUE] } ENDLOOP; remnant ¬ IF partiallyFilled THEN partsFilled*TiogaVoicePrivate.soundRopeResolution+samplesAccounted ELSE 0; soundRope ¬ TiogaVoicePrivate.DisplayCharMarks[soundRope, viewerInfo.charMarkList, skipChars] }; LastSilenceInSoundList: PUBLIC PROC [ soundList: TiogaVoicePrivate.SoundList, lengthGreaterThan: INT ¬ 0] RETURNS [startsAt: INT, lasts: INT ¬ -1] = { l: TiogaVoicePrivate.SoundList ¬ soundList; currSilenceStart: INT ¬ 0; WHILE l # NIL DO IF l.first.silence > lengthGreaterThan THEN { startsAt ¬ currSilenceStart; lasts ¬ l.first.silence }; currSilenceStart ¬ currSilenceStart + l.first.silence + l.first.sound; l ¬ l.rest ENDLOOP }; AppendSound: PROC [ list: TiogaVoicePrivate.SoundList, entry: TiogaVoicePrivate.Sound] RETURNS [TiogaVoicePrivate.SoundList] = { oneElementList: TiogaVoicePrivate.SoundList ¬ CONS[entry, NIL]; hangOffPoint: TiogaVoicePrivate.SoundList ¬ list; IF list = NIL THEN RETURN [oneElementList]; WHILE hangOffPoint.rest # NIL DO hangOffPoint ¬ hangOffPoint.rest ENDLOOP; hangOffPoint.rest ¬ oneElementList; RETURN [list] }; AppendSoundToSoundInterval: PROC [ soundInterval: TiogaVoicePrivate.SoundInterval, entry: TiogaVoicePrivate.Sound] = { oneElementList: TiogaVoicePrivate.SoundList ¬ CONS[entry, NIL]; hangOffPoint: TiogaVoicePrivate.SoundList ¬ soundInterval.soundList; IF hangOffPoint = NIL THEN {soundInterval.soundList ¬ oneElementList; RETURN}; WHILE hangOffPoint.rest # NIL DO hangOffPoint ¬ hangOffPoint.rest ENDLOOP; hangOffPoint.rest ¬ oneElementList }; AppendSoundListToSoundList: PROC [ head, tail: TiogaVoicePrivate.SoundList] = { hangOffPoint: TiogaVoicePrivate.SoundList ¬ head; IF tail = NIL THEN RETURN; WHILE hangOffPoint.rest # NIL DO hangOffPoint ¬ hangOffPoint.rest ENDLOOP; IF hangOffPoint.first.sound = 0 OR tail.first.silence = 0 THEN -- these two can be amalgamated { hangOffPoint.first.sound ¬ hangOffPoint.first.sound + tail.first.sound; hangOffPoint.first.silence ¬ hangOffPoint.first.silence + tail.first.silence; tail ¬ tail.rest }; hangOffPoint.rest ¬ tail }; CreateVoiceViewerPopUpButtons[]; RegisterButtonBehaviors[]; voiceTIPTable ¬ TIPUser.InstantiateNewTIPTable["TiogaVoice.tip"]; soundViewerIcon ¬ Icons.NewIconFromFile["Finch.icons", 14]; dirtySoundViewerIcon ¬ Icons.NewIconFromFile["Finch.icons", 15]; Commander.Register[key: "ReadyToRoll", proc: HereWeGoAgain, doc: "ReadyToRoll: do it before running this again!"]; TEditInput.Register[$BackSpace, InterceptBackSpace]; TEditInput.Register[$BackWord, InterceptBackWord]; TEditInput.Register[$Delete, InterceptDelete]; TEditInput.Register[$DoEdit, InterceptEdit]; textClassDescriptor.notify ¬ InterceptAllInput; }. = VoiceViewersImpl.mesa Copyright Ó 1987, 1992 by Xerox Corporation. All rights reserved. Ades, May 1, 1986 1:34:00 pm PDT Swinehart, June 7, 1992 11:25 am PDT Polle Zellweger (PTZ) April 20, 1990 9:42:31 pm PDT Voice Viewers Returns a ref to the $Text viewer that holds the voice capillary, not the outer container. wy: -1 makes these menus match Tioga menus in placement Do this after the call to CreateViewer because the init proc for $Text class viewers sets the tip table explicitly, so the tip table input param is effectively ignored. Viewer locking?? PaintViewer failed to paint $Text subviewer, so create iconic & then open PROC [view: View, instanceData, classData, key: REF ANY] PROC [view: View, instanceData, classData, key: REF ANY] PROC [view: View, instanceData, classData, key: REF ANY] PROC [view: View, instanceData, classData, key: REF ANY] PROC [view: View, instanceData, classData, key: REF ANY] PROC [view: View, instanceData, classData, key: REF ANY] PROC [view: View, instanceData, classData, key: REF ANY] PROC [view: View, instanceData, classData, key: REF ANY] TiogaVoice voice viewer menu commands. These are registered with Tioga so that other clients can use Tioga's extension mechanism to call their own procedures before or after these. Clients can find the button parameters in the viewer property $ButtonParams as type TiogaVoicePrivate.ButtonParams. We register $TVfoo (where $foo is used by the PopUpButtons mechanism) because PopUpButtons shows its atoms to the user, and we don't want collisions with Tioga atoms. TiogaOps.RegisterCommand[name: $TVDictationViewer, proc: TiogaVoicePrivate.DictationMachine]; -- done already by VoiceInTextImpl TiogaOps.RegisterCommand[name: $TVSTOP, proc: TiogaVoicePrivate.CancelProc]; -- done already by VoiceInTextImpl ** ought to check here that the rope actually exists ** **** the Plass-bug: shouldn't be necessary and he says he'll have a look at it IF viewerInfo.textMarkList # NIL THEN ViewerOps.PaintViewer[viewer: viewer, hint: client, clearClient: TRUE, whatChanged: NIL]; Set the contents, style, and looks for a voice viewer. Used to set looks to provide voice playback feedback. Note: all voice viewers contain only one node. This procedure marks a voice viewer as edited: viewer, viewerInfo, and icon. This procedure will make a voice viewer think itself unedited, when we wish to assert that there is no voice rope held only in it - use carefully!! This is similar to the above, except that it ensures that the viewer has the same idea about whether the voice is edited as the VoiceInfo record. For use by functions [such as moving a visual cue along a voice viewer during playback] that need to edit the viewer but not the voice and then want to reflect this fact in the viewer's status Check for same or split text viewer (can't split voice viewers). Set text parent viewer for voice viewer. Returns a list of voiceViewerInfo describing all voice viewers whose parent is the text viewer. Removes the parentViewer pointers for all voice viewers pointing to this text viewer. If findSplits is TRUE, will replace with a pointer to a split of the text viewer if one exists. The text viewer being destroyed has been split, so this isn't really the last one. PROC [viewer: Viewer _ NIL] RETURNS [recordAtom: BOOL _ TRUE, quit: BOOL _ FALSE]; viewer prop $ButtonParams = TiogaVoicePrivate.ButtonParams **** locking against edits in this and the next one? Now need to remove all old links from doc to this viewer ($voiceWindow charprops). Don't want to remove the one we just put in, but we wanted to be sure it would succeed before removing the old ones. Do this by excepting the location we just added (could also check for the new rope value as the associated $voice property). PROC [viewer: Viewer _ NIL] RETURNS [recordAtom: BOOL _ TRUE, quit: BOOL _ FALSE]; viewer prop $ButtonParams = TiogaVoicePrivate.ButtonParams Voice Edit Intercept Routines to pick up edit events affecting voice viewers and pass control from normal Tioga ops. This code stinks for a number of reasons i) it only picks up a subset of the events which could affect a voice viewer ii) it redefines the 'notify proc' for the $Text class!! - it does exactly what the ousted notify proc would do in the case of non-voice viewers, by calling a TEditInput procedure. At the cost of another procedure call it could be more clean my saving the ousted procedure and calling that, but since I've had to import the Impl anyhow . . . . The way things should be is that either an equivalent package to tioga is produced specially for voice editing windows or TEditInputOps is rewritten to call TiogaVoice code for viewers managed by TiogaVoice [detectable from the properties that those viewers have]. PROC [viewer: ViewerClasses.Viewer _ NIL] RETURNS [recordAtom: BOOL _ TRUE, quit: BOOL _ FALSE]; PROC [viewer: ViewerClasses.Viewer _ NIL] RETURNS [recordAtom: BOOL _ TRUE, quit: BOOL _ FALSE]; PROC [viewer: ViewerClasses.Viewer _ NIL] RETURNS [recordAtom: BOOL _ TRUE, quit: BOOL _ FALSE]; this part of the code is rather sordid: it really should be in TEditInputImpl and for that reason it references TEditInputBackdoor variables 23 times: it gets called in front of the Tioga DoEdit procedure and returns quit=TRUE under all circumstances in order to prevent the real one ever being called this and the following series of 'Check' procedures test if the proposed edit is applied to normal text: if so they first call the appropriate SourceMarkerTracking routine and then pass control to TEditInputOps [just like TEditInputImpl.DoEdit always would], otherwise they pass control to Voice Edit Ops needs to be filled in -- PTZ, June 12, 1987 7:49:54 pm PDT needs to be filled in -- PTZ, June 12, 1987 7:49:54 pm PDT This just whilste under development . . . Voice Edit Ops Routines to edit voice viewers, plus some routines to intercept operations deemed inapplicable for voice or voice/text viewer combinations. this routine will characterise the voice corresponding to a selection: if the selection is pendingDelete [or if forceDelete] then the selection.ropeInterval will have non-zero length; if returnSoundInterval then the sound interval corresponding to the whole selection will be characterised - to be saved and inserted somewhere else selections can only be described if the voiceLock can be taken out on them: this routine returns failed if it cannot get the lock; otherwise it returns with the lock still held. However the caller may pass a viewer in viewerAlreadyLocked to say that he already has that one locked and if this selection is in the same viewer then we may proceed work out the VoiceRopeInterval corresponding to the whole selection it is here that policy about trimming the sound selection e.g. to silence/sound boundaries could be implemented this routine actually does all the editing work for voice: it is passed a selection specification and a sound specification: if the selection is of non-zero length it is deleted; then if the sound is non-nil it is inserted this routine (i) works out what the updated voice viewer sound list and contents will be (ii) calls TiogaVoicePrivate to redraw the viewer [done there since the viewer may be somewhere in the queue of sounds to be played back] (iii) interacts with the voice rope data base to update the rope specification corresponding to the viewer it may turn out that there is nothing left in the viewer at the end of this action, in which case the viewer will simply be deleted (unless Copy/Transpose with both selections in the same viewer). However this is not done until after TiogaVoicePrivate.RedrawViewer has been called since a viewer cannot be destroyed whilst the playback caret is in it: after a call to RedrawViewer the playback caret cannot be in it!! those three need only be [and are only] approximate done for efficiency - no need to recalculate the first characters again redrawing is done in TiogaVoicePrivate for synch with any playback cues the last but one parameter governs whether this edit will cause all previous edits to age [only if insertChars # 0]. It is simply set TRUE here: if there were any use for such a facility a parameter could be passed by caller of ReplaceSelectionWithSavedInterval to pass on here viewerDeleted _ destroyOK AND TextEdit.Size[selection.displayNode] = 0; so the old voice viewer will allow itself to be destroyed this will cause the event proc which clears down the voiceViewerInfo etc. to be called delete the primary selection if source selection is pending delete and ahead of the destination selection in the same viewer, then after the first action of deleting source the destination will be nearer the start of the sound we drop the selections here because after a ReplaceSelectionWithSavedInterval call a viewer may not exist! If a selection above failed we should also drop the selections here as well - if selections are left around after an aborted tioga editing operation unpleasant thing tend to happen source is pending delete: this means two edit operations that is the test for overlapping selections: if source is pending delete the copy operation then reduces to "delete the region(s) lying in the destination but not in the source selection" - done here as a special case done by substituting first and second regions for source and destination selections and setting soundInterval to NIL: note that either of these regions may now have zero length don't let the viewer be destroyed yet, because hopefully we are about to add to it ReplaceSelectionWithSavedInterval redraws the viewer, which makes a new version of the internal node. Update destSelection to point to the new node. SoundList Sound lists are descriptions of the silence/sound profiles of a ropeInterval and form part of the data structure behind a voice viewer. voiceViewerInfo contains the sound list for a complete rope. Use the interval specification from soundInterval.ropeInterval to create a new sound list for that interval, placing it in soundInterval.soundList. voiceViewerInfo contains the sound list for a complete rope. Cut out the specified portion and at the cutting point insert the replacement soundlist. No tests for shooting off the end in this routine. If we get an error then our caller has gone badly wrong and there is no obvious way to recover. With the head of the old rope in voiceViewerInfo.soundList, delete the unwanted sound from the tail. We can now join up our three lists. Produce the graphical representation of the SoundList within a VoiceViewerInfo record. The caller already has the first skipChars of the representation in his hand, so omit these from the returned rope. If the sound list does not exactly fill a whole number of characters, return as remnant the number of samples left over. Any marker characters indicated in the VoiceViewerInfo are inserted. 15 represents 'all bits are sound' - i.e. 2**soundRopeCharDivisions-1 All INTs are values in samples, as for the rest of this interface. lasts=-1 indicates that there are no silence intervals in the list of lengthGreaterThan that specified. Utilities Head is assumed non-nil. Initialization stand back - this is the dangerous bit . . . Swinehart, April 8, 1987 11:59:57 am PDT Cedar 7. Merge many files. changes to: DIRECTORY, VoiceViewersImpl, voiceViewerMenu, CreateVoiceViewerMenu, SetViewerContents, ViewerTools, SetParentViewer, IF, {, SaveVoice, DestroyVoiceInfo, RedrawViewer, ChangeColumnEvent, }, END Polle Zellweger (PTZ), June 7, 1987 7:01:17 pm PDT Tip table for voice viewers, to handle CR correctly and to allow for accelerators later; also inhibits bad things like CTRL-CR so that users can't edit voice viewers into multiple nodes. Also a new icon for voice viewers. changes to: DIRECTORY, VoiceViewersImpl, MakeVoiceNotEdited, SetVoiceViewerEditStatus, BuildVoiceViewer, initialization Polle Zellweger (PTZ), June 8, 1987 7:04:09 pm PDT New routines exploit commonality & remove selection-based changing of looks. new: SetTiogaViewerParams, SetVoiceRopeInfo, SetVoiceLooks changes to: DIRECTORY, SetViewerContents Polle Zellweger (PTZ), June 16, 1987 6:12:33 pm PDT Check for voice edit in progress before destroying voice viewer. changes to: DestroyVoiceInfo Polle Zellweger (PTZ), June 17, 1987 1:10:44 pm PDT Set dirty sound icon. changes to: MakeVoiceEdited, Delete, Copy Polle Zellweger (PTZ), June 19, 1987 12:17:02 pm PDT Utilities for new policy: voice markers in text viewers are only a hint; must be validated. Can't be copied or moved across doc boundaries. new: SameViewerDoc, GetVoiceViewerInfo changes to: RemoveParentViewer, DestroyVoiceInfo Polle Zellweger (PTZ), June 22, 1987 7:04:25 pm PDT Problems with copying pending-delete voice when source and dest are in the same voice viewer, caused by redrawing in the middle and thus destroying the dest node. changes to: Copy, ReplaceSelectionWithSavedInterval Polle Zellweger (PTZ) July 10, 1987 1:09:26 pm PDT Make voice viewer numbers monotonically increasing throughout a session so a copied text loc can't have a $voiceWindow "pointer" to the wrong viewer after destroy;edit. Make voiceViewerInfoList into a real list; make private to VoiceViewersImpl. changes to: DIRECTORY, voiceViewerInfoList, nextVoiceViewerNumber, FindAttachedVoiceViewers, RemoveParentPointersTo, GetVoiceViewerInfo, InsertVoiceViewerInfo, RemoveVoiceViewerInfo + others w/ type renames Polle Zellweger (PTZ) January 11, 1988 2:44:04 pm PST Started Jan 7: Replace Tioga menus with PopUpButtons in voice viewers to document different mouse button functions. Leave voiceViewerInfo pointing to the inner $Text viewer, but paints and destroys should in general refer to the outer container. changes to: DIRECTORY, BuildVoiceViewer, CreateVoiceViewerPopUpButton, QProcRef, QProcBody, QProc, AddProc, PlayProc, StopProc, SaveProc, StoreProc, MarkProc, RedrawProc, AdjustSilencesProc, MakeVoiceEdited, DestroyViewerEvent, RedrawViewerMenuProc, ChangeColumnEvent Polle Zellweger (PTZ) January 15, 1988 12:05:25 pm PST Had dictationOps as part of Add & Play buttons. Broke out into own button because of different rules (def of fresh voice, auto stop of play|record when request new operation). changes to: addClass, BuildVoiceViewer, CreateVoiceViewerPopUpButtons, AddProc, PlayProc, DictationOpsProc Polle Zellweger (PTZ) January 18, 1988 11:06:41 am PST These utilities are sometimes called even if there is no voice viewer, so protect against dereferencing NIL. changes to: MakeVoiceEdited, MakeVoiceNotEdited, SetVoiceViewerEditStatus Polle Zellweger (PTZ) January 20, 1988 11:29:03 am PST Set editInProgress for old viewer FALSE so it can be destroyed; refer to outer container when destroying voice viewer. changes to: ReplaceSelectionWithSavedInterval Polle Zellweger (PTZ) September 13, 1988 1:15:44 pm PDT Changes to allow clients to alter button behaviors via Tioga registry (for WalnutTiogaVoice). Also allow voice viewers to become empty, just don't allow save or store of empty viewer. changes to: ViewerContainsVoice, StoreVoice, SaveVoice, ReplaceSelectionWithSavedInterval, AddProc, PlayProc, StopProc, SaveProc, StoreProc, MarkProc, DictationOpsProc, AdjustSilencesProc, RegisterButtonBehaviors, DIRECTORY Polle Zellweger (PTZ) September 8, 1989 4:52:02 pm PDT Update to new PopUpButtons ported to PCedar. changes to: AddProc, PlayProc, StopProc, SaveProc, StoreProc, MarkProc, DictationOpsProc, AdjustSilencesProc Polle Zellweger (PTZ) April 20, 1990 7:06:39 pm PDT Allow copying text between voice viewers and text viewers. changes to: CheckCopy Ê.ê•NewlineDelimiter –(cedarcode) style™šœ™Icodešœ Ïeœ7™BKšœÏk™ K™$™3K™——šž ˜ Kšœžœ˜ Kšœžœ ˜Kšœ žœ˜(Kšœ žœ/˜?Kšœžœ˜Kšœžœ˜*Kšžœžœ˜$Kšœžœ˜"Kšœžœ˜$Kšœžœ˜+Kšœ žœ>˜PKšœžœžœ)˜9Kšœžœ˜Kšœžœ˜"Kšœ žœH˜XKšœžœG˜_Kšœžœa˜tKšœžœ˜1Kšœ žœ˜#Kšœ žœ˜(Kšœ žœ‹˜™Kšœ žœ1˜CKš œžœÏrœ—ŸœlŸ$œ˜˜•Kšœžœ$˜1Kšœžœ#˜6Kšœ žœH˜ZKšœ žœ˜'Kšœ žœ\˜kKšœ žœ˜.Kšœ žœ5˜FKšœ žœE˜TKšœžœ˜$K˜—K™šÐlnœžœž˜Kšžœ,žœ‡˜¾Kšžœ˜Kšœ˜—K˜šÐbl ™ K˜šœ}˜}K˜—Kšœžœ%˜:Kšœžœ)˜BKšœžœ(˜@K˜Kšœ+žœ˜/šœžœ˜K™—Kšœ"žœ˜&K˜+K˜0K˜šÏnœžœžœ˜Kš œžœžœžœžœKžœ˜›K™ZKšœ ˜ Kšœ žœ˜Kšœžœ˜Kšœ˜K˜K˜K˜%˜šœY˜YKšœ žœžœžœ˜>Kšœžœ˜Kšœ žœ˜Kšœ˜šœ ž˜ Kšœ˜——Kšœžœ˜K˜—K™7KšœXžœžœ˜K˜2KšœZžœžœ˜ƒK˜2KšœZžœžœ˜ƒK˜2Kšœjžœžœ˜“K˜2KšœZžœžœ˜ƒK˜2Kšœ\žœžœ˜…K˜2KšœZžœžœ˜ƒK˜2Kšœnžœžœ˜—K˜˜˜Kšœ˜K˜Kšœ˜Kšœ˜Kšœ˜—Kšœžœ˜—Kšœ(˜(K˜šœ"Ïc1˜SKšœ˜šœ˜Kšœ˜Kšœ9˜9Kšœ žœ˜šœ ž˜Kšœ˜——Kšœžœ˜—Kšœ*˜*Kšœ*˜*˜ Jšœ©Ïtœ¤™»—K˜K˜'Kšœ8˜8K˜K˜)K˜Kšœƒžœ˜‰KšœŒžœ˜“K˜KšœH˜HK˜šœ$˜$KšœI™I—šœ˜K˜——š¢œžœ˜'˜#K˜Kšœ žœ˜šœ žœ˜Kšœ8˜8Kšœ7˜7K˜—Kšœ ˜ Kšœ˜K˜—˜$K˜Kšœ žœ˜šœ žœ˜Kšœ)˜)Kšœ9˜9K˜—Kšœ ˜ Kšœ˜K˜—˜$K˜Kšœ žœ˜šœ žœ˜Kšœ&£˜=K˜—Kšœ'˜'Kšœ˜K˜—˜,K˜Kšœ žœ˜šœ žœ˜KšœM˜MKšœP˜PKšœ4˜4K˜—Kšœ=˜=Kšœ˜K˜—˜$K˜Kšœ žœ˜šœ žœ˜Kšœ4˜4Kšœ8˜8Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ3˜3Kšœ>˜>K˜—Kšœ,˜,Kšœ˜K˜—˜%K˜Kšœ žœ˜Kšœ žœ˜šœ žœ˜Kšœ+˜+K˜—Kšœ-˜-Kšœ˜K˜—˜$K˜Kšœ žœ˜šœ žœ˜Kšœ:˜:K˜—Kšœ<˜˜>Kšœp™pKšœ@˜@K˜K™—š¢œžœ-žœžœžœžœžœ˜˜Kšœ#˜#Kšœžœ˜šžœ žœžœ˜Kš¤œ7¤™9KšœXžœ3˜ŽKšœCžœ:˜€K˜oK˜_K˜JKš œžœžœ žœžœžœ˜tK˜—šžœ˜K˜Kšœžœ˜&K˜—K˜K˜—š ¢œžœžœKžœžœžœ˜Ÿš¢ œžœ˜KšœžœB˜VK˜-šžœ žœžœ=ž˜UKšœ'˜'—Kšžœ5˜9J™J™NJš žœžœžœBžœžœ™K˜Kšœ'žœ£r˜žK˜—Kšœ6˜6˜K˜——š ¢œžœžœ2žœžœ"˜‚K™6š¢œžœ˜Kšœ˜˜+KšžœDžœ˜M—KšœNžœ˜UK˜&K˜5K˜*Kšœ7˜7K˜—Kšœ;˜;K˜K˜—š ¢ œžœžœ&žœžœ˜ZK™5š¢œžœ˜'K™.K•StartOfExpansion¹[root: TextEdit.RefTextNode, text: TextEdit.RefTextNode, remove: TextLooks.Looks, add: TextLooks.Looks, start: INT _ 0, len: INT _ 2147483647, event: TextEdit.Event _ NIL]˜K˜—Kšœ2˜2K˜K˜—š¢œž œ#˜?J™LKšœ˜Kšœ ˜ š¢œžœ˜Kšœ+žœ˜0K˜&Kšœ*žœ˜1K˜—Kšžœžœžœžœ˜Kšœ žœA˜TKš žœ žœžœžœ£˜žœ žœž˜Ušžœ#ž˜)Kšœžœ˜2—Kšžœ˜—˜K˜——š¢œžœžœ,žœ˜[J™¶šžœ>žœ žœž˜UK˜$šžœžœ˜$K˜9šžœ žœ žœžœ˜(J™RK˜Kšœ,˜,K˜—šžœ˜Kšœžœ˜Kš œžœ7žœžœ£Ðct£!¦˜–K˜—K˜—Kšž˜—K˜K˜—š¢œžœžœ"˜AKšœžœ˜˜K˜——š ¢œžœžœžœžœ"˜Ršžœ>žœžœž˜SKšžœžœžœ˜7Kšžœ˜—šœ˜K˜——š¢œž œžœ!˜LKšžœ˜K˜K˜—š¢œžœžœ%˜HKšœžœ˜(K˜3K˜2Kšœžœ%˜?˜K˜——š¢ œ˜$Kšžœžœžœžœžœžœžœ=™J™4KšœžœA˜eK˜@K˜šžœžœžœ˜)Kšœ.žœ˜4K˜Kšž˜K˜—šžœ4žœ˜=Jš¤œÈ¤™ÈKšœ˜šžœžœž˜Kšœm˜m—K˜—˜K˜——š¢ œ˜#Kšžœžœžœžœžœžœžœ=™KšœžœA˜eK˜šžœžœ˜Kšœ3žœ˜9Kšž˜K˜—šžœžœžœ˜)Kšœ.žœ˜4K˜Kšž˜K˜—šžœžœžœ˜'Kšœ?žœ˜EK˜Kšž˜K˜—šžœ»žœ˜ÃKšœCžœ˜IK˜Kšž˜K˜—Kšœ˜K˜K˜—š¢œžœ"˜=šžœžœžœ˜#šžœ(ž˜.Kšœ2žœ˜:—šžœ>žœžœž˜SKšžœžœ!žœ˜MKšžœ˜—K˜—Kšžœ£˜˜K˜——š¢œžœ%˜ZK˜—š¢œžœ˜Kšœžœ žœžœ˜AKšœžœA˜eK˜šžœ3žœ˜;KšœPžœ˜WKšœ˜Kšžœžœ˜K˜—šžœžœ˜#KšœHžœ˜OKšœ˜Kšžœžœ˜K˜—K˜KšœC˜CKšœM˜MK˜šžœžœž˜%Kšœžœ+žœ˜j—šž˜KšœYžœ˜c—K˜Kšœ"˜"K˜K˜—š¢œ˜(K˜&KšœžœA˜eKšžœ˜Kšžœ˜šœžœ6˜OKšœWžœ ˜iKšœ!˜!Kšœž˜!—Kšœ˜šœ˜K˜——š¢œ˜-Kšœ˜Kšœ£=˜UKšœ žœA˜TKšžœ,ž˜2˜,Kšžœ˜Kšžœ˜šœžœ6˜OKšœWžœ ˜iKšœ!˜!Kšœž˜!—Kšœ˜Kšž˜šœ˜žœ˜žK˜—K˜—K˜˜K˜——š¢ œžœžœžœ˜!Kšœžœ žœ˜3Kš žœžœžœžœžœ˜!Kšœžœ˜ Kšžœ˜KšžœIžœ˜RKšžœžœ˜ K˜—K˜—š¡™J™J™_™(J™Lšœ×™×J™——šœ‰™‰J™—š¢œ˜/Kšžœ/žœ˜5Kšžœ%£˜HKšžœ˜š œ žœžœžœžœžœ˜6šžœ žœžœžœžœžœ žœž˜Lšžœžœž˜Kšœžœžœ˜šœžœžœžœ˜šœ+˜+Kšœ ž˜—K˜K˜4—K˜Kšœžœ˜šœžœžœžœ˜šœ+˜+Kšœ ž˜—K˜Kšœ$˜$—K˜Kšœžœžœ˜šœžœžœžœ˜šœ+˜+Kšœ ž˜—K˜Kšœ6˜6—K˜šžœ˜ Kšžœžœ˜Kšžœ˜šœžœžœ˜*K˜—K˜Kšžœ˜šœžœžœ˜4K˜&—K˜———Kšžœ˜Kšžœžœžœ)˜A—K˜˜K˜——š¢œ˜.šžœ!žœ™)Kš žœžœžœžœžœ™6—Kšžœžœžœ˜&Kšœ$˜$Kšžœžœ˜˜K˜——š¢œ˜-šžœ!žœ™)Kš žœžœžœžœžœ™6—Kšžœžœžœ˜&Kšœ#˜#Kšžœžœ˜˜K˜——š¢œ˜+šžœ!žœ™)Kš žœžœžœžœžœ™6—Kšžœ˜Kšžœ!˜%Kšž˜šœ ˜ Kšžœžœ˜—K˜˜K˜——š¢ œ˜*JšœÞžœK™­Kšœžœ˜ šžœžœžœ˜*Kš žœžœžœžœ £˜mKšœ žœ˜—šžœžœ žœ˜-Kšœ žœ-žœ˜F—Kš žœžœžœžœž˜@šžœ˜š¢œžœ˜šœžœž˜>K˜K˜K˜Kšžœžœ˜——Kšœ žœ˜šžœž˜ Kšœžœ˜#˜Kšœ$˜$Kšœ!˜!Kšœ˜Kšžœž˜)˜ Kšžœ#žœ!˜JK˜—K˜!K˜#Kšžœžœ˜—˜Kšœ$˜$Kšœ#˜#K˜Kšžœž˜)˜ Kšžœ#žœ!˜JK˜—K˜#K˜%Kšžœžœ˜—˜ Kšœ$˜$Kšœ˜K˜Kšžœž˜)K˜K˜K˜!Kšžœžœ˜—Kšžœžœ˜——K˜CKšœ@žœH˜Kšœ%£˜@Kšžœ"žœžœ˜CKšœ!žœ˜'K˜—šœžœ9˜MK˜—š¢œžœžœ˜8KšœžœPžœž˜bKš œžœRžœžœ žœž˜„Kš œžœRžœžœžœ ˜|˜K˜——šœ žœ˜$K˜—š¢ œžœžœ˜1Kš œžœPžœžœžœ˜t˜K˜——š¢ œžœžœžœ˜1Kšœ°™°šžœž˜Kšœ ˜ šœ$˜$Kšœ"˜"—Kšœ˜K˜—Kšž˜˜K˜——š¢ œžœ2˜Ašžœž˜šœ˜Kšžœžœ˜žœžœ+žœ˜}Kšœžœ˜šžœ4žœžœž˜GK˜SK˜-—Kšžœ˜Kšžœ%žœK˜v˜K˜——š¢œžœžœ˜KšœU˜UJšœÐ™ÐKšœ žœ$˜4Kšœ žœ3˜AKšœžœ˜Kšœžœ˜K˜š žœDžœžœžœž˜oK˜K˜K˜K˜.Kšžœžœ˜šœžœ˜Kšžœ˜šœžœ(˜FKšœžœ%˜D—K˜Kšžœ˜Kšžœ˜šœžœ"˜DKšœžœ ˜;—K˜KšœC˜C—K˜—Kšž˜˜K˜——š¢œžœžœ˜Kšœ,žœ žœ/˜nJšœ—™—Kšœ.˜.Kšœžœ˜&Kšžœ˜šžœ˜K˜!Kšœž˜—šœžœ˜Kšžœ9ž˜>šœ“™“K˜PKšžœžœž˜%—Kšžœ˜K˜Kšœžœ˜K˜Kšžœžœ£/˜Ošœžœžœ3˜NKšœ žœžœ1˜GKšœžœ%˜0K˜DK˜=—K˜—˜K˜—Jšœd™dšœž˜K˜Kšžœžœžœ˜(K˜DKšžœžœžœ˜&K˜—Kšžœ˜K˜Kšžœžœ£4˜Wšœžœžœ.˜HKšœ žœžœ,˜BK˜#K˜—K˜K˜J™$Kšžœžœžœžœ/˜`Kšžœžœžœ)žœC˜—šœ˜K˜——š¢ œžœžœ˜Kš œ(žœžœžœžœ žœ˜gJšœˆ™ˆK˜>Kšœžœžœ˜!K˜?Kšœžœ/žœžœ˜NK˜RKšœ žœ˜ Kšœ žœ3˜CK˜š¢œžœ žœ žœ˜0K˜š ¢ œžœžœ žœžœ>˜Kšœžœ˜KšœžœK˜\K˜2Kšžœžœ1˜>Kšžœ9˜;Kšž˜˜[K˜K˜K˜Kšžœ7˜9Kšž˜Kšœžœ˜˜K˜Kšœžœ˜šžœžœžœ/ž˜>Kšœ!žœžœžœ˜C—Kšžœ˜Kšœ+žœžœžœ£©˜‹—K˜—K˜—K˜šžœžœ ž˜%Kšœžœ ˜*K˜—Kšžœ˜K˜šžœ1ž˜8Kšœ+žœžœ žœ˜MJšœF™FK˜7—Kšžœ˜K˜šžœ ž˜Kšœžœ ˜*K˜—Kšž˜—K˜K˜šžœ4žœžœž˜GKšœ žœ˜Kšœžœ˜Kšžœ ˜ Kšž˜šœžœ˜Kšž˜˜#Kšœ žœ˜Kšœ!žœ˜(Kšœžœ˜$—K˜Kšž˜˜'šžœ˜Kšž˜˜Kšœ žœ˜Kšœžœ˜$—K˜Kšžœ#˜'——K˜—K˜Kšž˜šœ$žœ˜+Kšœžœ˜$—K˜—Kšžœ˜Kšœ žœžœDžœ˜lK˜]˜Kšœ˜——š¢œžœžœ˜%Kš œ;žœžœ žœ žœ ˜pKšœª™ªK˜+Kšœžœ˜šžœžœž˜Kšžœ%ž˜+˜K˜—K˜K˜FK˜ —Kšž˜˜K˜——™ J™—š¢ œžœ˜KšœCžœ"˜lKšœ.žœžœ˜?K˜1Kšžœžœžœžœ˜+Kšžœžœžœ"žœ˜JK˜#Kšžœ˜ ˜K˜——š¢œžœ˜"KšœS˜SKšœ.žœžœ˜?K˜DKšžœžœžœ,žœ˜OKšžœžœžœ"žœ˜JK˜"˜K˜——š¢œžœ˜"Kšœ,˜,J™K˜1Kšžœžœžœžœ˜Kšžœžœžœ"žœ˜JKšžœžœžœ£˜^˜JK˜MK˜—K˜K˜˜K˜——K˜—š¡™J™Kšœ ˜ KšŸœ˜K˜AK˜;K˜@Kšœr˜rK˜Kšœ4˜4Kšœ2˜2Kšœ.˜.Kšœ,˜,K˜J™,K˜/K˜—Kšœ˜K˜šœ%ž™(K™Kšœ Ðkr Ÿm§ŸF§™Í—šœ2™2K™ÞKšœ § Ÿb™w—šœ2™2K™LKšœŸ5™:Kšœ § Ÿ™(—šœ3™3K™@Kšœ Ÿ™—šœ3™3K™Kšœ Ÿ™)—šœ4™4KšœZŸœ0™‹KšœŸ!™&Kšœ Ÿ$™0—šœ3™3K™¢Kšœ Ÿ'™3—™2K™öKšœ ŸÂ™Î—™5K™öKšœ Ÿÿ™‹—™6K™°Kšœ Ÿ^™j—™6K™lKšœ Ÿ=™I—™6K™vKšœ Ÿ!™-—™7K™¸Kšœ ŸÓ™ß—™6K™,Kšœ Ÿ`™l—™3K™:Kšœ Ÿ ™—K™K™—…—Áä-ì