WalnutSendOpsImpl.mesa
Copyright © 1984, 1985, 1986 by Xerox Corporation. All rights reserved.
Willie-Sue, August 15, 1986 5:02:43 pm PDT
Contents: Implementations for WalnutSendOps and WalnutParseMsg
Created by Willie-Sue, December 13, 1983
DIRECTORY
DefaultRemoteNames USING [Get],
FS USING [Error, defaultStreamOptions, StreamOptions, StreamOpen],
GVAnswer USING [MakeHeader],
GVMailParse,
InputFocus USING [GetInputFocus],
Menus,
IO,
Process USING [Detach],
Rope,
RopeList USING [Append],
RuntimeError USING [BoundsFault],
TiogaOps
USING [ Pattern, Ref, Location, ViewerDoc,
CallWithLocks, CancelSelection, CaretBefore, CreateSimplePattern, Delete, FindWord,
FindText, GetSelection, InsertRope, NextPlaceholder, SelectDocument, SelectPoint,
SelectionRoot, SelectionSearch, SetSelection, SetSelectionLooks, ToPrimary],
ViewerOps,
ViewerClasses,
ViewerEvents USING [EventProc, RegisterEventProc, UnRegisterEventProc],
ViewerTools,
WalnutDocumentRope,
WalnutParseMsg,
WalnutSendInternal,
WalnutSendOps,
WalnutSendOpsExtras;
WalnutSendOpsImpl: CEDAR MONITOR
IMPORTS
DefaultRemoteNames, FS, IO, Process, Rope, RopeList, RuntimeError,
GVAnswer, GVMailParse,
InputFocus, Menus, TiogaOps, ViewerEvents, ViewerOps, ViewerTools,
WalnutDocumentRope, WalnutSendInternal, WalnutSendOps, WalnutSendOpsExtras
EXPORTS
WalnutSendInternal, WalnutSendOps, WalnutSendOpsExtras, WalnutParseMsg
SHARES Menus =
BEGIN OPEN WalnutSendOps, WalnutSendInternal, WalnutParseMsg;
************************************************************************
ROPE: TYPE = Rope.ROPE;
Viewer: TYPE = ViewerClasses.Viewer;
STREAM: TYPE = IO.STREAM;
TiogaContents: TYPE = ViewerTools.TiogaContents;
viewerStart: ViewerTools.SelPos = NEW[ViewerTools.SelPosRec ← [0, 0]];
nullIndex: INT = LAST[INT];
defaultRegistry: PUBLIC ROPE ← DefaultRemoteNames.Get[].registry;
messageParseArray:
PUBLIC
ARRAY MessageFieldIndex
OF MessageInfo ←
[ ["Reply-to", simpleRope],
-- this is really wrong, a special case for now
["Sender", simpleRope],
["From", simpleRope],
["To", rNameList],
["cc", rNameList],
["c", rNameList],
["bcc", rNameList],
["Date", simpleRope],
["Subject", simpleRope],
["Categories", rCatList],
["In-reply-to", simpleRope],
["VoiceFileID", simpleRope]
];
Answer:
PUBLIC
PROC[msgHeaders:
ROPE, who: Viewer←
NIL]
RETURNS [v: Viewer] = {
notOk: BOOL;
errorIndex: INT;
answer: ROPE;
answerForm: WalnutSendOps.Form;
AnswerGetChar: PROC[pos: INT] RETURNS[CHAR] = {RETURN[msgHeaders.Fetch[pos]]};
[notOk, answer, errorIndex]← GVAnswer.MakeHeader[AnswerGetChar, msgHeaders.Length[],
simpleUserName, defaultRegistry];
IF notOk
THEN {
start, end: INT ← errorIndex;
BEGIN
ENABLE RuntimeError.BoundsFault => {start ← 0; end ← 1;
CONTINUE};
IF start = nullIndex
THEN start ← 0
ELSE
{
UNTIL msgHeaders.Fetch[start] = '\n
DO start ← start - 1;
ENDLOOP;
start ← start + 1};
IF end = nullIndex
THEN end ← start + 1
ELSE
{
UNTIL msgHeaders.Fetch[end] = '\n
DO end ← end + 1;
ENDLOOP;
end ← end - 1};
END;
IF who # NIL THEN ShowErrorFeedback[who, start, end];
WalnutSendOpsExtras.SenderReport[
IO.PutFR[
"\nSyntax error in header line \"%g\"", IO.rope[msgHeaders.Substr[start, end-start+1] ]]];
IF answer.Length[] = 0 THEN RETURN;
WalnutSendOpsExtras.SenderReport["\n*****Partial answer has been generated\n"];
};
answerForm ←
NEW[ WalnutSendOps.FormRec ←
[formText: answerText, fields: ParseAnswerHeader[answer] ] ];
v← BuildSendViewer[TRUE, FALSE, answerForm, who];
ClearFileAssoc[v];
GrabFocus[v];
};
ParseAnswerHeader:
PROC[header:
ROPE]
RETURNS[fields:
LIST
OF
ROPE] = {
HeaderLines: ARRAY[0..3] OF Rope.ROPE;
startPos: INT ← Rope.Find[header, ": ", 0];
endOfLine: INT ← Rope.Find[header, "\n", startPos];
FOR i:
INT
IN [0..3]
DO
HeaderLines[i] ← Rope.Substr[header, startPos+2, endOfLine-startPos-2];
startPos ← Rope.Find[header, ": ", endOfLine];
IF startPos = -1 THEN EXIT ELSE endOfLine ← Rope.Find[header, "\n", startPos]
ENDLOOP;
RETURN[ LIST[ HeaderLines[0], HeaderLines[1], HeaderLines[2], HeaderLines[3] ] ]
Forward:
PUBLIC
PROC[msg: Viewer, who: Viewer ←
NIL]
RETURNS[v: Viewer] =
TRUSTED {
forwardForm: WalnutSendOps.Form =
NEW[WalnutSendOps.FormRec ← [formText: forwardText, fields: NIL]];
messagePattern: TiogaOps.Pattern = TiogaOps.CreateSimplePattern["ForwardedMessage"];
headerPattern: TiogaOps.Pattern = TiogaOps.CreateSimplePattern["MessageHeader"];
pstart, pend: TiogaOps.Location;
root: TiogaOps.Ref;
found: BOOL;
v← BuildSendViewer[TRUE, FALSE, forwardForm, who];
ClearFileAssoc[v];
TiogaOps.SelectDocument[v];
root ← TiogaOps.SelectionRoot[];
pstart ← TiogaOps.Location[ root, 0 ];
TiogaOps.SelectPoint[ v, pstart ];
found ← TiogaOps.SelectionSearch[pattern: headerPattern];
IF found
THEN
{ [ ,pstart, pend, , , ] ← TiogaOps.GetSelection[];
TiogaOps.SetSelection[viewer: v, start: pstart, end: pend,
level: word, pendingDelete: TRUE];
TiogaOps.InsertRope[ msg.name ] };
found ← TiogaOps.SelectionSearch[pattern: messagePattern];
IF NOT found THEN RETURN;
[ ,pstart, pend, , , ] ← TiogaOps.GetSelection[];
TiogaOps.SetSelection[ viewer: v, start: pstart, end: pend, level: node, pendingDelete: TRUE ];
TiogaOps.SelectDocument[ viewer: msg, which: secondary ];
TiogaOps.ToPrimary[];
UnsetNewVersion[v];
GrabFocus[v]
};
ReSend:
PUBLIC
PROC[msg: Viewer, who: Viewer ←
NIL]
RETURNS[v: ViewerClasses.Viewer] = {
reSendForm: WalnutSendOps.Form =
NEW[WalnutSendOps.FormRec ← [formText: ViewerTools.GetTiogaContents[msg], fields: NIL]];
v ← BuildSendViewer[TRUE, FALSE, reSendForm, who];
ClearFileAssoc[v];
TiogaOps.SelectDocument[v];
IF TiogaOps.FindText[viewer: v, rope: "Date:", whichDir: anywhere, case:
FALSE]
THEN {
TiogaOps.CaretBefore[];
TiogaOps.SetSelectionLooks[];
TiogaOps.InsertRope["Original-"];
};
IF TiogaOps.FindText[viewer: v, rope: "Sender:", whichDir: anywhere, case:
FALSE]
THEN {
TiogaOps.CaretBefore[];
TiogaOps.SetSelectionLooks[];
TiogaOps.InsertRope["Original-"];
};
IF TiogaOps.FindWord[viewer: v, rope: "From:", whichDir: anywhere, case:
FALSE]
THEN {
TiogaOps.CaretBefore[];
TiogaOps.SetSelectionLooks[];
TiogaOps.InsertRope["Originally-"];
};
UnsetNewVersion[v];
};
ClearFileAssoc:
PUBLIC
PROC[v: Viewer] = {
IF v.file # NIL THEN v.file← NIL;
v.name← sendCaption;
ViewerOps.PaintViewer[v, caption];
};
SenderNewVersion:
PUBLIC
PROC[viewer: Viewer] = {
OPEN Menus;
menu: Menus.Menu = viewer.menu;
firstForm: Menus.MenuEntry = Menus.GetLine[formsMenu, 0];
firstForm: Menus.MenuEntry ← NIL;
getEntry: Menus.MenuEntry← Menus.FindEntry[menu, "Get"];
IF getEntry # NIL THEN Menus.SetGuarded[getEntry, TRUE];
getEntry← Menus.FindEntry[menu, "Default"];
IF getEntry # NIL THEN Menus.SetGuarded[getEntry, TRUE];
getEntry← Menus.FindEntry[menu, "PrevMsg"];
IF getEntry # NIL THEN Menus.SetGuarded[getEntry, TRUE];
FOR i: Menus.MenuLine
IN [1..5)
DO
thisLine: Menus.MenuEntry = Menus.GetLine[menu, i];
IF thisLine = NIL THEN EXIT;
IF firstForm = NIL THEN EXIT;
IF Rope.Equal[thisLine.name, firstForm.name] THEN
FOR entry: MenuEntry ← thisLine, entry.link
UNTIL entry =
NIL
DO
SetGuarded[entry, TRUE]
ENDLOOP;
ENDLOOP;
ViewerOps.PaintViewer[viewer, menu]; -- show as guarded
viewer.newVersion← TRUE;
};
UnsetNewVersion:
PUBLIC
PROC[viewer: Viewer] = {
OPEN Menus;
menu: Menu = viewer.menu;
firstForm: Menus.MenuEntry ← NIL;
firstForm: Menus.MenuEntry = Menus.GetLine[formsMenu, 0];
getEntry: Menus.MenuEntry← Menus.FindEntry[menu, "Get"];
IF getEntry # NIL THEN Menus.SetGuarded[getEntry, FALSE];
getEntry← Menus.FindEntry[menu, "Default"];
IF getEntry # NIL THEN Menus.SetGuarded[getEntry, FALSE];
getEntry← Menus.FindEntry[menu, "PrevMsg"];
IF getEntry # NIL THEN Menus.SetGuarded[getEntry, FALSE];
FOR i: Menus.MenuLine
IN [1..5)
DO
thisLine: Menus.MenuEntry = Menus.GetLine[menu, i];
IF thisLine = NIL THEN EXIT;
IF firstForm = NIL THEN EXIT;
IF Rope.Equal[thisLine.name, firstForm.name] THEN
FOR entry: MenuEntry ← thisLine, entry.link
UNTIL entry =
NIL
DO
SetGuarded[entry, FALSE]
ENDLOOP;
ENDLOOP;
ViewerOps.PaintViewer[viewer, menu]; -- show as unguarded
viewer.newVersion← FALSE;
ViewerOps.PaintViewer[viewer, caption];
};
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
TiogaTextFromStrm:
PUBLIC
PROC [strm:
IO.
STREAM, startPos:
INT← 0, len:
INT←
LAST[
INT]]
RETURNS [contents: TiogaContents] = {
returns NIL IF endOfStream encountered during read
ENABLE IO.EndOfStream => GOTO TooShort;
fulltext: ROPE ← RopeFromStream[strm, startPos, len];
formatPos: INT ← Rope.Index[fulltext, 0, "\000\000"];
contents← NEW[ViewerTools.TiogaContentsRec];
contents.contents← Rope.Substr[fulltext, 0, formatPos];
IF formatPos < Rope.Length[fulltext] THEN contents.formatting← Rope.Substr[fulltext, formatPos];
EXITS TooShort => RETURN[NIL];
};
RopeFromStream:
PUBLIC
PROC [strm:
STREAM, startPos, len:
INT]
RETURNS [contents:
ROPE] = {
Reads arbitrary length ROPE from a stream. If some caller catches the error, then the caller can diagnose it. Otherwise we do something more or less reasonable here.
rem: INT ← IO.GetLength[strm] - startPos;
IF rem < len THEN len ← rem;
IF len < 2048
THEN
TRUSTED {
Short enough to make in one piece
nat: NAT ← len;
text: Rope.Text ← Rope.NewText[nat];
IO.SetIndex[strm, startPos];
[] ← IO.GetBlock[strm, LOOPHOLE[text], 0, nat];
RETURN [text];
};
The rope is too large to make in one piece, so we divide the length in two and recursviely call ourselves. This gets at least 1024 characters in each piece in a balanced binary tree, so the recursion is quite acceptable even for large messages.
rem ← len/2;
len ← len - rem;
contents ← RopeFromStream[strm, startPos, len];
Do this first to ensure that the evaluation order is OK.
contents ← Rope.Concat[contents, RopeFromStream[strm, startPos+len, rem] ];
};
GetFieldBody:
PUBLIC PROC[text, fieldName:
ROPE]
RETURNS[fieldBody:
ROPE] = {
expects text to be the text of a message; parses the header fields looking for a field named fieldName and returns the value of the fieldBody, or NIL if no such fieldName is found
OPEN GVMailParse;
mPos: INT← 0;
lastCharPos: INT← text.Length[];
pH: GVMailParse.ParseHandle← GVMailParse.InitializeParse[];
NextChar: PROC[] RETURNS [ch: CHAR] =
{
IF mPos > lastCharPos
THEN ch← endOfInput
ELSE ch← text.Fetch[mPos];
mPos← mPos + 1;
};
BEGIN
field: ROPE← NIL;
DO
found, fieldNotRecognized: BOOL← TRUE;
[field, found]←
GVMailParse.GetFieldName[pH, NextChar ! ParseError => GOTO parseErrorExit];
IF ~found THEN EXIT;
IF Rope.Equal[fieldName, field,
FALSE]
THEN
-- ignore case
{ fieldBody← GVMailParse.GetFieldBody[pH, NextChar];
EXIT}
ELSE []← GVMailParse.GetFieldBody[pH, NextChar, TRUE];
ENDLOOP;
GVMailParse.FinalizeParse[pH];
EXITS
parseErrorExit => { GVMailParse.FinalizeParse[pH]; RETURN[NIL]};
END;
};
GetRecipients:
PUBLIC PROC[sender: Viewer]
RETURNS[rList:
LIST
OF
ROPE, parseError:
BOOL] = {
expects sender to contain a message to be sent; will parse the headers and return a list of all the recipients of the message; if an error occurs while parsing the viewer, parseError=TRUE is returned, and the viewer will have the offending field underlined.
text: ROPE;
status: SendParseStatus;
sPos, mPos: INT;
TRUSTED
{text ← WalnutDocumentRope.Create[LOOPHOLE [TiogaOps.ViewerDoc[sender]]]};
[status, sPos, mPos, rList] ← Parse[text];
IF (status # ok) AND (status # includesPublicDL) THEN
BEGIN
SELECT status
FROM
fieldNotAllowed =>
IF sPos # mPos THEN
{ ShowErrorFeedback[sender, sPos, mPos];
WalnutSendOpsExtras.SenderReport[Rope.Substr[text, MAX[0, sPos-1], mPos-sPos]];
WalnutSendOpsExtras.SenderReport[" field is not allowed\n"]}
ELSE WalnutSendOpsExtras.SenderReport[IO.PutFR[" field at pos %g is not allowed\n", IO.int[sPos]]];
syntaxError =>
IF sPos # mPos THEN
{ ShowErrorFeedback[sender, sPos, mPos];
WalnutSendOpsExtras.SenderReport["\nSyntax error on line beginning with "];
WalnutSendOpsExtras.SenderReport[Rope.Substr[text, MAX[0, sPos-1], mPos-sPos]]}
ELSE WalnutSendOpsExtras.SenderReport[IO.PutFR["..... Syntax error at position %g ", IO.int[sPos]]];
includesPrivateDL => WalnutSendOpsExtras.SenderReport[" Private dl's are not yet implemented\n"];
ENDCASE => ERROR;
ViewerOps.BlinkIcon[sender, IF sender.iconic THEN 0 ELSE 1];
RETURN[NIL, TRUE]
END;
RETURN[rList, FALSE]
};
the following are taken from the former WalnutParseMsgImpl
ParseHeadersFromRope:
PUBLIC PROC[headers:
ROPE, proc: ParseProc]
RETURNS[msgHeaders: MsgHeaders] =
proc is called for each fieldName encountered in the headers; if proc = NIL then all fields are returned
BEGIN OPEN GVMailParse;
mPos: INT ← 0;
len: INT ← headers.Length[];
pH: GVMailParse.ParseHandle ← GVMailParse.InitializeParse[];
NextChar:
PROC[]
RETURNS [ch:
CHAR] = {
IF mPos >= len THEN ch ← endOfInput ELSE ch ← headers.Fetch[mPos];
mPos ← mPos + 1;
};
msgHeaders ← NIL;
IF headers.Fetch[0] = '\n THEN mPos ← 1; -- ignore initial CR (tioga formatting nonsense)
BEGIN ENABLE ParseError => GOTO parseErrorExit;
fieldName: ROPE ← NIL;
found: BOOL;
wantThisField, continue: BOOL ← TRUE;
DO
[fieldName, found] ← GVMailParse.GetFieldName[pH, NextChar];
IF ~found THEN EXIT;
IF proc # NIL THEN [wantThisField, continue] ← proc[fieldName];
IF wantThisField
THEN
msgHeaders ← CONS[[fieldName, GVMailParse.GetFieldBody[pH, NextChar]], msgHeaders]
ELSE [] ← GVMailParse.GetFieldBody[pH, NextChar, TRUE];
IF ~continue THEN EXIT;
ENDLOOP;
GVMailParse.FinalizeParse[pH];
EXITS
parseErrorExit => { GVMailParse.FinalizeParse[pH]; RETURN[msgHeaders]};
END;
END;
ParseMsgFromStream:
PUBLIC PROC[strm:
IO.
STREAM, len:
INT, proc: ParseProc]
RETURNS[msgHeaders: MsgHeaders] =
expects strm to be the beginning of a message; parses the header fields looking for fields, calling proc with each fieldName as found; if proc=NIL then return all fields
BEGIN OPEN GVMailParse;
mPos: INT ← 0;
pH: GVMailParse.ParseHandle ← GVMailParse.InitializeParse[];
NextChar: PROC[] RETURNS [ch: CHAR] =
{
IF mPos > len
THEN ch ← endOfInput
ELSE ch ← strm.GetChar[ ! IO.EndOfStream => {mPos ← len; ch ← endOfInput; CONTINUE} ];
mPos ← mPos + 1;
};
msgHeaders ← NIL;
IF strm.PeekChar[] = '\n
THEN {
-- ignore initial CR (tioga formatting nonsense)
[] ← strm.GetChar[];
mPos ← 1;
};
BEGIN ENABLE ParseError => GOTO parseErrorExit;
fieldName: ROPE ← NIL;
found: BOOL;
wantThisField, continue: BOOL ← TRUE;
DO
[fieldName, found] ← GVMailParse.GetFieldName[pH, NextChar];
IF ~found THEN EXIT;
IF proc # NIL THEN [wantThisField, continue] ← proc[fieldName];
IF wantThisField
THEN
msgHeaders ← CONS[[fieldName, GVMailParse.GetFieldBody[pH, NextChar]], msgHeaders]
ELSE [] ← GVMailParse.GetFieldBody[pH, NextChar, TRUE];
IF ~continue THEN EXIT;
ENDLOOP;
GVMailParse.FinalizeParse[pH];
EXITS
parseErrorExit => { GVMailParse.FinalizeParse[pH]; RETURN[msgHeaders]};
END;
END;
Parse:
PUBLIC PROC[text:
ROPE]
RETURNS[status: SendParseStatus, sPos, mPos:
INT, rList:
LIST
OF
ROPE] =
BEGIN
OPEN GVMailParse;
mLF: MessageInfo;
lastCharPos: INT ← text.Length[] - 1;
lastCharIsCR: BOOL ← (text.Fetch[lastCharPos] = '\n);
GetNextChar: PROC[] RETURNS [ch: CHAR] =
{
IF mPos <= lastCharPos
THEN ch ← text.Fetch[mPos]
ELSE
IF (mPos=lastCharPos+1)
AND ~lastCharIsCR
THEN ch ← '\n
ELSE ch ← endOfInput;
mPos ← mPos + 1;
};
RNameListField:
PROC[index: MessageFieldIndex] =
BEGIN
fieldBody, fbEnd: LIST OF ROPE ← NIL;
AnotherRName:
PROC[r1, r2:
ROPE, isFile, isNested:
BOOL]
RETURNS [
ROPE,
BOOLEAN] =
BEGIN
name: ROPE ← CanonicalName[r1, r2];
countOfRecipients: INT ← 0; -- too lazy to figure this out now
IF fbEnd=
NIL
THEN fbEnd ← fieldBody ←
CONS[name,
NIL]
ELSE fbEnd ← fbEnd.rest ← CONS[name, NIL];
IF isFile
THEN status ← includesPrivateDL
ELSE
IF name.Find["^"] < 0
THEN countOfRecipients ← countOfRecipients + 1
ELSE IF status # includesPrivateDL THEN status ← includesPublicDL;
RETURN[NIL, FALSE];
END;
ParseNameList[pH, GetNextChar, AnotherRName,
NIL];
SELECT index
FROM
toF, ccF, cF, bccF =>
IF rList =
NIL
THEN rList ← fieldBody
ELSE rList ← RopeList.Append[rList, fieldBody];
ENDCASE => NULL;
END;
pH: ParseHandle;
field: ROPE ← NIL;
fieldNotRecognized: BOOL;
mPos ← 0; -- where we are in the fulltext
status ← ok; -- start with good status
pH ← InitializeParse[];
DO
sPos ← mPos;
[field, fieldNotRecognized] ← GetFieldName[pH, GetNextChar ! ParseError =>
{ FinalizeParse[pH]; GOTO errorExit}];
IF ~fieldNotRecognized THEN EXIT;
FOR i: MessageFieldIndex
IN MessageFieldIndex
DO
{ mLF ← messageParseArray[i];
IF Rope.Equal[messageParseArray[i].name, field,
FALSE]
THEN
{ fieldNotRecognized ←
FALSE;
IF mLF.fType = rNameList
THEN
RNameListField[i ! ParseError => GOTO errorExit]
ELSE [] ← GetFieldBody[pH, GetNextChar, TRUE];
};
};
ENDLOOP;
IF fieldNotRecognized
THEN
[] ← GetFieldBody[pH, GetNextChar, TRUE]; -- skip anything not recognized
ENDLOOP;
FinalizeParse[pH];
EXITS
errorExit => RETURN[syntaxError, sPos, mPos, NIL];
END;
CanonicalName:
PUBLIC
PROC [simpleName, registry:
ROPE]
RETURNS[name:
ROPE] =
BEGIN
name ← simpleName;
IF registry.Length[] = 0
THEN name ← name.Cat[".", WalnutSendOps.defaultRegistry]
ELSE name ← name.Cat[".", registry];
END;
procs moved out of WalnutSendcontrolImpl in the great gfi hackery
GetSendForm:
PUBLIC
PROC[fileName:
ROPE]
RETURNS[text: ViewerTools.TiogaContents] = {
s:
IO.
STREAM;
so: FS.StreamOptions ← FS.defaultStreamOptions;
so[tiogaRead] ← FALSE;
s ← FS.StreamOpen[fileName: fileName, streamOptions: so ! FS.Error => { s ← NIL; CONTINUE }];
IF s #
NIL
THEN
{ text ← TiogaTextFromStrm[s !
FS.Error => { text ←
NIL;
CONTINUE }];
s.Close[ ! FS.Error => { s ← NIL; CONTINUE }] }
};
InternalDisplayTioga:
PUBLIC
PROC[senderInfo: SenderInfo, tc: TiogaContents, grab:
BOOL] = {
really an INTERNAL PROC
senderV: Viewer ← senderInfo.senderV;
iHadFocus: BOOL ← InputFocus.GetInputFocus[].owner = senderV;
IF senderV.link # NIL THEN InternalDestroySplits[senderV];
IF TiogaOps.GetSelection[feedback].viewer = senderV THEN TiogaOps.CancelSelection[feedback];
ViewerTools.SetTiogaContents[senderV, tc];
-- test if I had the focus & no-one else has it now
IF grab
AND iHadFocus
AND InputFocus.GetInputFocus[].owner =
NIL
THEN
{ ViewerTools.SetSelection[senderV, viewerStart];
[] ← TiogaOps.NextPlaceholder[gotoend: FALSE] };
UnsetNewVersion[senderV];
senderInfo.successfullySent ← FALSE;
};
InsertForm:
PUBLIC
PROC[ sender: SenderInfo, form: WalnutSendOps.Form, force:
BOOL ] = {
senderV: Viewer = sender.senderV;
whoHasIt: Viewer;
IF senderV.iconic
THEN
{ ViewerOps.AddProp[senderV, $WalnutSendOpsForm, form];
sender.openEvent ← ViewerEvents.RegisterEventProc[
proc: OpenSendViewer, event: open, filter: senderV, before: FALSE];
UnsetNewVersion[senderV]; -- not strictly true but want to reuse
RETURN};
-- first stuff the text of the form into the Viewer
InternalDisplayTioga[sender, form.formText, FALSE];
IF force
OR ((whoHasIt ← InputFocus.GetInputFocus[].owner) =
NIL)
OR (whoHasIt = senderV) THEN DoPlaceHolders[senderV, form.fields]
ELSE
{ ViewerOps.AddProp[senderV, $WalnutSendOpsFields, form.fields];
sender.focusEvent ← ViewerEvents.RegisterEventProc[
proc: SetFocusInSendViewer, event: setInputFocus, filter: senderV, before: FALSE];
UnsetNewVersion[senderV]; -- not strictly true but want to reuse
};
};
OpenSendViewer: ViewerEvents.EventProc =
BEGIN
OPEN Menus;
senderInfo: SenderInfo ← NARROW[ViewerOps.FetchProp[viewer, $SenderInfo]];
form: WalnutSendOps.Form;
ra: REF ANY;
IF senderInfo = NIL THEN RETURN[FALSE];
IF (ra ← ViewerOps.FetchProp[viewer, $WalnutSendOpsForm]) = NIL THEN RETURN[FALSE];
form ← NARROW[ra];
ViewerEvents.UnRegisterEventProc[senderInfo.openEvent, open];
senderInfo.openEvent ← NIL;
TRUSTED { Process.Detach[FORK EntryInsertForm[senderInfo, form]]};
RETURN[FALSE];
END;
SetFocusInSendViewer: ViewerEvents.EventProc =
BEGIN
OPEN Menus;
senderInfo: SenderInfo ← NARROW[ViewerOps.FetchProp[viewer, $SenderInfo]];
fieldsList: LIST OF ROPE;
ra: REF ANY;
IF senderInfo = NIL THEN RETURN[FALSE];
IF (ra ← ViewerOps.FetchProp[viewer, $WalnutSendOpsFields]) = NIL THEN RETURN[FALSE];
fieldsList ← NARROW[ra];
ViewerEvents.UnRegisterEventProc[senderInfo.focusEvent, setInputFocus];
senderInfo.focusEvent ← NIL;
TRUSTED { Process.Detach[FORK EntryPlaceHolders[senderInfo, fieldsList]]};
RETURN[FALSE];
END;
DoPlaceHolders:
PUBLIC
PROC[senderV: Viewer, fieldsList:
LIST
OF
ROPE] = {
AddFields:
PROC[ root: TiogaOps.Ref ] = {
-- now try to find all of the placeholders in the text and match them with the entries in the fields list
FOR rl:
LIST
OF
ROPE ← fieldsList, rl.rest
UNTIL rl =
NIL
DO
field: ROPE = rl.first;
IF NOT TiogaOps.NextPlaceholder[gotoend: FALSE].found THEN EXIT;
IF field = NIL THEN LOOP;
TiogaOps.Delete[];
TiogaOps.InsertRope[field]
ENDLOOP };
IF senderV.destroyed OR senderV.iconic THEN RETURN; --woops
ViewerTools.SetSelection[ senderV, viewerStart];
TiogaOps.CallWithLocks[ AddFields ];
UnsetNewVersion[senderV];
ViewerTools.SetSelection[ senderV, viewerStart];
[] ← TiogaOps.NextPlaceholder[gotoend: FALSE];
};
END.