<> <> <> <> <> <> <<>> DIRECTORY Convert USING [RopeFromInt, IntFromRope], ImagerFont USING [Find, Font, Escapement], Menus USING [MenuProc], MessageWindow USING [Append, Blink], Real USING [Fix], Rope USING [ROPE, Length, Substr, Concat, Fetch, Cat, Find], SoundList USING [SoundChars], TextEdit USING [PutCharProp], TextNode USING [Ref], TiogaButtons USING [TextNodeRef], TiogaOps USING [GetSelection, FirstChild, ViewerDoc, CallWithLocks], TiogaOpsDefs USING [Location, Ref], ViewerClasses USING [Viewer], ViewerOps USING [FetchProp, PaintViewer], VoicePlayBack USING [RedrawViewer], VoiceViewers USING [VoiceViewerInfo, GetVoiceLock, SetVoiceViewerEditStatus, TextMarkEntry, TextMarkRec, SoundInterval, soundRopeCharLength], VoiceMarkers; <<>> VoiceMarkersImpl: CEDAR PROGRAM IMPORTS Convert, ImagerFont, MessageWindow, Real, Rope, SoundList, TextEdit, TiogaButtons, TiogaOps, ViewerOps, VoicePlayBack, VoiceViewers, VoiceMarkers EXPORTS VoiceMarkers = BEGIN <> <<---- routines to do with character markers: these are transitory and do not form part of the contents of the viewer>> AddCharMark: PUBLIC Menus.MenuProc = { <> viewer: ViewerClasses.Viewer; start, end: TiogaOpsDefs.Location; pendingDelete, caretBefore: BOOLEAN; viewerInfo: VoiceViewers.VoiceViewerInfo; [viewer: viewer, start: start, end: end, pendingDelete: pendingDelete, caretBefore: caretBefore] _ TiogaOps.GetSelection[]; viewerInfo _ NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], VoiceViewers.VoiceViewerInfo]; IF viewerInfo = NIL THEN { MessageWindow.Append["Make a selection in a voice viewer first", TRUE]; MessageWindow.Blink[]; RETURN }; IF VoiceViewers.GetVoiceLock[viewerInfo] THEN { IF ~ (start.node = end.node AND start.node = TiogaOps.FirstChild[TiogaOps.ViewerDoc[viewer]]) THEN ERROR; IF pendingDelete OR caretBefore THEN MarkChar[viewerInfo, start.where]; IF pendingDelete OR ~caretBefore THEN MarkChar[viewerInfo, end.where]; { trueContents: Rope.ROPE _ SoundList.SoundChars[viewerInfo].soundRope; [] _ VoicePlayBack.RedrawViewer[viewer, trueContents, 0, 0, 0, viewerInfo.remnant, FALSE, unAltered]; VoiceViewers.SetVoiceViewerEditStatus[viewer]; viewerInfo.editInProgress _ FALSE } } }; MarkChar: PROC [viewerInfo: VoiceViewers.VoiceViewerInfo, position: INT] = { restOfList: LIST OF INT; IF viewerInfo.charMarkList = NIL THEN viewerInfo.charMarkList _ CONS [position, NIL] ELSE { IF viewerInfo.charMarkList.first >= position THEN { IF viewerInfo.charMarkList.first > position THEN viewerInfo.charMarkList _ CONS [position, viewerInfo.charMarkList] } ELSE { FOR restOfList _ viewerInfo.charMarkList, restOfList.rest WHILE restOfList.rest # NIL DO IF restOfList.rest.first >= position THEN { IF restOfList.rest.first > position THEN restOfList.rest _ CONS [position, restOfList.rest]; RETURN } ENDLOOP; restOfList.rest _ CONS [position, NIL] } } }; LockedAddCharMark: PUBLIC PROC [viewer: ViewerClasses.Viewer, position: INT] = { <> viewerInfo: VoiceViewers.VoiceViewerInfo _ NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], VoiceViewers.VoiceViewerInfo]; IF viewerInfo = NIL THEN ERROR; MarkChar[viewerInfo, position]; { trueContents: Rope.ROPE _ SoundList.SoundChars[viewerInfo].soundRope; [] _ VoicePlayBack.RedrawViewer[viewer, trueContents, 0, 0, 0, viewerInfo.remnant, FALSE, unAltered]; VoiceViewers.SetVoiceViewerEditStatus[viewer]; } }; << >> DeleteCharMarks: PUBLIC Menus.MenuProc = { viewer: ViewerClasses.Viewer; start, end: TiogaOpsDefs.Location; viewerInfo: VoiceViewers.VoiceViewerInfo; RemoveMarkChars: PROC [from, to: INT] = { WHILE viewerInfo.charMarkList # NIL AND viewerInfo.charMarkList.first IN [from..to] DO viewerInfo.charMarkList _ viewerInfo.charMarkList.rest ENDLOOP; IF viewerInfo.charMarkList # NIL THEN FOR l: LIST OF INT _ viewerInfo.charMarkList, l.rest WHILE l # NIL AND l.rest # NIL DO IF l.rest.first IN [from..to] THEN l.rest _ l.rest.rest ENDLOOP -- the dual WHILE condition is because we can delete the next entry and it could be the last }; IF mouseButton = blue THEN -- right click means delete all marks in the viewer { viewer _ NARROW[parent, ViewerClasses.Viewer]; viewerInfo _ NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], VoiceViewers.VoiceViewerInfo]; IF VoiceViewers.GetVoiceLock[viewerInfo] THEN { viewerInfo.charMarkList _ NIL; { trueContents: Rope.ROPE _ SoundList.SoundChars[viewerInfo].soundRope; [] _ VoicePlayBack.RedrawViewer[viewer, trueContents, 0, 0, 0, viewerInfo.remnant, FALSE, unAltered]; VoiceViewers.SetVoiceViewerEditStatus[viewer]; viewerInfo.editInProgress _ FALSE } } } ELSE -- both other clicks mean delete all marks from selection { [viewer: viewer, start: start, end: end] _ TiogaOps.GetSelection[]; viewerInfo _ NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], VoiceViewers.VoiceViewerInfo]; IF viewerInfo = NIL THEN { MessageWindow.Append["Make a selection in a voice viewer first", TRUE]; MessageWindow.Blink[]; RETURN }; IF VoiceViewers.GetVoiceLock[viewerInfo] THEN { IF ~ (start.node = end.node AND start.node = TiogaOps.FirstChild[TiogaOps.ViewerDoc[viewer]]) THEN ERROR; RemoveMarkChars[start.where, end.where]; { trueContents: Rope.ROPE _ SoundList.SoundChars[viewerInfo].soundRope; [] _ VoicePlayBack.RedrawViewer[viewer, trueContents, 0, 0, 0, viewerInfo.remnant, FALSE, unAltered]; VoiceViewers.SetVoiceViewerEditStatus[viewer]; viewerInfo.editInProgress _ FALSE } } } }; DisplayCharMarks: PUBLIC PROC [unMarkedRope: Rope.ROPE, charMarkList: LIST OF INT, skipChars: INT] RETURNS [markedRope: Rope.ROPE] = { <> endChar: INT _ unMarkedRope.Length - 1; lastMark: INT _ -1; nextMark: INT; markedRope _ NIL; WHILE charMarkList # NIL AND charMarkList.first < skipChars DO charMarkList _ charMarkList.rest ENDLOOP; DO nextMark _ IF charMarkList = NIL THEN endChar + 1 ELSE charMarkList.first - skipChars; markedRope _ markedRope.Concat[unMarkedRope.Substr[lastMark+1, nextMark-lastMark-1]]; IF nextMark = endChar + 1 THEN RETURN; markedRope _ markedRope.Concat["@"]; -- the marker character lastMark _ nextMark; charMarkList _ charMarkList.rest ENDLOOP }; <<>> ExtractCharMarks: PUBLIC PROC [viewerInfo: VoiceViewers.VoiceViewerInfo, soundInterval: VoiceViewers.SoundInterval] = { <> currLastInList: LIST OF INT; soughtStart: INT _ soundInterval.ropeInterval.start/VoiceViewers.soundRopeCharLength; soughtEnd: INT _ (soundInterval.ropeInterval.start + soundInterval.ropeInterval.length)/VoiceViewers.soundRopeCharLength; soundInterval.charMarkList _ NIL; -- should be already so, but . . . FOR l: LIST OF INT _ viewerInfo.charMarkList, l.rest WHILE l # NIL AND l.first < soughtEnd DO IF l.first >= soughtStart THEN { IF soundInterval.charMarkList = NIL THEN { soundInterval.charMarkList _ CONS [l.first - soughtStart, NIL]; currLastInList _ soundInterval.charMarkList; } ELSE { currLastInList.rest _ CONS [l.first - soughtStart, NIL]; currLastInList _ currLastInList.rest } } ENDLOOP }; <<>> <<>> EditCharMarks: PUBLIC PROC [viewerInfo: VoiceViewers.VoiceViewerInfo, unchangedHead, deleteChars, insertChars: INT, soundInterval: VoiceViewers.SoundInterval] = { <> <> atHead: BOOLEAN _ TRUE; l: LIST OF INT; WHILE atHead AND viewerInfo.charMarkList # NIL DO SELECT viewerInfo.charMarkList.first FROM >= unchangedHead+deleteChars => {viewerInfo.charMarkList.first _ viewerInfo.charMarkList.first + insertChars - deleteChars; atHead _ FALSE}; >= unchangedHead => viewerInfo.charMarkList _ viewerInfo.charMarkList.rest; ENDCASE => atHead _ FALSE; ENDLOOP; IF viewerInfo.charMarkList # NIL THEN { l _ viewerInfo.charMarkList; WHILE l # NIL AND l.rest # NIL DO --the dual WHILE condition is because we can delete the next entry and it could be the last SELECT l.rest.first FROM >= unchangedHead+deleteChars => {l.rest.first _ l.rest.first + insertChars - deleteChars; l _ l.rest}; >= unchangedHead => l.rest _ l.rest.rest; ENDCASE => l _ l.rest ENDLOOP }; IF soundInterval # NIL AND soundInterval.charMarkList # NIL THEN -- now put the character marks from the soundInterval into the list at the appropriate point { beforeInsert, afterInsert: LIST OF INT _ NIL; newSection, endNewSection: LIST OF INT; IF viewerInfo.charMarkList # NIL THEN { IF viewerInfo.charMarkList.first >= unchangedHead THEN afterInsert _ viewerInfo.charMarkList ELSE { l _ viewerInfo.charMarkList; WHILE l.rest # NIL AND l.rest.first < unchangedHead DO l _ l.rest ENDLOOP; beforeInsert _ l; afterInsert _ l.rest } }; { insertMarks: LIST OF INT _ soundInterval.charMarkList.rest; newSection _ CONS [soundInterval.charMarkList.first + unchangedHead, NIL]; endNewSection _ newSection; WHILE insertMarks # NIL DO endNewSection.rest _ CONS [soundInterval.charMarkList.first + unchangedHead, NIL]; endNewSection _ endNewSection.rest; insertMarks _ insertMarks.rest ENDLOOP }; endNewSection.rest _ afterInsert; IF beforeInsert = NIL THEN viewerInfo.charMarkList _ newSection ELSE beforeInsert.rest _ newSection } }; <<---- routines to do with textual markers: these markers are really a part of the viewer contents. They are saved when the voice is saved and altering them makes the window status 'edited'>> TextInput: PUBLIC PROC [viewer: ViewerClasses.Viewer, input: Rope.ROPE] = { node: TextNode.Ref; textEntry: VoiceViewers.TextMarkEntry; AddArtwork: PROC [root: TiogaOpsDefs.Ref] = { TextEdit.PutCharProp[node, textEntry.position, $Artwork, NARROW["VoiceMarker", Rope.ROPE]]; -- the NARROW prevents a REF TEXT being created TextEdit.PutCharProp[node, textEntry.position, $VoiceMark, textEntry.text.Substr[0, textEntry.displayChars]]; }; selectionViewer: ViewerClasses.Viewer; start, end, char: TiogaOpsDefs.Location; insertPosition: INT; caretBefore: BOOLEAN; viewerInfo: VoiceViewers.VoiceViewerInfo; charsAlreadyDisplayed: INT _ 0; redraw: BOOLEAN _ FALSE; [viewer: selectionViewer, start: start, end: end, caretBefore: caretBefore] _ TiogaOps.GetSelection[]; IF selectionViewer # viewer THEN ERROR; viewerInfo _ NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], VoiceViewers.VoiceViewerInfo]; IF ~VoiceViewers.GetVoiceLock[viewerInfo] THEN RETURN; char _ IF caretBefore THEN start ELSE end; node _ TiogaButtons.TextNodeRef[char.node]; insertPosition _ char.where; IF viewerInfo.textMarkList = NIL OR viewerInfo.textMarkList.first.position > insertPosition THEN { textEntry _ NEW [VoiceViewers.TextMarkRec _ [insertPosition, input, 0, 0]]; viewerInfo.textMarkList _ CONS [textEntry, viewerInfo.textMarkList]; LimitText[textEntry, IF viewerInfo.textMarkList.rest = NIL THEN LAST[INT] ELSE viewerInfo.textMarkList.rest.first.position - insertPosition]; TiogaOps.CallWithLocks[AddArtwork]; redraw _ TRUE } ELSE { IF viewerInfo.textMarkList.first.position = insertPosition THEN { textEntry _ viewerInfo.textMarkList.first; charsAlreadyDisplayed _ textEntry.displayChars; textEntry.text _ textEntry.text.Concat[input]; LimitText[textEntry, IF viewerInfo.textMarkList.rest = NIL THEN LAST[INT] ELSE viewerInfo.textMarkList.rest.first.position - insertPosition]; IF textEntry.displayChars # charsAlreadyDisplayed THEN {TiogaOps.CallWithLocks[AddArtwork]; redraw _ TRUE} } ELSE { l: LIST OF VoiceViewers.TextMarkEntry _ viewerInfo.textMarkList; WHILE l.rest # NIL AND l.rest.first.position < insertPosition DO l _ l.rest ENDLOOP; IF l.rest = NIL OR l.rest.first.position > insertPosition THEN -- insert a new entry: previous entry may need trimming { oldWidth: INT _ l.first.width; textEntry _ l.first; LimitText[textEntry, insertPosition - textEntry.position]; IF textEntry.width # oldWidth THEN {TiogaOps.CallWithLocks[AddArtwork]; redraw _ TRUE}; textEntry _ NEW [VoiceViewers.TextMarkRec _ [insertPosition, input, 0, 0]]; l.rest _ CONS [textEntry, l.rest] } ELSE -- appending text to an entry already there { textEntry _ l.rest.first; textEntry.text _ textEntry.text.Concat[input]; charsAlreadyDisplayed _ textEntry.displayChars }; l _ l.rest; -- point at the new/modified entry LimitText[textEntry, IF l.rest = NIL THEN LAST[INT] ELSE l.rest.first.position - insertPosition]; IF textEntry.displayChars # charsAlreadyDisplayed THEN {TiogaOps.CallWithLocks[AddArtwork]; redraw _ TRUE} } }; <<**** a Plass-bug: shouldn't be necessary and he says he'll have a look at it>> IF redraw THEN ViewerOps.PaintViewer[viewer: viewer, hint: client, clearClient: TRUE, whatChanged: NIL]; viewerInfo.edited _ TRUE; IF ~redraw THEN VoiceViewers.SetVoiceViewerEditStatus[viewer]; -- not needed if the redraw was performed, as viewer's displayed status will be `edited' already viewerInfo.editInProgress _ FALSE }; LimitText: PROC [entry: VoiceViewers.TextMarkEntry, maxLength: INT] = { <> maxEscapement: REAL _ REAL[maxLength]*VoiceMarkers.voiceCharWidth; textLength: INT _ entry.text.Length; currPos: INT _ 0; currEscapement: REAL _ 0.0; WHILE currPos < textLength DO newEscapement: REAL _ currEscapement + ImagerFont.Escapement[VoiceMarkers.voiceMarkerFont, [VoiceMarkers.voiceCharSet, entry.text.Fetch[currPos]-'\000]].x; IF newEscapement > maxEscapement THEN EXIT; currPos _ currPos + 1; currEscapement _ newEscapement ENDLOOP; <> <> <<>> entry.width _ Real.Fix[(currEscapement+VoiceMarkers.voiceCharWidth-1.0)/ VoiceMarkers.voiceCharWidth]; entry.displayChars _ currPos; }; <<>> BackSpace: PUBLIC PROC [viewer: ViewerClasses.Viewer] = { <> node: TextNode.Ref; textEntry: VoiceViewers.TextMarkEntry; AddArtwork: PROC [root: TiogaOpsDefs.Ref] = { TextEdit.PutCharProp[node, textEntry.position, $Artwork, NARROW["VoiceMarker", Rope.ROPE]]; -- the NARROW prevents a REF TEXT being created TextEdit.PutCharProp[node, textEntry.position, $VoiceMark, textEntry.text.Substr[0, textEntry.displayChars]]; }; selectionViewer: ViewerClasses.Viewer; start, end, char: TiogaOpsDefs.Location; deletePosition, charsToNextPosition: INT; caretBefore: BOOLEAN; viewerInfo: VoiceViewers.VoiceViewerInfo; [viewer: selectionViewer, start: start, end: end, caretBefore: caretBefore] _ TiogaOps.GetSelection[]; IF selectionViewer # viewer THEN ERROR; viewerInfo _ NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], VoiceViewers.VoiceViewerInfo]; IF ~VoiceViewers.GetVoiceLock[viewerInfo] THEN RETURN; char _ IF caretBefore THEN start ELSE end; node _ TiogaButtons.TextNodeRef[char.node]; deletePosition _ char.where; textEntry _ NIL; FOR l: LIST OF VoiceViewers.TextMarkEntry _ viewerInfo.textMarkList, l.rest WHILE l # NIL DO IF l.first.position >= deletePosition THEN { IF l.first.position = deletePosition THEN { textEntry _ l.first; charsToNextPosition _ IF l.rest = NIL THEN LAST[INT] ELSE l.rest.first.position - deletePosition }; EXIT } ENDLOOP; IF textEntry = NIL THEN MessageWindow.Append["No textual annotation at point of selection", TRUE] ELSE { charsOnDisplay: INT _ textEntry.displayChars; textEntry.text _ textEntry.text.Substr[0, textEntry.text.Length -1]; LimitText[textEntry, charsToNextPosition]; viewerInfo.edited _ TRUE; IF charsOnDisplay # textEntry.displayChars THEN { TiogaOps.CallWithLocks[AddArtwork]; <<**** a Plass-bug: shouldn't be necessary and he says he'll have a look at it>> ViewerOps.PaintViewer[viewer: viewer, hint: client, clearClient: TRUE, whatChanged: NIL] } ELSE VoiceViewers.SetVoiceViewerEditStatus[selectionViewer] }; viewerInfo.editInProgress _ FALSE }; BackWord: PUBLIC PROC [viewer: ViewerClasses.Viewer] = { <> node: TextNode.Ref; textEntry: VoiceViewers.TextMarkEntry; AddArtwork: PROC [root: TiogaOpsDefs.Ref] = { TextEdit.PutCharProp[node, textEntry.position, $Artwork, IF textEntry.text = NIL THEN NIL ELSE NARROW["VoiceMarker", Rope.ROPE]]; -- the NARROW prevents a REF TEXT being created TextEdit.PutCharProp[node, textEntry.position, $VoiceMark, IF textEntry.text = NIL THEN NIL ELSE textEntry.text.Substr[0, textEntry.displayChars]]; }; selectionViewer: ViewerClasses.Viewer; start, end: TiogaOpsDefs.Location; textRemoved: BOOLEAN _ FALSE; viewerInfo: VoiceViewers.VoiceViewerInfo; [viewer: selectionViewer, start: start, end: end] _ TiogaOps.GetSelection[]; IF selectionViewer # viewer THEN ERROR; IF start.node # end.node THEN ERROR; viewerInfo _ NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], VoiceViewers.VoiceViewerInfo]; node _ TiogaButtons.TextNodeRef[start.node]; IF ~VoiceViewers.GetVoiceLock[viewerInfo] THEN RETURN; IF viewerInfo.textMarkList = NIL THEN { viewerInfo.editInProgress _ FALSE; RETURN }; IF viewerInfo.textMarkList.first.position >= start.where THEN -- any text to removed is at the head of the list WHILE viewerInfo.textMarkList # NIL AND viewerInfo.textMarkList.first.position <= end.where DO textEntry _ viewerInfo.textMarkList.first; textEntry.text _ NIL; TiogaOps.CallWithLocks[AddArtwork]; textRemoved _ TRUE; viewerInfo.textMarkList _ viewerInfo.textMarkList.rest ENDLOOP ELSE { l: LIST OF VoiceViewers.TextMarkEntry _ viewerInfo.textMarkList; WHILE l.rest # NIL AND l.rest.first.position < start.where DO l _ l.rest ENDLOOP; WHILE l.rest # NIL AND l.rest.first.position <= end.where DO textEntry _ l.rest.first; textEntry.text _ NIL; TiogaOps.CallWithLocks[AddArtwork]; textRemoved _ TRUE; l.rest _ l.rest.rest ENDLOOP; IF textRemoved THEN { currChars: INT; textEntry _ l.first; currChars _ textEntry.displayChars; LimitText[textEntry, IF l.rest = NIL THEN LAST[INT] ELSE l.rest.first.position - textEntry.position]; IF currChars # textEntry.displayChars THEN TiogaOps.CallWithLocks[AddArtwork] } }; IF textRemoved THEN { viewerInfo.edited _ TRUE; <<**** a Plass-bug: shouldn't be necessary and he says he'll have a look at it>> ViewerOps.PaintViewer[viewer: viewer, hint: client, clearClient: TRUE, whatChanged: NIL] }; viewerInfo.editInProgress _ FALSE }; RedrawTextMarkers: PUBLIC PROC [viewer: ViewerClasses.Viewer, voiceCharNode: TiogaOpsDefs.Ref] = { <> AddArtwork: PROC [root: TiogaOpsDefs.Ref] = { textEntry: VoiceViewers.TextMarkEntry _ l.first; TextEdit.PutCharProp[node, textEntry.position, $Artwork, NARROW["VoiceMarker", Rope.ROPE]]; -- the NARROW prevents a REF TEXT being created TextEdit.PutCharProp[node, textEntry.position, $VoiceMark, textEntry.text.Substr[0, textEntry.displayChars]]; }; node: TextNode.Ref _ TiogaButtons.TextNodeRef[voiceCharNode]; viewerInfo: VoiceViewers.VoiceViewerInfo _ NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], VoiceViewers.VoiceViewerInfo]; l: LIST OF VoiceViewers.TextMarkEntry; FOR l _ viewerInfo.textMarkList, l.rest WHILE l # NIL DO TiogaOps.CallWithLocks[AddArtwork] ENDLOOP }; ExtractTextMarks: PUBLIC PROC [viewerInfo: VoiceViewers.VoiceViewerInfo, soundInterval: VoiceViewers.SoundInterval] = { <> currLastInList: LIST OF VoiceViewers.TextMarkEntry; soughtStart: INT _ soundInterval.ropeInterval.start/VoiceViewers.soundRopeCharLength; soughtEnd: INT _ (soundInterval.ropeInterval.start + soundInterval.ropeInterval.length)/VoiceViewers.soundRopeCharLength; soundInterval.textMarkList _ NIL; -- should be already so, but . . . FOR l: LIST OF VoiceViewers.TextMarkEntry _ viewerInfo.textMarkList, l.rest WHILE l # NIL AND l.first.position < soughtEnd DO IF l.first.position >= soughtStart THEN { newEntry: VoiceViewers.TextMarkEntry _ NEW [VoiceViewers.TextMarkRec _ l.first^]; newEntry.position _ newEntry.position - soughtStart; IF soundInterval.textMarkList = NIL THEN { soundInterval.textMarkList _ CONS [newEntry, NIL]; currLastInList _ soundInterval.textMarkList; } ELSE { currLastInList.rest _ CONS [newEntry, NIL]; currLastInList _ currLastInList.rest } } ENDLOOP }; EditTextMarks: PUBLIC PROC [viewerInfo: VoiceViewers.VoiceViewerInfo, unchangedHead, deleteChars, insertChars: INT, soundInterval: VoiceViewers.SoundInterval] = { <> <> atHead: BOOLEAN _ TRUE; l: LIST OF VoiceViewers.TextMarkEntry; <> WHILE atHead AND viewerInfo.textMarkList # NIL DO SELECT viewerInfo.textMarkList.first.position FROM >= unchangedHead+deleteChars => {viewerInfo.textMarkList.first.position _ viewerInfo.textMarkList.first.position + insertChars - deleteChars; atHead _ FALSE}; >= unchangedHead => viewerInfo.textMarkList _ viewerInfo.textMarkList.rest; ENDCASE => atHead _ FALSE; ENDLOOP; IF viewerInfo.textMarkList # NIL THEN { l _ viewerInfo.textMarkList; WHILE l # NIL AND l.rest # NIL DO --the dual WHILE condition is because we can delete the next entry and it could be the last SELECT l.rest.first.position FROM >= unchangedHead+deleteChars => {l.rest.first.position _ l.rest.first.position + insertChars - deleteChars; l _ l.rest}; >= unchangedHead => l.rest _ l.rest.rest; ENDCASE => l _ l.rest ENDLOOP }; IF soundInterval # NIL AND soundInterval.textMarkList # NIL THEN -- now put the textual marks from the soundInterval into the list at the appropriate point { beforeInsert, afterInsert: LIST OF VoiceViewers.TextMarkEntry _ NIL; newSection, endNewSection: LIST OF VoiceViewers.TextMarkEntry; IF viewerInfo.textMarkList # NIL THEN { IF viewerInfo.textMarkList.first.position >= unchangedHead THEN afterInsert _ viewerInfo.textMarkList ELSE { l _ viewerInfo.textMarkList; WHILE l.rest # NIL AND l.rest.first.position < unchangedHead DO l _ l.rest ENDLOOP; beforeInsert _ l; afterInsert _ l.rest } }; { insertMarks: LIST OF VoiceViewers.TextMarkEntry _ soundInterval.textMarkList.rest; newSection _ CONS [NEW [VoiceViewers.TextMarkRec _ soundInterval.textMarkList.first^], NIL]; newSection.first.position _ newSection.first.position + unchangedHead; endNewSection _ newSection; WHILE insertMarks # NIL DO endNewSection.rest _ CONS [NEW [VoiceViewers.TextMarkRec _ soundInterval.textMarkList.first^], NIL]; endNewSection _ endNewSection.rest; insertMarks _ insertMarks.rest; endNewSection.first.position _ endNewSection.first.position + unchangedHead ENDLOOP }; endNewSection.rest _ afterInsert; LimitText[endNewSection.first, IF endNewSection.rest = NIL THEN LAST[INT] ELSE endNewSection.rest.first.position - endNewSection.first.position]; IF beforeInsert = NIL THEN viewerInfo.textMarkList _ newSection ELSE { beforeInsert.rest _ newSection; LimitText[beforeInsert.first, newSection.first.position - beforeInsert.first.position] } } ELSE -- no new entries to be inserted, but we must LimitText on the entry previous to the cut {IF viewerInfo.textMarkList # NIL AND viewerInfo.textMarkList.first.position < unchangedHead THEN { beforeCut: LIST OF VoiceViewers.TextMarkEntry _ viewerInfo.textMarkList; WHILE beforeCut.rest # NIL AND beforeCut.rest.first.position < unchangedHead DO beforeCut _ beforeCut.rest ENDLOOP; LimitText[beforeCut.first, IF beforeCut.rest = NIL THEN LAST[INT] ELSE beforeCut.rest.first.position - beforeCut.first.position] } } }; RopeFromTextList: PUBLIC PROC [l: LIST OF VoiceViewers.TextMarkEntry] RETURNS [r: Rope.ROPE _ NIL] = { <> WHILE l # NIL DO textEntry: Rope.ROPE _ l.first.text; positionInSamples: INT _ l.first.position*VoiceViewers.soundRopeCharLength; r _ r.Cat[Convert.RopeFromInt[positionInSamples], ":", Convert.RopeFromInt[textEntry.Length], ":"]; r _ r.Concat[textEntry]; l _ l.rest ENDLOOP }; TextListFromRope: PUBLIC PROC [r: Rope.ROPE] RETURNS [l: LIST OF VoiceViewers.TextMarkEntry _ NIL] = { <> endOfList: LIST OF VoiceViewers.TextMarkEntry; WHILE r.Length > 0 DO nextColon: INT _ r.Find[":"]; newEntry: VoiceViewers.TextMarkEntry _ NEW [VoiceViewers.TextMarkRec _ [Convert.IntFromRope[r.Substr[0, nextColon]]/VoiceViewers.soundRopeCharLength, NIL, 0, 0]]; textLength: INT; r _ r.Substr[nextColon+1]; nextColon _ r.Find[":"]; textLength _ Convert.IntFromRope[r.Substr[0, nextColon]]; r _ r.Substr[nextColon+1]; newEntry.text _ r.Substr[0, textLength]; r _ r.Substr[textLength]; IF l = NIL THEN { l _ CONS [newEntry, NIL]; endOfList _ l } ELSE { endOfList.rest _ CONS [newEntry, NIL]; endOfList _ endOfList.rest } ENDLOOP; FOR list: LIST OF VoiceViewers.TextMarkEntry _ l, list.rest WHILE list # NIL DO LimitText[list.first, IF list.rest = NIL THEN LAST[INT] ELSE list.rest.first.position - list.first.position] ENDLOOP }; voiceMarkerFont: PUBLIC ImagerFont.Font _ ImagerFont.Find["xerox/tiogafonts/tioga10"]; END.