-- File: WalnutSendControlImpl.mesa
-- Contents: Control module for WalnutSend

-- Last Edited by: Willie-Sue, December 13, 1983 1:43 pm
-- Last Edited by: Donahue, October 7, 1983 9:42 am
-- Willie-Sue on: December 20, 1983 1:21 pm

DIRECTORY
Commander USING [CommandProc, Register],
FS USING [defaultStreamOptions, Error, StreamOptions, StreamOpen],
GVBasics USING [ItemType],
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,
WalnutVoice USING [AddNuthatchHandleToViewer];

WalnutSendControlImpl: CEDAR MONITOR
IMPORTS
Commander, IO, Process, Rope, FS,
  GVSend,
Icons, InputFocus, Menus,
TEditSplit, TiogaMenuOps, TiogaOps, TypeScript,
ViewerEvents, ViewerIO, ViewerMenus, ViewerOps, ViewerTools,
UserCredentials, UserProfile, ViewerBLT,
WalnutSendInternal, WalnutSendOps, WalnutVoice
EXPORTS
WalnutSendInternal, WalnutSendOps
SHARES Menus =

BEGIN OPEN WalnutSendOps, WalnutSendInternal;

-- Global types & variables

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[];

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;
TiogaCTRL: PUBLIC GVBasics.ItemType;
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]];

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 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};
senderIcon← Icons.NewIconFromFile["/Indigo/Cedar/Walnut/Walnut.icons", 6];
edittingIcon← Icons.NewIconFromFile["/Indigo/Cedar/Walnut/Walnut.icons", 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];
 WalnutVoice.AddNuthatchHandleToViewer[senderV];
 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 ] = {
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 }] } };

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 ] = {
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
  };
};

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 };
 ViewerTools.SetSelection[ senderV, viewerStart];
TiogaOps.CallWithLocks[ AddFields ];
UnsetNewVersion[senderV];
ViewerTools.SetSelection[ senderV, viewerStart];
[] ← TiogaOps.NextPlaceholder[gotoend: FALSE];
END;

AddToSendMenu: PUBLIC ENTRY PROC[label: ROPE, proc: Menus.MenuProc] =
BEGIN ENABLE UNWIND => NULL;
 Menus.AppendMenuEntry[sendMenu, Menus.CreateEntry[label, proc]];
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
  DeleteChars[viewer, senderInfo.numCharsToDelete];
  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];
 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;

TRUSTED {Process.Detach[FORK ViewerOps.DestroyViewer[senderV]]};
END;

SenderReport: PUBLIC ENTRY PROC[msg: ROPE] =
BEGIN ENABLE UNWIND => NULL;
InternalReport[msg];
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] =
{ 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];
};

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 LOOP;
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;
  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: Menus.MenuProc =
BEGIN
 self: Viewer = NARROW[parent];
 filename: ROPENARROW[clientData];
IF filename.Length[] = 0 THEN RETURN;
 LoadFile[self, filename, TRUE];
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: BOOLEANFALSE;
 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
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: BOOLFALSE] =
BEGIN
 status: FileStatus;
 s: ROPE;
IF Rope.Find[filename, "."] = -1 THEN filename ← Rope.Concat[filename, ".form"];
 [status, s]← 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, filename ];
  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: ROPE] =
BEGIN
 s: IO.STREAM;
IF name.Find["]>/"] = -1 THEN fileStatus← local ELSE fileStatus← remote;
 s← FS.StreamOpen[name ! 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}];
IF s # NIL THEN s.Close[];
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[];
 AppendMenuEntry[formsMenu, CreateEntry["NewForm", NewMsgFormProc]];
 AppendMenuEntry[formsMenu, CreateEntry["PrevMsg", PrevMsgProc]];

FOR fl: LIST OF ROPE ← forms, fl.rest UNTIL fl = NIL DO
form: ROPE ← fl.first;
shortName: ROPE;
startPos, nextBreak: INT ← 0;
-- add ".form" if one does not exist
IF form.Length[] < 6 OR
 form.Find[s2: ".form", pos1: form.Length[]-5, case: FALSE] = -1 THEN
  form ← Rope.Concat[base: form, rest: ".form"];
-- if the file is a remote one, then strip off the leading server/directory info
WHILE (nextBreak ← Rope.SkipTo[form, startPos, "]>/"]) < form.Length[] DO
startPos ← nextBreak+1; ENDLOOP;
  shortName ← Rope.Substr[base: form, start: startPos];
-- now strip off the trailing ".form" for printing purposes
  shortName ← shortName.Replace[start: shortName.Length-5, len: 5, with: NIL];
  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["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

TRUSTED { TiogaCTRL ← LOOPHOLE[1013B, GVBasics.ItemType] };

Commander.Register["WalnutSend", WalnutSendMail, "Mail sending tool"];
UserProfile.CallWhenProfileChanges[WalnutSendInit];

END.