<> <> <> <> <> <<>> 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, 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], TextNode USING [Ref], TiogaButtons USING [TextNodeRef], 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, TiogaButtons, 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 { <<** 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: 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]; <<>> <<**** 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 }; 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", TiogaButtons.TextNodeRef[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: TiogaButtons.TextNodeRef[root], text: TiogaButtons.TextNodeRef[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[TiogaButtons.TextNodeRef[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.PutFR["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 = { <> <<**** locking against edits in this and the next one?>> 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 { <<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).>> 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.PutFR["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] = { <<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], 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, 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 _ 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[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; 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> destSelection.displayNode _ sourceSelection.displayNode; <> } ELSE IF NOT ReplaceSelectionWithSavedInterval[sourceSelection, NIL, FALSE].viewerDeleted THEN sourceSelection.voiceViewerInfo.editInProgress _ FALSE; } ELSE IF sourceSelection.viewer # destSelection.viewer THEN sourceSelection.voiceViewerInfo.editInProgress _ FALSE; destSelection.ropeInterval.start _ destSelection.ropeInterval.start - destMovesForward; [] _ ReplaceSelectionWithSavedInterval[destSelection, sourceInterval, TRUE]; destSelection.voiceViewerInfo.editInProgress _ FALSE; }; Transpose: PUBLIC PROC [target: TEditDocument.SelectionId _ primary] = { IF target = feedback THEN ERROR; TiogaVoicePrivate.DebugRope["Voice transpose:\n"]; SelectionStates[]; TiogaVoicePrivate.DebugRope["not yet implemented.\n"]; }; SelectionStates: PROC = { TiogaVoicePrivate.DebugRope[" Primary selection is"]; IF ~TiogaOps.GetSelection[primary].pendingDelete THEN TiogaVoicePrivate.DebugRope[" not"]; TiogaVoicePrivate.DebugRope[" pending delete,\n secondary selection is"]; IF ~TiogaOps.GetSelection[secondary].pendingDelete THEN TiogaVoicePrivate.DebugRope[" not"]; TiogaVoicePrivate.DebugRope[" pending delete\n"] }; <> <<>> <> <<>> 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: 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 '!]]; <<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 }; <> <<>> 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; }. <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <<>> <<>>