VoiceInTextImpl.mesa:
basic code to add voice to textual tioga documents and to replay that voice
Ades, September 24, 1986 5:52:47 pm PDT
DIRECTORY
Convert USING [RopeFromInt, IntFromRope],
Menus USING [MenuProc, MenuEntry, FindEntry, CreateEntry, AppendMenuEntry, ReplaceMenuEntry, Menu, MenuLine, GetNumberOfLines, GetLine, SetLine, InsertMenuEntry],
MBQueue USING [Queue, Create, CreateMenuEntry],
Rope USING [ROPE, Concat, Equal, Substr, Find],
ViewerBLT USING [ChangeNumberOfLines],
ViewerOps USING [FetchProp, PaintViewer],
MessageWindow USING [Append, Blink],
ViewerClasses USING [Viewer],
SourceMarkerTracking USING [RemoveParentPointersTo, RegisterViewer],
TiogaAccess USING [Reader, TiogaChar, FromSelection, EndOf, Get, FromViewer],
TiogaOps USING [CallWithLocks, Ref, CommandProc, RegisterCommand, GetSelection, NoSelection, ViewerDoc, StepForward, SelectionGrain, SetSelection, Root, SaveSelA, SelectDocument, RestoreSelA],
TiogaOpsDefs USING [Location],
TiogaButtons USING [TextNodeRef],
TextNode USING [Ref],
TextEdit USING [PutCharProp, GetCharProp, PutProp, Size],
IO USING [STREAM, PutF, PutFR, int],
Atom USING [PropList],
VoiceMarkers USING [RopeFromTextList],
VoiceRecord USING [AddVoiceProc, StopRecording, DictationMachine],
VoiceRope USING [Open, Handle, Stop, VoiceRope, VoiceRopeInterval, Length],
Commander USING [CommandProc, Register],
TiogaMenuOps USING [tiogaMenu],
VoiceViewers USING [BuildVoiceViewer, VoiceViewerInfo, SetParentViewer, RemoveParentViewer],
VoicePlayBack USING [CancelPlayBack, PlayRopeWithoutCue, PlayBackMenuProc],
VoiceInText;
VoiceInTextImpl: CEDAR PROGRAM IMPORTS Convert, Menus, MBQueue, Rope, ViewerBLT, ViewerOps, MessageWindow, TiogaAccess, SourceMarkerTracking, TiogaOps, TiogaButtons, TextEdit, IO, Commander, TiogaMenuOps, VoiceViewers, VoicePlayBack, VoiceMarkers, VoiceRecord, VoiceRope EXPORTS VoiceInText SHARES Menus = BEGIN
ChangeMenu:
PUBLIC
PROC [viewer: ViewerClasses.Viewer, subMenu: Menus.MenuEntry] = {
see comments in interface about this
menu: Menus.Menu ← viewer.menu;
found: BOOL ← FALSE;
numLines: Menus.MenuLine = Menus.GetNumberOfLines[menu];
newLines: Menus.MenuLine ← numLines;
FOR i: Menus.MenuLine
IN [1..numLines)
DO
see if already showing the submenu
IF Rope.Equal[Menus.GetLine[menu,i].name, subMenu.name]
THEN {
-- yes, so remove it
FOR j: Menus.MenuLine
IN (i..numLines)
DO
Menus.SetLine[menu, j-1, Menus.GetLine[menu, j]];
ENDLOOP;
newLines ← newLines-1;
found ← TRUE; EXIT
};
ENDLOOP;
IF ~found
THEN {
add it. do insertion sort to get it in the right place
GoesBefore:
PROC [m1, m2: Menus.MenuEntry]
RETURNS [
BOOL] = {
Priority:
PROC [m: Menus.MenuEntry]
RETURNS [
INTEGER] = {
higher priority means goes above in series of submenus
looks at rope for first item to identify the subMenu.
RETURN [
SELECT
TRUE
FROM
Rope.Equal[m.name, "Find"] => 1,
Rope.Equal[m.name, "FirstLevelOnly"] => 0,
ENDCASE => -1 -- unknown menu goes at bottom -- ]
};
RETURN [Priority[m1] > Priority[m2]]
};
newLast: Menus.MenuLine = MIN[numLines, LAST[Menus.MenuLine]];
newLines ← newLines+1;
FOR i: Menus.MenuLine
IN [1..numLines)
DO
IF GoesBefore[subMenu, Menus.GetLine[menu, i]]
THEN {
put it here
FOR j: Menus.MenuLine
DECREASING
IN (i..newLast]
DO
Menus.SetLine[menu, j, Menus.GetLine[menu, j-1]]; ENDLOOP;
Menus.SetLine[menu, i, subMenu];
found ← TRUE; EXIT
};
ENDLOOP;
IF ~found THEN Menus.SetLine[menu, newLast, subMenu];
};
ViewerBLT.ChangeNumberOfLines[viewer, newLines];
};
ApplyToCharsInPrimarySelection:
PUBLIC
PROC [
ActionProc:
PROC [TiogaOpsDefs.Location]] = {
a convenience: looks after all the tree walking and calls the given ActionProc once for each character in the primary selection
ScanLocked:
PROC [root: TiogaOps.Ref] = {
current, end: TiogaOpsDefs.Location;
[start: current, end: end] ← TiogaOps.GetSelection[];
IF current.node = end.node
THEN FOR i: INT IN [current.where..end.where] DO ActionProc[[current.node, i]] ENDLOOP
ELSE
{
FOR i:
INT
IN [current.where..TextEdit.Size[TiogaButtons.TextNodeRef[current.node]])
DO ActionProc[[current.node, i]]
ENDLOOP;
DO
current.node ← TiogaOps.StepForward[current.node];
IF current.node = end.node THEN
{
FOR i:
INT
IN [0..end.where)
DO ActionProc[[current.node, i]]
ENDLOOP;
RETURN
}
ELSE FOR i: INT IN [0..TextEdit.Size[TiogaButtons.TextNodeRef[current.node]]) DO ActionProc[[current.node, i]] ENDLOOP
ENDLOOP
}
};
TiogaOps.CallWithLocks[ScanLocked]
};
ApplyToLockedChars: PUBLIC PROC [ActionProc: PROC [TiogaOpsDefs.Location]] = {
this is the same as above, except that it should be called when the primary selection is already locked
current, end: TiogaOpsDefs.Location;
[start: current, end: end] ← TiogaOps.GetSelection[];
IF current.node = end.node
THEN FOR i: INT IN [current.where..end.where] DO ActionProc[[current.node, i]] ENDLOOP
ELSE
{
FOR i:
INT
IN [current.where..TextEdit.Size[TiogaButtons.TextNodeRef[current.node]])
DO ActionProc[[current.node, i]]
ENDLOOP;
DO
current.node ← TiogaOps.StepForward[current.node];
IF current.node = end.node THEN
{
FOR i:
INT
IN [0..end.where)
DO ActionProc[[current.node, i]]
ENDLOOP;
RETURN
}
ELSE FOR i: INT IN [0..TextEdit.Size[TiogaButtons.TextNodeRef[current.node]]) DO ActionProc[[current.node, i]] ENDLOOP
ENDLOOP
}
};
StoreVoiceAtSelection: PUBLIC PROC [voiceViewerInfo: VoiceViewers.VoiceViewerInfo] RETURNS [succeeded: BOOLEAN ← FALSE] = {
procedure to put voice from a voice viewer into a text viewer
selectedViewer: ViewerClasses.Viewer;
suitableViewer: BOOLEAN;
alreadyVoiceThere: BOOLEAN;
SuitableViewer:
PROC
RETURNS [
BOOLEAN] = {
RETURN [selectedViewer.class.flavor = $Text AND ViewerOps.FetchProp[selectedViewer, $voiceViewerInfo] = NIL] };
AddVoiceMarkerAtPrimarySelection:
PROC [root: TiogaOps.Ref] = {
startChar, endChar, targetChar: TiogaOpsDefs.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[];
IF pendingDelete AND suitableViewer THEN
{ ApplyToLockedChars[DeleteVoiceFromChar];
TiogaOps.SetSelection[viewer: selectedViewer, start: startChar, end: endChar,
level: level, caretBefore: caretBefore,
pendingDelete: FALSE, which: primary] -- simply makes not pending delete
IF suitableViewer THEN
{ targetChar ←
IF caretBefore
THEN startChar
ELSE endChar;
node ← TiogaButtons.TextNodeRef[targetChar.node]; -- just a type convertor
alreadyVoiceThere ← TextEdit.GetCharProp[node, targetChar.where, $voice] # NIL;
IF alreadyVoiceThere THEN RETURN;
TextEdit.PutCharProp[node, targetChar.where, $voice, voiceViewerInfo.ropeInterval.ropeID];
TextEdit.PutCharProp[node, targetChar.where, $textInVoice, VoiceMarkers.RopeFromTextList[voiceViewerInfo.textMarkList]];
next line places a 'talks bubble' on the selected character - see TalksBubbleImpl
TextEdit.PutCharProp[node, targetChar.where, $Artwork, NARROW["TalksBubble", Rope.ROPE]]; -- the NARROW prevents a REF TEXT being created
TextEdit.PutCharProp[node, targetChar.where, $voiceWindow, NEW[VoiceInText.VoiceWindowRec ← [label: Rope.Concat["Sound Viewer #", Convert.RopeFromInt[voiceViewerInfo.viewerNumber]]]]];
voiceViewerInfo.parentViewer ← selectedViewer;
voiceViewerInfo.positionInParent ← targetChar;
SourceMarkerTracking.RegisterViewer[selectedViewer]
}
};
IF voiceViewerInfo.ropeInterval.ropeID =
NIL
THEN
RETURN;
TiogaOps.CallWithLocks[AddVoiceMarkerAtPrimarySelection ! TiogaOps.NoSelection => {suitableViewer ← FALSE; CONTINUE}];
test for failure conditions and report them to the user after releasing the viewer lock
IF NOT suitableViewer THEN
{ MessageWindow.Append["Make a selection in a tioga viewer first",
TRUE];
MessageWindow.Blink[]
}
ELSE
{
IF alreadyVoiceThere
THEN
{ MessageWindow.Append["Cannot add sound on top of another sound",
TRUE];
MessageWindow.Blink[]
}
ELSE succeeded ← TRUE
}
};
DeleteVoiceProc: Menus.MenuProc = {
ApplyToCharsInPrimarySelection[DeleteVoiceFromChar] };
DeleteVoiceFromChar:
PUBLIC
PROC [position: TiogaOpsDefs.Location] = {
node: TextNode.Ref ← TiogaButtons.TextNodeRef[position.node];
offset: INT ← position.where;
voiceWindowRef: VoiceInText.VoiceWindowRef;
voiceWindowRope: Rope.ROPE;
TextEdit.PutCharProp[node, offset, $Artwork, NIL];
TextEdit.PutCharProp[node, offset, $voice, NIL];
TextEdit.PutCharProp[node, offset, $textInVoice, NIL];
voiceWindowRef ← NARROW[TextEdit.GetCharProp[node, offset, $voiceWindow], VoiceInText.VoiceWindowRef];
voiceWindowRope ← IF voiceWindowRef = NIL THEN NIL ELSE voiceWindowRef.label;
IF voiceWindowRope # NIL THEN
{ TextEdit.PutCharProp[node, offset, $voiceWindow,
NIL];
VoiceViewers.RemoveParentViewer[Convert.IntFromRope[ voiceWindowRope.Substr[voiceWindowRope.Find["#"]+1]]]
}
PlaySelection:
PUBLIC
PROC = {
selectStream: TiogaAccess.Reader ← TiogaAccess.FromSelection[];
charsInSelection: INT ← 0;
soundsInSelection: INT ← 0;
heavyChar: TiogaAccess.TiogaChar;
props: Atom.PropList;
IF
NOT TiogaAccess.EndOf[selectStream]
THEN
DO
heavyChar ← TiogaAccess.Get[selectStream];
IF TiogaAccess.EndOf[selectStream] THEN EXIT;
charsInSelection ← charsInSelection + 1;
FOR props ← heavyChar.propList, props.rest
WHILE props #
NIL
DO
IF props.first.key = $voice THEN
{ VoicePlayBack.PlayRopeWithoutCue[
NARROW[props.first.val, Rope.
ROPE]];
soundsInSelection ← soundsInSelection + 1
};
ENDLOOP
ENDLOOP;
IF soundsInSelection = 0 THEN MessageWindow.Append["No sounds in selection", TRUE];
DebugRope[IO.PutFR["%d characters in selection\n", IO.int[charsInSelection]]]
};
CancelProc:
PUBLIC Menus.MenuProc = {
VoiceRope.Stop[thrushHandle];
VoicePlayBack.CancelPlayBack[];
VoiceRecord.StopRecording[]
};
EditVoiceProc: Menus.MenuProc = {
this procedure is in three parts: first look in the primary selection for characters with the property $voice but not $voiceWindow; next open up a window for each: then add $voiceWindow to the relevant characters. If the expected characters don't occur in the expected order then voice window(s) may be created without association to the parent viewer.
voiceList: LIST OF Rope.ROPE ← NIL;
textInVoiceList: LIST OF Rope.ROPE ← NIL;
voiceViewerInfoList: LIST OF VoiceViewers.VoiceViewerInfo ← NIL;
voiceViewerNumberList:
LIST
OF
INT ←
NIL;
these routines are simply append procedures for the three list types above [alas]
AppendRope:
PROC [list:
LIST
OF Rope.
ROPE, entry: Rope.
ROPE]
RETURNS [
LIST
OF Rope.
ROPE] = {
oneElementList: LIST OF Rope.ROPE ← CONS[entry, NIL];
hangOffPoint: LIST OF Rope.ROPE ← list;
IF list = NIL THEN RETURN [oneElementList];
WHILE hangOffPoint.rest # NIL DO hangOffPoint ← hangOffPoint.rest ENDLOOP;
hangOffPoint.rest ← oneElementList;
RETURN [list]
};
AppendViewerInfo:
PROC [list:
LIST
OF VoiceViewers.VoiceViewerInfo, entry: VoiceViewers.VoiceViewerInfo]
RETURNS [
LIST
OF VoiceViewers.VoiceViewerInfo] = {
oneElementList: LIST OF VoiceViewers.VoiceViewerInfo ← CONS[entry, NIL];
hangOffPoint: LIST OF VoiceViewers.VoiceViewerInfo ← list;
IF list = NIL THEN RETURN [oneElementList];
WHILE hangOffPoint.rest # NIL DO hangOffPoint ← hangOffPoint.rest ENDLOOP;
hangOffPoint.rest ← oneElementList;
RETURN [list]
};
AppendInt:
PROC [list:
LIST
OF
INT, entry:
INT]
RETURNS [
LIST
OF
INT] = {
oneElementList: LIST OF INT ← CONS[entry, NIL];
hangOffPoint: LIST OF INT ← list;
IF list = NIL THEN RETURN [oneElementList];
WHILE hangOffPoint.rest # NIL DO hangOffPoint ← hangOffPoint.rest ENDLOOP;
hangOffPoint.rest ← oneElementList;
RETURN [list]
};
SearchForVoice:
PROC [targetChar: TiogaOpsDefs.Location] = {
node: TextNode.Ref ← TiogaButtons.TextNodeRef[targetChar.node];
voiceRopeID: Rope.ROPE ← NARROW[TextEdit.GetCharProp[node, targetChar.where, $voice], Rope.ROPE];
IF voiceRopeID # NIL
THEN
{
fullRope: VoiceRope.VoiceRope ← NEW [VoiceRope.VoiceRopeInterval ← [voiceRopeID, 0, 0]];
fullRope.length ← VoiceRope.Length[handle: thrushHandle, vr: fullRope];
IF fullRope.length <= 0
THEN
MessageWindow.Append["non-existant or zero length voice utterance(s) found in selection", TRUE]
ELSE
{
IF TextEdit.GetCharProp[node, targetChar.where, $voiceWindow] =
NIL
THEN
{ voiceList ← AppendRope[voiceList, voiceRopeID];
textInVoiceList ← AppendRope[textInVoiceList, NARROW[TextEdit.GetCharProp[node, targetChar.where, $textInVoice], Rope.ROPE]]
}
ELSE DebugRope["Voice already has an associated viewer\n"]
}
}
};
AddVoiceWindowProps:
PROC [targetChar: TiogaOpsDefs.Location] = {
because we are under a tioga lock, it is safe to muck with the 'edited' status of the viewer
philosophically, putting a source marker in a voice viewer does not constitute editing it, so keep the 'edited' status of the viewer constant through this operation
node: TextNode.Ref ← TiogaButtons.TextNodeRef[targetChar.node];
voiceRopeID: Rope.ROPE ← NARROW[TextEdit.GetCharProp[node, targetChar.where, $voice], Rope.ROPE];
IF voiceList = NIL THEN RETURN;
IF voiceRopeID = NIL THEN RETURN;
IF TextEdit.GetCharProp[node, targetChar.where, $voiceWindow] #
NIL
THEN
RETURN;
IF voiceRopeID.Equal[voiceList.first] THEN
{ parentViewer: ViewerClasses.Viewer ← TiogaOps.GetSelection[].viewer;
alreadyEdited: BOOLEAN ← parentViewer.newVersion;
TextEdit.PutCharProp[node, targetChar.where, $voiceWindow, NEW[VoiceInText.VoiceWindowRec ← [label: Rope.Concat["Sound Viewer #", Convert.RopeFromInt[voiceViewerNumberList.first]]]]];
VoiceViewers.SetParentViewer[voiceViewerInfoList.first, parentViewer, targetChar];
IF ~alreadyEdited THEN
{ parentViewer.newVersion ←
FALSE;
ViewerOps.PaintViewer[parentViewer, caption, FALSE]
};
voiceList ← voiceList.rest;
voiceViewerInfoList ← voiceViewerInfoList.rest;
voiceViewerNumberList ← voiceViewerNumberList.rest
we only move down the voice (etc.) lists if the char property voice rope ID matched the head of the voice list. A non-match may be due to the rope being non-existant (and hence having been filtered out from voiceList by SearchForVoice). The other possibility is that the selection has been changed, in which case voice viewers may or may not be given correct links to their parents
};
};
ApplyToCharsInPrimarySelection[SearchForVoice ! TiogaOps.NoSelection => GOTO Quit];
IF voiceList = NIL THEN
{ MessageWindow.Append["No undisplayed sounds in selection",
TRUE];
RETURN
};
FOR l:
LIST
OF Rope.
ROPE ← voiceList, l.rest
WHILE l #
NIL
DO
newInfo: VoiceViewers.VoiceViewerInfo;
newNumber: INT;
[viewerInfo: newInfo, viewerNumber: newNumber] ← VoiceViewers.BuildVoiceViewer[voiceID: l.first, textInVoice: textInVoiceList.first, youngVoice: FALSE];
voiceViewerInfoList ← AppendViewerInfo[voiceViewerInfoList, newInfo];
voiceViewerNumberList ← AppendInt[voiceViewerNumberList, newNumber];
textInVoiceList ← textInVoiceList.rest
ENDLOOP;
ApplyToCharsInPrimarySelection[AddVoiceWindowProps ! TiogaOps.NoSelection => CONTINUE]
};
DeleteSourceMarker:
PUBLIC
PROC [viewer: ViewerClasses.Viewer, positionInParent: TiogaOpsDefs.Location, voiceViewerNumber:
INT] = {
DoIt:
PROC [root: TiogaOps.Ref] = {
node: TextNode.Ref ← TiogaButtons.TextNodeRef[positionInParent.node];
voiceWindowRope: Rope.ROPE ← Rope.Concat["Sound Viewer #", Convert.RopeFromInt[voiceViewerNumber]];
IF TiogaOps.ViewerDoc[viewer] # TiogaOps.Root[positionInParent.node]
THEN
{ DebugRope["source marker's viewer and node do not match!!\n"];
RETURN
};
IF voiceWindowRope.Equal[NARROW[TextEdit.GetCharProp[node, positionInParent.where, $voiceWindow], VoiceInText.VoiceWindowRef].label]
THEN
{
-- see comments about 'edited' status at head of procedure AddVoiceWindowProps
alreadyEdited: BOOLEAN ← viewer.newVersion;
TextEdit.PutCharProp[node, positionInParent.where, $voiceWindow, NIL];
may be that the source marker was put in as a result of DictationMachine being bugged, in which case there is no voice rope present - remove the artwork
IF TextEdit.GetCharProp[node, positionInParent.where, $voice] = NIL THEN
TextEdit.PutCharProp[node, positionInParent.where, $Artwork, NIL];
IF ~alreadyEdited THEN
{ viewer.newVersion ←
FALSE;
ViewerOps.PaintViewer[viewer, caption, FALSE]
}
}
ELSE DebugRope["source marker not found at expected position!!\n"]
TiogaOps.CallWithLocks[DoIt, TiogaOps.ViewerDoc[viewer]];
};
SaveRopeAtSourceMarker:
PUBLIC
PROC [viewer: ViewerClasses.Viewer, positionInParent: TiogaOpsDefs.Location, voiceViewerNumber:
INT, voiceRopeID: Rope.
ROPE, textInVoice: Rope.
ROPE]
RETURNS [succeeded:
BOOLEAN ←
FALSE] = {
DoIt:
PROC [root: TiogaOps.Ref] = {
node: TextNode.Ref ← TiogaButtons.TextNodeRef[positionInParent.node];
voiceWindowRope: Rope.ROPE ← Rope.Concat["Sound Viewer #", Convert.RopeFromInt[voiceViewerNumber]];
IF TiogaOps.ViewerDoc[viewer] # TiogaOps.Root[positionInParent.node]
THEN
{ DebugRope["source marker's viewer and node do not match!!\n"];
RETURN
};
IF voiceWindowRope.Equal[NARROW[TextEdit.GetCharProp[node, positionInParent.where, $voiceWindow], VoiceInText.VoiceWindowRef].label]
THEN
{ TextEdit.PutCharProp[node, positionInParent.where, $voice, voiceRopeID];
TextEdit.PutCharProp[node, positionInParent.where, $textInVoice, textInVoice];
succeeded ← TRUE
}
ELSE DebugRope["source marker not found at expected position!!\n"]
TiogaOps.CallWithLocks[DoIt, TiogaOps.ViewerDoc[viewer]];
};
ScanForVoice: TiogaOps.CommandProc = {
IF viewer.newFile OR viewer.newVersion THEN RecordVoiceInstancesAtRoot[viewer] ELSE MessageWindow.Append["File is not altered", TRUE]
};
DeleteLinks: Menus.MenuProc = {
this procedure severs any parent pointers that voice viewers might have back to this viewer and removes the source marker artworks. [It can also be used to get rid of source marker artworks embarassingly left in a file by bad tracking etc.]
viewer: ViewerClasses.Viewer ← NARROW[parent];
RemoveAnySourceMarker:
PROC [position: TiogaOpsDefs.Location] = {
node: TextNode.Ref ← TiogaButtons.TextNodeRef[position.node];
IF TextEdit.GetCharProp[node, position.where, $voiceWindow] # NIL THEN
{
-- see comments about 'edited' status at head of procedure AddVoiceWindowProps
alreadyEdited: BOOLEAN ← viewer.newVersion;
TextEdit.PutCharProp[node, position.where, $voiceWindow, NIL];
IF ~alreadyEdited THEN
{ viewer.newVersion ←
FALSE;
ViewerOps.PaintViewer[viewer, caption, FALSE]
}
}
};
TiogaOps.SaveSelA[];
TiogaOps.SelectDocument[viewer: viewer, level: branch];
ApplyToCharsInPrimarySelection[RemoveAnySourceMarker];
TiogaOps.RestoreSelA[];
SourceMarkerTracking.RemoveParentPointersTo[viewer];
RecordVoiceInstancesAtRoot:
PROC [viewer: ViewerClasses.Viewer] = {
wholeFile: TiogaAccess.Reader ← TiogaAccess.FromViewer[viewer];
heavyChar: TiogaAccess.TiogaChar;
props: Atom.PropList;
soundsInDocument: INT ← 0;
rootNode: TextNode.Ref;
voiceList: Rope.ROPE ← NIL;
DebugRope["Voice messages in document:"];
IF
NOT TiogaAccess.EndOf[wholeFile]
THEN
DO
heavyChar ← TiogaAccess.Get[wholeFile];
IF TiogaAccess.EndOf[wholeFile] THEN EXIT;
FOR props ← heavyChar.propList, props.rest
WHILE props #
NIL
DO
IF props.first.key = $voice THEN
{ DebugRope["\n"];
DebugRope[NARROW[props.first.val, Rope.ROPE]];
soundsInDocument ← soundsInDocument + 1;
voiceList ← voiceList.Concat["&"]; -- just used here as a separator: a character not found in the IDs
voiceList ← voiceList.Concat[NARROW[props.first.val, Rope.ROPE]]
}
ENDLOOP
ENDLOOP;
rootNode ← TiogaButtons.TextNodeRef[TiogaOps.ViewerDoc[viewer]]; -- TextNodeRef is just a type convertor - replace with a more cautious implementation?? [several references throughout TiogaVoice]
TextEdit.PutProp[rootNode, $voicelist, voiceList];
IF soundsInDocument = 0 THEN DebugRope[" none\n"] ELSE DebugRope["\n"]
InstallMenuButton:
PROC [name: Rope.
ROPE, proc: Menus.MenuProc] = {
old: Menus.MenuEntry =
Menus.FindEntry[menu: TiogaMenuOps.tiogaMenu, entryName: name];
new: Menus.MenuEntry =
Menus.CreateEntry[name: name, proc: proc];
IF old =
NIL
THEN Menus.AppendMenuEntry[TiogaMenuOps.tiogaMenu, new]
ELSE Menus.ReplaceMenuEntry[TiogaMenuOps.tiogaMenu, old, new];
};
thrushHandle: PUBLIC VoiceRope.Handle ← VoiceRope.Open[];
voiceMenu: Menus.MenuEntry;
voiceButtonQueue: PUBLIC MBQueue.Queue ← MBQueue.Create[];
all buttons registered using MBQueue.CreateMenuEntry are serialised on this one queue: all the buttons in this voice system, or having to do with voice [e.g. Finch buttons], ought to be put on this queue, except for those which simply toggle menus on and off the display
debugStream: IO.STREAM ← NIL;
DebugRope:
PUBLIC
PROC [rope: Rope.
ROPE] = {
IF debugStream # NIL THEN debugStream.PutF[rope]
};
DebugStreamInit: Commander.CommandProc = { debugStream ← cmd.out };
Commander.Register[key: "VoiceInfo", proc: DebugStreamInit, doc: "VoiceInfo: registers an output stream for debugging messages"];
**** voiceButtonQueue should be deleted, or used for ALL button activities
Menus.InsertMenuEntry[TiogaMenuOps.tiogaMenu, MBQueue.CreateMenuEntry[voiceButtonQueue, "DeleteLinks", DeleteLinks], 1];
Menus.InsertMenuEntry[TiogaMenuOps.tiogaMenu, MBQueue.CreateMenuEntry[voiceButtonQueue, "DictationMachine", VoiceRecord.DictationMachine], 1];
Menus.InsertMenuEntry[TiogaMenuOps.tiogaMenu, MBQueue.CreateMenuEntry[voiceButtonQueue, "DeleteVoice", DeleteVoiceProc], 1];
Menus.InsertMenuEntry[TiogaMenuOps.tiogaMenu, MBQueue.CreateMenuEntry[voiceButtonQueue, "EditVoice", EditVoiceProc], 1];
Menus.InsertMenuEntry[TiogaMenuOps.tiogaMenu, MBQueue.CreateMenuEntry[voiceButtonQueue, "STOP", CancelProc], 1];
Menus.InsertMenuEntry[TiogaMenuOps.tiogaMenu, MBQueue.CreateMenuEntry[voiceButtonQueue, "PlayVoice", VoicePlayBack.PlayBackMenuProc], 1];
Menus.InsertMenuEntry[TiogaMenuOps.tiogaMenu, MBQueue.CreateMenuEntry[voiceButtonQueue, "AddVoice", VoiceRecord.AddVoiceProc], 1];
voiceMenu ← Menus.GetLine[TiogaMenuOps.tiogaMenu, 1];
InstallMenuButton["Voice", VoiceMenu];
Menus.SetLine[TiogaMenuOps.tiogaMenu, 1, NIL];
TiogaOps.RegisterCommand[name: $RedSave, proc: ScanForVoice];
TiogaOps.RegisterCommand[name: $YellowSave, proc: ScanForVoice];
TiogaOps.RegisterCommand[name: $BlueSave, proc: ScanForVoice];
TiogaOps.RegisterCommand[name: $RedStore, proc: ScanForVoice];
TiogaOps.RegisterCommand[name: $YellowStore, proc: ScanForVoice];
TiogaOps.RegisterCommand[name: $BlueStore, proc: ScanForVoice];
END.