WalnutMsgSetDisplayerImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Willie-Sue, October 14, 1986 6:30:51 pm PDT
Donahue, May 9, 1986 10:39:11 am PDT
Contents: Implementation of the WalnutMsgSet displayers.
Last edit by:
Donahue, December 13, 1984 11:42:00 am PST
(Added Nut connection)
(Changed message set version number to be part of viewer information, rather than part of message set buttons)
(Display now checks to see if versions match and does not do redisplay if so)
(Changed to use TiogaButtons - May 6, 1985)
DIRECTORY
Containers USING [ChildXBound, ChildYBound],
IO,
Menus USING [MouseButton, Menu, MenuProc],
Nut USING[SetNutInfo],
Process USING [Pause, MsecToTicks],
Rope,
TBQueue USING [CreateTiogaButton],
ThisMachine USING [Address],
TiogaButtons USING [
TiogaButton, TiogaButtonProc,
CreateViewer, ChangeButtonLooks, DeleteButton, SetStyleFromRope],
TiogaOps USING [InsertRope, SaveSelA, SetSelection, Delete, RestoreSelA, CancelSelection, GetSelection, InsertChar],
UserProfile USING [Boolean, Token],
ViewerClasses USING [Column, Viewer],
ViewerEvents USING [EventProc, EventRegistration, RegisterEventProc, UnRegisterEventProc],
ViewerOps USING [AddProp, ComputeColumn, CreateViewer, DestroyViewer,
FetchProp, GrowViewer, OpenIcon, PaintViewer, SetMenu],
ViewerSpecs USING [bwScreenHeight, openLeftWidth, openRightWidth],
ViewerTools USING [TiogaContents, TiogaContentsRec, GetSelectedViewer,
GetSelectionContents, GetTiogaContents],
WalnutParseMsg USING [ParseProc, MsgHeaders, ParseHeadersFromRope],
WalnutDefs USING [dontCareMsgSetVersion, Error],
WalnutOps USING [MsgSet, ServerInfo, ActiveMsgSetName, DeletedMsgSetName,
AcceptNewMail, AddMsg, CreateMsg, EnumerateMsgsInSet, GetDisplayProps, GetNewMail,
MoveMsg, MsgSetExists, RemoveMsg, SetHasBeenRead],
WalnutDisplayerInternal USING [ DisplayMsgFromMsgSet, MsgCategories],
WalnutMsgSetDisplayerPrivate USING [
MsgSetInfo, MsgSetInfoRec, MsgSetFieldHandle, MsgSetFieldHandleRec,
MsgInfo, MsgInfoRec,
activeMenu, buildingMenu, deletedMenu, displayerMenu, readOnlyMenu],
WalnutPrintOps USING [PrintMsgList],
WalnutWindowInternal USING [MsgSetButton,
activeMsgSetButton, deletedMsgSetButton, initialActiveIconic, initialActiveOpen,
initialActiveRight, msgSetIcon, msgSetNamePrefix, personalMailDB, readOnlyAccess,
msgSetsVersion, walnutQueue,
GetButton, GetSelectedMsgSets, Report, ReportRope, RetrieveNewMail];
WalnutMsgSetDisplayerImpl: CEDAR PROGRAM
IMPORTS
IO, Nut, Process, Rope, UserProfile,
WalnutParseMsg, WalnutDefs, WalnutOps,
WalnutDisplayerInternal, WalnutMsgSetDisplayerPrivate,
WalnutPrintOps, WalnutWindowInternal,
Containers, TBQueue, ThisMachine, TiogaButtons, TiogaOps,
ViewerEvents, ViewerOps, ViewerSpecs, ViewerTools
EXPORTS
WalnutDisplayerInternal, WalnutMsgSetDisplayerPrivate, WalnutPrintOps =
BEGIN OPEN WalnutMsgSetDisplayerPrivate;
Types
TiogaButton: TYPE = TiogaButtons.TiogaButton;
Viewer: TYPE = ViewerClasses.Viewer;
ROPE: TYPE = Rope.ROPE;
MsgSet: TYPE = WalnutOps.MsgSet;
MsgSetButton: TYPE = WalnutWindowInternal.MsgSetButton;
MsgSetInfo: TYPE = WalnutMsgSetDisplayerPrivate.MsgSetInfo;
MsgSetFieldHandle: TYPE = WalnutMsgSetDisplayerPrivate.MsgSetFieldHandle;
MsgInfo: TYPE = WalnutMsgSetDisplayerPrivate.MsgInfo;
tocDefaultLooks: PUBLIC ROPE
UserProfile.Token[key: "Walnut.TOCDefaultLooks", default: ""];
tocSelectedLooks: PUBLIC ROPE
UserProfile.Token[key: "Walnut.TOCSelectedLooks", default: "sb"];
tocUnreadLooks: PUBLIC ROPE
UserProfile.Token[key: "Walnut.TOCUnreadLooks", default: "i"];
userWantsQMs: PUBLIC BOOL
UserProfile.Boolean[key: "Walnut.ShowUnreadWithQMs", default: TRUE];
scrollWaitTime: CARDINAL ← 500;  -- Milliseconds
Procedures
destroyedMsg: ROPE = "Selected msg viewer has been destroyed";
GetMsgSetName: PUBLIC PROC[v: Viewer] RETURNS[msName: ROPE] = {
of v is a Viewer for a Walnut Entity, then return its name else NIL
msName ← NARROW[ViewerOps.FetchProp[v, $WalnutMsgSetName]];
IF msName = NIL THEN WalnutWindowInternal.Report[" Not a Walnut Message Set viewer"];
};
QDisplayMsgSet: PUBLIC PROC[msb: MsgSetButton, oldV: Viewer, shift: BOOL, repaint: BOOL]
RETURNS[v: Viewer] = {
IF oldV = NIL THEN {
IF msb = NIL THEN RETURN[NIL]; -- no such msgSet
oldV ← msb.msViewer };
IF oldV # NIL THEN
IF oldV.destroyed THEN oldV ← msb.msViewer ← NIL;
v ← MSViewer[msb: msb, oldV: oldV, shift: shift];
msb.msViewer ← v;
};
AddNewMsgsToActive: PUBLIC PROC[active: MsgSetButton]
RETURNS[responses: LIST OF WalnutOps.ServerInfo, complete: BOOL, numNew: INT] = {
activeV: Viewer = active.msViewer;
msI: MsgSetInfo;
NewMsgProc: PROC[msg, TOCentry: ROPE, startOfSubject: INT] = {
numNew ← numNew + 1;
BuildMsgLineViewer[msI, CreateMsgInfo[msg, FALSE, TOCentry, startOfSubject]];
};
numNew ← 0;
IF activeV = NIL OR activeV.destroyed THEN {
numNew ← -1;  -- don't know, won't count
[responses, complete] ← WalnutOps.GetNewMail[WalnutDefs.dontCareMsgSetVersion, NIL];
IF ~complete THEN RETURN;
WalnutOps.AcceptNewMail[WalnutDefs.dontCareMsgSetVersion];
RETURN
};
msI ← NARROW[ViewerOps.FetchProp[activeV, $MsgSetInfo]];
[responses, complete] ← WalnutOps.GetNewMail[active.msgSet.version, NewMsgProc];
IF ~complete THEN RETURN;
WalnutOps.AcceptNewMail[active.msgSet.version];
active.msgSet.version ← active.msgSet.version+1
};
RemoveMsgFromMsgSetDisplayer: PUBLIC PROC[msg: ROPE, msgSet: MsgSet] = {
msb: MsgSetButton = WalnutWindowInternal.GetButton[msgSet.name];
mfH: MsgSetFieldHandle;
msI: MsgSetInfo;
IF msb.msViewer = NIL OR msb.msViewer.destroyed THEN RETURN;
msI ← NARROW[ViewerOps.FetchProp[msb.msViewer, $MsgSetInfo]];
mfH ← msI.lastMFH;
DO
IF mfH = NIL THEN RETURN;  -- not there
IF msg.Equal[mfH.msgInfo.msg, FALSE] THEN EXIT;
mfH ← mfH.prev;
ENDLOOP;
RemoveFromDisplayedMsgSet[
msI, msb.msViewer, mfH, msb = WalnutWindowInternal.deletedMsgSetButton];
};
MsgSetNamePrefix: PUBLIC PROC RETURNS[ROPE] =
{ RETURN[WalnutWindowInternal.msgSetNamePrefix] };
TryToDisplayMsg: PROC[mfH: MsgSetFieldHandle] = {
DisplayOneMsg[mfH, FALSE ! WalnutDefs.Error => CONTINUE] };
MoveToProc: PUBLIC Menus.MenuProc = {
msViewer: Viewer = NARROW[parent];
msI: MsgSetInfo = NARROW[ViewerOps.FetchProp[msViewer, $MsgSetInfo]];
selected: MsgSetFieldHandle;
displayProcess: PROCESS;
IF msI = NIL THEN RETURN;
selected ← msI.selected;
IF selected = NIL THEN
{ WalnutWindowInternal.Report[" No selected msg to be moved"]; RETURN};
IF selected.tocButton = NIL THEN
{ WalnutWindowInternal.Report[destroyedMsg]; RETURN};
BEGIN
thisMsgSetB: MsgSetButton = msI.button;
thisName: ROPE = thisMsgSetB.msgSet.name;
msgSetList: LIST OF MsgSetButton = WalnutWindowInternal.GetSelectedMsgSets[];
first: BOOLTRUE;
moveToSelf: BOOLFALSE;
msg: ROPE = selected.msgInfo.msg;
fromDeleted: BOOL = msI.button = WalnutWindowInternal.deletedMsgSetButton;
IF msgSetList = NIL THEN
{ WalnutWindowInternal.Report[" No selected MsgSets to Move msg to"]; RETURN};
First display the next message to keep the user busy
IF mouseButton#red THEN displayProcess ← FORK TryToDisplayMsg[selected.next];
FOR msL: LIST OF MsgSetButton ← msgSetList, msL.rest UNTIL msL=NIL DO
IF thisName.Equal[msL.first.msgSet.name, FALSE] THEN moveToSelf ← TRUE
ELSE IF ~WalnutOps.AddMsg[msg, thisMsgSetB.msgSet, msL.first.msgSet].exists THEN {
IF msL.first.msViewer # NIL THEN
AddToDisplayedMsgSet[msL.first, selected.msgInfo, NIL];
IF fromDeleted AND first THEN {
msg has been automatically removed from Deleted, so bump its version number
first ← FALSE;
thisMsgSetB.msgSet.version ← thisMsgSetB.msgSet.version + 1;
};
};
ENDLOOP;
IF ~fromDeleted THEN
IF NOT moveToSelf THEN [] ← WalnutOps.RemoveMsg[
msg, thisMsgSetB.msgSet, MsgSetVersion[WalnutWindowInternal.deletedMsgSetButton] ];
first ← TRUE;
FOR msL: LIST OF MsgSetButton ← msgSetList, msL.rest UNTIL msL=NIL DO
IF first THEN
{ ReportDisposition[selected.msgInfo, "Moved to:"]; first ← FALSE}
ELSE WalnutWindowInternal.ReportRope[","];
WalnutWindowInternal.ReportRope[" "];
WalnutWindowInternal. ReportRope[msL.first.msgSet.name];
ENDLOOP;
WalnutWindowInternal.ReportRope["\n"];
IF ~moveToSelf OR fromDeleted THEN
RemoveFromDisplayedMsgSet[msI, msViewer, selected, fromDeleted]
ELSE [] ← AdvanceSelection[selected];
Finally, make sure the displaying process is done
IF displayProcess # NIL THEN TRUSTED { [] ← JOIN displayProcess }
END;
};
MsgSetVersion: PROC[msb: MsgSetButton] RETURNS[msgSetVersion: INT] = {
IF msb = NIL OR msb.msViewer = NIL OR msb.msViewer.destroyed THEN
RETURN[WalnutDefs.dontCareMsgSetVersion]
ELSE RETURN[msb.msgSet.version] };
NewMailProc: PUBLIC PROC[viewer: Viewer] = {
oldM: Menus.Menu = viewer.menu;
ViewerOps.SetMenu[viewer, buildingMenu];
BEGIN ENABLE UNWIND => ViewerOps.SetMenu[viewer, oldM];
[] ← WalnutWindowInternal.RetrieveNewMail[];
ViewerOps.SetMenu[viewer, oldM];
END;
};
CategoriesProc: PUBLIC PROC[viewer: Viewer] = {
msI: MsgSetInfo = NARROW[ViewerOps.FetchProp[viewer, $MsgSetInfo]];
selected: MsgSetFieldHandle;
IF msI = NIL THEN RETURN;
selected ← msI.selected;
IF selected = NIL THEN {WalnutWindowInternal.Report[" No selected msg"]; RETURN};
IF selected.tocButton = NIL THEN {WalnutWindowInternal.Report[destroyedMsg]; RETURN};
WalnutDisplayerInternal.MsgCategories[selected.msgInfo.msg];
};
DeleteProc: PUBLIC Menus.MenuProc = {
msViewer: Viewer = NARROW[parent];
msI: MsgSetInfo = NARROW[ViewerOps.FetchProp[msViewer, $MsgSetInfo]];
displayProcess: PROCESS;
selected: MsgSetFieldHandle;
IF msI = NIL THEN RETURN;
selected ← msI.selected;
IF selected = NIL THEN
{ WalnutWindowInternal.Report[" No selected msg to be deleted"]; RETURN};
IF selected.tocButton = NIL THEN
{ WalnutWindowInternal.Report[destroyedMsg]; RETURN};
BEGIN
thisMsgSet: MsgSet = msI.button.msgSet;
nowInDeleted: BOOL;
IF thisMsgSet.name.Equal[WalnutOps.DeletedMsgSetName] THEN RETURN; -- already deleted
IF mouseButton#red THEN displayProcess ← FORK TryToDisplayMsg[selected.next];
nowInDeleted ← WalnutOps.RemoveMsg[
selected.msgInfo.msg, thisMsgSet,
MsgSetVersion[WalnutWindowInternal.deletedMsgSetButton] ];
RemoveFromDisplayedMsgSet[msI, msViewer, selected, FALSE];
ReportDisposition[selected.msgInfo, "Deleted from "];
WalnutWindowInternal.Report[thisMsgSet.name];
IF nowInDeleted THEN
AddToDisplayedMsgSet[WalnutWindowInternal.deletedMsgSetButton, selected.msgInfo, NIL];
IF displayProcess # NIL THEN TRUSTED { [] ← JOIN displayProcess }
END;
};
AddProc: PUBLIC Menus.MenuProc = {
msViewer: Viewer = NARROW[parent];
msI: MsgSetInfo = NARROW[ViewerOps.FetchProp[msViewer, $MsgSetInfo]];
selected: MsgSetFieldHandle;
displayProcess: PROCESS;
IF msI = NIL THEN RETURN;
selected ← msI.selected;
IF selected = NIL THEN
{ WalnutWindowInternal.Report[" No selected msg to be moved"]; RETURN};
IF selected.tocButton = NIL THEN
{ WalnutWindowInternal.Report[destroyedMsg]; RETURN};
BEGIN
thisMsgSet: MsgSet = msI.button.msgSet;
msgSetList: LIST OF MsgSetButton = WalnutWindowInternal.GetSelectedMsgSets[];
msg: ROPE = selected.msgInfo.msg;
isDeleted: BOOL = thisMsgSet.name.Equal[WalnutOps.DeletedMsgSetName, FALSE];
noLongerInDeleted: BOOLFALSE;
first: BOOLTRUE;
addedTo: LIST OF MsgSetButton;
IF msgSetList = NIL THEN
{ WalnutWindowInternal.Report[" No selected MsgSets to Add msg to"]; RETURN};
IF mouseButton#red THEN displayProcess ← FORK TryToDisplayMsg[selected.next];
FOR msL: LIST OF MsgSetButton ← msgSetList, msL.rest UNTIL msL=NIL DO
exists, undeleted: BOOL;
msgSet: MsgSet;
exists ← WalnutOps.AddMsg[msg, thisMsgSet, msgSet ← msL.first.msgSet];
noLongerInDeleted ← noLongerInDeleted OR undeleted;
IF ~exists THEN
BEGIN
IF first THEN
{ ReportDisposition[selected.msgInfo, "Added to: "]; first ← FALSE}
ELSE WalnutWindowInternal.ReportRope[", "];
WalnutWindowInternal.ReportRope[msgSet.name];
addedTo ← CONS[msL.first, addedTo];
END;
ENDLOOP;
WalnutWindowInternal.ReportRope["\n"];
now update display
FOR msL: LIST OF MsgSetButton ← addedTo, msL.rest UNTIL msL=NIL DO
AddToDisplayedMsgSet[msL.first, selected.msgInfo, NIL];
ENDLOOP;
IF mouseButton # red THEN [] ← AdvanceSelection[selected];
IF noLongerInDeleted THEN
RemoveMsgFromMsgSetDisplayer[msg, WalnutWindowInternal.deletedMsgSetButton.msgSet];
IF displayProcess # NIL THEN TRUSTED { [] ← JOIN displayProcess }
END;
};
AdvanceSelection: PROC[msfH: MsgSetFieldHandle] RETURNS[next: MsgSetFieldHandle] = {
IF msfH = NIL THEN RETURN[NIL];
next ← msfH.next;
IF next = NIL THEN { WalnutWindowInternal.Report[" No Next message"]; RETURN };
[] ← SelectMsgInMsgSet[next]
};
DisplayProc: PUBLIC Menus.MenuProc = {
msViewer: Viewer = NARROW[parent];
msI: MsgSetInfo = NARROW[ViewerOps.FetchProp[msViewer, $MsgSetInfo]];
selected: MsgSetFieldHandle;
IF msI = NIL THEN RETURN;
selected ← msI.selected;
IF mouseButton # red THEN selected ← AdvanceSelection[selected];
IF selected = NIL THEN
{ WalnutWindowInternal.Report[" No selected msg to be displayed"]; RETURN};
IF selected.tocButton = NIL THEN
{ WalnutWindowInternal.Report[destroyedMsg]; RETURN};
DisplayOneMsg[selected, shift]
};
PrintSelectedProc: PUBLIC PROC[viewer: Viewer, usePress: BOOL] = {
PrintSel[msViewer: viewer, usePress: usePress]
};
PrintSel: PROC[
 msViewer: Viewer, server: ROPENIL, usePress: BOOLTRUE , copies: INT ← 1] = {
msI: MsgSetInfo = NARROW[ViewerOps.FetchProp[msViewer, $MsgSetInfo]];
selected: MsgSetFieldHandle;
IF msI = NIL THEN RETURN;
selected ← msI.selected;
IF selected = NIL THEN
{ WalnutWindowInternal.Report[" No selected msg to be printed"]; RETURN};
IF selected.tocButton = NIL THEN
{ WalnutWindowInternal.Report[destroyedMsg]; RETURN};
[] ← WalnutPrintOps.PrintMsgList[
LIST[selected.msgInfo.msg], msViewer, server, usePress, copies];
};
PrintSelCmd: PUBLIC PROC[msgSet: ROPE, server: ROPE, usePress: BOOL, copies: INT] = {
msb: MsgSetButton = WalnutWindowInternal.GetButton[msgSet];
IF msb = NIL THEN RETURN;
IF msb.msViewer = NIL THEN RETURN;
PrintSel[msb.msViewer, server, usePress, copies];
};
AppendMsgProc: PUBLIC PROC[msViewer: Viewer] = {
curSel: ROPE = ViewerTools.GetSelectionContents[];
curV: Viewer = ViewerTools.GetSelectedViewer[];
msI: MsgSetInfo = NARROW[ViewerOps.FetchProp[msViewer, $MsgSetInfo]];
tc: ViewerTools.TiogaContents;
from, sender, gvID, me, TOCentry: ROPE;
date: ROPE = IO.PutFR[NIL, IO.time[]];
startOfSubject: INT;
hasBeenRead: BOOL;
msgInfo: MsgInfo;
mh: WalnutParseMsg.MsgHeaders;
active: MsgSetButton ← WalnutWindowInternal.activeMsgSetButton;
WantThisField: WalnutParseMsg.ParseProc = {
SELECT TRUE FROM
fieldName.Equal["From", FALSE] => RETURN[TRUE, TRUE];
fieldName.Equal["Sender", FALSE] => RETURN[TRUE, TRUE];
ENDCASE => RETURN[FALSE, TRUE];
};
IF msI = NIL THEN RETURN;
IF curSel.Length[] < 2 THEN tc ← ViewerTools.GetTiogaContents[curV]
ELSE {
tc ← NEW[ViewerTools.TiogaContentsRec];
tc.contents ← curSel;
};
mh ← WalnutParseMsg.ParseHeadersFromRope[tc.contents, WantThisField];
FOR mhL: WalnutParseMsg.MsgHeaders ← mh, mhL.rest UNTIL mhL=NIL DO
fieldName: ROPE = mhL.first.fieldName;
SELECT TRUE FROM
fieldName.Equal["Sender", FALSE] => sender ← mhL.first.value;
fieldName.Equal["From", FALSE] => from ← mhL.first.value;
ENDCASE => NULL;
ENDLOOP;
IF sender = NIL THEN sender ← from;
IF sender = NIL THEN sender ← "UnknownSender";
me ← ThisMachine.Address[];
gvID ← IO.PutFR["%g $ %g@%g",
IO.rope[sender], IO.rope[me.Substr[len: me.Length[] - 1] ], IO.rope[date] ];
WalnutOps.CreateMsg[gvID, tc];
[hasBeenRead, TOCentry, startOfSubject] ← WalnutOps.GetDisplayProps[gvID];
msgInfo ← CreateMsgInfo[gvID, hasBeenRead, TOCentry, startOfSubject];
IF msI.button # active THEN {
active.msgSet.version ← active.msgSet.version + 1;
[] ← WalnutOps.MoveMsg[msg: gvID, from: active.msgSet, to: msI.button.msgSet];
active.msgSet.version ← active.msgSet.version + 1;
};
AddToDisplayedMsgSet[msI.button, msgInfo, msI]
};
ReportDisposition: PROC[msgInfo: MsgInfo, r: ROPE] = {
msgSubject: ROPE ← Rope.Substr[msgInfo.tocName, msgInfo.startOfSubject];
IF msgSubject.Length[] > 24 THEN msgSubject ← Rope.Concat[msgSubject.Substr[0, 21], " ..."];
WalnutWindowInternal.ReportRope["Msg: ", msgSubject];
WalnutWindowInternal.ReportRope[": has been ", r];
};
* * * * * * * * * * * * * * * * * * * * * *
MSViewer: PROC[msb: MsgSetButton, oldV: Viewer, shift: BOOL] RETURNS[msV: Viewer] = {
OPEN WalnutWindowInternal;
iconic: BOOLFALSE;
whichSide: ViewerClasses.Column ← left;
msgSet: MsgSet = msb.msgSet;
caption: ROPE = Rope.Concat[msgSet.name, " Messages"];
msI: MsgSetInfo;
IF msgSet.name.Equal[WalnutOps.ActiveMsgSetName, FALSE] THEN {
iconic ← initialActiveIconic AND initialActiveOpen;
IF initialActiveRight THEN whichSide ← right
};
IF oldV # NIL THEN IF oldV.destroyed THEN oldV ← NIL;
IF oldV # NIL THEN
{ oldMsgSetName: ROPE = NARROW[ViewerOps.FetchProp[oldV, $WalnutMsgSetName]];
msI ← NARROW[ViewerOps.FetchProp[oldV, $MsgSetInfo]];
oldV.inhibitDestroy ← TRUE;
IF ~Rope.Equal[msgSet.name, oldMsgSetName, FALSE] THEN {
oldV.name ← caption;
WalnutWindowInternal.GetButton[oldMsgSetName].msViewer ← NIL;
WalnutWindowInternal.GetButton[msgSet.name].msViewer ← oldV }
ELSE { -- check the version to see if all's well
IF msI.button.msgSet.version = WalnutOps.MsgSetExists[
msgSet.name, WalnutWindowInternal.msgSetsVersion].version THEN {
oldV.inhibitDestroy ← FALSE;
RETURN[oldV]
}
};
ViewerOps.DestroyViewer[msI.tiogaViewer];
msI.selected ← NIL;
msI.lastMFH ← NIL;
msI.tiogaViewer ← NIL;
msV ← oldV;
}
ELSE {
msV ← ViewerOps.CreateViewer[
flavor: $Container,
info: [name: caption, column: whichSide, menu: NIL,
iconic: iconic, icon: msgSetIcon, scrollable: FALSE, inhibitDestroy: TRUE]];
msI ← NEW[MsgSetInfoRec];
msI.button ← msb;
msI.container ← msV;
msI.destroyER ←
ViewerEvents.RegisterEventProc[DestroyMSViewer, destroy, msV, TRUE];
};
IF msI.tiogaViewer = NIL THEN {
width: INTIF msV.column = right THEN ViewerSpecs.openRightWidth ELSE
ViewerSpecs.openLeftWidth;
msI.tiogaViewer ← TiogaButtons.CreateViewer[
info: [parent: msV, border: FALSE, ww: width, wh: ViewerSpecs.bwScreenHeight]];
TiogaButtons.SetStyleFromRope[v: msI.tiogaViewer, styleRope: msgSetStyle];
Containers.ChildXBound[msV, msI.tiogaViewer];
Containers.ChildYBound[msV, msI.tiogaViewer];
};
ViewerOps.AddProp[msV, $MsgSetInfo, msI];
Nut.SetNutInfo[msV, $Walnut, "MsgSet", msgSet.name];
ViewerOps.AddProp[msV, $WalnutMsgSetName, msgSet.name];
ViewerOps.AddProp[msV, $IconLabel, msgSet.name];
MsgSetInViewer[msgSet, msI, shift];
};
msgSetStyle: ROPE ← "BeginStyle
(Cedar) AttachStyle
(header) \"a message header\" {
default
200 pt restIndent
} StyleRule
EndStyle";
DestroyMSViewer: ViewerEvents.EventProc = {
msI: MsgSetInfo = NARROW[ViewerOps.FetchProp[viewer, $MsgSetInfo]];
msI.button.msgSet.version ← WalnutDefs.dontCareMsgSetVersion;
msI.button.msViewer ← NIL;
ViewerEvents.UnRegisterEventProc[msI.destroyER, destroy];
};
AutoScrollRef: TYPE = REF AutoScrollObject;
AutoScrollObject: TYPE = RECORD
[ eventReg: ViewerEvents.EventRegistration, v: Viewer, firstUnread, last: INT ];
MsgSetInViewer: PROC[msgSet: MsgSet, msI: MsgSetInfo, shift: BOOL] = {
OPEN ViewerOps;
firstUnread, last: INT;
autoScroll: BOOL = UserProfile.Boolean[key: "Walnut.AutoScrollMsgSets", default: TRUE];
v: Viewer = msI.tiogaViewer;
parent: Viewer = v.parent;
menu: Menus.Menu ←
IF WalnutWindowInternal.readOnlyAccess THEN readOnlyMenu ELSE
IF msgSet.name.Equal[WalnutOps.ActiveMsgSetName, FALSE] THEN
IF WalnutWindowInternal.personalMailDB THEN activeMenu ELSE displayerMenu ELSE
IF msgSet.name.Equal[WalnutOps.DeletedMsgSetName, FALSE] THEN deletedMenu ELSE displayerMenu;
ScrollMsgSet: PROC = {
IF autoScroll AND last#-1 THEN
{ IF v.parent.iconic THEN
{ scroll: ViewerEvents.EventRegistration ←
ViewerEvents.RegisterEventProc[AutoScroll, open, v.parent, FALSE];
after
ViewerOps.AddProp[v.parent, $autoScroll,
NEW[AutoScrollObject ← [scroll, v, firstUnread, last]]];
}
ELSE DoScroll[v, firstUnread, last];
};
SetMenu[parent, menu];
};
[] ← v.class.scroll[v, thumb, 0];  -- position at beginning
IF ~parent.iconic THEN ComputeColumn[parent.column, TRUE];
parent.newVersion ← TRUE; PaintViewer[parent, caption];
SetMenu[parent, buildingMenu];
[firstUnread, last] ← FillInMsgSetWindow[msI, msgSet.name];
ScrollMsgSet[];
ViewerLocks.CallUnderWriteLock[ScrollMsgSet, v];
parent.newVersion ← FALSE; PaintViewer[parent, caption];
IF shift THEN
{ IF parent.iconic THEN ViewerOps.OpenIcon[parent, shift] ELSE ViewerOps.GrowViewer[parent]};
parent.inhibitDestroy ← FALSE;
};
DoScroll: PROC[viewer: Viewer, firstUnread, last: INT] = {
top, bottom, howMuch: INTEGER;
count: INTEGER ← 0;
DO
[top, bottom] ← viewer.class.scroll[viewer, query, 0];
IF (bottom = LAST[INTEGER]) AND ((count ← count + 1) < 5) THEN {
Process.Pause[Process.MsecToTicks[scrollWaitTime]];
LOOP
};
EXIT;
ENDLOOP;
IF bottom >= 95 OR last <=0 THEN RETURN;
howMuch ← IF firstUnread <= 0 THEN (last - 3) ELSE
IF firstUnread > (last - 3) THEN (last - 3) ELSE firstUnread;
[] ← viewer.class.scroll[viewer, thumb, (howMuch*100)/last];
};
AutoScroll: ViewerEvents.EventProc = {
autoScroll: AutoScrollRef ← NARROW[ViewerOps.FetchProp[viewer, $autoScroll]];
ViewerEvents.UnRegisterEventProc[autoScroll.eventReg, open];  -- once only
DoScroll[autoScroll.v, autoScroll.firstUnread, autoScroll.last];
};
* * * * * * * * * * * * * * * * * * * * * *
for adding new messages to displayed MsgSet
AddToDisplayedMsgSet: PROC[msb: MsgSetButton, msgInfo: MsgInfo, msI: MsgSetInfo] = {
IF msb.msViewer # NIL THEN {
IF msb.msViewer.destroyed THEN { msb.msViewer ← NIL; RETURN };
IF msI = NIL THEN msI ← NARROW[ViewerOps.FetchProp[msb.msViewer, $MsgSetInfo]];
BuildMsgLineViewer[msI, msgInfo];
msI.button.msgSet.version ← msb.msgSet.version ← msI.button.msgSet.version+1;
};
};
called with parent & MsgSetFieldHandle 'displaying' msg to be deleted
RemoveFromDisplayedMsgSet: PROC[
msI: MsgSetInfo, msViewer: Viewer, mfH: MsgSetFieldHandle, fromDeleted: BOOL] = {
prev, next: MsgSetFieldHandle;
bt: TiogaButton;
selected: MsgSetFieldHandle ← msI.selected;
bt ← mfH.tocButton;
IF selected = mfH THEN
TiogaButtons.ChangeButtonLooks[
button: bt, addLooks: tocDefaultLooks, removeLooks: tocSelectedLooks];
prev ← mfH.prev;
next ← mfH.next;
take which out of chain of mfh's
IF prev # NIL THEN prev.next ← next;
IF next # NIL THEN next.prev ← prev;
TiogaButtons.DeleteButton[bt];
IF selected = mfH THEN {
msI.selected ← next;
IF next # NIL THEN
TiogaButtons.ChangeButtonLooks[
button: next.tocButton, addLooks: tocSelectedLooks, removeLooks: tocDefaultLooks];
};
IF msI.lastMFH = mfH THEN msI.lastMFH ← prev;
IF ~fromDeleted THEN msI.button.msgSet.version ← msI.button.msgSet.version+1;
};
FillInMsgSetWindow: PROC[msI: MsgSetInfo, msgSet: ROPE] RETURNS[firstUnread, last: INT] = {
MsgInfoProc: PROC[msg, TOCentry: ROPE, hasBeenRead: BOOL, startOfSubject: INT] = {
BuildMsgLineViewer[msI, CreateMsgInfo[msg, hasBeenRead, TOCentry, startOfSubject]];
last ← last + 1;
IF firstUnread = -1 AND ~msI.lastMFH.msgInfo.hasBeenRead THEN
firstUnread ← last;
};
firstUnread ← -1;
last ← 0;
msI.button.msgSet.version ← WalnutOps.EnumerateMsgsInSet[msgSet, TRUE, MsgInfoProc];
};
DoMsgInfo: PROC[msg: ROPE] RETURNS[msgInfo: MsgInfo] = {
tocEntry: ROPE;
hasBeenRead: BOOL;
startOfSubject: INT;
[hasBeenRead, tocEntry, startOfSubject] ← WalnutOps.GetDisplayProps[msg];
RETURN[CreateMsgInfo[msg, hasBeenRead, tocEntry, startOfSubject]];
};
CreateMsgInfo: PROC[msg: ROPE, hasBeenRead: BOOL, tocEntry: ROPE, startOfSubject: INT]
RETURNS[msgInfo: MsgInfo] = {
needsQ: BOOL ← userWantsQMs OR (tocUnreadLooks = NIL);
sos: INTIF needsQ THEN 1 ELSE 0;
msgInfo ← NEW[MsgInfoRec ← [msg, NIL, startOfSubject+sos, hasBeenRead]];
msgInfo.tocName ← IF needsQ THEN Rope.Concat["\t", tocEntry] ELSE tocEntry;
};
BuildMsgLineViewer: PROC[msI: MsgSetInfo, msgInfo: MsgInfo] = {
tocLooks: ROPEIF msgInfo.hasBeenRead THEN tocDefaultLooks ELSE tocUnreadLooks;
needsQ: BOOL ← userWantsQMs OR (tocUnreadLooks = NIL);
mfh: MsgSetFieldHandle ← NEW[MsgSetFieldHandleRec ← [
prev: msI.lastMFH,
tocButton: NIL,
container: msI.container,
msgInfo: msgInfo]];
mfh.tocButton ← TBQueue.CreateTiogaButton[
q: WalnutWindowInternal.walnutQueue,
viewer: msI.tiogaViewer,
rope: IF ~msgInfo.hasBeenRead AND needsQ THEN Rope.Concat["?", msgInfo.tocName] ELSE msgInfo.tocName,
format: "header",
looks: tocLooks,
proc: MsgSetSelectionProc,
clientData: mfh];
IF msI.lastMFH # NIL THEN msI.lastMFH.next ← mfh;
msI.lastMFH ← mfh;
};
noMsgRope: ROPE = "Msg is no longer in the MsgSet";
MsgSetSelectionProc: TiogaButtons.TiogaButtonProc = {
button: TiogaButton = NARROW[parent];
IF button = NIL THEN WalnutWindowInternal.Report[noMsgRope]
ELSE
{ mfH: MsgSetFieldHandle = NARROW[clientData];
IF (mouseButton = red) AND shift THEN
{ TiogaOps.InsertRope[mfH.msgInfo.tocName]; RETURN };
[] ← SelectMsgInMsgSet[mfH];
IF control THEN DeleteProc[parent: mfH.container, mouseButton: mouseButton]
ELSE IF mouseButton#red THEN DisplayOneMsg[mfH, shift]
};
};
DisplayOneMsg: PROC[mfH: MsgSetFieldHandle, shift: BOOL] = {
IF mfH # NIL THEN {
WalnutDisplayerInternal.DisplayMsgFromMsgSet[mfH.msgInfo.msg, mfH.container, shift];
IF ~mfH.msgInfo.hasBeenRead THEN MarkMsgAsRead[mfH];
};
};
SelectMsgInMsgSet: PROC[mfH: MsgSetFieldHandle] RETURNS[sameAsBefore: BOOL] = {
IF mfH.tocButton = NIL THEN WalnutWindowInternal.Report[noMsgRope]
ELSE
{ msI: MsgSetInfo = NARROW[ViewerOps.FetchProp[mfH.container, $MsgSetInfo]];
prevSelected: MsgSetFieldHandle = msI.selected;
IF prevSelected = mfH THEN RETURN[TRUE];
turn off previous selection
IF prevSelected # NIL AND prevSelected.tocButton#NIL THEN
TiogaButtons.ChangeButtonLooks[
button: prevSelected.tocButton,
addLooks: tocDefaultLooks,
removeLooks: tocSelectedLooks];
TiogaButtons.ChangeButtonLooks[button: mfH.tocButton, addLooks: tocSelectedLooks];
msI.selected ← mfH;
};
RETURN[FALSE];
};
MarkMsgAsRead: PROC[mfH: MsgSetFieldHandle] = {
used by selection
msI: MsgSetInfo = NARROW[ViewerOps.FetchProp[mfH.container, $MsgSetInfo]];
WalnutOps.SetHasBeenRead[mfH.msgInfo.msg];
mfH.msgInfo.hasBeenRead ← TRUE;
IF userWantsQMs THEN {
viewer: Viewer = TiogaOps.GetSelection[].viewer;
IF viewer # NIL THEN TiogaOps.SaveSelA[];
TiogaOps.SetSelection[viewer: msI.tiogaViewer, start: [mfH.tocButton.startLoc.node, 0], end: [mfH.tocButton.endLoc.node, 0]];
TiogaOps.Delete[];
TiogaOps.InsertChar[' ];
IF viewer # NIL THEN TiogaOps.RestoreSelA[] ELSE TiogaOps.CancelSelection[] };
TiogaButtons.ChangeButtonLooks[
button: mfH.tocButton, addLooks: tocDefaultLooks, removeLooks: tocUnreadLooks];
};
END.