-- 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: 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 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: 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:"]; 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: BOOL_ FALSE, initialActive: BOOL_ FALSE] 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: 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.