-- File: WalnutMsgDisplayerImpl.mesa
-- Contents: Implementation of the Walnut Msg Display windows.
-- Last Edited by: Willie-Sue, December 8, 1982 10:25 am


-- Status: complete.
-- Created by: Rick Cattell & Willie-Sue Haugeland, 17-Feb-82
-- Last Edited by: Donahue, August 26, 1983 4:29 pm
-- Rick on: April 13, 1982 11:50 am
-- Willie-Sue on: November 22, 1983 5:10 pm

DIRECTORY
Ascii USING [ Letter ],
DB
USING [Aborted, Failure, GetP, GetName, GetPList, V2S],
IO,
Menus,
Rope,
WalnutDocumentRope USING [Create],
TEditSplit USING[ Split ],
TiogaOps USING [CancelSelection, GetSelection, ViewerDoc],
TiogaMenuOps USING[ tiogaMenu ],
ViewerClasses USING [Viewer],
ViewerEvents USING [ EventRegistration, EventProc, ViewerEvent,
     RegisterEventProc, UnRegisterEventProc ],
ViewerLocks USING[ CallUnderWriteLock ],
ViewerOps USING [AddProp, CreateViewer, DestroyViewer, FetchProp, GrowViewer, OpenIcon,
     PaintViewer, SetMenu],
ViewerTools USING [TiogaContents, InhibitUserEdits, SetTiogaContents],
WalnutViewer USING [CreateMenuEntry],
WalnutSendOps USING [Answer, Forward],
WalnutDB USING [Msg, Value,
     mCategoryIs, mSubjectIs, mTOCEntryIs, mTOCEntryOf,
     DeclareMsg, GetName, TiogaMsgFromLog, V2E],
WalnutVoice USING [AddNuthatchHandleToViewer],
WalnutLog USING [TiogaTextFromLog],
WalnutDisplayerOps,
WalnutMsgOps USING [MsgSetFieldHandle],
WalnutPrintOps USING [MsgPrintProc],
WalnutWindow USING [msgIcon, walnutQueue, Report, ReportRope];

WalnutMsgDisplayerImpl: CEDAR MONITOR
IMPORTS
  Ascii, DB, IO, Rope,
  WalnutDocumentRope, TiogaMenuOps, TiogaOps, TEditSplit,
  Menus, ViewerEvents, ViewerLocks, ViewerOps, ViewerTools,
  WalnutSendOps, WalnutDB, WalnutLog, WalnutPrintOps,
  WalnutViewer, WalnutVoice, WalnutWindow
EXPORTS
WalnutDisplayerOps, WalnutMsgOps
SHARES Menus =

BEGIN OPEN WalnutDB, WalnutWindow;

ROPE: TYPE = Rope.ROPE;
TiogaContents: TYPE = ViewerTools.TiogaContents;
Viewer: TYPE = ViewerClasses.Viewer;

msgMenu: PUBLIC Menus.Menu ← Menus.CreateMenu[];
nilMenu: Menus.Menu ← Menus.CreateMenu[];
msgName: PUBLIC ROPENIL; -- set by WalnutWindowImpl when DB changes
msgMenuAvailable: PUBLIC BOOLFALSE;

----------------------------
-- BuildMsgDisplayer and MsgInViewer are used by "outside callers"
-- they traffic mostly in ropes

BuildMsgViewer: PUBLIC PROC[mName: ROPE, msg: Msg, shift, paint: BOOLFALSE]
  RETURNS[v: Viewer] =
BEGIN
 shortName, name, date: ROPE;
LockedSetMenu: PROC = {ViewerOps.SetMenu[v, msgMenu]};
 [shortName, date] ← ShortMsgName[msg];
 name ← Rope.Cat[ shortName, " ", date, " ",
       DB.V2S[DB.GetP[msg, WalnutDB.mSubjectIs]]];
 v ← ViewerOps.CreateViewer[flavor: $Text, paint: paint,
     info: [name: name, icon: msgIcon, scrollable: TRUE, iconic: FALSE]];
 ViewerLocks.CallUnderWriteLock[LockedSetMenu, v];
IF shift AND paint THEN ViewerOps.GrowViewer[v];
ViewerOps.AddProp[v, $Entity, msgName.Concat[mName]];
ViewerOps.AddProp[v, $IconLabel, Rope.Cat[StripForIcon[shortName], " .", date]];
WalnutVoice.AddNuthatchHandleToViewer[v];
END;

MsgInViewer: PUBLIC PROC[mName: ROPE, msg: Msg, v: Viewer, shift: BOOLFALSE] =
BEGIN
 ShowMsgInMsgViewer[v, TiogaMsgFromLog[msg].contents];
END;

onlyInTextViewer: ROPE = "Can only display msgs in Text viewers";
noSplitsAllowed: ROPE = "Can't display msg in a split viewer";
----------------------------
DisplayMsgFromMsgSet:
PUBLIC PROC[mfh: WalnutMsgOps.MsgSetFieldHandle, msViewer: Viewer, shift: BOOLFALSE]
  RETURNS[v: Viewer] =
BEGIN
 contents: TiogaContents;
 shortName, name, date: ROPE;
LockedSetMenu: PROC = {ViewerOps.SetMenu[v, msgMenu]};
 [shortName, date] ← ShortMsgName[mfh.msg];
 name ← Rope.Cat[ shortName, " ", date, " ",
       DB.V2S[DB.GetP[mfh.msg, WalnutDB.mSubjectIs]]];
 v ← NARROW[ViewerOps.FetchProp[msViewer, $LastSpawned]];

IF v # NIL AND ~v.destroyed THEN
  { v.name← name;
  ViewerOps.AddProp[v, $IconLabel, StripForIcon[shortName]];
  ViewerOps.PaintViewer[v, caption]}
ELSE
  { v ← ViewerOps.CreateViewer[flavor: $Text, paint: ~shift,
     info: [name: name, icon: msgIcon, scrollable: TRUE, iconic: FALSE]];
  ViewerLocks.CallUnderWriteLock[LockedSetMenu, v];
  IF shift THEN ViewerOps.GrowViewer[v];
  shift← FALSE;  -- so don't end up toggling the Grow
   ViewerOps.AddProp[v, $WhoSpawnedMe, msViewer];
   ViewerOps.AddProp[msViewer, $LastSpawned, v];
   ViewerOps.AddProp[v, $DestroyMsgDisplayer,
     ViewerEvents.RegisterEventProc[DestroyMsgDisplayer, destroy, v]];
   WalnutVoice.AddNuthatchHandleToViewer[v];
  };

 ViewerOps.AddProp[v, $Entity, msgName.Concat[GetName[mfh.msg]]];
 ViewerOps.AddProp[v, $WalnutEntity, mfh.msg];
-- Note how we cleverly put in a "." here to get the date to appear on a new line
 ViewerOps.AddProp[v, $IconLabel, Rope.Cat[StripForIcon[shortName], " .", date]];

IF mfh.posOK THEN
  contents← WalnutLog.TiogaTextFromLog[mfh.headersPos, mfh.msgLength]
ELSE
  { hp, len: INT;
  [contents, hp, len]← TiogaMsgFromLog[mfh.msg];
  mfh.headersPos← hp;
  mfh.msgLength← len;
  mfh.posOK← TRUE };

 ShowMsgInMsgViewer[v, contents];
IF v.iconic THEN ViewerOps.OpenIcon[v, shift]
  ELSE IF shift THEN ViewerOps.GrowViewer[v];
END;

FixUpMsgViewer: PUBLIC PROC[mName: ROPE, v: Viewer] =
BEGIN
 entityName: ROPE;
 msg: Msg;

IF (mName.Length[] = 0) OR v.destroyed THEN RETURN;

 entityName← mName.Substr[msgName.Length[]];
 msg← DeclareMsg[entityName, OldOnly].msg;
IF msg = NIL THEN
  { Report["Msg: ", entityName, " doesn't exist; destroying viewer"];
  ViewerOps.DestroyViewer[v];
RETURN
  };
 ShowMsgInMsgViewer[v, TiogaMsgFromLog[msg].contents];
 ViewerOps.AddProp[v, $WalnutEntity, msg];
END;

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

-- these two procs are backdoors into Walnut, so they need to be guarded

GetMsgName: PUBLIC PROC[v: Viewer] RETURNS[mName: ROPE] =
-- of v is a Viewer for a Walnut Entity, then return its name else NIL
BEGIN
 ra: REF ANY = ViewerOps.FetchProp[v, $Entity];
IF ra # NIL THEN
 { fullName: ROPENARROW[ra];
IF fullName.Find[msgName, 0] = 0 THEN RETURN[fullName.Replace[0, msgName.Length[]]]
  };
 Report[" Not a Walnut Msg viewer"];
END;

StuffMsgContents: PUBLIC PROC[v: Viewer, mName: ROPE] RETURNS[found: BOOLEAN] =
-- if mName is the name of a msg, then display that msg in v else return found← FALSE
BEGIN
ENABLE
DB.Aborted, DB.Failure, IO.Error => GOTO notFound;

 msg: Msg← DeclareMsg[mName, OldOnly].msg;
IF (found← msg#NIL) THEN
  { IF v.class.flavor # $Text THEN
   { Report[onlyInTextViewer]; RETURN[FALSE]};
   IF ViewerOps.FetchProp[v, $WhoSpawnedMe] # NIL THEN
    { ShowMsgInMsgViewer[v, TiogaMsgFromLog[msg].contents]; RETURN};
   IF v.link # NIL THEN
    { Report[noSplitsAllowed]; RETURN[FALSE]};
   ViewerTools.SetTiogaContents[v, WalnutDB.TiogaMsgFromLog[msg].contents, FALSE]
  };
EXITS
  notFound => RETURN[FALSE];
END;

AddToMsgMenu: PUBLIC PROC[label: ROPE, proc: Menus.MenuProc, onQueue: BOOLFALSE] =
BEGIN
IF onQueue THEN Menus.AppendMenuEntry[ msgMenu,
WalnutViewer.CreateMenuEntry[walnutQueue, label, proc]]
ELSE Menus.AppendMenuEntry[ msgMenu, Menus.CreateEntry[label, proc]];
END;

-- ***********************************************************
-- menu command procs:

MsgForwardProc: Menus.MenuProc =
{self: Viewer← NARROW[parent];
[]← WalnutSendOps.Forward[self, self];
};

MsgAnswerProc: Menus.MenuProc =
{ OPEN TiogaOps;
self: Viewer← NARROW[parent];
msgR: ROPE;
self.inhibitDestroy← TRUE;
TRUSTED { msgR← WalnutDocumentRope.Create[LOOPHOLE[ViewerDoc[self]]]};
[]← WalnutSendOps.Answer[msgR, self];
self.inhibitDestroy← FALSE;
};

MsgCategoriesProc: Menus.MenuProc =
BEGIN
 self: Viewer← NARROW[parent];
 ra: REF ANY← ViewerOps.FetchProp[self, $WalnutEntity];
 msg: Msg← V2E[ra];
 MsgCategories[msg];
END;

MsgGvIdProc: Menus.MenuProc =
BEGIN
 self: Viewer← NARROW[parent];
 ra: REF ANY← ViewerOps.FetchProp[self, $WalnutEntity];
 msg: Msg← V2E[ra];
 Report["gvID for msg is: ", GetName[msg]];
END;

MsgFreezeProc: Menus.MenuProc =
{ self: Viewer = NARROW[parent];
  msViewer: Viewer = NARROW[ViewerOps.FetchProp[self, $WhoSpawnedMe]];
  frozen: Menus.MenuEntry = Menus.FindEntry[self.menu, "Freeze"];
IF frozen # NIL THEN FreezeViewer[self];
-- now freeze everyone linked to you
IF self.link # NIL THEN -- you are part of a split viewer
FOR newV: Viewer ← self.link, newV.link UNTIL newV = self DO
  FreezeViewer[newV]
ENDLOOP;
-- only need to do this once
IF msViewer # NIL THEN ViewerOps.AddProp[msViewer, $LastSpawned, NIL] };

FreezeViewer: PROC[ v: Viewer ] = {
freezeButton: Menus.MenuEntry = Menus.FindEntry[v.menu, "Freeze"];
ViewerOps.AddProp[ v, $Frozen, v ];
IF freezeButton # NIL THEN
  { Menus.ReplaceMenuEntry[v.menu, freezeButton]; ViewerOps.PaintViewer[v, menu] } };

MsgSplitProc: Menus.MenuProc = {
self: Viewer = NARROW[parent];
newV: Viewer;
frozen: REF ANY = ViewerOps.FetchProp[self, $Frozen];
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;
ViewerOps.AddProp[ newV, $Entity, ViewerOps.FetchProp[ self, $Entity ] ];
ViewerOps.AddProp[ newV, $WalnutEntity, ViewerOps.FetchProp[ self, $WalnutEntity ] ];
ViewerOps.AddProp[ newV, $WhoSpawnedMe, ViewerOps.FetchProp[ self, $WhoSpawnedMe ] ];
IF frozen # NIL THEN FreezeViewer[newV]
ELSE ViewerOps.AddProp[ newV, $DestroyMsgDisplayer,
ViewerEvents.RegisterEventProc[DestroyMsgDisplayer, destroy, newV] ];
newV.icon ← msgIcon };

DestroyMsgDisplayer: ViewerEvents.EventProc = {
eventProc: ViewerEvents.EventRegistration;
next: Viewer = viewer.link;
spawner: Viewer = NARROW[ ViewerOps.FetchProp[viewer, $WhoSpawnedMe] ];
IF ViewerOps.FetchProp[viewer, $Frozen] # NIL THEN RETURN; -- you're not involved
IF spawner # NIL THEN ViewerOps.AddProp[ spawner, $LastSpawned, next ];
eventProc← NARROW[ ViewerOps.FetchProp[viewer, $DestroyMsgDisplayer]];
ViewerEvents.UnRegisterEventProc[ eventProc, destroy]; };

MsgPlacesProc: Menus.MenuProc =
  Menus.CopyEntry[ Menus.FindEntry[ TiogaMenuOps.tiogaMenu, "Places" ] ].proc;
  
MsgLevelsProc: Menus.MenuProc =
  Menus.CopyEntry[ Menus.FindEntry[ TiogaMenuOps.tiogaMenu, "Levels" ] ].proc;

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

MsgCategories: PUBLIC PROC[msg: Msg] =
BEGIN
 msL: LIST OF Value← DB.GetPList[msg, mCategoryIs];
 first: BOOLTRUE;
 name, date: ROPE;
 [name, date] ← ShortMsgName[msg];
 ReportRope[IO.PutFR[" %g is in: ", IO.rope[Rope.Cat[name, " ", date]]]];
IF msL = NIL THEN {Report["no MsgSets! This is a bug."]; RETURN};
FOR mL: LIST OF Value← msL, mL.rest UNTIL mL=NIL DO
  name: ROPEDB.GetName[V2E[mL.first]];
IF first THEN first← FALSE ELSE ReportRope[", "];
  ReportRope[name];
ENDLOOP;
 ReportRope["\n"];
END;

ShortMsgName: PROC[msg: Msg] RETURNS[toEntry: ROPE, date: ROPE] =
BEGIN
 TOCEntry: ROPE =
   DB.V2S[DB.GetP[e: msg, aIs: WalnutDB.mTOCEntryIs, aOf: WalnutDB.mTOCEntryOf]];
 lastNonBlank: INT ← Rope.Length[TOCEntry]-1;
date ← Rope.Substr[TOCEntry, 0, 9];
WHILE Rope.Fetch[TOCEntry, lastNonBlank] = '\n DO
  lastNonBlank ← lastNonBlank-1 ENDLOOP;
toEntry ← RemoveComments[Rope.Substr[TOCEntry, 11, lastNonBlank-10]];
END;

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

ShowMsgInMsgViewer: PROC[v: Viewer, contents: TiogaContents] =
BEGIN
 isFormated: BOOL;
 KillFeedbackSel[v];
IF (isFormated← contents.formatting.Length[] # 0) THEN
  {v.newVersion← TRUE; ViewerOps.PaintViewer[v, caption]};
IF v.link # NIL THEN DestroySplitMsgDisplayers[v];
 ViewerTools.SetTiogaContents[v, contents, FALSE];
IF isFormated THEN v.newVersion← FALSE;
 ViewerTools.InhibitUserEdits[v];
 ViewerOps.PaintViewer[v, all]
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;

DestroySplitMsgDisplayers: ENTRY PROC[keepThisOne: Viewer] =
BEGIN ENABLE UNWIND => NULL;
 next: Viewer← keepThisOne.link;
 next2: Viewer;
 event: ViewerEvents.EventRegistration;
DO
IF next = keepThisOne THEN EXIT;
IF (event← NARROW[ViewerOps.FetchProp[next, $DestroyMsgDisplayer]]) = NIL THEN LOOP;
  ViewerEvents.UnRegisterEventProc[event, destroy];
  next2← next.link;
  ViewerOps.DestroyViewer[next];  -- DON'T FORK here
  next← next2;
ENDLOOP;
END;

StripForIcon: PROC[ name: ROPE ] RETURNS[ iconLabel: ROPE ] = {
-- first remove any "< . . .>" in the name
start: INT;
dot: INT;
suffixLength: NAT;
name ← Rope.Concat[base: RemoveComments[name], rest: " "];
-- now see if there are any grapevine registries in the names
-- this is a real hack: look for two to four (for ".ARPA") alphabetics following a "."
start ← 0;
WHILE (dot ← Rope.Find[Rope.Substr[name, start], "."]) # -1 DO
suffixLength ← 0;
dot ← dot+start;
WHILE Ascii.Letter[Rope.Fetch[name, dot+1+suffixLength]] DO
suffixLength ← suffixLength+1;
IF suffixLength > 4 THEN EXIT
ENDLOOP;
IF suffixLength <= 4 THEN
{ name ← Rope.Replace[name, dot, suffixLength+1]; start ← dot }
ELSE start ← dot+suffixLength
ENDLOOP;
iconLabel ← Rope.Substr[name, 0, Rope.Length[name]-1] };

RemoveComments: PROC[ name: ROPE ] RETURNS[ shortName: ROPE ] = {
start, end: INT;
name ← Rope.Concat[base: name, rest: " "];
-- first remove any "< . . .>" in the name
start ← Rope.Find[name, "<"];
IF start > 0 THEN
{ end ← Rope.Find[s1: name, s2: ">", pos1: start+1];
IF end > 0 THEN name ← Rope.Replace[name, start, end-start+1] };
-- then do the same for any ( . . . ) in the name
start ← Rope.Find[name, "("];
IF start > 0 THEN
{ end ← Rope.Find[s1: name, s2: ")", pos1: start+1];
IF end > 0 THEN name ← Rope.Replace[name, start, end-start+1] };
shortName ← Rope.Substr[name, 0, Rope.Length[name]-1] };

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

-- initialize menues:
{ OPEN Menus;
AppendMenuEntry[ msgMenu,
      WalnutViewer.CreateMenuEntry[walnutQueue, "Freeze", MsgFreezeProc]];
AppendMenuEntry[ msgMenu,
    WalnutViewer.CreateMenuEntry[walnutQueue, "Categories", MsgCategoriesProc]];
AppendMenuEntry[ msgMenu,
  WalnutViewer.CreateMenuEntry[walnutQueue, "Answer", MsgAnswerProc]];
AppendMenuEntry[msgMenu,
  WalnutViewer.CreateMenuEntry[walnutQueue, "Forward", MsgForwardProc]];
AppendMenuEntry[msgMenu,
  WalnutViewer.CreateMenuEntry[walnutQueue, "Print", WalnutPrintOps.MsgPrintProc]];
AppendMenuEntry[msgMenu,
  WalnutViewer.CreateMenuEntry[walnutQueue, "gvID", MsgGvIdProc]];
AppendMenuEntry[ msgMenu,
WalnutViewer.CreateMenuEntry[walnutQueue, "Split", MsgSplitProc]];
AppendMenuEntry[ msgMenu,
 WalnutViewer.CreateMenuEntry[walnutQueue, "Places", MsgPlacesProc]];
AppendMenuEntry[ msgMenu,
 WalnutViewer.CreateMenuEntry[walnutQueue, "Levels", MsgLevelsProc]];

msgMenuAvailable← TRUE;
};
END.

Change Log.
WSH on March 4, 1983: take out all DBText stuff