WalnutSendControlImpl.mesa
Copyright © 1984 by Xerox Corporation. All rights reserved.
Willie-Sue, September 17, 1986 10:46:06 am PDT
Contents: Control module for WalnutSend
Last Edited by: Willie-Sue, January 17, 1984 10:20:02 am PST
Last Edited by: Donahue, October 7, 1983 9:42 am
DIRECTORY
Commander USING [CommandProc, Register],
DefaultRemoteNames USING [Get],
FS USING [ComponentPositions, Error, OpenFile, Close, ExpandName, Open],
GVSend USING [Abort],
Icons USING [IconFlavor, NewIconFromFile],
InputFocus USING [GetInputFocus],
Menus USING [AppendMenuEntry, CopyEntry, CreateEntry, CreateMenu,
  FindEntry, Menu, MenuEntry,
  MenuProc, MouseButton, ReplaceMenuEntry, targetNotFound],
IO,
PopUpSelection USING [Request],
Process USING [Detach],
Rope,
TEditSplit USING[ Split ],
TiogaMenuOps USING[ tiogaMenu, Empty, Load, Store ],
TiogaOps USING [ Ref, Location, CancelSelection, GetSelection, IsComment, NextPlaceholder, StepForward, ViewerDoc ],
TypeScript USING [Create],
ViewerEvents USING [EventProc, EventRegistration, RegisterEventProc, UnRegisterEventProc],
ViewerOps USING [AddProp, BlinkIcon, ComputeColumn, CreateViewer, DestroyViewer,
  FetchProp, MoveBelowViewer, OpenIcon, PaintViewer, SetMenu, SetNewVersion],
ViewerIO USING [CreateViewerStreams, GetViewerFromStream],
ViewerTools USING [EnableUserEdits, GetSelectionContents, InhibitUserEdits,
  SelPos, SelPosRec, SetSelection, SetTiogaContents, TiogaContents, TiogaContentsRec],
ViewerClasses USING [Viewer],
ViewerMenus USING [ Save ],
UserCredentials USING [Get],
UserProfile USING [Boolean, CallWhenProfileChanges, ProfileChangedProc,
  ListOfTokens, Token],
WalnutSendInternal,
WalnutSendOps,
WalnutSendOpsExtras USING [];
WalnutSendControlImpl: CEDAR MONITOR
IMPORTS
Commander, DefaultRemoteNames, IO, PopUpSelection, Process, Rope, FS,
GVSend,
Icons, InputFocus, Menus,
TEditSplit, TiogaMenuOps, TiogaOps, TypeScript,
ViewerEvents, ViewerIO, ViewerMenus, ViewerOps, ViewerTools,
UserCredentials, UserProfile,
WalnutSendInternal, WalnutSendOps
EXPORTS
WalnutSendInternal, WalnutSendOps, WalnutSendOpsExtras
SHARES Menus =
BEGIN OPEN WalnutSendOps, WalnutSendInternal;
-- Global types & variables
ROPE: TYPE = Rope.ROPE;
Viewer: TYPE = ViewerClasses.Viewer;
TiogaContents: TYPE = ViewerTools.TiogaContents;
viewerStart: ViewerTools.SelPos = NEW[ViewerTools.SelPosRec ← [0, 0]];
userRName: PUBLIC ROPENIL;    -- user name with regsitry
simpleUserName: PUBLIC ROPENIL;  -- user name without registry
replyToSelf: PUBLIC BOOLFALSE;
alwaysOpenSender: BOOLFALSE;
sendCaption: PUBLIC ROPE ← "Walnut Sender";
destroyAfterSend: BOOLFALSE;
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[];
popUpFormsList: LIST OF ROPENIL;
popUpFormsFileNames: LIST OF ROPENIL;
sendingSynch: CONDITION;
needToAuthenticate: PUBLIC BOOLTRUE;
iconicSender: BOOLTRUE;  -- make iconic initially only
senderIcon: Icons.IconFlavor ← tool;
edittingIcon: Icons.IconFlavor ← tool;
reporter: IO.STREAMNIL;
senderTS: Viewer ← NIL;
labelFont: PUBLIC ROPENIL; -- 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",
   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\t\227\000\205\227\000\000\000\000\000>\000\000\000\200\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\000",
   formatting: "\000\000\000\006\000\000\235\312\000I\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\232\001\234\t\230\t\227\000\205\227\000\000\000\000\000V\000\000\000\245\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] ] ];
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: BOOLFALSE] RETURNS[senderInfo: SenderInfo] =
BEGIN ENABLE UNWIND =>
IF senderInfo # NIL AND senderInfo.senderV # NIL
THEN senderInfo.senderV.inhibitDestroy ← FALSE;
senderV: Viewer;
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;
IF TiogaOps.GetSelection[feedback].viewer = senderV THEN
 TiogaOps.CancelSelection[feedback];
IF senderV.iconic THEN {  -- inlined a call to ClearOpenEvent
ra: REF ANY ← ViewerOps.FetchProp[senderV, $WalnutSendOpsForm];
IF ra # NIL THEN {
form: WalnutSendOps.Form = NARROW[ra];
ViewerEvents.UnRegisterEventProc[senderInfo.openEvent, open];
senderInfo.openEvent ← NIL;
};
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;
-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
DoUserNameAndRegistry: PROC = {
uN: ROPE = UserCredentials.Get[].name;
pos: INT;
IF (pos ← Rope.SkipTo[s: uN, skip: "."]) = Rope.Length[uN]
THEN {
simpleUserName ← uN;
defaultRegistry ← DefaultRemoteNames.Get[].registry;
userRName ← Rope.Cat[uN, ".", defaultRegistry];
}
ELSE {
simpleUserName ← Rope.Substr[uN, 0, pos];
defaultRegistry ← Rope.Substr[uN, pos+1];
userRName ← uN;
};
};
ChangeUserRName: PUBLIC PROC[newUserRName: ROPE] = {
userRName ← newUserRName;
simpleUserName ← newUserRName.Substr[0, newUserRName.Find["."]];
};
WalnutSendInit: UserProfile.ProfileChangedProc = {
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;
DoUserNameAndRegistry[];
needToAuthenticate ← TRUE;
replyToSelf ← UserProfile.Boolean[key: "WalnutSend.ReplyToSelf", default: FALSE];
destroyAfterSend ← UserProfile.Boolean[key: "WalnutSend.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.MsgHeadersLooks", default: "sb"];
SetFormsList[];
};
DisplayTxtInSender: ENTRY PROC[senderV: Viewer, txt: ROPE] = {
ENABLE UNWIND => NULL;
senderInfo: SenderInfo ← NARROW[ViewerOps.FetchProp[senderV, $SenderInfo]];
tc: ViewerTools.TiogaContents ← NEW[ViewerTools.TiogaContentsRec];
tc.contents ← txt;
InternalDisplayTioga[senderInfo, tc, TRUE];
};
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]
};
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;
old: Menus.MenuEntry ← Menus.FindEntry[sendMenu, label];
IF old = NIL THEN
Menus.AppendMenuEntry[sendMenu, Menus.CreateEntry[label, proc]];
FOR sL: LIST OF SenderInfo ← senderList, sL.rest UNTIL sL = NIL DO
thisMenu: Menus.Menu;
v: Viewer = sL.first.senderV;
IF v = NIL OR v.destroyed THEN LOOP;
old ← Menus.FindEntry[thisMenu ← sL.first.senderV.menu, label];
IF old = NIL THEN {
Menus.AppendMenuEntry[thisMenu, Menus.CreateEntry[label, proc]];
ViewerOps.PaintViewer[sL.first.senderV, menu];
};
ENDLOOP;
END;
RemoveFromSendMenu: PUBLIC ENTRY 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];
FOR sL: LIST OF SenderInfo ← senderList, sL.rest UNTIL sL = NIL DO
thisMenu: Menus.Menu;
v: Viewer = sL.first.senderV;
IF v = NIL OR v.destroyed THEN LOOP;
old ← Menus.FindEntry[thisMenu ← sL.first.senderV.menu, name];
IF old # NIL THEN {
Menus.ReplaceMenuEntry[thisMenu, old ! Menus.targetNotFound => CONTINUE];
ViewerOps.PaintViewer[sL.first.senderV, menu];
};
ENDLOOP;
END;
-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-- Menu procedures
NewMsgFormProc: ENTRY PROC[viewer: Viewer] = {
ENABLE UNWIND => NULL;
NewMsgForm[viewer]
};
PrevMsgProc: ENTRY PROC[viewer: Viewer] =
BEGIN ENABLE UNWIND => NULL;
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 {
IF TiogaOps.GetSelection[feedback].viewer = viewer THEN
TiogaOps.CancelSelection[feedback];
ViewerTools.SetTiogaContents[viewer, senderInfo.prevMsg];
ViewerOps.PaintViewer[viewer, menu];  -- shouldn't be necessary
UnsetNewVersion[viewer];
}
END;
NewSenderProc: PROC = {
senderInfo: SenderInfo ←
  BuildSender[paint: TRUE, iconic: FALSE, form: defaultForm, forceNew: TRUE];
GrabFocus[senderInfo.senderV];
};
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;
EntryInsertForm: PUBLIC ENTRY PROC[senderInfo: SenderInfo, form: WalnutSendOps.Form] =
BEGIN ENABLE UNWIND => NULL;
InsertForm[senderInfo, form, TRUE];
END;
EntryPlaceHolders: PUBLIC 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: BOOLTRUE;
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;
should take senderInfo off of chain of senderInfo's, byt why bother?
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: BOOLFALSE] =
BEGIN ENABLE UNWIND => NULL;
InternalReport[msg, blink];
END;
InternalReport: INTERNAL PROC[msg: ROPE, blink: BOOLFALSE] =
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;
MessageSendCheckedProc: PUBLIC Menus.MenuProc =
BEGIN
self: Viewer ← NARROW[parent];
iHadFocus: BOOL ← InputFocus.GetInputFocus[].owner = self;
hasComments: BOOLFALSE;
ViewerTools.InhibitUserEdits[self];
check if any comment nodes, and alert user if so
FOR next: TiogaOps.Ref ← TiogaOps.ViewerDoc[self], TiogaOps.StepForward[next]
UNTIL next = NIL DO
IF hasComments ← TiogaOps.IsComment[next] THEN EXIT;
ENDLOOP;
IF hasComments THEN {
TRUSTED {Process.Detach[FORK CheckProc[self, mouseButton, iHadFocus]] };
RETURN;
};
IF self.link#NIL THEN DestroySplits[self];
TRUSTED {Process.Detach[FORK SendProc[self, mouseButton, iHadFocus]]};
END;
CheckProc: PROC[self: Viewer, mouseButton: Menus.MouseButton, iHadFocus: BOOL] = {
senderInfo: SenderInfo = NARROW[ViewerOps.FetchProp[self, $SenderInfo]];
SenderReport["\n*** Message to be sent contains comment nodes - are you sure you want to send it?"];
IF ~Confirmation[senderInfo] THEN {
SenderReport[" Message NOT sent"];
ViewerTools.EnableUserEdits[self];
RETURN;
};
IF self.link#NIL THEN DestroySplits[self];
SendProc[self, mouseButton, iHadFocus];
};
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: PUBLIC PROC[keepThisOne: Viewer] = {
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];
};
SenderPlacesProc: Menus.MenuProc =
Menus.CopyEntry[ Menus.FindEntry[ TiogaMenuOps.tiogaMenu, "Places" ] ].proc;
SenderLevelsProc: Menus.MenuProc =
Menus.CopyEntry[ Menus.FindEntry[ TiogaMenuOps.tiogaMenu, "Levels" ] ].proc;
SenderGetFormProc: ENTRY PROC[self: Viewer, filename: ROPE] =
BEGIN ENABLE UNWIND => NULL;
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 = {
viewer: Viewer = NARROW[parent];
which: INT;
sel: LIST OF ROPE ← popUpFormsFileNames;
IF popUpFormsList = NIL THEN RETURN;
which ← PopUpSelection.Request["Forms", popUpFormsList];
SELECT which FROM
<= 0 => RETURN;    -- no selection
1 => { NewSenderProc[]; RETURN };
2 => { NewMsgFormProc[viewer]; RETURN };
3 => { PrevMsgProc[viewer]; RETURN };
ENDCASE => which ← which - 3;
FOR i: INT IN [1..which) DO
sel ← sel.rest;
ENDLOOP;
SenderGetFormProc[viewer, sel.first];
};
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 mouseButton = blue THEN
{ IF ~self.newVersion THEN UnsetNewVersion[self] }
ELSE IF ~self.newVersion THEN ViewerOps.SetNewVersion[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 mouseButton = blue THEN
{ IF ~self.newVersion THEN UnsetNewVersion[self] }
ELSE IF ~self.newVersion THEN ViewerOps.SetNewVersion[self];
};
ENDCASE => BadSaveOrStore[status, exp, self.name];
END;
-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
LoadFile: PROC[self: Viewer, filename: ROPE, noName: BOOLFALSE] =
BEGIN
status: FileStatus;
s, fName, nameToUse: ROPE;
[status, s, fName] ← CheckForFile[filename, FALSE];
IF status = localNotFound OR status = remoteNotFound THEN {
[status, s, fName] ← CheckForFile[filename];  -- try .form extension
nameToUse ← fName;
}
ELSE nameToUse ← 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, nameToUse ];
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, addExt: BOOLTRUE]
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 AND addExt 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;
-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-- read the user profile line for message forms and add one menu entry for each
SetFormsList: PROC =
BEGIN OPEN Menus;
fLLast: LIST OF ROPENIL;
popUpFormsFileNames ←
UserProfile.ListOfTokens[key: "WalnutSend.MsgForms", default: NIL];
popUpFormsList ← NIL;
FOR fl: LIST OF ROPE ← popUpFormsFileNames, 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"];
IF fLLast = NIL THEN popUpFormsList ← fLLast ← LIST[shortName]
ELSE {
fLLast.rest ← LIST[shortName];
fLLast ← fLLast.rest;
};
ENDLOOP;
popUpFormsList ←
CONS["NewSender", CONS["Default Form", CONS["Previous Msg", popUpFormsList]]];
END;
-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-- Start code: create menus
BEGIN
OPEN Menus;
AppendMenuEntry[sendMenu,
  CreateEntry[name: "Send", proc: MessageSendProc, fork: FALSE]];
AppendMenuEntry[sendMenu,
  CreateEntry[name: "SendChecked", proc: MessageSendCheckedProc, 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["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];
DoUserNameAndRegistry[];
BEGIN
path: ROPE = "Walnut.icons";
senderIcon ← Icons.NewIconFromFile[path, 6 ! FS.Error => GOTO notFound];
edittingIcon ← Icons.NewIconFromFile[path, 9];
EXITS
notFound => NULL;
END;
END.