DIRECTORY Atom USING [ PropList ], BasicTime USING [ Now, ToPupTime ], GList USING [ Append, Member, Remove ], IO, Jukebox USING [ bytesPerMS ], MessageWindow USING [ Append, Blink ], Process USING [ MsecToTicks, Pause ], Rope, TEditSelectionOpsEtc USING [ ShowGivenPositionRange ], TextEdit USING [ GetCharProp, PutCharProp, PutProp, Size ], TextNode USING [ LocNumber, Ref, StepForward ], TiogaAccess USING [ EndOf, FromViewer, Get, GetIndex, Reader, TiogaChar ], TiogaButtons USING [ TextNodeRef, TiogaOpsRef ], TiogaOps USING [ CallWithLocks, GetSelection, Location, LocOffset, LocRelative, NoSelection, Ref, SelectionGrain, SetSelection, ViewerDoc ], ViewerClasses USING [ Viewer ], ViewerOps USING [ FetchProp ], ViewRec USING [ ViewRef ], VoiceInText USING [ PlaySelection, thrushHandle ], VoiceRope USING [ Length, Stop, VoiceRopeInterval ] ; NarratedDocsImpl: CEDAR PROGRAM IMPORTS BasicTime, GList, IO, MessageWindow, Process, Rope, TEditSelectionOpsEtc, TextEdit, TextNode, TiogaAccess, TiogaButtons, TiogaOps, ViewerOps, ViewRec, VoiceInText, VoiceRope = { ScriptList: TYPE ~ LIST OF Script; Script: TYPE ~ REF ScriptBody; ScriptBody: TYPE ~ RECORD [ sid: ScriptID _ NIL, sName: ScriptName _ "", sDesc: ScriptDesc _ NIL, sCreator: ScriptCreator _ "", numEntries: INT _ 0, firstEntry, lastEntry: ScriptEntry _ NIL, file: FileID ]; ScriptID: TYPE = INT; ScriptName: TYPE ~ Rope.ROPE; ScriptDesc: TYPE ~ Rope.ROPE; ScriptCreator: TYPE ~ Rope.ROPE; ScriptEntry: TYPE ~ REF ScriptEntryBody; ScriptEntryBody: TYPE ~ RECORD [ entryID: EntryID _ NIL, file: FileID _ NIL, next, prev: ScriptEntry _ NIL, pauseBefore: INT _ 0, -- in msec (is sec more reasonable?) pauseAfter: INT _ 0, -- ditto action: Rope.ROPE _ "", charIndex: INT _ -1, -- trust this only if document is not edited seqNum: INT _ -1 -- trust this only if document is not edited ]; ScriptExt: TYPE ~ REF ScriptExtBody; EntryID: TYPE ~ REF EntryIDBody; EntryIDBody: TYPE ~ RECORD [ sid: ScriptName _ "", eid: INT _ 0 ]; EntryIDList: TYPE ~ LIST OF EntryID; FileID: TYPE = RECORD [ name: Rope.ROPE _ "", createTime: BasicTime.GMT _ BasicTime.nullGMT ]; ScriptTool: TYPE = REF ScriptToolBody; ScriptToolBody: TYPE = RECORD [ scriptName: ScriptName _ "", playScript: PROC, stop: PROC, seqNum: INT _ 0, playEntry: PROC, findEntry: PROC, nextEntry: PROC, prevEntry: PROC, createScript: PROC, destroyScript: PROC, action: Rope.ROPE _ "", time: INT _ 0, addEntry: PROC, deleteEntry: PROC, listScripts: PROC, listEntries: PROC, extractScript: PROC, applyScript: PROC, msg: Rope.ROPE ]; scriptList: ScriptList _ NIL; scriptStyleParam: Rope.ROPE _ "0 outlineBoxBearoff 1 outlineBoxThickness"; SuitableViewer: PROC [selectedViewer: ViewerClasses.Viewer] RETURNS [BOOLEAN] = { RETURN [selectedViewer.class.flavor = $Text AND ViewerOps.FetchProp[selectedViewer, $voiceViewerInfo] = NIL] }; InsertSelectedAnnotationsAfter: PUBLIC PROC [script: Script, seqNum: INT] RETURNS [newSeqNum: INT] ~ { selectedViewer: ViewerClasses.Viewer; suitableViewer: BOOLEAN; voiceThere: BOOLEAN; AddSelectedAnnotations: PROC [root: TiogaOps.Ref] = { charsInSelection: INT _ 0; soundsInSelection: INT _ 0; startChar, endChar, targetChar: TiogaOps.Location; node: TextNode.Ref; caretBefore: BOOLEAN; pendingDelete: BOOLEAN; level: TiogaOps.SelectionGrain; [viewer: selectedViewer, start: startChar, end: endChar, caretBefore: caretBefore, pendingDelete: pendingDelete, level: level] _ TiogaOps.GetSelection[]; suitableViewer _ SuitableViewer[selectedViewer]; IF suitableViewer AND pendingDelete THEN { TiogaOps.SetSelection[viewer: selectedViewer, start: startChar, end: endChar, level: level, caretBefore: caretBefore, pendingDelete: FALSE, which: primary] -- simply makes not pending delete }; IF suitableViewer AND NOT level = point THEN { targetChar _ startChar; DO charsInSelection _ charsInSelection + 1; node _ TiogaButtons.TextNodeRef[targetChar.node]; -- just a type converter voiceThere _ TextEdit.GetCharProp[node, targetChar.where, $voice] # NIL; IF voiceThere THEN { newEntryID: EntryID _ MakeNewEntryID[script]; newEntryIDList: EntryIDList_ LIST[newEntryID]; entryList: EntryIDList _ NARROW[TextEdit.GetCharProp[node, targetChar.where, $script]]; entryList _ NARROW[GList.Append[entryList, newEntryIDList]]; TextEdit.PutCharProp[node, targetChar.where, $script, entryList]; TextEdit.PutCharProp[node, targetChar.where, $Postfix, scriptStyleParam]; TextEdit.PutCharProp[node, targetChar.where, $Artwork, NIL]; AddEntryViaSeqNum[newEntryID, script, seqNum]; seqNum _ seqNum + 1; soundsInSelection _ soundsInSelection + 1; }; IF targetChar = endChar THEN EXIT; targetChar _ TiogaOps.LocRelative[location: targetChar, count: 1, break: 1, skipCommentNodes: FALSE]; ENDLOOP; }; IF soundsInSelection = 0 THEN MessageWindow.Append["No sounds in selection", TRUE] ELSE ScriptToolMsg[IO.PutFR["%d entries added to script\n", IO.int[soundsInSelection]]]; }; TiogaOps.CallWithLocks[AddSelectedAnnotations ! TiogaOps.NoSelection => {suitableViewer _ FALSE; CONTINUE}]; IF NOT suitableViewer THEN { MessageWindow.Append["Make a selection in a Tioga viewer first", TRUE]; MessageWindow.Blink[]; newSeqNum _ -1; -- This may be a dumb thing to do } ELSE newSeqNum _ seqNum; }; DeleteSelectedAnnotations: PUBLIC PROC [script: Script, seqNum: INT] RETURNS [newSeqNum: INT] ~ { selectedViewer: ViewerClasses.Viewer; suitableViewer: BOOLEAN; voiceThere: BOOLEAN; RemoveSelectedAnnotations: PROC [root: TiogaOps.Ref] = { charsInSelection: INT _ 0; soundsInSelection: INT _ 0; startChar, endChar, targetChar: TiogaOps.Location; node: TextNode.Ref; caretBefore: BOOLEAN; pendingDelete: BOOLEAN; level: TiogaOps.SelectionGrain; [viewer: selectedViewer, start: startChar, end: endChar, caretBefore: caretBefore, pendingDelete: pendingDelete, level: level] _ TiogaOps.GetSelection[]; suitableViewer _ SuitableViewer[selectedViewer]; IF suitableViewer AND pendingDelete THEN { TiogaOps.SetSelection[viewer: selectedViewer, start: startChar, end: endChar, level: level, caretBefore: caretBefore, pendingDelete: FALSE, which: primary] -- simply makes not pending delete }; IF suitableViewer AND NOT level = point THEN { targetChar _ startChar; DO charsInSelection _ charsInSelection + 1; node _ TiogaButtons.TextNodeRef[targetChar.node]; -- just a type converter voiceThere _ TextEdit.GetCharProp[node, targetChar.where, $voice] # NIL; IF voiceThere THEN { oldEntryID: EntryID _ GetEntryIDFromSeqNum[script, seqNum]; entryList: EntryIDList _ NARROW[TextEdit.GetCharProp[node, targetChar.where, $script]]; IF GList.Member[oldEntryID, entryList] THEN entryList _ NARROW[GList.Remove[oldEntryID, entryList]] ELSE EXIT; -- No more in sequence. Note that seqNum stays constant throughout this operation TextEdit.PutCharProp[node, targetChar.where, $script, entryList]; IF entryList = NIL THEN { TextEdit.PutCharProp[node, targetChar.where, $Postfix, NIL]; TextEdit.PutCharProp[node, targetChar.where, $Artwork, NARROW["TalksBubble", Rope.ROPE]]; -- the NARROW prevents a REF TEXT being created }; DeleteEntryViaSeqNum[script, seqNum]; soundsInSelection _ soundsInSelection + 1; }; IF seqNum > script.numEntries THEN seqNum _ script.numEntries; IF targetChar = endChar THEN EXIT; targetChar _ TiogaOps.LocRelative[location: targetChar, count: 1, break: 1, skipCommentNodes: FALSE]; ENDLOOP; }; IF soundsInSelection = 0 THEN MessageWindow.Append["No sounds in selection", TRUE] ELSE ScriptToolMsg[IO.PutFR["%d entries removed from script\n", IO.int[soundsInSelection]]]; }; TiogaOps.CallWithLocks[RemoveSelectedAnnotations ! TiogaOps.NoSelection => {suitableViewer _ FALSE; CONTINUE}]; IF NOT suitableViewer THEN { MessageWindow.Append["Make a selection in a Tioga viewer first", TRUE]; MessageWindow.Blink[]; newSeqNum _ -1; -- This may be a dumb thing to do } ELSE newSeqNum _ seqNum; }; AddEntryViaSeqNum: PROC [entryID: EntryID, script: Script, seqNum: INT] ~ { newEntry, curPtr: ScriptEntry; IF seqNum < 0 THEN ERROR; --  newEntry _ NEW[ScriptEntryBody _ [entryID: entryID, next: NIL, prev: NIL] ]; IF seqNum = 0 THEN { newEntry.next _ script.firstEntry; newEntry.prev _ NIL; script.firstEntry _ newEntry; } ELSE { curPtr _ FindPositionOfSeqNum[script, seqNum]; newEntry.next _ curPtr.next; curPtr.next _ newEntry; newEntry.prev _ curPtr; }; IF newEntry.next # NIL THEN newEntry.next.prev _ newEntry ELSE { script.lastEntry _ newEntry; }; script.numEntries _ script.numEntries + 1; }; DeleteEntryViaSeqNum: PROC [script: Script, seqNum: INT] ~ { curPtr: ScriptEntry; IF seqNum <= 0 THEN ERROR; -- other checks could be done too curPtr _ FindPositionOfSeqNum[script, seqNum]; IF curPtr.prev = NIL THEN { script.firstEntry _ curPtr.next; } ELSE curPtr.prev.next _ curPtr.next; IF curPtr.next # NIL THEN curPtr.next.prev _ curPtr.prev ELSE { script.lastEntry _ curPtr.prev; }; script.numEntries _ script.numEntries - 1; }; FindPositionOfSeqNum: PROC [script: Script, seqNum: INT] RETURNS [s: ScriptEntry] ~ { IF seqNum < script.numEntries/2 THEN { -- go forward from start s _ script.firstEntry; FOR n: INT IN (1..seqNum] DO s _ s.next; ENDLOOP; } ELSE { -- go backward from end s _ script.lastEntry; FOR n: INT DECREASING IN [seqNum..script.numEntries) DO s _ s.prev; ENDLOOP; }; }; GetEntryIDFromSeqNum: PROC [script: Script, seqNum: INT] RETURNS [entryID: EntryID] ~ { curPtr: ScriptEntry _ FindPositionOfSeqNum[script, seqNum]; RETURN[curPtr.entryID] }; GetSeqNumFromEntryID: PROC [script: Script, entryID: EntryID] RETURNS [seqNum: INT] ~ { seqNum _ 0; FOR se: ScriptEntry _ script.firstEntry, se.next WHILE se # NIL DO seqNum _ seqNum + 1; IF EqualEntryID[se.entryID, entryID] THEN EXIT; ENDLOOP; }; GetScriptEntryFromEntryID: PROC [script: Script, entryID: EntryID] RETURNS [se: ScriptEntry] ~ { FOR se _ script.firstEntry, se.next WHILE se # NIL DO IF EqualEntryID[se.entryID, entryID] THEN EXIT; ENDLOOP; }; EqualEntryID: PROC [e1, e2: EntryID] RETURNS [BOOL] ~ { RETURN[e1.sid = e2.sid AND e1.eid = e2.eid]; }; MakeNewEntryID: PROC [script: Script] RETURNS [entryID: EntryID] ~ { entryID _ NEW[EntryIDBody _ [sid: script.scriptName, eid: script.numEntries + 1]]; }; CreateNewScript: PROC [scriptName: Rope.ROPE] RETURNS [new: BOOL _ TRUE] ~ { IF LookupScript[scriptName]#NIL THEN RETURN[FALSE] ELSE script _ NEW[ScriptBody _ [scriptName: scriptName] ]; scriptList _ CONS[script, scriptList]; }; GetCharIndexFromEntryID: PROC [viewer: ViewerClasses.Viewer, entryID: EntryID] RETURNS [charIndex: INT _ -1] ~ { wholeFile: TiogaAccess.Reader _ TiogaAccess.FromViewer[viewer]; c: TiogaAccess.TiogaChar; props: Atom.PropList; WHILE charIndex < 0 DO IF TiogaAccess.EndOf[wholeFile] THEN EXIT; c _ TiogaAccess.Get[wholeFile]; FOR props _ c.propList, props.rest WHILE props # NIL DO IF props.first.key = $script AND GList.Member[entryID, props.first.val] THEN charIndex _ TiogaAccess.GetIndex[wholeFile]; ENDLOOP; ENDLOOP; charIndex _ charIndex -1; }; CheckPropProc: TYPE ~ PROC [r: REF, rlist: REF] RETURNS [BOOL]; CProp: CheckPropProc ~ { e: EntryID _ NARROW[r]; FOR elist: EntryIDList _ NARROW[rlist], elist.rest WHILE elist # NIL DO IF EqualEntryID[elist.first, e] THEN RETURN [TRUE]; ENDLOOP; RETURN [FALSE] }; FindCharProp: PROC [viewer: ViewerClasses.Viewer, propName: ATOM, propVal: REF, checkPropProc: CheckPropProc] RETURNS [foundLoc: TiogaOps.Location] ~ { rootNode: TextNode.Ref _ TiogaButtons.TextNodeRef[TiogaOps.ViewerDoc[viewer]]; -- TextNodeRef is just a type convertor - replace with a more cautious implementation?? foundLoc _ [NIL, -1]; FOR n: TextNode.Ref _ rootNode, TextNode.StepForward[n] UNTIL n = NIL DO SearchCharProps: PROC RETURNS [found: BOOLEAN _ FALSE] ~ { FOR i: INT IN [startChar..endChar) UNTIL found DO propList: REF _ TextEdit.GetCharProp[n, i, propName]; IF checkPropProc[r: propVal, rlist: propList] THEN {foundLoc.where _ i; found _ TRUE;} ENDLOOP; }; startChar: INT _ 0; endChar: INT _ TextEdit.Size[n]; IF n.hascharprops AND SearchCharProps[].found THEN { foundLoc.node _ TiogaButtons.TiogaOpsRef[n]; EXIT; }; ENDLOOP; }; GetSeqNumsFromSelection: PROC [script: Script, seqNum: INT] RETURNS [s: ScriptEntry] ~ { seqNumList: LIST OF INT; node: TextNode.Ref; targetChar: TiogaOps.Location; FOR entryList: EntryIDList _ NARROW[TextEdit.GetCharProp[node, targetChar.where, $script]], entryList.rest DO seqNumList _ CONS[GetSeqNumFromEntryID[script, entryList.first], seqNumList]; ENDLOOP; }; StoreScriptsRepAtRoot: PROC [viewer: ViewerClasses.Viewer] ~ { rootNode: TextNode.Ref _ TiogaButtons.TextNodeRef[TiogaOps.ViewerDoc[viewer]]; -- TextNodeRef is just a type convertor - replace with a more cautious implementation?? [several references throughout TiogaVoice] FillInCharIndices[rootNode]; stream: IO.STREAM _ IO.ROS[]; FOR s: Script _ scriptList.first, s.rest WHILE s # NIL DO WriteOneScript[s, stream]; ENDLOOP; TextEdit.PutProp[rootNode, $scriptList, IO.RopeFromROS[stream]]; }; TraverseCharProps: PROC [viewer: ViewerClasses.Viewer, propName: ATOM, propVal: REF, checkPropProc: CheckPropProc] RETURNS [foundLoc: TiogaOps.Location] ~ { rootNode: TextNode.Ref _ TiogaButtons.TextNodeRef[TiogaOps.ViewerDoc[viewer]]; -- TextNodeRef is just a type convertor - replace with a more cautious implementation?? foundLoc _ [NIL, -1]; FOR n: TextNode.Ref _ rootNode, TextNode.StepForward[n] UNTIL n = NIL DO startChar: INT _ 0; endChar: INT _ TextEdit.Size[n]; IF n.hascharprops THEN { FOR i: INT IN [startChar..endChar) DO propList: REF _ TextEdit.GetCharProp[n, i, $script]; IF GetProp[r: propVal, rlist: propList] THEN {foundLoc.where _ i; found _ TRUE;} ENDLOOP; }; ENDLOOP; }; GetProp: CheckPropProc ~ { FOR elist: EntryIDList _ NARROW[rlist], elist.rest WHILE elist # NIL DO e: EntryID _ elist.first; s: Script _ LookupScript[e.sid]; se: ScriptEntry _ GetScriptEntryFromEntryID[s, e.entryID]; se.charIndex _ TiogaOps.LocOffset[[FirstChild[rootNode],0], [n, i]]; ENDLOOP; RETURN [FALSE] }; WriteOneScript: PROC [s: Script, stream: IO.STREAM] ~ { stream.Put["(", Convert.RopeFromRope[s.scriptName], " ", Convert.RopeFromRope[s.scriptDesc], " "]; stream.Put[Convert.RopeFromInt[s.numEntries, " "]; FOR se: ScriptEntry _ s.firstEntry, se.next DO WriteOneEntry[se, stream]; ENDLOOP; stream.Put[")"]; }; WriteOneEntry: PROC [se: ScriptEntry, stream: IO.STREAM] ~ { stream.Put["(", Convert.RopeFromInt[se.entryID.eid], " ", Convert.RopeFromInt[se.charIndex], ")"]; }; LookupScript: PROC [scriptName: ScriptName] RETURNS [script: Script _ NIL] ~ { FOR s: ScriptList _ scriptList, scriptList.rest WHILE s # NIL DO IF Rope.Equal[s.first.scriptName, scriptName] THEN RETURN [s.first] ENDLOOP; }; PlayEntry: PROC [script: Script, seqNum: INT] ~ { viewer: ViewerClasses.Viewer; start: TiogaOps.Location; [viewer, start] _ FindEntry[script, seqNum]; -- probably should lock things here VoiceInText.PlaySelection[]; WaitForPlayToFinish[start]; ShowPosition[viewer, start, FALSE]; }; WaitForPlayToFinish: PROC [charLoc: TiogaOps.Location] ~ { node: TextNode.Ref _ TiogaButtons.TextNodeRef[charLoc.node]; -- just a type converter voiceID: Rope.ROPE _ NARROW[TextEdit.GetCharProp[node, charLoc.where, $voice]]; ropeLength: INT _ VoiceRope.Length[handle: VoiceInText.thrushHandle, vr: NEW [VoiceRope.VoiceRopeInterval _ [voiceID, 0, 0]]]; ropeLengthInMsec: INT _ ropeLength/Jukebox.bytesPerMS; Process.Pause[Process.MsecToTicks[ropeLengthInMsec]]; }; FindEntry: PROC [script: Script, seqNum: INT] RETURNS [selectedViewer: ViewerClasses.Viewer, start: TiogaOps.Location] ~ { scriptEntry: ScriptEntry _ FindPositionOfSeqNum[script, seqNum]; [viewer: selectedViewer] _ TiogaOps.GetSelection[]; start _ FindCharProp[selectedViewer, $script, scriptEntry.entryID, CProp]; IF start.node # NIL THEN { ShowPosition[selectedViewer, start, TRUE]; }; }; ShowPosition: PROC [viewer: ViewerClasses.Viewer, charLoc: TiogaOps.Location, pendingDelete: BOOL]~ { charIndex: INT _ TextNode.LocNumber[at: [TiogaButtons.TextNodeRef[charLoc.node], charLoc.where], skipCommentNodes: FALSE]; TEditSelectionOpsEtc.ShowGivenPositionRange[viewer: viewer, selectionId: primary, posI: charIndex, posF: charIndex, skipCommentNodes: FALSE, pendingDelete: pendingDelete]; }; st: ScriptTool; InitScriptTool: PROC ~ { st _ NEW[ScriptToolBody _ [ scriptName: "", playScript: PlayScriptProc, stop: StopProc, seqNum: INT _ 0, playEntry: PlayEntryProc, findEntry: FindEntryProc, nextEntry: NextEntryProc, prevEntry: PrevEntryProc, createScript: CreateScriptProc, destroyScript: DestroyScriptProc, action: Rope.ROPE _ "", time: INT _ 0, addEntry: AddEntriesProc, deleteEntry: DeleteEntriesProc, listScripts: ListScriptsProc, listEntries: ListEntriesProc, extractScript: ExtractScriptProc, applyScript: ApplyScriptProc, msg: "" ] ]; [] _ ViewRec.ViewRef[agg: st, viewerInit: [name: "Script Tool"]]; }; AddEntriesProc: PROC ~ { ScriptToolMsg[""]; st.seqNum _ InsertSelectedAnnotationsAfter[LookupScript[st.scriptName], st.seqNum] }; DeleteEntriesProc: PROC ~ { ScriptToolMsg[""]; st.seqNum _ DeleteSelectedAnnotations[LookupScript[st.scriptName], st.seqNum] }; FindEntryProc: PROC ~ { ScriptToolMsg[""]; [] _ FindEntry[LookupScript[st.scriptName], st.seqNum]; }; NextEntryProc: PROC ~ { ScriptToolMsg[""]; IF st.seqNum < LookupScript[st.scriptName].numEntries THEN { st.seqNum _ st.seqNum + 1; [] _ FindEntry[LookupScript[st.scriptName], st.seqNum]; } ELSE ScriptToolMsg["End of script"]; }; PrevEntryProc: PROC ~ { ScriptToolMsg[""]; IF st.seqNum > 1 THEN { st.seqNum _ st.seqNum - 1; [] _ FindEntry[LookupScript[st.scriptName], st.seqNum]; } ELSE ScriptToolMsg["Beginning of script"]; }; PlayEntryProc: PROC ~ { ScriptToolMsg[""]; [] _ PlayEntry[LookupScript[st.scriptName], st.seqNum]; }; PlayScriptProc: PROC ~ { script: Script _ LookupScript[st.scriptName]; ScriptToolMsg[""]; FOR n: INT _ 1, n+1 WHILE n <= script.numEntries DO st.seqNum _ n; -- user feedback PlayEntry[script, n]; ENDLOOP; }; StopProc: PROC ~ { ScriptToolMsg[""]; VoiceRope.Stop[VoiceInText.thrushHandle]; }; CreateScriptProc: PROC ~ { IF CreateNewScript[st.scriptName].new = TRUE THEN { ScriptToolMsg["new script created"]; st.seqNum _ 0; }; ELSE ScriptToolMsg["duplicate script name -- select another"]; }; DestroyScriptProc: PROC ~ { ScriptToolMsg[""]; [] _ CreateNewScript[st.scriptName]; }; ListScriptsProc: PROC ~ { st.msg _ ""; FOR s: ScriptList _ scriptList, s.rest WHILE s # NIL DO st.msg _ Rope.Cat[st.msg, IO.PutFR["%d ", IO.card[s.first.scriptName]]]; ENDLOOP; }; ScriptToolMsg: PROC [msg: Rope.ROPE] ~ { st.msg _ msg; }; InitScriptTool[]; }. IF seqNum < curNum THEN { IF seqNum < curNum/2 THEN { curPtr _ script.firstEntry; FOR curNum: INT IN [2..seqNum] DO curPtr _ curPtr.next; ENDLOOP; } ELSE { FOR curNum: INT _ curNum-1, curNum-1 WHILE curNumseqNum DO curPtr _ curPtr.prev; ENDLOOP; } }; &XNarratedDocsImpl.mesa Copyright c 1986 by Xerox Corporation. All rights reserved. Polle Zellweger (PTZ) January 6, 1987 7:20:47 pm PST EXPORTS NarratedDocs Types do we record a filename and/or viewer here, or do we require the user to click in the scripted viewer to give us a pointer? may be good to have entry descs too, so user can figure out what is going on. But would these be voice rope descs & uniquefied for multiple copies in a single script, or would they be an entry desc? Note that the same voice rope can be attached to different places in the same document and appear in a script at each location, which is of course different than the same annotation appearing multiple times in a script. So an entry is described by (script, seqNum, docLoc, voiceRope). voiceRope can be determined from docLoc, but is not VISIBLE from it. Navigating in a Complex Invisible Space. these are an internal representation for the script tool these are an external representation that will be stored (as $scriptList property) on root these are stored (in a list, as $script property) on characters in the document Declarations Script Creation and Editing GList operations assume type = LIST OF REF X for some X. Is seqNum> script.numEntries an ERROR or a way to specify the end of the list? Copied from VoiceInTextImpl Add all voice annotations in selection to script in order following seqNum. Can't use TiogaAccess because need to change char props. Add annotation to script: char props Give user visual feedback that this voice entry is in some script. WHICH script is a bit more complicated than this. There are also some real difficulties with crossing document boundaries -- when to resolve the references? Could be editing 2 anonymous viewers and building up a script across both of them. So even at the SAVE of one, you don't know what the filename of the other is. And if you did, the user could change it thereafter. Damn! Obviously need something like Interest relations to manage this. Update seqNum field in tool if middle moused? test for failure conditions and report them to the user after releasing the viewer lock Delete the sequence of voice annotations in selection from script starting at seqNum Remove annotation from script: char props Give user visual feedback that this voice entry is no longer in any script. WHICH script is a bit more complicated than this. removed last entry or seqNum out of range! Update seqNum field in tool if middle moused? test for failure conditions and report them to the user after releasing the viewer lock Add entry to script AFTER seqNum. IF seqNum # script.numEntries THEN ERROR; --  Delete entry seqNum from script. must ensure that there aren't 2 scripts with the same name for the same doc!! Complicated in the presence of multiple docs, private/public scripts, etc. Good enough for now, but should include something about filename/creator/machinename later. GList.Member just checks ref equality, not the internal representation. This is only ok until scripts are stored and recreated. Searches document forward from the beginning for occurrence of character property propName with value propVal. ..... TYPES ARE ALL WRONG HERE!!! ..... if seqNumList has one elt, fill in seqNum field in tool, else clear seqNum field and display seqNumList in msg area Document has <$script, entryUIDList> properties scattered throughout it. Script Tool has linked list of IF document has been edited THEN CheckScriptsForDuplicates[]; Searches document forward from the beginning for occurrence of character property propName with value propVal. PROC [r: REF, rlist: REF] RETURNS [BOOL]; Script Playback Should do this in a separate process, so that can accept other user actions (esp stop) during playback. Should really use progress reports from VoiceRope rather than independent timing. may be too long? ignores start & length values specified in voice rope Could of course click PlayEntry, then click Play immediately => the timing would be off. For now, don't do that. this may need to be adjusted by 1?? Script Tool Call this only once per user function, otherwise user won't be able to read it before it disappears. Body of NarratedDocsImpl TiogaOps.RegisterCommand[name: $RedSave, proc: StoreScriptsRepAtRoot]; TiogaOps.RegisterCommand[name: $YellowSave, proc: StoreScriptsRepAtRoot]; TiogaOps.RegisterCommand[name: $BlueSave, proc: StoreScriptsRepAtRoot]; TiogaOps.RegisterCommand[name: $RedStore, proc: StoreScriptsRepAtRoot]; TiogaOps.RegisterCommand[name: $YellowStore, proc: StoreScriptsRepAtRoot]; TiogaOps.RegisterCommand[name: $BlueStore, proc: StoreScriptsRepAtRoot]; Polle Zellweger (PTZ) August 21, 1986 5:34:34 pm PDT changes to: DIRECTORY, NarratedDocsImpl Polle Zellweger (PTZ) August 21, 1986 11:27:32 pm PDT changes to: AddSelectedAnnotationsToScript, AddEntryToScriptRep, scriptStyleParam, DeleteEntryViaSeqNum, FindPositionOfSeqNum, SuitableViewer (local of InsertSelectedAnnotationsAfter), AddSelectedAnnotations (local of InsertSelectedAnnotationsAfter), DeleteSelectedAnnotations, RemoveSelectedAnnotations (local of DeleteSelectedAnnotations), GetSeqNumsFromSelection, GetEntryIDFromSeqNum -- curPtr _ FindPositionOfSeqNum[script, seqNum]; go forward from start go backward from curPtr go forward from curPtr go backward from end Polle Zellweger (PTZ) August 22, 1986 7:17:07 pm PDT changes to: AddEntryViaSeqNum, DeleteEntryViaSeqNum, FindPositionOfSeqNum, ELSE, DIRECTORY, RemoveSelectedAnnotations (local of DeleteSelectedAnnotations), GetEntryIDFromSeqNum, GetCharFromEntryID, AddSelectedAnnotations (local of InsertSelectedAnnotationsAfter) Polle Zellweger (PTZ) August 23, 1986 6:20:37 pm PDT changes to: DIRECTORY, NarratedDocsImpl, NconcIntervals, scriptStyleParam, SuitableViewer, InsertSelectedAnnotationsAfter, AddSelectedAnnotations (local of InsertSelectedAnnotationsAfter), DeleteSelectedAnnotations, RemoveSelectedAnnotations (local of DeleteSelectedAnnotations), AddEntryViaSeqNum, DeleteEntryViaSeqNum, FindPositionOfSeqNum, GetEntryIDFromSeqNum, MakeNewEntryID, CreateNewScript, GetCharIndexFromEntryID, GetSeqNumsFromSelection, IF, ELSE, StoreScriptRepAtRoot, ScriptTool, ScriptToolBody, }, st, InitScriptTool, AddEntries, LookupScript, DeleteEntries, FindEntry, PlayEntryProc, Play, Play Polle Zellweger (PTZ) August 24, 1986 8:37:19 pm PDT changes to: InitScriptTool, AddEntriesProc, DeleteEntriesProc, FindEntryProc, PlayEntry, PlayEntryProc, PlayProc, StopProc, NewScriptProc, ListScriptsProc, ListScriptsProc, MakeNewEntryID, GetCharIndexFromEntryID, StoreScriptsRepAtRoot, DIRECTORY, NarratedDocsImpl, ScriptList, Script, ScriptBody, ScriptEntry, ScriptEntryBody, EntryID, EntryIDBody, ScriptTool, ScriptToolBody, scriptStyleParam, SuitableViewer, InsertSelectedAnnotationsAfter, AddSelectedAnnotations (local of InsertSelectedAnnotationsAfter), DeleteSelectedAnnotations, RemoveSelectedAnnotations (local of DeleteSelectedAnnotations), AddEntryViaSeqNum, DeleteEntryViaSeqNum, FindPositionOfSeqNum, GetEntryIDFromSeqNum, CreateNewScript, GetSeqNumsFromSelection, LookupScript, InitScriptTool, IF, ELSE, EntryIDList, scriptList, FindEntry, GetSeqNumFromEntryID, LookupScript Polle Zellweger (PTZ) August 24, 1986 10:30:46 pm PDT changes to: ScriptBody, ScriptID, ScriptEntry, EntryIDBody, ScriptToolBody, CreateNewScript, LookupScript Polle Zellweger (PTZ) August 24, 1986 11:05:38 pm PDT changes to: AddSelectedAnnotations (local of InsertSelectedAnnotationsAfter), RemoveSelectedAnnotations (local of DeleteSelectedAnnotations), FindPositionOfSeqNum Polle Zellweger (PTZ) August 25, 1986 9:31:28 pm PDT changes to: DIRECTORY, NarratedDocsImpl, GetCharIndexFromEntryID, CheckPropProc, FindCharProp, PlayEntry, FindEntry Polle Zellweger (PTZ) August 25, 1986 11:56:46 pm PDT changes to: ScriptEntryBody, ScriptBody, GetSeqNumFromEntryID, EqualEntryID, MakeNewEntryID, CheckPropProc, CProp, FindCharProp, SearchCharProps (local of FindCharProp), PlayEntry, FindEntry, DIRECTORY, NarratedDocsImpl, FindEntryProc, PlayProc Polle Zellweger (PTZ) August 26, 1986 0:25:01 am PDT changes to: ListScriptsProc, AddSelectedAnnotations (local of InsertSelectedAnnotationsAfter), RemoveSelectedAnnotations (local of DeleteSelectedAnnotations) Polle Zellweger (PTZ) August 26, 1986 6:10:42 pm PDT changes to: AddEntryViaSeqNum, DeleteEntryViaSeqNum Polle Zellweger (PTZ) August 27, 1986 10:58:34 am PDT changes to: ScriptToolBody, RemoveSelectedAnnotations (local of DeleteSelectedAnnotations), InitScriptTool, NextEntryProc, PrevEntryProc, PlayEntryProc Polle Zellweger (PTZ) August 27, 1986 12:21:02 pm PDT changes to: PlayEntry, WaitForPlayToFinish, FindEntry, ShowPosition, AddEntriesProc, DeleteEntriesProc, FindEntryProc, NextEntryProc, PrevEntryProc, PlayEntryProc, PlayProc, StopProc, NewScriptProc Polle Zellweger (PTZ) October 17, 1986 2:51:42 pm PDT changes to: ScriptBody, ScriptName, ScriptEntryBody, EntryIDBody, EntryIDList, FileID, ScriptToolBody, MakeNewEntryID, CreateNewScript, LookupScript, InitScriptTool, AddEntriesProc, DeleteEntriesProc, FindEntryProc, NextEntryProc, PrevEntryProc, PlayEntryProc, PlayScriptProc, CreateScriptProc, DestroyScriptProc, ListScriptsProc Polle Zellweger (PTZ) October 24, 1986 3:05:28 pm PDT changes to: ScriptEntry, ScriptEntryBody, ScriptExt, EntryID, FindCharProp, FindCharProp, StoreScriptsRepAtRoot Polle Zellweger (PTZ) November 19, 1986 6:06:19 pm PST changes to: DIRECTORY, ScriptBody, ScriptEntryBody, GetSeqNumFromEntryID, GetScriptEntryFromEntryID, CProp, StoreScriptsRepAtRoot, TraverseCharProps, GetProp, WriteOneScript, WriteOneEntry, LookupScript Polle Zellweger (PTZ) January 6, 1987 7:20:47 pm PST changes to: ScriptBody, ScriptID, ScriptName, ScriptDesc, ScriptCreator สึ• voicelist(&PolleZ.pa#588500764&PolleZ.pa#588500831˜Jšœ™šœ ฯmœ1™K˜K˜—šœ žœžœ˜$K™Z—K˜šœ žœžœ ˜ KšœO™O—šœ žœžœ˜Kšœ˜Kšœžœ˜ K˜—K˜Kšœ žœžœžœ ˜$K˜šœžœžœ˜Kšœ žœ˜Kšœžœ˜-K˜—K˜Jšœ žœžœ˜&šœžœžœ˜Jšœ˜Jšœ žœ˜Jšœžœ˜ Jšœžœ˜Jšœ žœ˜Jšœ žœ˜Jšœ žœ˜Jšœ žœ˜Jšœžœ˜Jšœžœ˜Jšœ žœ˜Jšœžœ˜Jšœ žœ˜Jšœ žœ˜Jšœ žœ˜Jšœ žœ˜Jšœžœ˜Jšœ žœ˜Jšœ ž˜J˜J˜——™ J˜J˜J˜—™J™Kšœ8™8K™KšฯtœNข™PK˜Kšœžœ/˜JK˜šŸœžœ'žœžœ˜RJšขœข™Jšžœ&žœ9žœ˜o—J˜š Ÿœžœžœžœžœ žœ˜fK™KK™8Kšœ%˜%Jšœžœ˜Jšœ žœ˜J˜šŸœžœ˜5Jšœžœ˜Jšœžœ˜Jšœ2˜2Jšœ˜Jšœ žœ˜Jšœžœ˜Jšœ˜J˜Jšœ™˜™Jšœ0˜0J˜šžœžœžœ˜*Jšœ…žœก"˜พJ˜—J˜šžœžœžœžœ˜.Kšœ˜šž˜Kšœ(˜(Jšœ2ก˜JJšœDžœ˜Hšžœ žœ˜Jšœ-˜-Jšœžœ ˜.J™$Jšœžœ8˜WJšœ žœ*˜Kšœขœข™,—Kšžœžœžœ˜"Jšœ^žœ˜eKšžœ˜—J˜J˜—šžœžœ˜Jšœ/žœ˜4—šž˜Jšœžœ+žœ˜W—Jšขœ-ข™/J˜J˜—Jšœ]žœžœ˜oJ™Wšžœžœžœ˜JšœAžœ˜GJšœ˜Jšœกฃกฃ˜4J˜—Jšžœ˜J˜J˜—šŸœžœ,žœ˜KK™!Kšœ˜Kšžœ žœžœกฃ˜Kšœ žœ,žœžœ˜Lšžœ žœ˜Kšœ"˜"Kšœžœ˜Kšœ˜K˜—šžœ˜Kšœ.˜.K˜K˜K˜K˜—šžœžœž˜Kšœ˜—šžœ˜Kšžœžœžœกฃ™/Kšœ˜K˜—K˜*K˜K˜—šŸœžœžœ˜Kšœ.˜.šžœžœžœ˜Kšœ ˜ K˜—šž˜K˜—šžœžœž˜Kšœ˜—šžœ˜Kšœ˜K˜—K˜*K˜—K˜šŸœžœžœžœ˜Ušžœžœก˜?Kšœ˜šžœžœžœ ž˜Kšœ ˜ Kšžœ˜—K˜—šžœก˜Kšœ˜š žœžœž œžœž˜7Kšœ ˜ Kšžœ˜—K˜—K˜—K˜šŸœžœžœžœ˜WKšœ;˜;Kšžœ˜K˜—K˜šŸœžœ$žœ žœ˜WKšœ ˜ šžœ.žœžœž˜BKšœ˜Kšžœ#žœžœ˜/Kšžœ˜—Kšœ˜K˜—šŸœžœ$žœ˜`šžœ!žœžœž˜5Kšžœ#žœžœ˜/Kšžœ˜—Kšœ˜K˜—šŸ œžœžœžœ˜7Kšžœžœ˜,K˜K˜—šŸœžœžœ˜DKšœ žœE˜RK˜—K˜š Ÿœžœžœžœžœžœ˜LKš žœžœžœžœžœ˜2šž˜Kšœ žœ)˜5Kšขœ}ข™›Kšขœ[ข™]—Kšœ žœ˜&K˜K˜—šŸœžœ2žœ žœ ˜pJšœ?˜?Jšœ˜Jšœ˜J˜šžœž˜Jšžœžœžœ˜*Jšœ˜šžœ žœ žœž˜7šžœžœ(ž˜LJšขœ€ข™‚Jšœ,˜,—Jšžœ˜—Jšžœ˜—Kšœ˜K˜—K˜Kš œžœžœžœ žœžœžœ˜?K˜šŸœ˜Jšœ žœ˜š žœžœžœ žœž˜GJšžœžœžœžœ˜3Jšžœ˜—Jšžœžœ˜J˜K˜—š Ÿ œžœ*žœ žœ žœ"˜—Kšœn™nJšœOกW˜ฆKšœ žœ˜šžœ5žœžœž˜Hš Ÿœžœžœ žœžœ˜:š žœžœžœžœž˜1Kšœ žœ(˜5Kšžœ,žœžœ˜WKšžœ˜—Kšœ˜—Kšœ žœ˜Kšœ žœ˜ šžœžœžœ˜4Kšœ,˜,Kšžœ˜Kšœ˜—Kšžœ˜—K˜K˜K˜—šŸœžœžœžœ˜XKšขœข™Kšœ žœžœžœ˜K˜Kšœ˜šžœžœHž˜mšœ žœ<˜MK™—Kšžœ˜—Kšขœข™Kšœs™sK˜—K˜šŸœžœ#˜>K™Kšขžœžœข™?JšœOกƒ˜าKšœ˜Kš œžœžœžœžœ˜šžœ&žœžœž˜9Kšœ˜Kšžœ˜—Kšœ(žœ˜@K˜—K˜š Ÿœžœ*žœ žœ žœ"˜œKšœn™nJšœOกW˜ฆKšœ žœ˜šžœ5žœžœž˜HKšœ žœ˜Kšœ žœ˜ šžœžœ˜šžœžœžœž˜%Kšœ žœ'˜4Kšžœ&žœžœ˜QKšžœ˜—Kšœ˜—Kšžœ˜—K˜K˜—šŸœ˜Jš žœžœ žœžœžœ™)š žœžœžœ žœž˜GKšœ˜Kšœ ˜ Kšœ:˜:JšœD˜DJšžœ˜—Jšžœžœ˜J˜K˜—šŸœžœžœžœ˜7Kšœb˜bKšœ2˜2šžœ)ž˜.Kšœ˜Kšžœ˜—Kšœ˜K˜K˜—šŸ œžœžœžœ˜