-- File: WalnutMsgSetDisplayerImpl.mesa
-- Contents: Implementation of the WalnutMsg Editor windows.
-- Status: mostly here functionally, but not yet completed.
-- Last Edited by: Willie-Sue, December 9, 1982 12:29 pm

-- Last edit by:
-- Rick on: April 29, 1982 7:02 pm
-- Donahue, April 4, 1983 8:38 am
-- Willie-Sue on: June 2, 1983 4:11 pm

DIRECTORY
Atom USING [GetPropFromList],
Buttons,
Menus USING [AppendMenuEntry, CreateMenu, Menu, MenuProc],
Rope,
ViewerClasses,
ViewerLocks USING [CallUnderWriteLock],
ViewerOps USING [AddProp, ComputeColumn, CreateViewer, DestroyViewer, FetchProp,
   GrowViewer, MoveViewer, OpenIcon, PaintViewer, SetMenu],
WalnutDB,
WalnutLog,
WalnutDisplayerOps,
WalnutViewer,
WalnutWindow;

WalnutMsgSetDisplayerImpl: CEDAR PROGRAM
IMPORTS
Atom, Rope,
WalnutDB, WalnutLog,
WalnutDisplayerOps, WalnutViewer, WalnutWindow,
Buttons, Menus, ViewerLocks, ViewerOps
EXPORTS
WalnutDisplayerOps =

BEGIN OPEN WalnutDB, WalnutWindow, WalnutDisplayerOps;

displayerMenu: PUBLIC Menus.Menu = Menus.CreateMenu[];
activeMenu: PUBLIC Menus.Menu = Menus.CreateMenu[];
deletedMenu: PUBLIC Menus.Menu = Menus.CreateMenu[];
buildingMenu: PUBLIC Menus.Menu = Menus.CreateMenu[];
msgSetName: PUBLIC ROPE = "Walnut!MsgSet!";

BuildDisplayerMenu: PROC =
BEGIN
Menus.AppendMenuEntry[displayerMenu,
  WalnutViewer.CreateMenuEntry[walnutQueue, "Categories", CategoriesProc]];
Menus.AppendMenuEntry[displayerMenu,
  WalnutViewer.CreateMenuEntry[walnutQueue, "MoveTo", MoveToProc]];
Menus.AppendMenuEntry[displayerMenu,
  WalnutViewer.CreateMenuEntry[walnutQueue, "Display", DisplayProc]];
Menus.AppendMenuEntry[displayerMenu,
  WalnutViewer.CreateMenuEntry[walnutQueue, "Delete", DeleteProc]];
Menus.AppendMenuEntry[displayerMenu,
  WalnutViewer.CreateMenuEntry[walnutQueue, "AddTo", AddProc]];

Menus.AppendMenuEntry[activeMenu,
  WalnutViewer.CreateMenuEntry[walnutQueue, "Categories", CategoriesProc]];
Menus.AppendMenuEntry[activeMenu,
  WalnutViewer.CreateMenuEntry[walnutQueue, "MoveTo", MoveToProc]];
Menus.AppendMenuEntry[activeMenu,
  WalnutViewer.CreateMenuEntry[walnutQueue, "Display", DisplayProc]];
Menus.AppendMenuEntry[activeMenu,
  WalnutViewer.CreateMenuEntry[walnutQueue, "Delete", DeleteProc]];
Menus.AppendMenuEntry[activeMenu,
  WalnutViewer.CreateMenuEntry[walnutQueue, "AddTo", AddProc]];
Menus.AppendMenuEntry[activeMenu,
  WalnutViewer.CreateMenuEntry[walnutQueue, "NewMail", NewMailProc]];

Menus.AppendMenuEntry[deletedMenu,
  WalnutViewer.CreateMenuEntry[walnutQueue, "Categories", CategoriesProc]];
Menus.AppendMenuEntry[deletedMenu,
  WalnutViewer.CreateMenuEntry[walnutQueue, "MoveTo", MoveToProc]];
Menus.AppendMenuEntry[deletedMenu,
  WalnutViewer.CreateMenuEntry[walnutQueue, "Display", DisplayProc]];
END;

-- * * * * * * * * * * * * * * * * * * * * * *
MoveToProc: Menus.MenuProc =
BEGIN
 viewer: Viewer = NARROW[parent];
 selected: Viewer = NARROW[ViewerOps.FetchProp[viewer, $selectedMsg]];
IF selected = NIL THEN Report[" No selected msg to be moved"]
ELSE
  { ra: REF ANY = NARROW[ViewerOps.FetchProp[viewer, $WalnutEntity]];
  thisMsgSet: MsgSet = V2E[ra];
  mfh: MsgSetFieldHandle = NARROW[ViewerOps.FetchProp[selected, $mfH]];
  msgSetList: LIST OF MsgSet = GetSelectedMsgSets[];
  first: BOOLTRUE;
  moveToSelf: BOOLFALSE;
IF msgSetList = NIL THEN
  { Report[" No selected MsgSets to Move msg to"]; RETURN};

IF ~mfh.hasBeenRead THEN MarkMsgAsRead[mfh, selected];
FOR msL: LIST OF MsgSet ← msgSetList, msL.rest UNTIL msL=NIL DO
   IF EqEntities[msL.first, thisMsgSet] THEN moveToSelf ← TRUE
ENDLOOP;
  ReportDisposition[mfh.msg]; ReportRope["Moved to:"];
FOR msL: LIST OF MsgSet← msgSetList, msL.rest UNTIL msL=NIL DO
IF first THEN first ← FALSE ELSE ReportRope[","];
  ReportRope[" "]; ReportRope[GetName[msL.first]];
ENDLOOP;
  ReportRope["\n"];
IF ~moveToSelf THEN RemoveFromDisplayedMsgSet[viewer, selected, mfh.msg]
   ELSE [] ← AdvanceSelection[mfh];
IF mouseButton#red THEN DisplaySelectedMsg[viewer, shift];
FOR msL: LIST OF MsgSet ← msgSetList, msL.rest UNTIL msL=NIL DO
   IF NOT EqEntities[msL.first, thisMsgSet] THEN
    { rel: Relship; existed: BOOL;
    WalnutLog.LogAddMsg[mfh.msgName, GetName[msL.first]];
    [rel, existed]← WalnutDB.AddMsgToMsgSet[msg: mfh.msg, msgSet: msL.first];
    IF ~existed THEN AddToMsgSetDisplayer[mfh, msL.first, rel] };
ENDLOOP;
IF NOT moveToSelf THEN
   { WalnutLog.LogRemoveMsg[mfh.msgName, GetName[thisMsgSet]];
    []← WalnutDB.RemoveMsgFromMsgSet[mfh.msg, thisMsgSet, mfh.relship]
    };
  };
END;

NewMailProc: Menus.MenuProc =
BEGIN
 v: Viewer = NARROW[parent];
 oldM: Menus.Menu = v.menu;
 ViewerOps.SetMenu[v, buildingMenu];
 []← WalnutWindow.RetrieveNewMail[];
 v.menu ← oldM;
 ViewerOps.PaintViewer[v, menu];
END;

CategoriesProc: Menus.MenuProc =
BEGIN
 msViewer: Viewer← NARROW[parent];
 selected: Viewer← NARROW[ViewerOps.FetchProp[msViewer, $selectedMsg]];
 mfh: MsgSetFieldHandle;
IF selected = NIL THEN {Report[" No selected msg"]; RETURN};
 mfh← NARROW[ViewerOps.FetchProp[selected, $mfH]];
 MsgCategories[mfh.msg];
END;

DeleteProc: Menus.MenuProc =
BEGIN
 msViewer: Viewer = NARROW[parent];
 msName: ROPE;
 selected: Viewer = NARROW[ViewerOps.FetchProp[msViewer, $selectedMsg]];
IF selected = NIL THEN Report[" No selected msg to be deleted"]
ELSE
  { ra: REF ANY = NARROW[ViewerOps.FetchProp[msViewer, $WalnutEntity]];
  thisMsgSet: MsgSet = V2E[ra];
  mfh: MsgSetFieldHandle;
  newRel: Relship;
IF EqEntities[thisMsgSet, deletedMsgSet] THEN RETURN;  -- already deleted
  mfh← NARROW[ViewerOps.FetchProp[selected, $mfH]];
IF ~mfh.hasBeenRead THEN MarkMsgAsRead[mfh, selected];
  RemoveFromDisplayedMsgSet[msViewer, selected, mfh.msg];
  ReportDisposition[mfh.msg];
  ReportRope["Deleted from "]; Report[msName← GetName[thisMsgSet]];
IF mouseButton#red THEN DisplaySelectedMsg[msViewer, shift];
  WalnutLog.LogRemoveMsg[mfh.msgName, msName];
  newRel← WalnutDB.RemoveMsgFromMsgSet[mfh.msg, thisMsgSet, mfh.relship];
IF newRel # NIL THEN AddToMsgSetDisplayer[mfh, deletedMsgSet, newRel] };
END;

AddProc: Menus.MenuProc = {
viewer: Viewer = NARROW[parent];
 selected: Viewer = NARROW[ViewerOps.FetchProp[viewer, $selectedMsg]];
IF selected = NIL THEN Report[" No selected msg to be moved"]
ELSE
  { ra: REF ANY = NARROW[ViewerOps.FetchProp[viewer, $WalnutEntity]];
  thisMsgSet: MsgSet = V2E[ra];
  mfh: MsgSetFieldHandle = NARROW[ViewerOps.FetchProp[selected, $mfH]];
  msgSetList: LIST OF MsgSet = GetSelectedMsgSets[];
  msgSetAddedTo: MsgSet;
  first: BOOLTRUE;
IF msgSetList = NIL THEN
  { Report[" No selected MsgSets to Add msg to"]; RETURN};

IF ~mfh.hasBeenRead THEN MarkMsgAsRead[mfh, selected];
  ReportDisposition[mfh.msg]; ReportRope["Added to:"];
FOR msL: LIST OF MsgSet ← msgSetList, msL.rest UNTIL msL=NIL DO
IF first THEN first ← FALSE ELSE ReportRope[","];
  ReportRope[" "];
  ReportRope[GetName[msL.first]];
ENDLOOP;
  ReportRope["\n"];
IF mouseButton#red THEN
  { [] ← AdvanceSelection[mfh]; DisplaySelectedMsg[viewer, shift] };
FOR msL: LIST OF MsgSet ← msgSetList, msL.rest UNTIL msL=NIL DO
   IF NOT EqEntities[(msgSetAddedTo← msL.first), thisMsgSet] THEN
    { rel: Relship; existed: BOOL;
    WalnutLog.LogAddMsg[mfh.msgName, GetName[msgSetAddedTo]];
    [rel, existed]← AddMsgToMsgSet[msg: mfh.msg, msgSet: msgSetAddedTo];
    IF ~existed THEN AddToMsgSetDisplayer[mfh, msgSetAddedTo, rel] };
ENDLOOP }; };

AdvanceSelection: PROCEDURE [msfH: MsgSetFieldHandle] RETURNS [v: Viewer] = {
v ← msfH.next;
IF v = NIL THEN { Report[" No Next message"]; RETURN };
[] ← SelectMsgInMsgSet[v] };

DisplayProc: Menus.MenuProc =
BEGIN
 viewer: Viewer = NARROW[parent];
 selected: Viewer ← NARROW[ViewerOps.FetchProp[viewer, $selectedMsg]];
 msfH: MsgSetFieldHandle = IF selected = NIL THEN NIL
            ELSE NARROW[ViewerOps.FetchProp[selected, $mfH]];
IF mouseButton # red AND msfH # NIL THEN selected ← AdvanceSelection[msfH];
IF selected = NIL THEN {Report[" No selected msg to be displayed"]; RETURN};
 DisplayOneMsg[selected, shift]
END;

ReportDisposition: PROC[msg: Msg] =
BEGIN
 msgSubject: ROPE← V2S[MGetP[msg, mSubjectIs]];
IF msgSubject.Length[] > 24 THEN msgSubject← Rope.Concat[msgSubject.Substr[0, 21], " ..."];
 ReportRope[Rope.Cat["Msg: ", msgSubject, ": has been "]];
END;

DisplaySelectedMsg: PROC[viewer: Viewer, shift: BOOL] =
BEGIN
selected: Viewer← NARROW[ViewerOps.FetchProp[viewer, $selectedMsg]];
IF selected # NIL THEN DisplayOneMsg[selected, shift];
END;

-- * * * * * * * * * * * * * * * * * * * * * *
BuildMsgSetDisplayer: PUBLIC PROC
 [msgSet: MsgSet, name: ROPE, shift: BOOLFALSE, initialActive: BOOLFALSE]
  RETURNS[msV: Viewer] =
BEGIN
 iconic: BOOLFALSE;
 whichSide: ViewerClasses.Column← left;

IF EqEntities[msgSet, activeMsgSet] THEN
  { iconic← WalnutWindow.initialActiveIconic AND WalnutWindow.initialActiveOpen;
IF WalnutWindow.initialActiveRight THEN whichSide← right;
  };

 msV← ViewerOps.CreateViewer[
  flavor: $Container, paint: FALSE,
  info: [name: Rope.Concat["MsgSet: ", name], column: whichSide, menu: NIL,
    iconic: iconic, icon: msgSetIcon, inhibitDestroy: TRUE]];

 ViewerOps.AddProp[msV, $Entity, msgSetName.Concat[name]];
 ViewerOps.AddProp[msV, $WalnutEntity, msgSet];
IF shift THEN
  { IF iconic THEN ViewerOps.OpenIcon[msV, shift] ELSE ViewerOps.GrowViewer[msV]};
END;

DisplayMsgSetInViewer:
  PUBLIC PROC[msgSet: MsgSet, msViewer: Viewer, shift: BOOL← FALSE] =
BEGIN OPEN ViewerOps;
menu: Menus.Menu←
IF EqEntities[msgSet, activeMsgSet] THEN activeMenu ELSE
IF EqEntities[msgSet, deletedMsgSet] THEN deletedMenu ELSE displayerMenu;
IF ~msViewer.iconic THEN ComputeColumn[msViewer.column, TRUE];
 msViewer.newVersion← TRUE; PaintViewer[msViewer, caption];
 SetMenu[msViewer, buildingMenu];
 BuildTupleWindowButtons[msViewer, msgSet];
 SetMenu[msViewer, menu];
 msViewer.newVersion← FALSE; PaintViewer[msViewer, caption];
 msViewer.inhibitDestroy← FALSE;
END;

FixUpMsgSetViewer: PUBLIC PROC[msName: ROPE, v: Viewer] =
BEGIN
 msgSet: MsgSet← DeclareMsgSet[msName, OldOnly].msgSet;
 child: Viewer;

IF msgSet = NIL THEN
  { Report["MsgSet: ", msName, " doesn't exist; destroying viewer"];
  ViewerOps.DestroyViewer[v];
RETURN
  };
-- delete buttons & start again
UNTIL (child← v.child) = NIL DO ViewerOps.DestroyViewer[child, FALSE] ENDLOOP;
 ViewerOps.PaintViewer[v, client];  -- paint cleared viewer
 ViewerOps.AddProp[v, $selectedMsg, NIL];  -- no selected Msg
 ViewerOps.AddProp[v, $WalnutEntity, msgSet];
 DisplayMsgSetInViewer[msgSet, v];
END;

-- * * * * * * * * * * * * * * * * * * * * * *
-- for adding new messages to displayed MsgSet

AddParsedMsgToMSViewer: PUBLIC PROC
  [msg: Msg, msgR: WalnutLog.MsgRec, msViewer: Viewer, rel: Relship] =
BEGIN
 prevMfh, mfhN: MsgSetFieldHandle;
 name: ROPE;
 lastButton: Viewer← NARROW[ViewerOps.FetchProp[msViewer, $lastButton]];
IF lastButton#NIL THEN prevMfh← NARROW[ViewerOps.FetchProp[lastButton, $mfH]]
  ELSE lastButton← msViewer;
 name← Rope.Cat[IF msgR.hasBeenRead THEN " " ELSE "? ", msgR.tocEntry, msgR.subject];

 [mfhN, lastButton]← BuildMsgLineViewer[lastButton, name];
 mfhN.posOK← TRUE;
 mfhN.headersPos← msgR.headersPos;
 mfhN.msgLength← msgR.msgLength;
 mfhN.relship← rel;
 mfhN.msg← msg;
 mfhN.msgName← msgR.gvID;
 mfhN.hasBeenRead← msgR.hasBeenRead;
IF prevMfh # NIL THEN prevMfh.next← lastButton;
 ViewerOps.AddProp[msViewer, $lastButton, lastButton];
END;

AddToMsgSetDisplayer:PROC[mfh: MsgSetFieldHandle, msAddedTo: MsgSet, rel: Relship] =
BEGIN
 prevMfh, mfhN: MsgSetFieldHandle;
 lastButton: Viewer;

 msgSetViewer: Viewer← FindMSViewer[msAddedTo];
IF msgSetViewer = NIL THEN RETURN;  -- msgSet not displayed
 lastButton← NARROW[ViewerOps.FetchProp[msgSetViewer, $lastButton]];

IF lastButton#NIL THEN prevMfh← NARROW[ViewerOps.FetchProp[lastButton, $mfH]]
  ELSE lastButton← msgSetViewer;
 [mfhN, lastButton]← BuildMsgLineViewer[lastButton, mfh.msgTOC];
 mfhN.relship← rel;
 mfhN.msg← mfh.msg;

IF mfh.posOK THEN
  { mfhN.posOK← TRUE;
  mfhN.headersPos← mfh.headersPos;
  mfhN.msgLength← mfh.msgLength;
  };

IF prevMfh # NIL THEN prevMfh.next← lastButton;
 ViewerOps.AddProp[msgSetViewer, $lastButton, lastButton];
END;

AddMsgToMsgSetDisplayer: PUBLIC PROC[msg: Msg, msgSet: MsgSet, rel: Relship] =
BEGIN
 prevMfh, mfhN: MsgSetFieldHandle;
 lastButton: Viewer;
 msgSetViewer: Viewer← FindMSViewer[msgSet];
IF msgSetViewer = NIL THEN RETURN;  -- msgSet not displayed
 lastButton← NARROW[ViewerOps.FetchProp[msgSetViewer, $lastButton]];

IF lastButton#NIL THEN prevMfh← NARROW[ViewerOps.FetchProp[lastButton, $mfH]]
  ELSE lastButton← msgSetViewer;
 [mfhN, lastButton]← BuildMsgLineViewer[lastButton, MsgButtonName[msg, TRUE]];
 mfhN.relship← rel;
 mfhN.msg← msg;
IF prevMfh # NIL THEN prevMfh.next← lastButton;
 ViewerOps.AddProp[msgSetViewer, $lastButton, lastButton];
END;

-- called with parent & viewer 'displaying' msg to be deleted
RemoveFromDisplayedMsgSet: PROC[parent, which: Viewer, msg: Msg] =
BEGIN
delta: INTEGER;
mfhT, x: MsgSetFieldHandle;
bt, prev, next: Viewer;
oldName: ROPE;
selected: Viewer← NARROW[ViewerOps.FetchProp[parent, $selectedMsg]];

RemoveFrom: PROC =
BEGIN
  delta← mfhT.next.wy - which.wy;
  ViewerOps.DestroyViewer[which, FALSE];   -- don't paint
  FOR bt← next, mfhT.next UNTIL bt = NIL DO
   mfhT← NARROW[ViewerOps.FetchProp[bt, $mfH]];
   IF mfhT.next = NIL THEN-- last line, erase in current position
   { oldName← bt.name;
   bt.name← NIL;
   ViewerOps.PaintViewer[bt, all];
   bt.name← oldName;
   };
   ViewerOps.MoveViewer[bt, bt.wx, bt.wy-delta, bt.ww, bt.wh, FALSE];
   ViewerOps.PaintViewer[bt, all];
  ENDLOOP;
END;

IF which = NIL THEN which← FindMsgViewer[parent, msg];
IF selected = which THEN Buttons.SetDisplayStyle[which, $BlackOnWhite];
 mfhT← NARROW[ViewerOps.FetchProp[which, $mfH]];
 prev← mfhT.prev;
IF (next← mfhT.next) = NIL
THEN
{ ViewerOps.AddProp[parent, $lastButton, mfhT.prev]; -- last line
   which.name← NIL;
   ViewerOps.PaintViewer[which, all];  -- erase line
   ViewerOps.DestroyViewer[which, FALSE];   -- don't paint
  }
ELSE
IF parent.iconic THEN RemoveFrom[]
ELSE ViewerLocks.CallUnderWriteLock[RemoveFrom, parent];

-- take which out of chain of mfh's
IF prev # NIL THEN
 { x← NARROW[ViewerOps.FetchProp[prev, $mfH]];
IF x # NIL THEN x.next← next};
IF next # NIL THEN
  { x← NARROW[ViewerOps.FetchProp[next, $mfH]];
  x.prev← prev};

IF selected = which THEN
  { IF next = NIL THEN ViewerOps.AddProp[parent, $selectedMsg, NIL]  -- no selected Msg
  ELSE
   { ViewerOps.AddProp[parent, $selectedMsg, next];
   Buttons.SetDisplayStyle[next, $BlackOnGrey]; -- show it is selected
   ViewerOps.PaintViewer[next, all];
   };
  };
END;

RemoveFromMsgSetDisplayer: PUBLIC PROC[msgSet: MsgSet, msg: Msg] =
BEGIN
 msV: Viewer← FindMSViewer[msgSet];
IF msV = NIL THEN RETURN;
 RemoveFromDisplayedMsgSet[msV, FindMsgViewer[msV, msg], msg];
END;

FindMFH: PROC[msViewer: Viewer, rel: Relship] RETURNS[mfh: MsgSetFieldHandle] =
BEGIN
 lb: Viewer← NARROW[ViewerOps.FetchProp[msViewer, $lastButton]];
DO
IF lb = NIL THEN RETURN;
  mfh← NARROW[ViewerOps.FetchProp[lb, $mfH]];
IF EqEntities[mfh.relship, rel] THEN RETURN;
  lb← mfh.prev;
ENDLOOP;
END;

FindMsgViewer: PROC[msViewer: Viewer, msg: Msg] RETURNS[mViewer: Viewer] =
BEGIN
 mfh: MsgSetFieldHandle;
 mViewer← NARROW[ViewerOps.FetchProp[msViewer, $lastButton]];
DO
IF mViewer = NIL THEN RETURN;
  mfh← NARROW[ViewerOps.FetchProp[mViewer, $mfH]];
IF EqEntities[mfh.msg, msg] THEN RETURN;
  mViewer← mfh.prev;
ENDLOOP;
END;

BuildTupleWindowButtons: PROC[v: Viewer, e: Entity] =
BEGIN
tuplesList: LIST OF Relship;
prevMfh, mfh: MsgSetFieldHandle← NIL;
lastButton: Viewer← v;
DoTupleWindowButtons: PROC =
{ FOR tL: LIST OF Relship← tuplesList, tL.rest UNTIL tL = NIL DO
  m: Msg← V2E[DB.GetF[tL.first, mCategoryOf]]; -- follow relship to the Msg
  hasBeenRead: BOOL← V2B[DB.GetP[m, mHasBeenReadIs]];

  [mfh, lastButton]← BuildMsgLineViewer[lastButton, MsgButtonName[m, hasBeenRead]];
  mfh.relship← tL.first;
  mfh.msg← m;
  mfh.hasBeenRead← hasBeenRead;
  mfh.msgName← DB.GetName[m];
IF prevMfh # NIL THEN prevMfh.next← lastButton;
  prevMfh← mfh;
ENDLOOP;
  };

tuplesList← RelationSubsetList[mCategory, LIST[[mCategoryIs, e]]];
WalnutDB.AcquireDBLock[DoTupleWindowButtons];
ViewerOps.AddProp[v, $lastButton, lastButton]; -- for updating
END;

MsgButtonName: PROC[msg: Msg, hasBeenRead: BOOL] RETURNS [ROPE] =
{ RETURN[Rope.Cat[
   IF hasBeenRead THEN " " ELSE "? ",
   V2S[DB.GetP[msg, mTOCEntryIs]],
   V2S[DB.GetP[msg, mSubjectIs]]]];
};

BuildMsgLineViewer: PROC[lastButton: Viewer, name: ROPE]
  RETURNS[mfh: MsgSetFieldHandle, lb: Viewer] =
BEGIN
isIconic: BOOL;
BuildLine: PROC =
BEGIN
  IF lastButton.parent = NIL THEN
   lb← WalnutViewer.FirstButton[
 q: walnutQueue,
 name: name,
 proc: MsgSetSelectionProc,
 width: 1024,   -- hack for now = screenW
 parent: lastButton]
ELSE lb← WalnutViewer.AnotherButton[
 q: walnutQueue,
 name: name,
 proc: MsgSetSelectionProc,
 sib: lastButton,
 width: 1024,   -- hack for now = screenW
 newLine: TRUE];
END;

 isIconic← IF lastButton.parent = NIL THEN lastButton.iconic ELSE lastButton.parent.iconic;
IF isIconic THEN BuildLine[]
ELSE ViewerLocks.CallUnderWriteLock[BuildLine, lastButton];

-- Containers.ChildXBound[lb.parent, lb];
ViewerOps.AddProp[lb, $mfH, mfh← NEW[MsgSetFieldObject←
 [msgViewer: lb, msgTOC: name, prev: lastButton, posOK: FALSE]]];
END;

MsgSetSelectionProc: Buttons.ButtonProc =
{ viewer: Viewer = NARROW[parent];
[]← SelectMsgInMsgSet[viewer];
IF control THEN DeleteProc[parent: viewer.parent, mouseButton: mouseButton]
ELSE IF mouseButton#red THEN DisplayOneMsg[viewer, shift];
};

DisplayOneMsg: PROC[viewer: Viewer, shift: BOOL] =
BEGIN
 mfh: MsgSetFieldHandle = NARROW[ViewerOps.FetchProp[viewer, $mfH]];
 v: Viewer = DisplayMsgFromMsgSet[mfh, viewer.parent, shift];
IF ~mfh.hasBeenRead THEN MarkMsgAsRead[mfh, viewer];
END;

SelectMsgInMsgSet: PROC[msgButton: Viewer] RETURNS[sameAsBefore: BOOL] =
BEGIN
 prevSelected: Viewer = NARROW[ViewerOps.FetchProp[msgButton.parent, $selectedMsg]];
IF prevSelected = msgButton THEN RETURN[TRUE];
-- turn off previous selection
IF prevSelected # NIL AND ~prevSelected.destroyed THEN
  { Buttons.SetDisplayStyle[prevSelected, $BlackOnWhite];
  ViewerOps.PaintViewer[prevSelected, all]};

IF msgButton.destroyed THEN Report["Msg is no longer in the MsgSet"]
ELSE
  {Buttons.SetDisplayStyle[msgButton, $BlackOnGrey]; -- show it is selected
  ViewerOps.PaintViewer[msgButton, all];
  ViewerOps.AddProp[msgButton.parent, $selectedMsg, msgButton];
  };
RETURN[FALSE];
END;

MarkMsgAsRead: PROC[mfh: MsgSetFieldHandle, msgButton: Viewer] =
-- used by selection, MoveTo & Delete
BEGIN
 newName: ROPE = msgButton.name.Replace[0, 1, " "]; -- change ? to SP
 WalnutLog.LogMsgHasBeenRead[mfh.msgName];
 WalnutDB.SetMsgHasBeenRead[mfh.msg];
 mfh.hasBeenRead← TRUE;
 mfh.msgTOC← newName;
 Buttons.ReLabel[msgButton, newName];
END;

----------------------------
-- start code
BuildDisplayerMenu[];

END.