-- File: WalnutSendControlImpl.mesa
-- Contents: Control module for WalnutSend
-- Last Edited by: Willie-Sue, December 7, 1982 3:30 pm
-- Rick on: XXX
-- Willie-Sue on: June 3, 1983 4:48 pm

DIRECTORY
CIFS USING [Error],
Commander USING [CommandProc, Register],
FileIO USING [Open, OpenFailed],
GVAnswer USING [MakeHeader],
GVBasics USING [ItemType],
GVSend USING [Abort],
Icons USING [IconFlavor, NewIconFromFile],
InputFocus USING [GetInputFocus],
Menus,
IO,
Process USING [Detach],
Rope,
RopeIO USING [GetRope],
TEditSplit USING[ Split ],
TiogaMenuOps USING[ tiogaMenu ],
TiogaOps USING [CancelSelection, GetSelection],
TypeScript USING [Create],
ViewerEvents USING [EventProc, EventRegistration, RegisterEventProc, UnRegisterEventProc],
ViewerOps,
ViewerIO USING [CreateViewerStreams],
ViewerTools,
ViewerClasses,
UserCredentials USING [GetUserCredentials],
UserProfile USING [Boolean, CallWhenProfileChanges, ProfileChangedProc],
WalnutSendMail;

WalnutSendControlImpl: CEDAR MONITOR
IMPORTS
CIFS, Commander, FileIO, IO, Process, Rope,
  GVSend, GVAnswer,
Icons, InputFocus, Menus,
RopeIO, TEditSplit, TiogaMenuOps, TiogaOps, TypeScript,
ViewerEvents, ViewerIO, ViewerOps, ViewerTools,
UserCredentials, UserProfile,
WalnutSendMail
EXPORTS
WalnutSendMail
SHARES Menus =

BEGIN OPEN WalnutSendMail;

-- Global types & variables
sendCaption: ROPE← "WalnutSend...6/02/83";

userRName: PUBLIC ROPENIL;    -- user name with regsitry
simpleUserName: PUBLIC ROPENIL;  -- user name without registry
replyToSelf: PUBLIC BOOLFALSE;

destroyAfterSend: BOOLFALSE;
senderList: LIST OF SenderInfo← NIL;

sendMenu: PUBLIC Menus.Menu← Menus.CreateMenu[];
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;
senderEvent: ViewerEvents.EventRegistration;
TiogaCTRL: PUBLIC GVBasics.ItemType;

subTocc: ROPE
"Subject: \001Topic\002\nTo: \001Recipients\002\ncc: \001Copies To\002 ";
messageRope: ROPE← "\n\n\001Message\002\n";
answRope: ROPE← "\n\001Message\002\n";
fwdRope: ROPE
"\n\n\001CoveringMessage\002\n\n-------------------------------------\n";
fwdRope2: ROPE← "\n------------------------------------------------------------\n";

-- ************************************************************************
-- Sending messages is independent of CedarDB
WalnutSendMail: Commander.CommandProc = { WalnutSendProc[fromExec: TRUE]};

WalnutSendProc: PUBLIC PROC[fromExec: BOOL] =
BEGIN
 senderInfo: SenderInfo;
IF ~fromExec THEN iconicSender← FALSE;
 senderInfo← BuildSender[paint: TRUE, iconic: iconicSender, init: NewMsgForm[]];

IF ~iconicSender THEN GrabFocus[senderInfo.senderV];
 iconicSender← FALSE;  -- iconic only the first time
END;

GrabFocus: ENTRY PROC[senderV: Viewer] =
BEGIN ENABLE UNWIND => NULL;
 ViewerTools.SetSelection[senderV, NEW[ViewerTools.SelPosRec← [0, 0]]];
 senderV.class.notify[senderV, LIST[$NextPlaceholder]];
END;

-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
BuildSendViewer: PUBLIC PROC[paint, iconic: BOOL, who: Viewer← NIL]
  RETURNS[senderV: Viewer] =
{ RETURN[BuildSender[paint, iconic, NewMsgForm[], who].senderV] };

BuildSender: ENTRY PROC[paint, iconic: BOOL, init: ROPE← NIL, who: Viewer← NIL]
  RETURNS[senderInfo: SenderInfo] =
BEGIN ENABLE UNWIND => NULL;
 senderV: Viewer;
IF senderIcon = tool THEN
BEGIN ENABLE CIFS.Error => TRUSTED {GOTO notFound};
  senderIcon← Icons.NewIconFromFile["Walnut.icons", 6];
  edittingIcon← Icons.NewIconFromFile["Walnut.icons", 9];
EXITS
notFound =>
BEGIN ENABLE CIFS.Error => TRUSTED {CONTINUE};
senderIcon← Icons.NewIconFromFile["/Indigo/Cedar/Walnut/Walnut.icons", 6];
edittingIcon← Icons.NewIconFromFile["/Indigo/Cedar/Walnut/Walnut.icons", 9];
END;
END;

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];
  ViewerTools.SetContents[senderV, init];
  ViewerOps.PaintViewer[senderV, menu];  -- shouldn't be necessary
IF senderV.iconic THEN
  { 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];
  };
  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];
 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];

 ViewerTools.SetContents[senderV, init];
 ViewerOps.PaintViewer[senderV, menu];  -- shouldn't be necessary
 senderV.inhibitDestroy← FALSE;
IF iconic AND ~senderV.iconic THEN ViewerOps.PaintViewer[senderV, all];
END;

-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

WalnutSendInit
: UserProfile.ProfileChangedProc =
{ uN: ROPE← UserCredentials.GetUserCredentials[].name;
pos: INT;
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];
};

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
senderV: Viewer← senderInfo.senderV;
iHadFocus: BOOL← InputFocus.GetInputFocus[].owner = senderV;
tc: ViewerTools.TiogaContents← NEW[ViewerTools.TiogaContentsRec];
tc.contents← txt;

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 iHadFocus AND InputFocus.GetInputFocus[].owner = NIL THEN
{ ViewerTools.SetSelection[senderV, NEW[ViewerTools.SelPosRec← [0, 0]]];
senderV.class.notify[senderV, LIST[$NextPlaceholder]];
};
UnsetNewVersion[senderV];
ViewerOps.PaintViewer[senderV, caption];
senderInfo.successfullySent← FALSE;
END;

AnswerMsg: PUBLIC PROC[msgHeaders: ROPE, who: Viewer← NIL] RETURNS [v: Viewer] =
BEGIN
 notOk: BOOL;
 errorIndex: INT;
 answer, init: ROPE;
 senderInfo: SenderInfo;
AnswerGetChar: PROC[pos: INT] RETURNS[CHAR] = {RETURN[msgHeaders.Fetch[pos]]};

 [notOk, answer, errorIndex]← GVAnswer.MakeHeader[AnswerGetChar, msgHeaders.Length[],
   simpleUserName, defaultRegistry];

IF notOk THEN
  { SenderReport[IO.PutFR[
    "\nSyntax error in line previous to line containing pos %g (in message being answered)",
     IO.int[errorIndex]]];
RETURN
  };
 init← Rope.Concat[answer, answRope];
 senderInfo← BuildSender[TRUE, FALSE, init, who];
 GrabFocus[v← senderInfo.senderV];
END;

ForwardMsg: PUBLIC PROC[oldMsg: ViewerTools.TiogaContents, who: Viewer← NIL]
  RETURNS [v: Viewer] =
{ senderInfo: SenderInfo← BuildSender[TRUE, FALSE, NIL, who];
ViewerTools.SetTiogaContents[v← senderInfo.senderV, oldMsg];
ViewerOps.PaintViewer[v, menu];  -- shouldn't be necessary
InsertIntoViewer[v, Rope.Concat[subTocc, fwdRope], 0];
InsertIntoViewer[v, fwdRope2, -1];
ViewerTools.EnableUserEdits[v];
UnsetNewVersion[v];
ViewerOps.PaintViewer[v, caption];
GrabFocus[v];
};

NewMsgForm: PROC RETURNS [r: ROPE] =
{ RETURN[Rope.Cat[subTocc, simpleUserName, messageRope]]};

-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-- Menu procedures

NewMsgFormProc: ENTRY Menus.MenuProc =
BEGIN ENABLE UNWIND => NULL;
viewer: Viewer← NARROW[parent];
senderInfo: SenderInfo← NARROW[ViewerOps.FetchProp[viewer, $SenderInfo]];

-- IF ~senderInfo.successfullySent AND viewer.newVersion THEN
--  { InternalReport[confirmR];
--   IF ~SendingConfirm[senderInfo] THEN {InternalReport[denyR]; RETURN};
--  };
 InternalDisplayTxt[senderInfo: senderInfo, txt: NewMsgForm[]];
END;

-- confirmR: ROPE← "\nConfirm deletion of unsent message";
-- denyR: ROPE← " .. denied\n";

PrevMsgProc: ENTRY Menus.MenuProc =
BEGIN ENABLE UNWIND => NULL;
viewer: Viewer← NARROW[parent];
senderInfo: SenderInfo← NARROW[ViewerOps.FetchProp[viewer, $SenderInfo]];
-- IF viewer.newVersion THEN
-- { InternalReport[confirmR];
-- IF ~SendingConfirm[senderInfo] THEN {InternalReport[denyR]; RETURN};
-- };
IF viewer.link#NIL THEN InternalDestroySplits[viewer];
IF senderInfo.prevMsg = NIL THEN
InternalDisplayTxt[senderInfo, NewMsgForm[]]
ELSE
{ KillFeedbackSel[viewer];
ViewerTools.SetTiogaContents[viewer, senderInfo.prevMsg];
ViewerOps.PaintViewer[viewer, menu];  -- shouldn't be necessary
  DeleteChars[viewer, senderInfo.numCharsToDelete];
  };
  UnsetNewVersion[viewer];
  ViewerOps.PaintViewer[viewer, caption];
END;

-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
DestroySendViewer: ViewerEvents.EventProc =
BEGIN
 senderInfo: SenderInfo← NARROW[ViewerOps.FetchProp[viewer, $SenderInfo]];
IF senderInfo = NIL THEN RETURN[FALSE];
IF ViewerOps.FetchProp[viewer, $DestroySenderProc] = NIL THEN RETURN[FALSE];
TRUSTED {Process.Detach[FORK CheckForDestroy[viewer, senderInfo]]};
RETURN[TRUE];
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;

SenderNewVersion
: PROC[viewer: Viewer] =
BEGIN OPEN Menus;
 menu: Menu;
 SetGuarded[FindEntry[menu← viewer.menu, "Clear"], TRUE];
 SetGuarded[FindEntry[viewer.menu, "PrevMsg"], TRUE];
 SetGuarded[FindEntry[viewer.menu, "GetForm"], TRUE];
 ViewerOps.PaintViewer[viewer, menu];  -- show as guarded
 viewer.newVersion← TRUE;
END;

UnsetNewVersion
: PROC[viewer: Viewer] =
BEGIN OPEN Menus;
 menu: Menu;
 SetGuarded[FindEntry[menu← viewer.menu, "Clear"], FALSE];
 SetGuarded[FindEntry[viewer.menu, "PrevMsg"], FALSE];
 SetGuarded[FindEntry[viewer.menu, "GetForm"], FALSE];
 ViewerOps.PaintViewer[viewer, menu];  -- show as unguarded
 viewer.newVersion← 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];

 senderInfo.destroyEvent← NIL;
 senderInfo.closeEvent← NIL;
 senderInfo.editEvent← 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] =
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];
  };
 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]];
 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;
viewer.newVersion← TRUE;
viewer.name← "Sending ...";

PaintViewer[viewer, caption];
sendOK← SendMsg[senderV: viewer, doClose: mouseButton=blue];
IF ~sendOK THEN SenderNewVersion[viewer];
viewer.name← sendCaption;
viewer.icon← senderIcon;
PaintViewer[viewer, caption];
ViewerTools.EnableUserEdits[viewer];
IF sendOK THEN
{ IF mouseButton=blue AND destroyAfterSend THEN
  {DestroyViewer[viewer]; RETURN};
DisplayTxtInSender[senderV: viewer, txt: NewMsgForm[]];
  IF
mouseButton#blue THEN
   {IF iHadFocus AND InputFocus.GetInputFocus[].owner = NIL THEN GrabFocus[viewer]}
ELSE CloseViewer[viewer];
  };
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;

SenderGetProc: Menus.MenuProc =
BEGIN
 self: Viewer← NARROW[parent];
 strm: IO.STREAM;
 contents: ViewerTools.TiogaContents;
 filename: ROPE← ViewerTools.GetSelectionContents[];
SetTC: ENTRY PROC =
BEGIN ENABLE UNWIND => NULL;
  KillFeedbackSel[self];
  ViewerTools.SetTiogaContents[self, contents];
  ViewerOps.PaintViewer[self, menu];  -- shouldn't be necessary
  UnsetNewVersion[self];
END;
IF filename.Length[] = 0 THEN
  { SenderReport["\nNo file name selected ...\n"]; RETURN};
IF (strm← OpenFileForReading[filename]) = NIL THEN
  { SenderReport["\nCouldn't find file: "]; SenderReport[filename]; SenderReport["\n"];
  RETURN
};
IF self.link # NIL THEN DestroySplits[self];
 contents← TiogaTextFromStrm[strm, 0];
 strm.Close[];
 SetTC[];
 GrabFocus[self];
END;

OpenFileForReading: PROC[fileName: ROPE] RETURNS[strm: IO.STREAM] =
BEGIN
BEGIN ENABLE FileIO.OpenFailed => GOTO BadOpen;
  strm←
   FileIO.Open[fileName: fileName, accessOptions: read, createOptions: oldOnly, raw: TRUE];
EXITS
  BadOpen =>
BEGIN ENABLE FileIO.OpenFailed => {strm← NIL; CONTINUE};
IF fileName.Find["."] >= 0 THEN RETURN;  -- no hope
  fileName← fileName.Concat[".form"];
  strm←
  FileIO.Open[fileName: fileName, accessOptions: read, createOptions: oldOnly, raw: TRUE];
END;
END;
END;

SenderStoreProc: Menus.MenuProc =
BEGIN
 self: Viewer← NARROW[parent];
 strm: IO.STREAM;
 contents: ViewerTools.TiogaContents;
 filename: ROPE← ViewerTools.GetSelectionContents[];
IF filename.Length[] = 0 THEN
  { SenderReport[" No file name selected ...\n"]; RETURN};
IF (strm← OpenFileForWriting[filename]) = NIL THEN
  { SenderReport["Couldn't store to file: "]; SenderReport[filename]; SenderReport["\n"];
  RETURN
};
 contents← ViewerTools.GetTiogaContents[self];
 strm.PutRope[contents.contents];
 strm.PutRope[contents.formatting];
 strm.Close[];
 UnsetNewVersion[self];
 ViewerOps.PaintViewer[self, caption];
 SenderReport["\nMsg has been stored on file: "];
 SenderReport[filename];
 SenderReport["\n"];
END;

OpenFileForWriting: PROC [fileName: ROPE] RETURNS[strm: IO.STREAM] =
BEGIN
strm← FileIO.Open[fileName: fileName, accessOptions: overwrite, raw: TRUE !
FileIO.OpenFailed => CONTINUE;
CIFS.Error => CONTINUE];
END;

-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
TiogaTextFromStrm: PUBLIC PROC[strm: IO.STREAM, startPos: INT← 0, len: INTLAST[INT]]
RETURNS[contents: TiogaContents] =
-- returns NIL IF endOfStream encountered during read
BEGIN ENABLE IO.EndOfStream => GOTO TooShort;
 fulltext: ROPE;
 formatPos: INT;

 strm.SetIndex[startPos];
 fulltext← RopeIO.GetRope[strm, len];
 contents← NEW[ViewerTools.TiogaContentsRec];

IF (formatPos← fulltext.Find["\000\000"]) < 0 THEN  -- no formatting
  { contents.contents← fulltext; RETURN};

 contents.contents← fulltext.Substr[len: formatPos];
 contents.formatting← fulltext.Substr[formatPos];

EXITS TooShort => RETURN[NIL];
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;

-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-- Start code: create menus

BEGIN
OPEN Menus;
 AppendMenuEntry[sendMenu,
  CreateEntry[name: "Send", proc: MessageSendProc, fork: FALSE]];
 AppendMenuEntry[sendMenu, CreateEntry["Clear", NewMsgFormProc]];
 AppendMenuEntry[sendMenu, CreateEntry["PrevMsg", PrevMsgProc]];
 AppendMenuEntry[sendMenu, CreateEntry["GetForm", SenderGetProc]];
 AppendMenuEntry[sendMenu, CreateEntry["StoreMsg", SenderStoreProc]];
 AppendMenuEntry[sendMenu, CreateEntry["Split", SenderSplitProc]];
 AppendMenuEntry[sendMenu, CreateEntry["Places", SenderPlacesProc]];
 AppendMenuEntry[sendMenu, CreateEntry["Levels", SenderLevelsProc]];

 AppendMenuEntry[sendingMenu, CreateEntry["AbortSend", 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.