File: WalnutMsgDisplayerImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Contents: Implementation of the Walnut Msg Display windows.
Last Edited by: Willie-Sue, March 20, 1985 9:11:12 am PST
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: August 23, 1984 12:30:12 pm PDT
DIRECTORY
Ascii USING [ DEL, Letter ],
Basics USING [BYTE, LongNumber],
BasicTime USING [GMT, ToPupTime],
DB USING [Aborted, Error, Failure, GetP, GetPList, NameOf, V2S],
GVBasics USING [Timestamp],
IO,
Menus,
PupDefs USING [PupAddress, GetHostName],
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, RFC822Date],
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, BasicTime, DB, IO, PupDefs, 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] =
{ RETURN[SetUpViewer[mName: mName, msg: msg, shift: shift, paint: paint, v: NIL]]};
MsgInViewer: PUBLIC PROC[mName: ROPE, msg: Msg, v: Viewer, shift: BOOLFALSE] =
BEGIN
v← SetUpViewer[mName: mName, msg: msg, shift: shift, paint: TRUE, v: v];
ShowMsgInMsgViewer[v, TiogaMsgFromLog[msg].contents];
END;
SetUpViewer: PROC[mName: ROPE, msg: Msg, shift, paint: BOOLFALSE, v: Viewer]
RETURNS[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]]];
name← CheckName[name];
IF v = NIL THEN
{ 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];
RETURN[v];
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]]];
name← CheckName[name];
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.Error, 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];
self.inhibitDestroy← TRUE;
[]← WalnutSendOps.Forward[self, self];
self.inhibitDestroy← FALSE;
};
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];
name: ROPE = GetName[msg];
gID, other: ROPE;
Report["gvID for msg is: ", name];
IF mouseButton = red THEN RETURN;
[gID, other]← OtherIDFormats[name];
Report[gID];
Report[other];
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.NameOf[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;
CheckName: PROC[name: ROPE] RETURNS[checked: ROPE] =
BEGIN
transProc: Rope.TranslatorType =
{ RETURN[IF old > Ascii.DEL THEN Ascii.DEL ELSE old] };
IF name.Length[] > 60 THEN name← Rope.Concat[name.Substr[0, 57], " ..."];
checked← Rope.Translate[base: name, translator: transProc];
END;
OtherIDFormats: PROC[name: ROPE] RETURNS[gID, other: ROPE] =
BEGIN OPEN IO;
pupAddr: PupDefs.PupAddress;
ts: GVBasics.Timestamp;
h: IO.STREAMIO.RIS[name];
netAsInt, hostAsInt: INT;
tyme: BasicTime.GMT;
net, host: Basics.BYTE;
sender, gvTimeStamp, pupHost: ROPE;
sender← h.GetTokenRope[IO.IDProc].token;
UNTIL ([]← h.GetChar[]) = '$ DO ENDLOOP;
[]← h.GetChar[]; -- space
netAsInt← h.GetInt[];
[]← h.GetChar[];  -- #
hostAsInt← h.GetInt[];
[]← h.GetChar[];  -- @
tyme← h.GetTime[];
net← LOOPHOLE[netAsInt, Basics.LongNumber].ll;
host← LOOPHOLE[hostAsInt, Basics.LongNumber].ll;
pupAddr← [net: [net], host: [host], socket: [0, 0]];
ts← [net: net, host: host, time: BasicTime.ToPupTime[tyme]];
gvTimeStamp← RopeFromTimestamp[ts];
pupHost← PupDefs.GetHostName[pupAddr];
gID← PutFR["[sender: %g, gvTimeStamp: %g]", rope[sender], rope[gvTimeStamp]];
other← PutFR["[sender: %g, fromHost: %g, time: %g", rope[sender], rope[pupHost],
  rope[WalnutSendOps.RFC822Date[tyme]]];
END;
the one from GVBasics gets a bound fault
RopeFromTimestamp: PROC[stamp: GVBasics.Timestamp] RETURNS[Rope.ROPE] =
{ RETURN[ IO.PutFR["%b#%b@%g",
[integer[stamp.net]], [integer[stamp.host]], [cardinal[stamp.time]] ] ] };
* * * * * * * * * * * * * * * * * * * * * * * * * * * *
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