WalnutSendControlImpl.mesa - Control module for WalnutSend
Copyright © 1984, 1985 by Xerox Corporation. All rights reserved.
Donahue, October 7, 1983 9:42 am
Willie-Sue on: December 6, 1984 2:10:59 pm PST
Russ Atkinson (RRA) March 21, 1985 0:36:39 am PST
DIRECTORY
Commander USING [CommandProc, Register],
DefaultRemoteNames USING [Get],
FS USING [ComponentPositions, defaultStreamOptions, Error, OpenFile, StreamOptions, Close, ExpandName, Open, StreamOpen],
GVSend USING [Abort],
Icons USING [IconFlavor, NewIconFromFile],
InputFocus USING [GetInputFocus],
Menus,
IO,
Process USING [Detach],
Rope,
TEditSplit USING[ Split ],
TiogaMenuOps USING[ tiogaMenu, Empty, Load, Store ],
TiogaOps USING [ Ref, Location, CallWithLocks, CancelSelection, Delete, GetSelection, InsertRope, NextPlaceholder],
TypeScript USING [Create],
ViewerEvents USING [EventProc, EventRegistration, RegisterEventProc, UnRegisterEventProc],
ViewerOps,
ViewerIO USING [CreateViewerStreams, GetViewerFromStream],
ViewerTools,
ViewerClasses,
ViewerMenus USING [ Save ],
UserCredentials USING [Get],
UserProfile USING [Boolean, CallWhenProfileChanges, ProfileChangedProc, ListOfTokens, Token],
ViewerBLT USING[ ChangeNumberOfLines ],
WalnutSendInternal,
WalnutSendOps;
WalnutSendControlImpl:
CEDAR
MONITOR
IMPORTS Commander, DefaultRemoteNames, IO, Process, Rope, FS, GVSend, Icons, InputFocus, Menus, TEditSplit, TiogaMenuOps, TiogaOps, TypeScript, ViewerEvents, ViewerIO, ViewerMenus, ViewerOps, ViewerTools, UserCredentials, UserProfile, ViewerBLT, WalnutSendInternal, WalnutSendOps
EXPORTS WalnutSendInternal, WalnutSendOps
SHARES Menus = BEGIN OPEN WalnutSendOps, WalnutSendInternal;
ROPE: TYPE = Rope.ROPE;
TiogaContents: TYPE = ViewerTools.TiogaContents;
Viewer: TYPE = ViewerClasses.Viewer;
Global types & variables
userRName: PUBLIC ROPE← NIL; -- user name with regsitry
simpleUserName: PUBLIC ROPE← NIL; -- user name without registry
replyToSelf: PUBLIC BOOL← FALSE;
alwaysOpenSender: BOOL← FALSE;
sendCaption: PUBLIC ROPE← "Walnut Sender";
destroyAfterSend: BOOL← FALSE;
senderList: LIST OF SenderInfo← NIL;
sendMenu: PUBLIC Menus.Menu← Menus.CreateMenu[];
formsMenu: PUBLIC Menus.Menu;
sendingMenu: PUBLIC Menus.Menu← Menus.CreateMenu[];
blankMenu: PUBLIC Menus.Menu← Menus.CreateMenu[];
confirmMenu: PUBLIC Menus.Menu← Menus.CreateMenu[];
replyToMenu: PUBLIC Menus.Menu← Menus.CreateMenu[];
sendingSynch: CONDITION;
needToAuthenticate: PUBLIC BOOL← TRUE;
iconicSender: BOOL← TRUE; -- make iconic initially only
senderIcon: Icons.IconFlavor← tool;
edittingIcon: Icons.IconFlavor← tool;
reporter: IO.STREAM← NIL;
senderTS: Viewer← NIL;
labelFont: PUBLIC ROPE ← NIL; -- font to use for printing of header labels
senderEvent: ViewerEvents.EventRegistration;
defaultText: PUBLIC ViewerTools.TiogaContents;
answerText: PUBLIC ViewerTools.TiogaContents;
forwardText: PUBLIC ViewerTools.TiogaContents;
standardDefaultText: ViewerTools.TiogaContents =
NEW[ ViewerTools.TiogaContentsRec ← [
contents: "\nSubject: \001Topic\002\nTo: \001Recipients\002\nCc: \001Copies To\002\n\n\001Message\002\n\n\000",
formatting: "\000\000\000\006\000\000\235\312\000<\000\000\002\230\000J\232\002\320bs\007\234\t\230\020J\232\002\235\002\234\016\230\020J\232\002\235\002\234\n\230\017J\230\000J\230\012\227\000\205\227\000\000\000\000\000@\000\000\000\202\000\000"
] ];
standardAnswerText: ViewerTools.TiogaContents =
NEW[ ViewerTools.TiogaContentsRec ← [
contents: "\nSubject: \001Topic\002\nIn-reply-to: \001Message\002\nTo: \001Recipients\002\nCc: \001Copies To\002\n\n\001Message\002\n\n",
formatting: "\000\000\000\006\000\000\235\312\000E\000\000\002\230\000J\232\002\320bs\007\234\t\230\020J\232\002\235\013\234\013\230\026J\232\002\235\002\234\016\230\020J\232\002\235\002\234\n\230\017J\230\000J\230\012\227\000\205\227\000\000\000\000\000V\000\000\000\241\000\000"
] ];
standardForwardText: ViewerTools.TiogaContents =
NEW[ ViewerTools.TiogaContentsRec ← [
contents: "\nSubject: \001Topic\002\nTo: \001Recipients\002\nCc: \001Copies To\002\n\n\001CoveringMessage\002\n\n--------------------------------------\nForwardedMessage\n--------------------------------------\n\n\001ClosingComment\002\n",
formatting: "\000\000\000\006\000\000\235\312\000W\000\000\002\230\000J\232\002\320bs\007\234\t\230\020J\232\002\235\002\234\016\230\020J\232\002\235\002\234\n\230\017J\230\000J\230\021J\230\000\002\232\001\234&\230&J\230\020\227J\232\001\234&\230&J\230\000J\230\020\227\000\205\227\000\000\000\000\000\270\000\000\001\025\000\000"
] ];
defaultForm: WalnutSendOps.Form =
NEW[ WalnutSendOps.FormRec ← [ formText: defaultText, fields: LIST[ NIL, NIL, simpleUserName] ] ];
viewerStart: ViewerTools.SelPos = NEW[ViewerTools.SelPosRec← [0, 0]];
SenderPropRec: TYPE = RECORD[proc: PROC[Viewer, ATOM, REF ANY], data: REF ANY];
SenderPropValue: TYPE = REF SenderPropRec;
SenderProp: TYPE = RECORD[key: ATOM, value: SenderPropValue];
senderPropsList: LIST OF SenderProp← NIL;
FileStatus: TYPE = {illegalName, local, remote, localNotFound, remoteNotFound, otherError};
-- ************************************************************************
-- Sending messages is independent of CedarDB
WalnutSendMail: Commander.CommandProc = { WalnutSendProc[fromExec: TRUE]};
WalnutSendProc:
PUBLIC
PROC[fromExec:
BOOL] =
BEGIN
senderInfo: SenderInfo;
IF fromExec THEN fromExec← ~alwaysOpenSender;
IF ~fromExec THEN iconicSender← FALSE;
senderInfo← BuildSender[paint: TRUE, iconic: iconicSender, form: defaultForm];
IF ~iconicSender THEN GrabFocus[senderInfo.senderV];
iconicSender← FALSE; -- iconic only the first time
END;
GrabFocus:
PUBLIC ENTRY
PROC[senderV: Viewer] =
BEGIN
ENABLE
UNWIND =>
NULL;
IF senderV.iconic THEN RETURN;
ViewerTools.SetSelection[senderV, viewerStart];
[] ← TiogaOps.NextPlaceholder[gotoend: FALSE];
END;
-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
BuildSendViewer:
PUBLIC
PROC[paint, iconic:
BOOL, form: WalnutSendOps.Form ←
NIL, who: Viewer←
NIL]
RETURNS[v: Viewer] =
{ newForm: WalnutSendOps.Form =
IF form #
NIL
THEN form
ELSE defaultForm;
RETURN[BuildSender[paint, iconic, newForm, who].senderV] };
BuildSender:
ENTRY
PROC[paint, iconic:
BOOL, form: WalnutSendOps.Form, who: Viewer←
NIL, forceNew:
BOOL←
FALSE]
RETURNS[senderInfo: SenderInfo] =
BEGIN
ENABLE
UNWIND =>
IF senderInfo #
NIL
AND senderInfo.senderV #
NIL
THEN senderInfo.senderV.inhibitDestroy← FALSE;
senderV: Viewer;
IF senderIcon = tool
THEN
BEGIN
ENABLE
FS.Error =>
TRUSTED {
GOTO notFound};
senderIcon← Icons.NewIconFromFile["Walnut.icons", 6];
edittingIcon← Icons.NewIconFromFile["Walnut.icons", 9];
EXITS
notFound =>
BEGIN
ENABLE
FS.Error =>
TRUSTED {
CONTINUE};
remote: ROPE← DefaultRemoteNames.Get[].current;
full: ROPE← Rope.Concat[remote, "Walnut>Walnut.icons"];
senderIcon← Icons.NewIconFromFile[full, 6];
edittingIcon← Icons.NewIconFromFile[full, 9];
END;
END;
IF ~forceNew
THEN
FOR sL:
LIST
OF SenderInfo← senderList, sL.rest
UNTIL sL=
NIL
DO
senderInfo← sL.first;
IF ~(senderInfo.senderV.newVersion)
AND (senderInfo.sendHandle =
NIL)
AND ~senderInfo.senderV.destroyed
THEN
{ senderV← senderInfo.senderV;
IF senderV.link # NIL THEN InternalDestroySplits[senderV];
senderV.inhibitDestroy← TRUE;
KillFeedbackSel[senderV];
IF senderV.iconic
THEN
{ ClearOpenEvent[senderInfo];
IF (who #
NIL)
AND (senderV.column = who.column)
THEN
{ ViewerOps.OpenIcon[icon: senderV, paint:
FALSE];
ViewerOps.MoveBelowViewer[altered: senderV, static: who, paint: TRUE] }
ELSE ViewerOps.OpenIcon[senderV];
};
IF form #
NIL
THEN InsertForm[senderInfo, form,
TRUE]
ELSE TiogaMenuOps.Empty[senderV];
ViewerOps.PaintViewer[senderV, menu];
senderV.inhibitDestroy← FALSE;
RETURN };
ENDLOOP;
-- need to create a new sender
senderV← ViewerOps.CreateViewer[
flavor: $Text,
info: [ name: sendCaption,
column: IF who#NIL THEN who.column ELSE left,
iconic: iconic,
icon: senderIcon],
paint: who = NIL];
IF who #
NIL
THEN
{ViewerOps.MoveBelowViewer[altered: senderV, static: who, paint:
FALSE];
ViewerOps.ComputeColumn[column: senderV.column, paint: TRUE] };
senderV.inhibitDestroy← TRUE;
ViewerOps.SetMenu[senderV, sendMenu];
senderInfo← NEW[SenderInfoObject← [senderV: senderV]];
ViewerOps.AddProp[senderV, $SenderInfo, senderInfo];
FOR spl:
LIST
OF SenderProp← senderPropsList, spl.rest
UNTIL spl=
NIL
DO
ViewerOps.AddProp[senderV, spl.first.key, spl.first.value];
ENDLOOP;
senderList← CONS[senderInfo, senderList];
senderInfo.destroyEvent← ViewerEvents.RegisterEventProc[
proc: DestroySendViewer, event: destroy, filter: senderV, before: TRUE];
senderInfo.closeEvent← ViewerEvents.RegisterEventProc[
proc: CloseSendViewer, event: close, filter: senderV, before: TRUE];
senderInfo.editEvent← ViewerEvents.RegisterEventProc[
proc: FirstEditSendViewer, event: edit, filter: senderV, before: TRUE];
IF form # NIL THEN InsertForm[senderInfo, form, TRUE] ELSE
TiogaMenuOps.Empty[senderV];
ViewerOps.PaintViewer[senderV, menu]; -- shouldn't be necessary
senderV.inhibitDestroy← FALSE;
IF iconic AND ~senderV.iconic THEN ViewerOps.PaintViewer[senderV, all];
END;
ClearOpenEvent:
PROC[senderInfo: SenderInfo] =
BEGIN
ra: REF ANY← ViewerOps.FetchProp[senderInfo.senderV, $WalnutSendOpsForm];
form: WalnutSendOps.Form;
IF ra = NIL THEN RETURN;
form← NARROW[ra];
ViewerEvents.UnRegisterEventProc[senderInfo.openEvent, open];
senderInfo.openEvent← NIL;
END;
-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
WalnutSendInit: UserProfile.ProfileChangedProc =
{ uN:
ROPE = UserCredentials.Get[].name;
pos: INT;
defaultFormFile: ROPE = UserProfile.Token[key: "WalnutSend.DefaultForm", default: NIL];
answerFormFile: ROPE = UserProfile.Token[key: "WalnutSend.AnswerForm", default: NIL];
forwardFormFile: ROPE = UserProfile.Token[key: "WalnutSend.ForwardForm", default: NIL];
formContents: ViewerTools.TiogaContents← NIL;
IF (pos← Rope.SkipTo[s: uN, skip: "."]) = Rope.Length[uN]
THEN {simpleUserName← uN; userRName← Rope.Cat[uN, ".", defaultRegistry]}
ELSE {simpleUserName← Rope.Substr[uN, 0, pos]; userRName← uN};
needToAuthenticate← TRUE;
replyToSelf← UserProfile.Boolean[key: "Walnut.ReplyToSelf", default: FALSE];
destroyAfterSend← UserProfile.Boolean[key: "Walnut.DestroyAfterSend", default: FALSE];
alwaysOpenSender←
UserProfile.Boolean[key: "WalnutSend.AlwaysOpenSender", default: FALSE];
IF defaultFormFile # NIL THEN formContents ← GetSendForm[defaultFormFile];
IF formContents #
NIL
THEN
{ defaultText← defaultForm.formText← formContents; defaultForm.fields← NIL }
ELSE
{ defaultText ← defaultForm.formText ← standardDefaultText;
defaultForm.fields← LIST[NIL, NIL, simpleUserName]};
formContents← NIL;
IF answerFormFile # NIL THEN formContents ← GetSendForm[answerFormFile];
IF formContents #
NIL
THEN answerText ← formContents
ELSE answerText← standardAnswerText;
formContents← NIL;
IF forwardFormFile # NIL THEN formContents ← GetSendForm[forwardFormFile];
IF formContents #
NIL
THEN forwardText ← formContents
ELSE forwardText← standardForwardText;
labelFont ← UserProfile.Token[key: "WalnutSend.labelFont", default: NIL];
SetFormsMenu[];
};
GetSendForm:
PROC[ fileName:
ROPE ]
RETURNS[ text: ViewerTools.TiogaContents ] =
BEGIN
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 }] }
END;
DisplayTxtInSender:
ENTRY
PROC[senderV: Viewer, txt:
ROPE] =
BEGIN
ENABLE
UNWIND =>
NULL;
senderInfo: SenderInfo← NARROW[ViewerOps.FetchProp[senderV, $SenderInfo]];
InternalDisplayTxt[senderInfo, txt];
END;
InternalDisplayTxt:
INTERNAL
PROC[senderInfo: SenderInfo, txt:
ROPE] =
BEGIN
tc: ViewerTools.TiogaContents← NEW[ViewerTools.TiogaContentsRec];
tc.contents← txt;
InternalDisplayTioga[senderInfo, tc, TRUE];
END;
InternalDisplayTioga:
INTERNAL
PROC[senderInfo: SenderInfo, tc: TiogaContents, grab:
BOOL] =
BEGIN
senderV: Viewer← senderInfo.senderV;
iHadFocus: BOOL← InputFocus.GetInputFocus[].owner = senderV;
IF senderV.link # NIL THEN InternalDestroySplits[senderV];
KillFeedbackSel[senderV];
ViewerTools.SetTiogaContents[senderV, tc];
ViewerOps.PaintViewer[senderV, menu]; -- shouldn't be necessary
-- 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;
END;
ClearSendViewer:
PUBLIC
ENTRY
PROC[senderV: Viewer] =
{
ENABLE
UNWIND =>
NULL;
senderInfo: SenderInfo← NARROW[ViewerOps.FetchProp[senderV, $SenderInfo]];
IF senderInfo = NIL THEN RETURN; -- not a sender
InsertForm[senderInfo, defaultForm, TRUE] };
StuffForm:
PUBLIC
ENTRY
PROC[v: Viewer, contents: TiogaContents] =
{
ENABLE
UNWIND =>
NULL;
senderInfo: SenderInfo← NARROW[ViewerOps.FetchProp[v, $SenderInfo]];
IF senderInfo = NIL THEN RETURN; -- not a sender
InternalDisplayTioga[senderInfo, contents, TRUE] };
InsertForm:
INTERNAL
PROC[ sender: SenderInfo, form: WalnutSendOps.Form, force:
BOOL ] =
BEGIN
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
};
END;
DoPlaceHolders:
INTERNAL PROC[senderV: Viewer, fieldsList:
LIST
OF
ROPE] =
BEGIN
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;
AddToSenderProps:
PUBLIC
ENTRY
PROC
[key:
ATOM, proc:
PROC[Viewer,
ATOM,
REF
ANY], data:
REF
ANY] = {
ENABLE
UNWIND =>
NULL;
senderPropsList←
CONS[[key: key, value: NEW[SenderPropRec← [proc, data]]], senderPropsList];
};
RemoveFromSenderProps:
PUBLIC
ENTRY
PROC[key:
ATOM] = {
ENABLE UNWIND => NULL;
sp: LIST OF SenderProp;
IF senderPropsList = NIL THEN RETURN;
FOR spl:
LIST
OF SenderProp← senderPropsList, spl.rest
UNTIL spl =
NIL
DO
IF spl.first.key = key
THEN
IF spl = senderPropsList
THEN
{ senderPropsList← senderPropsList.rest; RETURN}
ELSE
{ sp.rest← spl.rest; RETURN};
sp← spl;
ENDLOOP;
};
AddToSendMenu:
PUBLIC ENTRY PROC[label:
ROPE, proc: Menus.MenuProc] =
BEGIN
ENABLE
UNWIND =>
NULL;
Menus.AppendMenuEntry[sendMenu, Menus.CreateEntry[label, proc]];
END;
RemoveFromSendMenu:
PUBLIC PROC[name:
ROPE] =
removes the named button from the sender menu, if such a button exists; no errors
BEGIN
old: Menus.MenuEntry← Menus.FindEntry[sendMenu, name];
IF old = NIL THEN RETURN;
Menus.ReplaceMenuEntry[sendMenu, old ! Menus.targetNotFound => CONTINUE];
END;
-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-- Menu procedures
NewMsgFormProc:
ENTRY Menus.MenuProc =
BEGIN
ENABLE
UNWIND =>
NULL;
viewer: Viewer← NARROW[parent];
NewMsgForm[viewer]
END;
PrevMsgProc:
ENTRY Menus.MenuProc =
BEGIN
ENABLE
UNWIND =>
NULL;
viewer: Viewer← NARROW[parent];
senderInfo: SenderInfo← NARROW[ViewerOps.FetchProp[viewer, $SenderInfo]];
IF viewer.link#NIL THEN InternalDestroySplits[viewer];
ClearFileAssoc[viewer];
IF senderInfo.prevMsg = NIL THEN InsertForm[senderInfo, defaultForm, TRUE]
ELSE
{ KillFeedbackSel[viewer];
ViewerTools.SetTiogaContents[viewer, senderInfo.prevMsg];
ViewerOps.PaintViewer[viewer, menu]; -- shouldn't be necessary
UnsetNewVersion[viewer] }
END;
NewSenderProc: Menus.MenuProc =
BEGIN
senderInfo: SenderInfo←
BuildSender[paint: TRUE, iconic: FALSE, form: defaultForm, forceNew: TRUE];
GrabFocus[senderInfo.senderV];
END;
NewMsgForm:
INTERNAL PROC[viewer: Viewer] =
BEGIN
senderInfo: SenderInfo← NARROW[ViewerOps.FetchProp[viewer, $SenderInfo]];
InsertForm[senderInfo, defaultForm, TRUE];
ClearFileAssoc[viewer];
END;
-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
DestroySendViewer: ViewerEvents.EventProc =
BEGIN
senderInfo: SenderInfo← NARROW[ViewerOps.FetchProp[viewer, $SenderInfo]];
IF senderInfo = NIL THEN RETURN[FALSE];
IF senderInfo.senderV # NIL THEN senderInfo.senderV.file ← NIL;
-- make sure that there is no file pointed to so that you don't get something saved in UnSavedDocumentCache
IF ViewerOps.FetchProp[viewer, $DestroySenderProc] = NIL THEN RETURN[FALSE];
TRUSTED {Process.Detach[FORK CheckForDestroy[viewer, senderInfo]]};
RETURN[TRUE];
END;
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;
EntryInsertForm:
ENTRY
PROC[senderInfo: SenderInfo, form: WalnutSendOps.Form] =
BEGIN
ENABLE
UNWIND =>
NULL;
InsertForm[senderInfo, form, TRUE];
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;
EntryPlaceHolders:
ENTRY
PROC[senderInfo: SenderInfo, fieldsList:
LIST
OF
ROPE] =
BEGIN
ENABLE
UNWIND =>
NULL;
DoPlaceHolders[senderInfo.senderV, fieldsList];
END;
CloseSendViewer: ViewerEvents.EventProc =
BEGIN
senderInfo: SenderInfo← NARROW[ViewerOps.FetchProp[viewer, $SenderInfo]];
IF senderInfo = NIL THEN RETURN[FALSE];
IF senderInfo.dontClose
THEN
{ SenderReport[" **** Sender cannot be closed right now\n",
TRUE];
RETURN[TRUE]
};
viewer.icon← IF viewer.newVersion THEN edittingIcon ELSE senderIcon;
RETURN[FALSE];
END;
FirstEditSendViewer: ViewerEvents.EventProc =
BEGIN
OPEN Menus;
senderInfo: SenderInfo← NARROW[ViewerOps.FetchProp[viewer, $SenderInfo]];
IF senderInfo = NIL THEN RETURN[FALSE];
SenderNewVersion[viewer]; -- show as guarded
RETURN[FALSE];
END;
CheckForDestroy:
ENTRY
PROC[senderV: Viewer, senderInfo: SenderInfo] =
BEGIN
ENABLE
UNWIND =>
NULL;
okToDestroy: BOOL← TRUE;
IF senderV.destroyed THEN RETURN; -- woops
IF senderV.newVersion
THEN
{InternalReport["\nConfirm quitting with last message unsent"];
IF okToDestroy← SendingConfirm[senderInfo] THEN senderV.newVersion← FALSE;
};
IF ~okToDestroy THEN RETURN;
IF senderInfo.destroyEvent #
NIL
THEN
ViewerEvents.UnRegisterEventProc[senderInfo.destroyEvent, destroy];
IF senderInfo.closeEvent #
NIL
THEN
ViewerEvents.UnRegisterEventProc[senderInfo.closeEvent, close];
IF senderInfo.editEvent #
NIL
THEN
ViewerEvents.UnRegisterEventProc[senderInfo.editEvent, edit];
IF senderInfo.openEvent #
NIL
THEN
ViewerEvents.UnRegisterEventProc[senderInfo.openEvent, open];
IF senderInfo.focusEvent #
NIL
THEN
ViewerEvents.UnRegisterEventProc[senderInfo.focusEvent, setInputFocus];
senderInfo.destroyEvent← NIL;
senderInfo.closeEvent← NIL;
senderInfo.editEvent← NIL;
senderInfo.openEvent← NIL;
senderInfo.focusEvent← NIL;
TRUSTED {Process.Detach[FORK ViewerOps.DestroyViewer[senderV]]};
END;
LockViewerOpen:
PUBLIC
ENTRY
PROC[viewer: Viewer] =
BEGIN
ENABLE
UNWIND =>
NULL;
senderInfo: SenderInfo← NARROW[ViewerOps.FetchProp[viewer, $SenderInfo]];
IF senderInfo = NIL THEN RETURN;
senderInfo.dontClose← TRUE;
END;
ReleaseOpenViewer:
PUBLIC
ENTRY
PROC[viewer: Viewer] =
BEGIN
ENABLE
UNWIND =>
NULL;
senderInfo: SenderInfo← NARROW[ViewerOps.FetchProp[viewer, $SenderInfo]];
IF senderInfo = NIL THEN RETURN;
senderInfo.dontClose← FALSE;
END;
SenderReport:
PUBLIC
ENTRY
PROC[msg:
ROPE, blink:
BOOL←
FALSE] =
BEGIN
ENABLE
UNWIND =>
NULL;
InternalReport[msg, blink];
END;
InternalReport:
INTERNAL
PROC[msg:
ROPE, blink:
BOOL←
FALSE] =
BEGIN
IF reporter =
NIL
THEN
{ senderTS← TypeScript.Create[
info: [name: "WalnutSend", iconic: FALSE, column: right, openHeight: 64]];
reporter← ViewerIO.CreateViewerStreams[NIL, senderTS].out;
senderEvent← ViewerEvents.RegisterEventProc[
proc: DestroyReporter, event: destroy, filter: senderTS, before: TRUE];
};
IF blink
THEN
{ viewer: Viewer = ViewerIO.GetViewerFromStream[reporter];
-- NIL for $Dribble class
IF viewer # NIL THEN ViewerOps.BlinkIcon[viewer];
};
reporter.PutRope[msg];
END;
DestroyReporter: ViewerEvents.EventProc =
BEGIN
IF senderEvent # NIL THEN ViewerEvents.UnRegisterEventProc[senderEvent, destroy];
senderEvent← NIL;
senderTS← NIL;
reporter← NIL;
RETURN[FALSE];
END;
RegisterReporter:
PUBLIC
ENTRY
PROC[strm:
IO.
STREAM] =
BEGIN
ENABLE
UNWIND =>
NULL;
IF senderTS #
NIL
THEN
{ reporter.Close[];
ViewerEvents.UnRegisterEventProc[senderEvent, destroy];
senderEvent← NIL;
ViewerOps.DestroyViewer[senderTS];
senderTS← NIL;
};
reporter← strm;
END;
UnregisterReporter:
PUBLIC
ENTRY
PROC[strm:
IO.
STREAM] =
BEGIN
ENABLE
UNWIND =>
NULL;
IF reporter = strm THEN reporter← NIL;
END;
DenySendProc:
ENTRY Menus.MenuProc =
BEGIN
ENABLE
UNWIND =>
NULL;
viewer: Viewer← NARROW[parent];
senderInfo: SenderInfo← NARROW[ViewerOps.FetchProp[viewer, $SenderInfo]];
senderInfo.confirmed← FALSE;
senderInfo.userResponded← TRUE;
BROADCAST sendingSynch;
END;
ConfirmSendProc:
ENTRY Menus.MenuProc =
BEGIN
ENABLE
UNWIND =>
NULL;
viewer: Viewer← NARROW[parent];
senderInfo: SenderInfo← NARROW[ViewerOps.FetchProp[viewer, $SenderInfo]];
senderInfo.confirmed← TRUE;
senderInfo.userResponded← TRUE;
BROADCAST sendingSynch;
END;
AbortSendProc:
ENTRY Menus.MenuProc =
BEGIN
ENABLE
UNWIND =>
NULL;
viewer: Viewer← NARROW[parent];
senderInfo: SenderInfo← NARROW[ViewerOps.FetchProp[viewer, $SenderInfo]];
ViewerOps.SetMenu[viewer, blankMenu];
senderInfo.aborted← TRUE;
END;
ReplyToSelfProc:
ENTRY Menus.MenuProc =
BEGIN
ENABLE
UNWIND =>
NULL;
viewer: Viewer← NARROW[parent];
senderInfo: SenderInfo← NARROW[ViewerOps.FetchProp[viewer, $SenderInfo]];
senderInfo.replyToResponse← self;
senderInfo.userResponded← TRUE;
BROADCAST sendingSynch;
END;
ReplyToAllProc:
ENTRY Menus.MenuProc =
BEGIN
ENABLE
UNWIND =>
NULL;
viewer: Viewer← NARROW[parent];
senderInfo: SenderInfo← NARROW[ViewerOps.FetchProp[viewer, $SenderInfo]];
senderInfo.replyToResponse← all;
senderInfo.userResponded← TRUE;
BROADCAST sendingSynch;
END;
ReplyToCancelProc:
ENTRY Menus.MenuProc =
BEGIN
ENABLE
UNWIND =>
NULL;
viewer: Viewer← NARROW[parent];
senderInfo: SenderInfo← NARROW[ViewerOps.FetchProp[viewer, $SenderInfo]];
senderInfo.replyToResponse← cancel;
senderInfo.userResponded← TRUE;
BROADCAST sendingSynch;
END;
----------------------------
ReplyToResponse:
PUBLIC
ENTRY
PROC[senderInfo: SenderInfo]
RETURNS [HowToReplyTo] =
BEGIN
ENABLE
UNWIND =>
NULL;
senderV: Viewer← senderInfo.senderV;
ViewerOps.BlinkIcon[senderV, IF senderV.iconic THEN 0 ELSE 1];
UNTIL senderInfo.userResponded DO WAIT sendingSynch; ENDLOOP;
senderInfo.userResponded← FALSE;
RETURN[senderInfo.replyToResponse]
END;
CheckForAbortSend:
PUBLIC
ENTRY
PROC[senderInfo: SenderInfo]
RETURNS[
BOOL] =
BEGIN
ENABLE
UNWIND =>
NULL;
IF senderInfo.aborted
THEN
{ InternalReport[" Aborting delivery ...\n"];
IF senderInfo.sendHandle#NIL THEN GVSend.Abort[senderInfo.sendHandle];
IF senderInfo.aborted THEN senderInfo.senderV.inhibitDestroy← FALSE;
};
RETURN[senderInfo.aborted];
END;
Confirmation:
PUBLIC
ENTRY
PROC[senderInfo: SenderInfo]
RETURNS [
BOOL] =
{ ENABLE UNWIND => NULL; RETURN[SendingConfirm[senderInfo]]};
SendingConfirm:
INTERNAL
PROC[senderInfo: SenderInfo]
RETURNS [
BOOL] =
BEGIN
senderV: Viewer← senderInfo.senderV;
oldM: Menus.Menu← ChangeMenu[senderV, confirmMenu];
ViewerOps.BlinkIcon[senderV, IF senderV.iconic THEN 0 ELSE 1];
UNTIL senderInfo.userResponded DO WAIT sendingSynch; ENDLOOP;
ViewerOps.SetMenu[senderV, oldM];
senderInfo.userResponded← FALSE;
RETURN[senderInfo.confirmed];
END;
ChangeMenu:
PROC[v: Viewer, menu: Menus.Menu]
RETURNS [oldM: Menus.Menu] =
BEGIN
oldM← v.menu;
ViewerOps.SetMenu[v, menu];
END;
MessageSendProc:
PUBLIC Menus.MenuProc =
BEGIN
self: Viewer← NARROW[parent];
iHadFocus: BOOL← InputFocus.GetInputFocus[].owner = self;
ViewerTools.InhibitUserEdits[self];
IF self.link#NIL THEN DestroySplits[self];
TRUSTED {Process.Detach[FORK SendProc[self, mouseButton, iHadFocus]]};
END;
SendProc:
PROC[viewer: Viewer, mouseButton: Menus.MouseButton, iHadFocus:
BOOL] =
BEGIN
OPEN ViewerOps;
sendOK: BOOL;
senderInfo: SenderInfo = NARROW[ViewerOps.FetchProp[viewer, $SenderInfo]];
viewer.newVersion← TRUE;
viewer.name← "Sending ...";
PaintViewer[viewer, caption];
viewer.file← NIL;
sendOK← WalnutSendOps.Send[v: viewer, doClose: mouseButton=blue];
IF ~sendOK THEN SenderNewVersion[viewer];
viewer.name← sendCaption;
PaintViewer[viewer, caption];
viewer.icon← senderIcon;
ViewerTools.EnableUserEdits[viewer];
IF sendOK
THEN
{
AfterSendInsert:
ENTRY
PROC =
BEGIN
ENABLE
UNWIND =>
NULL;
InsertForm[senderInfo, defaultForm, FALSE];
END;
IF mouseButton=blue AND destroyAfterSend THEN {DestroyViewer[viewer]; RETURN};
UnsetNewVersion[viewer];
AfterSendInsert[];
};
END;
SenderSplitProc: Menus.MenuProc =
BEGIN
self: Viewer = NARROW[parent];
newV: Viewer;
nsI, senderInfo: SenderInfo;
TEditSplit.Split[self];
-- now find the newest link in the chain to copy properties
FOR newV ← self.link, newV.link UNTIL newV.link = self DO ENDLOOP;
senderInfo← NARROW[ViewerOps.FetchProp[self, $SenderInfo]];
ViewerOps.AddProp[newV, $SenderInfo, nsI← NEW[SenderInfoObject← senderInfo^]];
nsI.senderV← newV;
nsI.destroyEvent← ViewerEvents.RegisterEventProc[
proc: DestroySendViewer, event: destroy, filter: newV, before: TRUE];
nsI.closeEvent← ViewerEvents.RegisterEventProc[
proc: CloseSendViewer, event: close, filter: newV, before: TRUE];
nsI.editEvent← ViewerEvents.RegisterEventProc[
proc: FirstEditSendViewer, event: edit, filter: newV, before: TRUE];
newV.icon ← self.icon
END;
DestroySplits:
ENTRY
PROC[keepThisOne: Viewer] =
BEGIN
ENABLE
UNWIND =>
NULL;
InternalDestroySplits[keepThisOne];
END;
InternalDestroySplits:
INTERNAL
PROC[keepThisOne: Viewer] =
BEGIN
next: Viewer← keepThisOne.link;
next2: Viewer;
sI: SenderInfo;
DO
IF next = keepThisOne THEN EXIT;
sI← NARROW[ViewerOps.FetchProp[next, $SenderInfo]];
IF sI #
NIL
THEN
{
IF sI.destroyEvent #
NIL
THEN
ViewerEvents.UnRegisterEventProc[sI.destroyEvent, destroy];
IF sI.closeEvent # NIL THEN ViewerEvents.UnRegisterEventProc[sI.closeEvent, close];
IF sI.editEvent # NIL THEN ViewerEvents.UnRegisterEventProc[sI.editEvent, edit];
sI.destroyEvent← NIL;
sI.closeEvent← NIL;
sI.editEvent← NIL;
};
next2← next.link;
IF sI # NIL THEN ViewerOps.DestroyViewer[next]; -- DON'T FORK here
next← next2;
ENDLOOP;
-- make sure this sender is on the list of senders
FOR sL:
LIST
OF SenderInfo← senderList, sL.rest
UNTIL sL=
NIL
DO
IF sL.first.senderV = keepThisOne THEN RETURN;
ENDLOOP;
sI← NARROW[ViewerOps.FetchProp[keepThisOne, $SenderInfo]];
IF sI = NIL THEN RETURN;
senderList← CONS[sI, senderList];
END;
SenderPlacesProc: Menus.MenuProc =
Menus.CopyEntry[ Menus.FindEntry[ TiogaMenuOps.tiogaMenu, "Places" ] ].proc;
SenderLevelsProc: Menus.MenuProc =
Menus.CopyEntry[ Menus.FindEntry[ TiogaMenuOps.tiogaMenu, "Levels" ] ].proc;
SenderGetFormProc:
ENTRY Menus.MenuProc =
BEGIN
ENABLE
UNWIND =>
NULL;
self: Viewer = NARROW[parent];
filename: ROPE ← NARROW[clientData];
fileStatus: FileStatus;
exp, fName: ROPE;
IF filename.Length[] = 0 THEN RETURN;
[fileStatus, exp, fName]← CheckForFile[filename];
IF fileStatus = local
OR fileStatus = remote
THEN
{ tc: ViewerTools.TiogaContents← GetSendForm[fName];
senderInfo: SenderInfo← NARROW[ViewerOps.FetchProp[self, $SenderInfo]];
InternalDisplayTioga[senderInfo, tc, TRUE];
ClearFileAssoc[self];
}
ELSE {InternalReport["\n", TRUE]; InternalReport[exp]; InternalReport["\n"]};
END;
SenderGetFileProc: Menus.MenuProc =
BEGIN
self: Viewer = NARROW[parent];
filename: ROPE ← ViewerTools.GetSelectionContents[];
IF filename.Length[] = 0 THEN RETURN;
LoadFile[self, filename];
END;
SenderFormsProc: Menus.MenuProc =
BEGIN
viewer: Viewer = NARROW[parent];
menu: Menus.Menu ← viewer.menu;
found: BOOLEAN ← FALSE;
firstForm: Menus.MenuEntry = Menus.GetLine[formsMenu, 0];
numLines: Menus.MenuLine = Menus.GetNumberOfLines[menu];
FOR i: Menus.MenuLine
IN [1..numLines)
DO
-- see if already showing the submenu
IF firstForm = NIL OR
(Menus.GetLine[menu,i] # NIL AND
Rope.Equal[Menus.GetLine[menu,i].name, firstForm.name])
THEN {
-- yes, so remove it
FOR j: Menus.MenuLine
IN (i..numLines)
DO
Menus.SetLine[menu, j-1, Menus.GetLine[menu, j]];
ENDLOOP;
menu.linesUsed ← menu.linesUsed-1;
found ← TRUE; EXIT };
ENDLOOP;
IF ~found
THEN
{ Menus.SetLine[menu, numLines, Menus.GetLine[Menus.CopyMenu[formsMenu], 0]] };
ViewerBLT.ChangeNumberOfLines[viewer, menu.linesUsed];
IF ~found AND viewer.newVersion THEN SenderNewVersion[viewer];
END;
SenderStoreProc: Menus.MenuProc =
BEGIN
self: Viewer← NARROW[parent];
fileName: Rope.ROPE = ViewerTools.GetSelectionContents[];
status: FileStatus;
exp: Rope.ROPE;
[status, exp]← CheckForFile[fileName];
SELECT status
FROM
local, localNotFound =>
{ TiogaMenuOps.Store[self, fileName];
IF ~self.newVersion THEN UnsetNewVersion[self] };
ENDCASE => BadSaveOrStore[status, exp, fileName];
END;
SenderSaveProc: Menus.MenuProc =
BEGIN
self: Viewer← NARROW[parent];
status: FileStatus;
exp: Rope.ROPE;
IF self.file = NIL THEN {SenderReport["\nCan't save - no file name\n"]; RETURN};
[status, exp]← CheckForFile[self.name];
SELECT status
FROM
local, localNotFound =>
{ ViewerMenus.Save[ self ]; IF ~self.newVersion THEN UnsetNewVersion[self] };
ENDCASE => BadSaveOrStore[status, exp, self.name];
END;
-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
LoadFile:
PROC[self: Viewer, filename:
ROPE, noName:
BOOL←
FALSE] =
BEGIN
status: FileStatus;
s, fName: ROPE;
[status, s, fName]← CheckForFile[filename];
IF status = local
OR status = remote
THEN
{ self.file ←
NIL;
-- make sure that no one else thinks you're saving something
TiogaMenuOps.Load[ self, fName ];
ViewerTools.EnableUserEdits[ self ]; -- in case remote file specified
IF noName
THEN
{ self.name← sendCaption;
ViewerOps.PaintViewer[self, caption];
self.file← NIL; -- don't want any file association
};
GrabFocus[self];
UnsetNewVersion[self];
}
ELSE ErrorReport[s];
END;
CheckForFile:
PROC[name:
ROPE]
RETURNS[fileStatus: FileStatus, explanation, fName:
ROPE] =
BEGIN
of: FS.OpenFile;
cp: FS.ComponentPositions;
explanation← NIL;
fName← name;
BEGIN
ENABLE
FS.Error =>
{
IF error.group = user
THEN
{
SELECT error.code
FROM
$illegalName => fileStatus← illegalName;
$unknownFile =>
fileStatus← IF fileStatus = local THEN localNotFound ELSE remoteNotFound;
ENDCASE => fileStatus← otherError}
ELSE fileStatus← otherError;
explanation← error.explanation;
CONTINUE
};
cp← FS.ExpandName[name, NIL].cp;
IF explanation # NIL THEN RETURN;
fName← IF cp.ext.length = 0 THEN name.Concat[".form"] ELSE name;
IF cp.server.length = 0 THEN fileStatus← local ELSE fileStatus← remote;
of← FS.Open[fName];
IF of # NIL THEN FS.Close[of];
END;
END;
ErrorReport:
ENTRY
PROC[exp:
ROPE] =
{
ENABLE
UNWIND =>
NULL;
InternalReport["\n", TRUE];
InternalReport[exp];
InternalReport["\n"];
};
BadSaveOrStore:
PROC[status: FileStatus, exp, name:
ROPE] =
BEGIN
SELECT status
FROM
remote, remoteNotFound =>
ErrorReport[Rope.Cat["Can't store on remote file: \"", name, "\""]];
illegalName, otherError => ErrorReport[exp];
ENDCASE => ERROR;
END;
-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-- bug in SetTiogaContents necessitates this
KillFeedbackSel:
PROC[v: Viewer] =
BEGIN
OPEN TiogaOps;
who: Viewer← GetSelection[feedback].viewer;
IF who = v THEN CancelSelection[feedback];
END;
-- read the user profile line for message forms and add one menu entry for each
SetFormsMenu:
PROC =
BEGIN
OPEN Menus;
forms: LIST OF ROPE = UserProfile.ListOfTokens[key: "Walnut.MsgForms", default: NIL];
formsMenu← Menus.CreateMenu[];
FOR fl:
LIST
OF
ROPE ← forms, fl.rest
UNTIL fl =
NIL
DO
form: ROPE ← fl.first;
shortName, fullName: ROPE;
cp: FS.ComponentPositions;
[fullName, cp, ]← FS.ExpandName[form, NIL];
-- if the file is a remote one, then strip off the leading server/directory info
shortName← fullName.Substr[cp.base.start, cp.base.length];
-- add ".form" if one does not exist
IF cp.ext.length = 0 THEN form← form.Concat[".form"];
Menus.AppendMenuEntry[formsMenu, CreateEntry[shortName, SenderGetFormProc, form]]
ENDLOOP;
END;
-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-- Start code: create menus
BEGIN
OPEN Menus;
AppendMenuEntry[sendMenu,
CreateEntry[name: "Send", proc: MessageSendProc, fork: FALSE]];
AppendMenuEntry[sendMenu, CreateEntry["Get", SenderGetFileProc]];
AppendMenuEntry[sendMenu,
CreateEntry[name: "Store", proc: SenderStoreProc, guarded: TRUE]];
AppendMenuEntry[sendMenu, CreateEntry["Save", SenderSaveProc]];
AppendMenuEntry[sendMenu, CreateEntry["Forms", SenderFormsProc]];
AppendMenuEntry[sendMenu, CreateEntry["New", NewSenderProc]];
AppendMenuEntry[sendMenu, CreateEntry["Default", NewMsgFormProc]];
AppendMenuEntry[sendMenu, CreateEntry["PrevMsg", PrevMsgProc]];
AppendMenuEntry[sendMenu, CreateEntry["Split", SenderSplitProc]];
AppendMenuEntry[sendMenu, CreateEntry["Places", SenderPlacesProc]];
AppendMenuEntry[sendMenu, CreateEntry["Levels", SenderLevelsProc]];
AppendMenuEntry[sendingMenu, CreateEntry["StopSending!", AbortSendProc]];
AppendMenuEntry[confirmMenu, CreateEntry["Confirm", ConfirmSendProc]];
AppendMenuEntry[confirmMenu, CreateEntry["Deny", DenySendProc]];
AppendMenuEntry[replyToMenu, CreateEntry["Self", ReplyToSelfProc]];
AppendMenuEntry[replyToMenu, CreateEntry["All", ReplyToAllProc]];
AppendMenuEntry[replyToMenu, CreateEntry["Cancel", ReplyToCancelProc]];
END;
----------------------------
-- start code; make menu and register ourselves
Commander.Register["WalnutSend", WalnutSendMail, "Mail sending tool"];
UserProfile.CallWhenProfileChanges[WalnutSendInit];
END.