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. ͺFile: WalnutMsgSetDisplayerImpl.mesa Copyright c 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 Κ!˜codešœ$™$Kšœ Οmœ1™K˜Kšœ žœΟc˜9Kšœžœ8 ˜SK˜šΟnœžœ˜šž˜Kšœq˜qKšœj˜jKšœk˜kKšœi˜iKšœe˜eKšœ–žœ˜Kšœw˜wK˜Kšœn˜nKšœg˜gKšœh˜hKšœf˜fKšœb˜bKšœh˜hKšœ“žœ˜šKšœt˜tK˜Kšœo˜oKšœh˜hKšœi˜iKšœ”žœ˜›Kšœu˜uK˜Kšœp˜pKšœj˜jKšœ•žœ˜œKšœv˜v—Kšžœ˜—K˜Kš .˜.K˜š‘ œ˜Kš°žœžœžœ.žœ žœžœ+žœžœžœžœ žœžœžœ^žœ9žœ7žœžœ1žœžœžœžœžœžœžœ7žœžœžœ#žœžœžœ%žœžœžœžœ)žœžœžœžœ?žœžœžœ$žœžœžœžœžœ žœžœUžœžœ žœ;žœ!žœžœ$ œžœžœžœ%žœžœžœžœžœžœ1žœžœ6žœžœžœ žœ6 -œžœžœžœ%žœžœžœžœžœ*žœ!žœ_žœ žœAžœžœžœ žœQžœ˜•—K˜š‘ œ˜šž˜Kšœ žœ ˜Kšœ˜Kšœ#˜#šžœžœžœ4˜GKšœ#˜#Kšœ˜Kšœ˜—Kšžœ˜—Kšžœ˜—K˜š‘œ˜ šž˜Kšœžœ ˜!Kšœžœ.˜FKšœ˜Kšžœ žœžœžœ˜  ˜JKšœ$˜$—Kšžœ˜—K˜š‘œžœžœ žœ˜9šž˜Kšœ žœ˜Kšœ˜Kšœ˜K˜Kšžœžœ žœžœ˜4K˜Kšœ/˜/Kšœ2˜2šžœ žœž˜šœG˜GKšœ˜Kšž˜—Kšœ˜—Kšœ˜Kšœ,˜,Kšœžœ ˜Kšœ˜Kšœ˜—Kšžœ˜—K˜š‘ œžœ ˜ Kšž˜š ˜Kšœ˜Kš žœžœžœ žœžœ˜NKšœ# ˜:Kšœ#žœ ˜˜>š‘œžœ#˜Bšž˜Kšœžœ˜Kšœ˜Kšœ˜Kšœ žœ˜Kšœžœ,˜DK˜š‘ œžœ˜šž˜Kšœ˜Kšœžœ ˜7šžœžœžœž˜)Kšœžœ ˜,šžœ žœžœ '˜?šœ˜Kšœ žœ˜ Kšœ˜Kšœ˜—Kšœ˜—Kšœ;žœ˜BKšœ˜—Kšžœ˜—Kšžœ˜—K˜Kšžœ žœžœ#˜6Kšžœžœ/˜GKšœžœ#˜/Kšœ˜šžœž˜šœ5  ˜AKšœ žœ˜Kšœ$  ˜1Kšœžœ ˜7—Kšœ˜šž˜šžœžœ ˜"Kšžœ4˜8——K˜—Kš #˜#šžœžœž˜šœžœ"˜-Kšžœžœžœ˜šžœžœž˜šœžœ"˜-Kšœ˜——šžœž˜Kš œžœžœžœ)žœ ˜T—šž˜šœ0˜0Kšœ- ˜CKšœ!˜!—Kšœ˜—Kšœ˜———Kšžœ˜—K˜š‘œžœžœ˜Bšž˜Kšœ"˜"Kšžœžœžœžœ˜Kšœ=˜=—Kšžœ˜—K˜š‘œžœ!žœ˜Ošž˜Kšœ žœ-˜?šž˜Kšžœžœžœžœ˜Kšœžœ ˜+Kšžœžœžœ˜,Kšœ ˜ —Kšžœ˜—Kšžœ˜—K˜š‘ œžœžœ˜Jšž˜Kšœ˜Kšœ žœ-˜<šž˜Kšžœ žœžœžœ˜Kšœžœ%˜0Kšžœžœžœ˜(Kšœ˜—Kšžœ˜—Kšžœ˜—K˜š‘œžœžœ#˜`šž˜Kšœ žœžœ ˜Kšœ!žœ˜%š‘œžœ˜šž˜š žœžœžœžœžœž˜>Kšœ žœ ˜IKšœ žœžœ˜3K˜KšœQ˜QKšžœžœžœžœ˜CKšœ˜Kšœ ˜ Kšœ˜Kšœ žœ ˜Kšžœ žœžœ˜/Kšœ ˜ —Kšžœ˜—Kšžœ˜—K˜Kšœ˜Kšœ*žœ˜BKšœ-˜-Kšœ/ ˜>—Kšžœ˜—K˜š ‘ œžœžœžœžœ˜AKš œžœžœ žœžœ'žœ+žœ˜¨Kšœ˜—K˜š‘œžœžœžœ&˜hšž˜Kšœ žœ˜š‘ œžœ˜šž˜Kšžœžœž˜Kš œŠ œ$žœ₯ œžœ˜£—Kšžœ˜K˜—Kš œ žœžœžœžœ˜Zšžœ žœ ˜Kš žœ/žœžœžœ žœ˜v—K˜Kš *˜*Kšœ!žœNžœ˜{—Kšžœ˜—K˜Kšœ žœ#˜2K˜š‘œ˜)šœžœ ˜"Kšžœžœ˜*šž˜šœ ˜ šžœ žœ<˜KKšžœžœžœ˜9——Kšœ˜——Kšœ˜—K˜š‘ œžœžœ˜2šž˜Kšœžœ$˜CKšœ<˜