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 _ 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; 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] = { 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] = { rem: INT _ IO.GetLength[strm] - startPos; IF rem < len THEN len _ rem; IF len < 2048 THEN TRUSTED { nat: NAT _ len; text: Rope.Text _ Rope.NewText[nat]; IO.SetIndex[strm, startPos]; [] _ IO.GetBlock[strm, LOOPHOLE[text], 0, nat]; RETURN [text]; }; rem _ len/2; len _ len - rem; contents _ RopeFromStream[strm, startPos, len]; contents _ Rope.Concat[contents, RopeFromStream[strm, startPos+len, rem] ]; }; GetFieldBody: PUBLIC PROC[text, fieldName: ROPE] RETURNS[fieldBody: ROPE] = { 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] = { 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] }; ParseHeadersFromRope: PUBLIC PROC[headers: ROPE, proc: ParseProc] RETURNS[msgHeaders: MsgHeaders] = 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] = 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; 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] = { 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. ˆWalnutSendOpsImpl.mesa Copyright c 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 ************************************************************************ firstForm: Menus.MenuEntry = Menus.GetLine[formsMenu, 0]; firstForm: Menus.MenuEntry = Menus.GetLine[formsMenu, 0]; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * returns NIL IF endOfStream encountered during read 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. Short enough to make in one piece 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. Do this first to ensure that the evaluation order is OK. 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 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. the following are taken from the former WalnutParseMsgImpl proc is called for each fieldName encountered in the headers; if proc = NIL then all fields are returned 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 procs moved out of WalnutSendcontrolImpl in the great gfi hackery really an INTERNAL PROC ΚΡ˜šΟb™Jšœ Οmœ=™HIcode™*—J™Jšœ>™>Jšœ(™(J˜šΟk ˜ KšœŸœ˜JšŸœŸœ:˜BJšœ Ÿœ˜J˜ Jšœ Ÿœ˜!J˜JšŸœ˜JšœŸœ ˜J˜Jšœ Ÿœ ˜Jšœ Ÿœ˜!šœ Ÿœ%˜3J˜SJ˜QJ˜L—J˜ J˜Jšœ Ÿœ5˜GJ˜ J˜Jšœ˜J˜Jšœ˜Jšœ˜J˜JšœŸœŸ˜ šŸ˜JšœŸœŸœ(˜BJ˜JšœB˜BJšœJ˜J—šŸ˜JšœF˜F—JšŸœ˜—˜JšŸœŸœ3˜=—J˜JšœH™HJ˜JšŸœŸœŸœ˜JšœŸœ˜$JšŸœŸœŸœŸœ˜JšœŸœ˜0Jšœ"Ÿœ!˜FJ˜Jšœ ŸœŸœŸœ˜J˜JšœŸœŸœ%˜AJ˜šœŸœŸœŸœ˜BšœΟc/˜LJ˜J˜J˜J˜J˜J˜J˜J˜J˜J˜J˜J˜——J˜š ΟnœŸœŸœ ŸœŸœŸœ˜OJšœŸœ˜ Jšœ Ÿœ˜JšœŸœ˜ J˜Jš ‘ œŸœŸœŸœŸœŸœ˜NJ˜˜TJ˜!J˜—šŸœŸœ˜Jšœ Ÿœ ˜šŸœŸœ2Ÿœ˜HšŸœŸœ ˜#JšŸ˜—šœŸœŸœŸœ˜CJ˜—šŸœŸœ˜'JšŸ˜—šœŸœŸœŸœ˜=J˜—JšŸœ˜—JšŸœŸœŸœ$˜5J˜šœ!Ÿœ˜*Jšœ'Ÿœ0˜Z—JšŸœŸœŸœ˜#JšœO˜OJšœ˜—J˜šœ Ÿœ˜)J˜=—JšœŸœŸœ˜2J˜J˜ Jšœ˜—J˜š‘œŸœ ŸœŸœ ŸœŸœŸœ˜GJšœ ŸœŸœŸœ˜&Jšœ Ÿœ˜+Jšœ Ÿœ%˜3šŸœŸœŸœŸ˜J˜GJ˜.JšŸœŸœŸœŸœ.˜M—JšŸœ˜JšŸœŸœD˜Pšœ˜J˜——š ‘œŸœŸœŸœŸœŸœ˜SJ˜!JšŸœ9Ÿœ˜BJ˜TJ˜PJ˜ J˜JšœŸœ˜ JšœŸœŸœ˜2J˜J˜J˜ J˜&J˜"J˜9šŸœŸ˜ ˜3˜:JšœŸœ˜"—J˜"——J˜:JšŸœŸœŸœŸœ˜J˜1JšœXŸœ˜_J˜9J˜J˜J˜ Jšœ˜—J˜š‘œŸœŸœŸœ˜3JšœŸœ˜&šœ ˜ JšŸœOŸœ˜X—JšœŸœŸœ˜2J˜Jšœ˜šŸœGŸœŸœ˜VJšœ˜Jšœ˜J˜!J˜—šŸœIŸœŸœ˜XJšœ˜Jšœ˜J˜!J˜—šŸœGŸœŸœ˜VJšœ˜Jšœ˜J˜#J˜—J˜J˜J˜—š‘œŸœŸœ˜*JšŸœ ŸœŸœ Ÿœ˜!J˜J˜"Jšœ˜J˜—š‘œŸœŸœ˜1šŸœ˜ J˜J™9JšœŸœ˜!J˜J˜8JšŸœ ŸœŸœŸœ˜8J˜+JšŸœ ŸœŸœŸœ˜8J˜+JšŸœ ŸœŸœŸœ˜8J˜šŸœŸœŸ˜"J˜3JšŸœ ŸœŸœŸœ˜JšŸœ ŸœŸœŸœ˜JšŸœ+Ÿ˜1šŸœ)Ÿœ ŸœŸ˜@JšœŸœ˜JšŸœ˜—JšŸœ˜—Jšœ& ˜8JšœŸœ˜—Jšœ˜J˜—š‘œŸœŸœ˜0JšŸœ˜ J˜JšœŸœ˜!J™9J˜J˜8JšŸœ ŸœŸœŸœ˜9J˜+JšŸœ ŸœŸœŸœ˜9J˜+JšŸœ ŸœŸœŸœ˜9J˜šŸœŸœŸ˜"J˜3JšŸœ ŸœŸœŸœ˜JšŸœ ŸœŸœŸœ˜JšŸœ+Ÿ˜1šŸœ)Ÿœ ŸœŸ˜@JšœŸœ˜JšŸœ˜—JšŸœ˜—Jšœ& ˜:JšœŸœ˜J˜'Jšœ˜J˜—JšœE™EJ˜š‘œŸœŸœŸœŸœ Ÿœ ŸœŸœŸœŸœ˜}Kšœ2™2KšŸœŸœŸœ ˜'Kšœ Ÿœ$˜5Kšœ Ÿœ'˜5K˜Kšœ Ÿœ˜,K˜Kšœ7˜7KšŸœ#Ÿœ7˜`K˜KšŸœ ŸœŸœ˜Kšœ˜K˜—š‘œŸœŸœŸœŸœŸœ Ÿœ˜[KšœŸœŒ™§K˜KšœŸœŸœ˜)KšŸœ Ÿœ ˜šŸœ ŸœŸœ˜Kšœ!™!KšœŸœ˜K˜$KšŸœ˜KšœŸœŸœ˜/KšŸœ˜K˜—Kšœυ™υK˜ K˜šœ/˜/Kšœ8™8—KšœK˜KKšœ˜—J˜š ‘ œŸ œŸœŸœ Ÿœ˜MJšœ³™³šŸœ ˜JšœŸœ˜ Jšœ Ÿœ˜ Jšœ;˜;J˜Jš‘œŸœŸœŸœ˜%šœŸœŸœŸœ˜FJ˜—J˜J˜JšŸ˜JšœŸœŸœ˜šŸ˜JšœŸœŸœ˜&šœ˜Jšœ6Ÿœ˜K—JšŸœŸœŸœ˜J˜šŸœŸœŸœ ˜:šœ5Ÿœ˜:JšŸœ,Ÿœ˜6———JšŸœ˜J˜Jšœ˜šŸ˜Jšœ3ŸœŸœ˜@—JšŸœ˜—Jšœ˜J˜—š‘ œŸ œŸœŸœŸœŸœŸœ˜]Jšœ‚™‚JšœŸœ˜ J˜Jšœ Ÿœ˜šŸ˜Jšœ"Ÿœ ˜J—Jšœ*˜*JšŸœŸœŸ˜5šŸ˜šŸœŸ˜šœ˜JšŸœ Ÿ˜šœ(˜(Jšœ3Ÿœ˜OJšœ<˜<—JšŸœ"Ÿœ-Ÿœ ˜d—šœ˜JšŸœ Ÿ˜šœ(˜(JšœK˜KJšœ3Ÿœ˜O—JšŸœ"Ÿœ.Ÿœ ˜e—Jšœa˜aJšŸœŸœ˜—JšœŸœŸœŸœ˜