File: WalnutMsgSetDisplayerImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Contents: Implementation of the WalnutMsg Editor windows.
Status: mostly here functionally, but not yet completed.
Rick Cattell on: April 29, 1982 7:02 pm
Donahue, July 27, 1983 2:22 pm
Willie-Sue, March 20, 1985 9:11:55 am PST
Russ Atkinson (RRA) April 25, 1985 3:09:11 am PST
Rick Beach, April 27, 1985 12:35:09 pm PST
DIRECTORY
Buttons USING [ButtonProc, ReLabel, SetDisplayStyle],
DB USING [GetF, GetP, NameOf],
Menus USING [AppendMenuEntry, CreateMenu, Menu, MenuProc],
Rope,
RuntimeError USING [BoundsFault],
UserProfile USING [Boolean],
VFonts USING [ StringWidth, CharWidth],
ViewerClasses USING [Column, Viewer],
ViewerEvents USING [EventProc, EventRegistration, RegisterEventProc, UnRegisterEventProc],
ViewerLocks USING [CallUnderWriteLock],
ViewerOps USING [AddProp, ComputeColumn, CreateViewer, DestroyViewer, FetchProp, GrowViewer, MoveViewer, OpenIcon, PaintViewer, SetMenu],
WalnutDB USING [Entity, Msg, MsgSet, Relship,
activeMsgSet, deletedMsgSet, mCategory, mCategoryIs, mCategoryOf,
mHasBeenReadIs, mSubjectIs, mTOCEntryIs,
AcquireDBLock, AddMsgToMsgSet, DeclareMsg, DeclareMsgSet, EqEntities,
MGetP, Null, RelationSubsetList, RemoveMsgFromMsgSet,
SetMsgHasBeenRead, V2B, V2E, V2S],
WalnutLog USING [MsgRec, LogAddMsg, LogMsgHasBeenRead, LogRemoveMsg],
WalnutDisplayerOps,
WalnutMsgOps USING [MsgSetFieldHandle, MsgSetFieldObject,
DisplayMsgFromMsgSet, MsgCategories],
WalnutPrintOps USING [MsgSetPrintProc, PrintMsgList],
WalnutViewer USING [AnotherButton, CreateMenuEntry, FirstButton],
WalnutWindow USING [MsgSetButton, initialActiveIconic, initialActiveOpen, initialActiveRight, personalMailDB, readOnlyAccess, msgSetIcon, walnutQueue, FindMSBForMsgSet, FindMSViewer, GetSelectedMsgSets, Report, ReportRope, RetrieveNewMail];
WalnutMsgSetDisplayerImpl:
CEDAR
PROGRAM
IMPORTS
Rope, RuntimeError, UserProfile,
WalnutDB, WalnutLog,
WalnutMsgOps, WalnutPrintOps, WalnutViewer, WalnutWindow,
Buttons, Menus, ViewerEvents, ViewerLocks, ViewerOps, VFonts
EXPORTS
WalnutDisplayerOps, WalnutMsgOps =
BEGIN OPEN WalnutDB, WalnutWindow, WalnutMsgOps;
Viewer: TYPE = ViewerClasses.Viewer;
ROPE: TYPE = Rope.ROPE;
displayerMenu: PUBLIC Menus.Menu = Menus.CreateMenu[];
activeMenu: PUBLIC Menus.Menu = Menus.CreateMenu[];
deletedMenu: PUBLIC Menus.Menu = Menus.CreateMenu[];
buildingMenu: PUBLIC Menus.Menu = Menus.CreateMenu[];
readOnlyMenu: PUBLIC Menus.Menu = Menus.CreateMenu[];
msgSetName: PUBLIC ROPE;
destroyedMsg: ROPE = "Selected msg viewer has been destroyed";
blankWidth: INT← VFonts.CharWidth[' ]; -- in default font
blanks: ROPE← " "; -- lotsa blanks
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[displayerMenu,
WalnutViewer.CreateMenuEntry[q: walnutQueue, name: "Print",
proc: WalnutPrintOps.MsgSetPrintProc, guarded: TRUE]];
Menus.AppendMenuEntry[displayerMenu,
WalnutViewer.CreateMenuEntry[walnutQueue, "PrintSelected", PrintSelectedProc]];
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[activeMenu,
WalnutViewer.CreateMenuEntry[q: walnutQueue, name: "Print",
proc: WalnutPrintOps.MsgSetPrintProc, guarded: TRUE]];
Menus.AppendMenuEntry[activeMenu,
WalnutViewer.CreateMenuEntry[walnutQueue, "PrintSelected", PrintSelectedProc]];
Menus.AppendMenuEntry[deletedMenu,
WalnutViewer.CreateMenuEntry[walnutQueue, "Categories", CategoriesProc]];
Menus.AppendMenuEntry[deletedMenu,
WalnutViewer.CreateMenuEntry[walnutQueue, "MoveTo", MoveToProc]];
Menus.AppendMenuEntry[deletedMenu,
WalnutViewer.CreateMenuEntry[walnutQueue, "Display", DisplayProc]];
Menus.AppendMenuEntry[deletedMenu,
WalnutViewer.CreateMenuEntry[q: walnutQueue, name: "Print",
proc: WalnutPrintOps.MsgSetPrintProc, guarded: TRUE]];
Menus.AppendMenuEntry[deletedMenu,
WalnutViewer.CreateMenuEntry[walnutQueue, "PrintSelected", PrintSelectedProc]];
Menus.AppendMenuEntry[readOnlyMenu,
WalnutViewer.CreateMenuEntry[walnutQueue, "Categories", CategoriesProc]];
Menus.AppendMenuEntry[readOnlyMenu,
WalnutViewer.CreateMenuEntry[walnutQueue, "Display", DisplayProc]];
Menus.AppendMenuEntry[readOnlyMenu,
WalnutViewer.CreateMenuEntry[q: walnutQueue, name: "Print",
proc: WalnutPrintOps.MsgSetPrintProc, guarded: TRUE]];
Menus.AppendMenuEntry[readOnlyMenu,
WalnutViewer.CreateMenuEntry[walnutQueue, "PrintSelected", PrintSelectedProc]];
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"]; RETURN};
IF selected.destroyed THEN Report[destroyedMsg]
ELSE
{ ra: REF ANY = NARROW[ViewerOps.FetchProp[viewer, $WalnutEntity]];
thisMsgSet: MsgSet = V2E[ra];
thisName: ROPE← MSNameFromViewer[viewer];
mfh: MsgSetFieldHandle = NARROW[ViewerOps.FetchProp[selected, $mfH]];
msgSetList: LIST OF MsgSetButton = GetSelectedMsgSets[];
first: BOOL← TRUE;
moveToSelf: BOOL← FALSE;
IF msgSetList = NIL THEN
{ Report[" No selected MsgSets to Move msg to"]; RETURN};
IF ~mfh.hasBeenRead THEN MarkMsgAsRead[mfh, selected];
FOR msL: LIST OF MsgSetButton ← msgSetList, msL.rest UNTIL msL=NIL DO
IF thisName.Equal[msL.first.selector.name, FALSE] THEN moveToSelf ← TRUE
ENDLOOP;
ReportDisposition[mfh.msg]; ReportRope["Moved to:"];
FOR msL: LIST OF MsgSetButton← msgSetList, msL.rest UNTIL msL=NIL DO
IF first THEN first ← FALSE ELSE ReportRope[","];
ReportRope[" "]; ReportRope[msL.first.selector.name];
ENDLOOP;
ReportRope["\n"];
IF ~moveToSelf THEN RemoveFromDisplayedMsgSet[viewer, selected, mfh.msg]
ELSE [] ← AdvanceSelection[mfh];
IF mouseButton#red THEN DisplaySelectedMsg[viewer, shift];
-- do ALL logging first
FOR msL: LIST OF MsgSetButton ← msgSetList, msL.rest UNTIL msL=NIL DO
msName: ROPE;
IF NOT thisName.Equal[msName← msL.first.selector.name, FALSE] THEN
WalnutLog.LogAddMsg[mfh.msgName, msName];
ENDLOOP;
IF NOT moveToSelf THEN
WalnutLog.LogRemoveMsg[mfh.msgName, thisName];
-- now change the database and the display
FOR msL: LIST OF MsgSetButton ← msgSetList, msL.rest UNTIL msL=NIL DO
IF NOT EqEntities[msL.first.msgSet, thisMsgSet] THEN
{ rel: Relship; existed: BOOL;
[rel, existed]← WalnutDB.AddMsgToMsgSet[msg: mfh.msg, msgSet: msL.first.msgSet];
IF ~existed THEN AddToMsgSetDisplayer[mfh, msL.first.msgSet, rel]
};
ENDLOOP;
IF NOT moveToSelf THEN
[]← 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];
BEGIN
ENABLE
UNWIND => {v.menu ← oldM; ViewerOps.PaintViewer[v, menu]};
[]← WalnutWindow.RetrieveNewMail[];
v.menu ← oldM;
ViewerOps.PaintViewer[v, menu];
END;
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};
IF selected.destroyed THEN {Report[destroyedMsg]; RETURN};
mfh← NARROW[ViewerOps.FetchProp[selected, $mfH]];
MsgCategories[mfh.msg];
END;
DeleteProc: Menus.MenuProc =
BEGIN
msViewer: Viewer = NARROW[parent];
msName: ROPE← MSNameFromViewer[msViewer];
selected: Viewer = NARROW[ViewerOps.FetchProp[msViewer, $selectedMsg]];
IF selected = NIL THEN {Report[" No selected msg to be deleted"]; RETURN};
IF selected.destroyed THEN {Report[destroyedMsg]; RETURN}
ELSE
{ ra:
REF
ANY =
NARROW[ViewerOps.FetchProp[msViewer, $WalnutEntity]];
thisMsgSet: MsgSet = V2E[ra];
mfh: MsgSetFieldHandle;
newRel: Relship;
IF msName.Equal["Deleted", FALSE] 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];
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"]; RETURN};
IF selected.destroyed THEN {Report[destroyedMsg]; RETURN}
ELSE
{ ra:
REF
ANY =
NARROW[ViewerOps.FetchProp[viewer, $WalnutEntity]];
thisMsgSet: MsgSet← V2E[ra];
mfh: MsgSetFieldHandle = NARROW[ViewerOps.FetchProp[selected, $mfH]];
msgSetList: LIST OF MsgSetButton = GetSelectedMsgSets[];
msAddedTo: ROPE;
msgSetAddedTo: MsgSet;
thisName: ROPE← MSNameFromViewer[viewer];
first: BOOL← TRUE;
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:"];
IF mouseButton#red
THEN
{ [] ← AdvanceSelection[mfh]; DisplaySelectedMsg[viewer, shift] };
-- do all logging first
FOR msL: LIST OF MsgSetButton ← msgSetList, msL.rest UNTIL msL=NIL DO
IF
NOT thisName.Equal[(msAddedTo← msL.first.selector.name),
FALSE]
THEN
{ WalnutLog.LogAddMsg[mfh.msgName, msAddedTo];
IF first THEN first ← FALSE ELSE ReportRope[","];
ReportRope[" "];
ReportRope[msAddedTo];
};
ENDLOOP;
ReportRope["\n"];
-- now update database and finish updating display
FOR msL:
LIST
OF MsgSetButton ← msgSetList, msL.rest
UNTIL msL=
NIL
DO
IF
NOT EqEntities[(msgSetAddedTo← msL.first.msgSet), thisMsgSet]
THEN
{ rel: Relship; existed:
BOOL;
[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
OR selected.destroyed)
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};
IF selected.destroyed THEN {Report[destroyedMsg]; RETURN};
DisplayOneMsg[selected, shift]
END;
PrintSelectedProc: Menus.MenuProc =
BEGIN
viewer: Viewer = NARROW[parent];
selected: Viewer ← NARROW[ViewerOps.FetchProp[viewer, $selectedMsg]];
msfH: MsgSetFieldHandle =
IF (selected =
NIL
OR selected.destroyed)
THEN
NIL
ELSE NARROW[ViewerOps.FetchProp[selected, $mfH]];
IF selected = NIL THEN {Report[" No selected msg to be printed"]; RETURN};
IF selected.destroyed THEN {Report[destroyedMsg]; RETURN};
[]← WalnutPrintOps.PrintMsgList[LIST[msfH.msg], viewer];
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;
-- * * * * * * * * * * * * * * * * * * * * * *
-- called from "outside", need to check MsgSetButtons first
BuildMsgSetViewer:
PUBLIC
PROC[msName:
ROPE, msgSet: MsgSet, shift, paint:
BOOL←
FALSE]
RETURNS[v: Viewer] =
-- if msName is already being displayed, returns that Viewer
BEGIN
msb: MsgSetButton← FindMSBForMsgSet[msgSet];
IF (v← msb.msViewer) # NIL THEN RETURN;
v← MSDisplayer[msgSet, msName, shift, FALSE, paint];
msb.msViewer← v;
END;
BuildListOfMsgsViewer: PUBLIC PROC[mL: LIST OF ROPE, name: ROPE, oldV: Viewer]
RETURNS[v: Viewer] =
-- builds a msgset-like displayer for a list of msgs, using oldV if given
BEGIN
OPEN ViewerOps;
prevMfh, mfh: MsgSetFieldHandle;
lastButton: Viewer;
DoListOfMsgs:
PROC =
BEGIN
FOR mlT:
LIST
OF
ROPE← mL, mlT.rest
UNTIL mlT=
NIL
DO
msg: Msg← DeclareMsg[mlT.first, OldOnly].msg;
hasBeenRead: BOOL;
IF Null[msg] THEN LOOP ELSE hasBeenRead← V2B[DB.GetP[msg, mHasBeenReadIs]];
[mfh, lastButton]←
BuildMsgLineViewer[lastButton, MsgButtonName[msg, hasBeenRead]];
mfh.msg← msg;
mfh.hasBeenRead← hasBeenRead;
mfh.msgName← mlT.first;
IF prevMfh # NIL THEN prevMfh.next← lastButton;
prevMfh← mfh;
ENDLOOP;
END;
IF oldV =
NIL
THEN
v← ViewerOps.CreateViewer[
flavor: $Container, paint: TRUE,
info: [name: name, menu: NIL, icon: msgSetIcon, inhibitDestroy: TRUE]]
-- info: [name: name, menu: NIL, icon: msgQueryIcon, inhibitDestroy: TRUE]]
ELSE
IF ViewerOps.FetchProp[oldV, $WalnutQuery] =
NIL
THEN
RETURN
ELSE ClearMSViewer[v← oldV];
ViewerOps.AddProp[v, $WalnutQuery, mL]; -- save guys to display
v.newVersion← TRUE; PaintViewer[v, caption];
lastButton← v;
WalnutDB.AcquireDBLock[DoListOfMsgs];
v.newVersion← FALSE; PaintViewer[v, caption];
v.inhibitDestroy← FALSE;
END;
BuildMsgSetDisplayer:
PUBLIC
PROC
[msgSet: MsgSet, name:
ROPE, shift:
BOOL←
FALSE, initialActive:
BOOL←
FALSE]
RETURNS[msV: Viewer] =
{ RETURN[MSDisplayer[msgSet, name, shift, initialActive, TRUE]]};
MSDisplayer:
PROC[msgSet: MsgSet, name:
ROPE, shift, initialActive, paint:
BOOL]
RETURNS[msV: Viewer] =
BEGIN
iconic: BOOL← FALSE;
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: paint,
info: [name: Rope.Concat[name, " Messages"], column: whichSide, menu: NIL,
iconic: iconic, icon: msgSetIcon, inhibitDestroy: TRUE]];
ViewerOps.AddProp[msV, $Entity, msgSetName.Concat[name]];
ViewerOps.AddProp[msV, $WalnutEntity, msgSet];
ViewerOps.AddProp[msV, $IconLabel, name];
IF shift
AND paint
THEN
{ IF iconic THEN ViewerOps.OpenIcon[msV, shift] ELSE ViewerOps.GrowViewer[msV]};
END;
AutoScrollRef: TYPE = REF AutoScrollObject;
AutoScrollObject:
TYPE =
RECORD[eventReg: ViewerEvents.EventRegistration, firstUnread, last: Viewer];
MsgSetInViewer:
PUBLIC
PROC[msName:
ROPE, msgSet: MsgSet, v: Viewer, shift:
BOOL←
FALSE] =
BEGIN
OPEN ViewerOps;
firstUnread, last: Viewer;
menu: Menus.Menu←
IF WalnutWindow.readOnlyAccess THEN readOnlyMenu ELSE
IF EqEntities[msgSet, activeMsgSet] THEN
IF WalnutWindow.personalMailDB THEN activeMenu ELSE displayerMenu ELSE
IF EqEntities[msgSet, deletedMsgSet] THEN deletedMenu ELSE displayerMenu;
autoScroll: BOOL← UserProfile.Boolean[key: "Walnut.AutoScrollMsgSets", default: TRUE];
ScrollMsgSet:
PROC =
BEGIN
IF autoScroll
AND last#
NIL
THEN
{
IF v.iconic
THEN
{ scroll: ViewerEvents.EventRegistration←
ViewerEvents.RegisterEventProc[AutoScroll, open, v, FALSE];
--after
ViewerOps.AddProp[v, $autoScroll,
NEW[AutoScrollObject← [scroll, firstUnread, last]]];
}
ELSE DoScroll[v, firstUnread, last];
};
SetMenu[v, menu];
END;
-- for calls from "outside" - might be changing the msgset being displayed
IF msName #
NIL
THEN
{ curName:
ROPE← MSNameFromViewer[v];
IF msName.Equal[curName,
FALSE]
THEN
{
IF ViewerOps.FetchProp[v, $lastButton] #
NIL
THEN
RETURN}
ELSE
{ ra:
REF
ANY← ViewerOps.FetchProp[v, $WalnutEntity];
oldMsgSet: MsgSet← V2E[ra];
msb: MsgSetButton← FindMSBForMsgSet[oldMsgSet];
msb.msViewer← NIL;
ClearMSViewer[v];
v.name← Rope.Concat[msName, " Messages"];
ViewerOps.AddProp[v, $Entity, msgSetName.Concat[msName]];
ViewerOps.AddProp[v, $WalnutEntity, msgSet];
ViewerOps.AddProp[v, $IconLabel, msName];
msb← FindMSBForMsgSet[msgSet];
msb.msViewer← v;
};
};
[]← v.class.scroll[v, thumb, 0]; -- position at beginning
IF ~v.iconic THEN ComputeColumn[v.column, TRUE];
v.newVersion← TRUE; PaintViewer[v, caption];
SetMenu[v, buildingMenu];
[firstUnread, last]← BuildTupleWindowButtons[v, msgSet];
ViewerLocks.CallUnderWriteLock[ScrollMsgSet, v];
v.newVersion← FALSE; PaintViewer[v, caption];
v.inhibitDestroy← FALSE;
END;
DoScroll:
PROC[viewer, firstUnread, last: Viewer] =
BEGIN
IF viewer.ch < (last.cy+last.ch)
THEN
IF firstUnread =
NIL
THEN
{ []← viewer.class.scroll[viewer, thumb, 100];
[]← viewer.class.scroll[viewer, down, viewer.ch - 54]
}
ELSE
{ []← viewer.class.scroll[viewer, thumb, 0];
[]← viewer.class.scroll[viewer, up, firstUnread.cy - 16]
};
END;
AutoScroll: ViewerEvents.EventProc =
BEGIN
autoScroll: AutoScrollRef← NARROW[ViewerOps.FetchProp[viewer, $autoScroll]];
firstUnread: Viewer← autoScroll.firstUnread;
last: Viewer← autoScroll.last;
ViewerEvents.UnRegisterEventProc[autoScroll.eventReg, open]; -- once only
DoScroll[viewer, firstUnread, last];
END;
FixUpMsgSetViewer:
PUBLIC
PROC[msName:
ROPE, v: Viewer] =
BEGIN
entityName: ROPE;
msgSet: MsgSet;
msb: MsgSetButton;
IF (msName.Length[] = 0) OR v.destroyed THEN RETURN;
entityName← msName.Substr[msgSetName.Length[]];
msgSet← DeclareMsgSet[entityName, OldOnly].msgSet;
IF msgSet =
NIL
THEN
{ Report["MsgSet: ", entityName, " doesn't exist; destroying viewer"];
ViewerOps.DestroyViewer[v];
RETURN
};
ClearMSViewer[v];
ViewerOps.AddProp[v, $WalnutEntity, msgSet];
MsgSetInViewer[NIL, msgSet, v];
msb← FindMSBForMsgSet[msgSet];
msb.msViewer← v;
END;
ClearMSViewer:
PROC[v: Viewer] =
BEGIN
-- delete buttons & start again
child: Viewer;
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, $lastButton, NIL];
END;
-- * * * * * * * * * * * * * * * * * * * * * *
-- for adding new messages to displayed MsgSet
AddParsedMsgToMSViewer:
PUBLIC
PROC
[msg: Msg, msgR: WalnutLog.MsgRec, msViewer: Viewer, rel: Relship] =
BEGIN
prevMfh, mfhN: MsgSetFieldHandle;
lastButton: Viewer← NARROW[ViewerOps.FetchProp[msViewer, $lastButton]];
IF lastButton#
NIL
THEN prevMfh←
NARROW[ViewerOps.FetchProp[lastButton, $mfH]]
ELSE lastButton← msViewer;
[mfhN, lastButton]←
BuildMsgLineViewer[lastButton, MsgButtonName[msg, msgR.hasBeenRead]];
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, MsgButtonName[mfh.msg, TRUE]];
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]
RETURNS[firstUnread, lastButton: Viewer] =
BEGIN
tuplesList: LIST OF Relship;
prevMfh, mfh: MsgSetFieldHandle← NIL;
DoTupleWindowButtons:
PROC =
BEGIN
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]];
IF ~hasBeenRead AND firstUnread = NIL THEN firstUnread← lastButton;
mfh.relship← tL.first;
mfh.msg← m;
mfh.hasBeenRead← hasBeenRead;
mfh.msgName← DB.NameOf[m];
IF prevMfh # NIL THEN prevMfh.next← lastButton;
prevMfh← mfh;
ENDLOOP;
END;
lastButton← v;
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 "? ",
SquashRopeIntoWidth[V2S[DB.GetP[msg, mTOCEntryIs]], 165],
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, IF lastButton.parent = NIL THEN lastButton ELSE lastButton.parent];
-- Containers.ChildXBound[lb.parent, lb];
ViewerOps.AddProp[lb, $mfH, mfh← NEW[MsgSetFieldObject←
[msgViewer: lb, msgTOC: name, prev: lastButton, posOK: FALSE]]];
END;
noMsgRope: ROPE← "Msg is no longer in the MsgSet";
MsgSetSelectionProc: Buttons.ButtonProc =
{ viewer: Viewer =
NARROW[parent];
IF viewer.destroyed THEN Report[noMsgRope]
ELSE
{ []← 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
IF msgButton.destroyed THEN Report[noMsgRope]
ELSE
{ 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]};
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;
MSNameFromViewer:
PROC[v: Viewer]
RETURNS[msName:
ROPE] =
{ fullName: ROPE← NARROW[ViewerOps.FetchProp[v, $Entity]];
RETURN[fullName.Substr[msgSetName.Length[]]]; -- less of a hack now
};
SquashRopeIntoWidth: PROC[s: ROPE, colWidth: INT] RETURNS[ROPE] =
-- Truncates s with "..." or expands it with blanks, so that it is about
-- colWidth characters wide. Not exact, uses a few heuristics here...
BEGIN
blankCount: INT;
width: INT;
BEGIN ENABLE RuntimeError.BoundsFault =>
GOTO doItTheHardWay;
width← VFonts.StringWidth[s];
DO
IF width<= colWidth THEN EXIT;
-- truncate
BEGIN
guessLength: INT← s.Length[] * colWidth / width;
s← Rope.Cat[s.Substr[0, MAX[0, guessLength-4]], "..."];
width← VFonts.StringWidth[s];
END;
ENDLOOP;
EXITS
doItTheHardWay => [width, s]← DoItTheHardWay[s, colWidth];
END; -- of enable
-- At this point s is shorter than colWidth and we want to extend it with blanks
blankCount← ((colWidth - width) / blankWidth) + 1; -- force at least one blank
s← Rope.Cat[s, Rope.Substr[blanks, 0, MIN[blankCount, blanks.Length[]]]];
RETURN[s]
END;
DoItTheHardWay:
PROC[s:
ROPE, colWidth:
INT]
RETURNS[width:
INT, s1:
ROPE] =
BEGIN
thisWidth: INTEGER;
dots: ROPE = "...";
nullWidth: INTEGER = VFonts.CharWidth['\000];
width← VFonts.StringWidth[dots];
FOR i:
INT
IN [0 .. s.Length[])
DO
thisWidth← VFonts.CharWidth[s.Fetch[i] ! RuntimeError.BoundsFault =>
thisWidth← nullWidth ];
width← width + thisWidth;
IF width > colWidth
THEN
{ width← width - thisWidth;
s1← Rope.Concat[s.Substr[0, MAX[0, i-1]], dots];
RETURN
};
ENDLOOP;
s1← s.Concat[dots];
END;
-- * * * * * * * * * * * * * * * * * * * * * * * * *
-- start code
BuildDisplayerMenu[];
END.