<> <> <> <> <> <<>> DIRECTORY Commander USING [CommandProc, Register], Convert USING [RopeFromInt], Icons USING [IconFlavor, NewIconFromFile], IO USING [PutFR, int, rope], MBQueue USING [CreateMenuEntry], Menus USING [Menu, AppendMenuEntry, CreateEntry, MenuProc, CreateMenu], MessageWindow USING [Append, Blink, Clear], Rope USING [ROPE, Concat, FromChar, FromRefText, Substr], TEditDocument USING [SelectionId], TEditInput USING [CommandProc, Register, UnRegister, editState, RecordRef], TEditInputImpl USING [editObject, pdelState, pDel, sel, selState, mouseColor, editMessage, InterpInput], TEditInputOps USING [Delete, Copy, CopyFormat, CopyLooks, SetStyleName, Transpose, TransposeLooks, TransposeFormat], TEditSelection USING [pSel, sSel, MakeSelection], TextEdit USING [ChangeLooks, Size], TextLooks USING [allLooks, RopeToLooks ], TiogaButtons USING [TextNodeRef], TiogaOps USING [CallWithLocks, CancelSelection, FirstChild, GetRope, GetSelection, Location, SelectPoint, ViewerDoc], TiogaOpsDefs USING [Location, Ref, SelectionGrain, WhichSelection], TiogaVoicePrivate USING [ AddCharMark, AddVoiceProc, AgeAllViewers, BackSpace, BackWord, CancelProc, DebugRope, DeleteCharMarks, DeleteSourceMarker, DictationMachine, DictationOps, DisplayCharMarks, EditAges, EditCharMarks, EditTextMarks, ExtractCharMarks, ExtractSoundList, ExtractTextMarks, oldest, PlayBackMenuProc, ReColorViewer, RedrawTextMarkers, RedrawViewer, RegisterViewer, RemoveViewerReferences, ReplaceSoundList, RopeFromTextList, SaveRopeAtSourceMarker, Selection, SelectionRec, Sound, SoundChars, SoundList, SoundInterval, SoundIntervalRec, SoundListFromIntervalSpecs, soundRopeCharDivisions, soundRopeCharLength, soundRopeResolution, StoreVoiceAtSelection, TextInput, TextListFromRope, thrushHandle, TrackDeletes, voiceButtonQueue, VoiceViewerInfo, 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, PaintViewer, SetMenu], ViewerTools USING [TiogaContents, TiogaContentsRec, SetTiogaContents], VoiceRope USING [ DescribeRope, IntervalSpecs, Length, Replace, VoiceRopeInterval ], WindowManager USING [colorDisplayOn] ; <<>> VoiceViewersImpl: CEDAR MONITOR IMPORTS Commander, Convert, Icons, IO, MBQueue, Menus, MessageWindow, Rope, TEditInput, TEditInputImpl, TEditInputOps, TEditSelection, TextEdit, TextLooks, TiogaButtons, TiogaOps, TiogaVoicePrivate, TIPUser, ViewerEvents, ViewerLocks, ViewerOps, ViewerTools, VoiceRope, WindowManager EXPORTS TiogaVoicePrivate SHARES TEditInputImpl = { <> voiceViewerMenu: PUBLIC Menus.Menu; CreateVoiceViewerMenu: PROC = { voiceViewerMenu _ Menus.CreateMenu[]; Menus.AppendMenuEntry[voiceViewerMenu, MBQueue.CreateMenuEntry[TiogaVoicePrivate.voiceButtonQueue, "Add", TiogaVoicePrivate.AddVoiceProc]]; Menus.AppendMenuEntry[voiceViewerMenu, MBQueue.CreateMenuEntry[TiogaVoicePrivate.voiceButtonQueue, "Play", TiogaVoicePrivate.PlayBackMenuProc]]; Menus.AppendMenuEntry[voiceViewerMenu, MBQueue.CreateMenuEntry[TiogaVoicePrivate.voiceButtonQueue, "STOP", TiogaVoicePrivate.CancelProc]]; Menus.AppendMenuEntry[voiceViewerMenu, MBQueue.CreateMenuEntry[TiogaVoicePrivate.voiceButtonQueue, "Save", SaveVoice]]; Menus.AppendMenuEntry[voiceViewerMenu, MBQueue.CreateMenuEntry[TiogaVoicePrivate.voiceButtonQueue, "Store", StoreVoice]]; Menus.AppendMenuEntry[voiceViewerMenu, MBQueue.CreateMenuEntry[TiogaVoicePrivate.voiceButtonQueue, "Redraw", RedrawViewerMenuProc]]; Menus.AppendMenuEntry[voiceViewerMenu, MBQueue.CreateMenuEntry[TiogaVoicePrivate.voiceButtonQueue, "Mark", TiogaVoicePrivate.AddCharMark]]; Menus.AppendMenuEntry[voiceViewerMenu, MBQueue.CreateMenuEntry[TiogaVoicePrivate.voiceButtonQueue, "DeleteMarks", TiogaVoicePrivate.DeleteCharMarks]]; Menus.AppendMenuEntry[voiceViewerMenu, MBQueue.CreateMenuEntry[TiogaVoicePrivate.voiceButtonQueue, "DictationMachine", TiogaVoicePrivate.DictationMachine]]; Menus.AppendMenuEntry[voiceViewerMenu, Menus.CreateEntry["DictationOps", TiogaVoicePrivate.DictationOps]]; }; voiceViewerInfoList: PUBLIC TiogaVoicePrivate.VoiceViewerInfo _ NIL; <<>> 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: TiogaVoicePrivate.VoiceViewerInfo, viewerNumber: INT] = { viewerInfo _ InsertVoiceViewerInfo[]; viewer _ ViewerOps.CreateViewer[ flavor: $Text, info: [name: Rope.Concat["Sound Viewer #", Convert.RopeFromInt[viewerInfo.viewerNumber]], column: IF WindowManager.colorDisplayOn THEN color ELSE left, iconic: FALSE ], paint: TRUE]; viewer.inhibitDestroy _ FALSE; viewer.icon _ soundViewerIcon; viewer.tipTable _ voiceTIPTable; <> viewerNumber _ viewerInfo.viewerNumber; ViewerOps.AddProp[viewer, $voiceViewerInfo, viewerInfo]; viewerInfo.viewer _ viewer; viewerInfo.color _ viewer.column = color; ViewerOps.SetMenu[viewer, voiceViewerMenu]; <<>> viewerInfo.destroyEvent _ ViewerEvents.RegisterEventProc[ proc: DestroyViewerEvent, event: destroy, filter: viewer, before: TRUE]; viewerInfo.changeColumnEvent _ ViewerEvents.RegisterEventProc[ proc: ChangeColumnEvent, event: changeColumn, filter: viewer, before: FALSE]; SetViewerContents[viewer, viewerInfo, voiceID, textInVoice, youngVoice]; MakeNotEdited[viewer]; }; SetVoiceRopeInfo: PROC [viewerInfo: TiogaVoicePrivate.VoiceViewerInfo, voiceID: Rope.ROPE, textInVoice: Rope.ROPE, youngVoice: BOOLEAN] RETURNS [soundRope: Rope.ROPE] ~ { intervals: VoiceRope.IntervalSpecs; viewerInfo.soundList _ NIL; IF voiceID # NIL THEN { <<** ought to check here that the rope actually exists **>> 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: TiogaVoicePrivate.VoiceViewerInfo, voiceID: Rope.ROPE, textInVoice: Rope.ROPE, youngVoice: BOOLEAN] = { 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]; <<>> <<**** the Plass-bug: shouldn't be necessary and he says he'll have a look at it>> <> viewerInfo.editInProgress _ (voiceID = NIL) -- can do this because the viewer is created with editInProgress = TRUE, so nobody else can have grabbed the lock }; SetTiogaViewerParams: PUBLIC PROC [viewer: ViewerClasses.Viewer, newcontents: Rope.ROPE] RETURNS [voiceNode: TiogaOpsDefs.Ref] = { rootNode: TiogaOpsDefs.Ref; viewercontents: ViewerTools.TiogaContents _ NEW[ViewerTools.TiogaContentsRec _ [contents: newcontents, formatting: NIL]]; ViewerTools.SetTiogaContents[viewer, viewercontents]; rootNode _ TiogaOps.ViewerDoc[viewer]; TEditInputOps.SetStyleName["voiceProfile", TiogaButtons.TextNodeRef[rootNode]]; voiceNode _ TiogaOps.FirstChild[rootNode]; SetVoiceLooks[viewer, voiceNode, 0, LAST[INT], "v"]; TiogaVoicePrivate.RedrawTextMarkers[viewer, voiceNode]; }; SetVoiceLooks: PUBLIC PROC [viewer: ViewerClasses.Viewer, node: TiogaOpsDefs.Ref, start, len: INT, looks: Rope.ROPE] ~ { DoIt: PROC [root: TiogaOpsDefs.Ref] ~ { <> TextEdit.ChangeLooks[root: TiogaButtons.TextNodeRef[root], text: TiogaButtons.TextNodeRef[node], remove: TextLooks.allLooks, add: TextLooks.RopeToLooks[looks], start: start, len: len, event: NIL] }; TiogaOps.CallWithLocks[DoIt, TiogaOps.ViewerDoc[viewer]]; }; MakeNotEdited: PROC [viewer: ViewerClasses.Viewer] = { <> DoIt: PROC = { viewerInfo: TiogaVoicePrivate.VoiceViewerInfo _ NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], TiogaVoicePrivate.VoiceViewerInfo]; viewerInfo.edited _ viewer.newVersion _ viewer.newFile _ FALSE; viewer.icon _ soundViewerIcon; ViewerOps.PaintViewer[viewer, caption, FALSE]; }; ViewerLocks.CallUnderWriteLock[DoIt, viewer] }; SetVoiceViewerEditStatus: PUBLIC PROC [ viewer: ViewerClasses.Viewer] = { <> DoIt: PROC = { viewerInfo: TiogaVoicePrivate.VoiceViewerInfo _ NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], TiogaVoicePrivate.VoiceViewerInfo]; IF viewerInfo.edited # viewer.newVersion THEN { viewer.newVersion _ viewerInfo.edited; IF ~viewerInfo.edited THEN { viewer.newFile _ FALSE; viewer.icon _ soundViewerIcon; } ELSE viewer.icon _ dirtySoundViewerIcon; ViewerOps.PaintViewer[viewer, caption, FALSE]; } }; ViewerLocks.CallUnderWriteLock[DoIt, viewer] }; SetParentViewer: PUBLIC PROC [ viewerInfo: TiogaVoicePrivate.VoiceViewerInfo, parentViewer: ViewerClasses.Viewer, positionInParent: TiogaOpsDefs.Location] = { viewerInfo.parentViewer _ parentViewer; viewerInfo.positionInParent _ positionInParent; TiogaVoicePrivate.RegisterViewer[parentViewer] }; RemoveParentViewer: PUBLIC PROC [viewerNumber: INT] = { FOR info: TiogaVoicePrivate.VoiceViewerInfo _ voiceViewerInfoList, info.nextInfoRec WHILE info # NIL DO IF info.viewerNumber = viewerNumber THEN {info.parentViewer _ NIL; RETURN} ENDLOOP }; InsertVoiceViewerInfo: PROC RETURNS [ newViewerInfo: TiogaVoicePrivate.VoiceViewerInfo] = { restOfList: TiogaVoicePrivate.VoiceViewerInfo _ voiceViewerInfoList; newViewerInfo _ NEW[TiogaVoicePrivate.VoiceViewerInfoRec]; IF voiceViewerInfoList = NIL THEN { newViewerInfo.viewerNumber _ 1; voiceViewerInfoList _ newViewerInfo; RETURN }; IF voiceViewerInfoList.viewerNumber > 1 THEN { newViewerInfo.viewerNumber _ 1; newViewerInfo.nextInfoRec _ voiceViewerInfoList; voiceViewerInfoList _ newViewerInfo; RETURN }; DO IF restOfList.nextInfoRec = NIL OR restOfList.nextInfoRec.viewerNumber > restOfList.viewerNumber + 1 THEN { newViewerInfo.viewerNumber _ restOfList.viewerNumber + 1; newViewerInfo.nextInfoRec _ restOfList.nextInfoRec; restOfList.nextInfoRec _ newViewerInfo; RETURN }; restOfList _ restOfList.nextInfoRec ENDLOOP }; StoreVoice: Menus.MenuProc = { <<**** locking against edits in this and the next one?>> viewerInfo: TiogaVoicePrivate.VoiceViewerInfo _ NARROW[ViewerOps.FetchProp[NARROW[parent], $voiceViewerInfo], TiogaVoicePrivate.VoiceViewerInfo]; oldParentViewer: ViewerClasses.Viewer _ viewerInfo.parentViewer; oldPosition: TiogaOpsDefs.Location _ viewerInfo.positionInParent; IF TiogaVoicePrivate.StoreVoiceAtSelection[viewerInfo] THEN { MakeNotEdited[NARROW[parent]]; IF oldParentViewer # NIL THEN TiogaVoicePrivate.DeleteSourceMarker[oldParentViewer, oldPosition, viewerInfo.viewerNumber] } }; SaveVoice: Menus.MenuProc = { viewer: ViewerClasses.Viewer _ NARROW[parent]; viewerInfo: TiogaVoicePrivate.VoiceViewerInfo _ NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], TiogaVoicePrivate.VoiceViewerInfo]; IF ~viewerInfo.edited THEN { MessageWindow.Append["voice viewer is not edited", TRUE]; RETURN }; IF viewerInfo.parentViewer = NIL THEN { MessageWindow.Append["voice viewer has no parent text viewer", TRUE]; MessageWindow.Blink[]; RETURN }; IF ~TiogaVoicePrivate.SaveRopeAtSourceMarker[viewerInfo.parentViewer, viewerInfo.positionInParent, viewerInfo.viewerNumber, viewerInfo.ropeInterval.ropeID, TiogaVoicePrivate.RopeFromTextList[viewerInfo.textMarkList]] THEN { MessageWindow.Append["unable to find source marker for voice window: voice not saved", TRUE]; MessageWindow.Blink[]; RETURN }; MakeNotEdited[viewer] }; RemoveVoiceViewerInfo: PROC [ viewerInfo: TiogaVoicePrivate.VoiceViewerInfo] = { IF voiceViewerInfoList = NIL THEN ERROR; IF voiceViewerInfoList = viewerInfo THEN voiceViewerInfoList _ viewerInfo.nextInfoRec ELSE { ptrToCurrInfo: TiogaVoicePrivate.VoiceViewerInfo _ voiceViewerInfoList; currInfo: TiogaVoicePrivate.VoiceViewerInfo _ voiceViewerInfoList.nextInfoRec; DO IF currInfo = NIL THEN ERROR; IF currInfo = viewerInfo THEN { ptrToCurrInfo.nextInfoRec _ viewerInfo.nextInfoRec; RETURN }; ptrToCurrInfo _ currInfo; currInfo _ currInfo.nextInfoRec ENDLOOP } }; DestroyViewerEvent: ViewerEvents.EventProc = { RETURN [~DestroyVoiceInfo[viewer]] }; DestroyVoiceInfo: PROC [ viewer: ViewerClasses.Viewer] RETURNS [didIt: BOOLEAN _ TRUE] = { viewerInfo: TiogaVoicePrivate.VoiceViewerInfo _ NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], TiogaVoicePrivate.VoiceViewerInfo]; IF ~TiogaVoicePrivate.RemoveViewerReferences[viewer] THEN { MessageWindow.Append["Wait until voice cue disappears before destroying viewer", TRUE]; MessageWindow.Blink[]; RETURN [FALSE] }; <> IF viewerInfo.parentViewer = NIL THEN TiogaVoicePrivate.DebugRope[IO.PutFR["Voice viewer %d had no parent\n", IO.int[viewerInfo.viewerNumber]]] ELSE TiogaVoicePrivate.DeleteSourceMarker[viewerInfo.parentViewer, viewerInfo.positionInParent, viewerInfo.viewerNumber]; RemoveVoiceViewerInfo[viewerInfo]; ViewerEvents.UnRegisterEventProc[viewerInfo.destroyEvent, destroy]; ViewerEvents.UnRegisterEventProc[viewerInfo.changeColumnEvent, changeColumn]; }; RedrawViewerMenuProc: Menus.MenuProc = { viewer: ViewerClasses.Viewer _ NARROW[parent]; viewerInfo: TiogaVoicePrivate.VoiceViewerInfo _ NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], TiogaVoicePrivate.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: TiogaVoicePrivate.VoiceViewerInfo _ NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], TiogaVoicePrivate.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: click REDRAW to change window between color and monchrome if the wrong state persists", TRUE]; MessageWindow.Blink[] } } }; GetVoiceLock: PUBLIC ENTRY PROC [ info: TiogaVoicePrivate.VoiceViewerInfo] RETURNS [gotIt: BOOLEAN] = { gotIt _ ~ 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 TEditInputImpl.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 { TEditInputImpl.InterpInput[self, passOnList]; passOnList _ NIL }; TiogaVoicePrivate.TextInput[self, Rope.FromChar[z^]] }; z: Rope.ROPE => { IF passOnList # NIL THEN { TEditInputImpl.InterpInput[self, passOnList]; passOnList _ NIL }; TiogaVoicePrivate.TextInput[self, z] }; z: REF TEXT => { IF passOnList # NIL THEN { TEditInputImpl.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 TEditInputImpl.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 TEditInputImpl.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 TEditInputImpl.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 TEditInputImpl.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 TEditInputImpl.editObject FROM text => CheckTranspose[]; looks => CheckTransposeLooks[]; format => CheckTransposeFormat[]; ENDCASE => ERROR }; ENDCASE => ERROR }; TEditInput.editState _ reset; TEditInputImpl.editObject _ text; TEditInputImpl.pdelState _ reset; TEditInputImpl.pDel _ FALSE; TEditInputImpl.selState _ reset; TEditInputImpl.sel _ primary; TEditInputImpl.mouseColor _ red; -- put these back to normal IF TEditInputImpl.editMessage # NIL THEN MessageWindow.Clear[]; TEditInputImpl.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]; ENDCASE => Mumble["Copy", "voice and text"] }; VoiceInSourceDocument: PROC [target: TEditDocument.SelectionId _ primary] RETURNS [BOOLEAN _ FALSE] = { <<needs to be filled in -- PTZ, June 12, 1987 7:49:54 pm PDT>> }; TrackTextCopy: PROC [target: TEditDocument.SelectionId _ primary] = { <<needs to be filled in -- PTZ, June 12, 1987 7:49:54 pm PDT>> }; 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], TiogaVoicePrivate.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: BOOLEAN] RETURNS [viewerDeleted: BOOLEAN, insertChars, positionAfterInsert: INT] = { <> <> <> <> <<>> 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 _ TextEdit.Size[TiogaButtons.TextNodeRef[selection.displayNode]] = 0; IF viewerDeleted THEN { MessageWindow.Append["voice viewer now completely empty - destroying", TRUE]; ViewerOps.DestroyViewer[selection.viewer] <> } 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; selection.voiceViewerInfo.edited _ TRUE; [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[TiogaButtons.TextNodeRef[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; destSelection.voiceViewerInfo.edited _ TRUE; IF sourceSelection.ropeInterval.length # 0 <> THEN { sourceSelection.voiceViewerInfo.edited _ TRUE; IF sourceSelection.viewer = destSelection.viewer -- special case of same viewer THEN { 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> <<>> <> <<>> SoundListFromIntervalSpecs: PUBLIC PROC [ intervalSpecs: VoiceRope.IntervalSpecs, lengthOfRopeInterval: INT] RETURNS [soundList: TiogaVoicePrivate.SoundList _ NIL] = { lastSoundEnd: INT _ 0; FOR l: VoiceRope.IntervalSpecs _ intervalSpecs, l.rest WHILE l # NIL DO soundList _ AppendSound[soundList, [l.first.start - lastSoundEnd, l.first.length]]; lastSoundEnd _ l.first.start + l.first.length ENDLOOP; IF lengthOfRopeInterval > lastSoundEnd THEN soundList _ AppendSound[soundList, [lengthOfRopeInterval-lastSoundEnd, 0]] }; ExtractSoundList: PUBLIC PROC [ voiceViewerInfo: TiogaVoicePrivate.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: TiogaVoicePrivate.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: TiogaVoicePrivate.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 '!]]; <<15 represents 'all bits are sound' - i.e. 2**soundRopeCharDivisions-1>> 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 }; <> <<>> CreateVoiceViewerMenu[]; voiceTIPTable _ TIPUser.InstantiateNewTIPTable["TiogaVoice.tip"]; soundViewerIcon _ Icons.NewIconFromFile["Finch.icons", 15]; dirtySoundViewerIcon _ Icons.NewIconFromFile["Finch.icons", 16]; 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; }. <> <> <> <> <> <> <> <> <> <>