DIRECTORY BasicTime USING [GetClockPulses, Pulses, PulsesToMicroseconds], Feedback, FeedbackClasses, Imager USING [black, MaskRectangle, MaskRectangleI, SetColor, SetFont, SetGray, SetXY, SetXYI, ShowRope, ShowText, white], ImagerBackdoor USING [invert], ImagerFont USING [RopeEscapement], IO, MessageWindow USING [], MessageWindowBackdoor USING [], Process, Real USING [Round], Rope USING [Equal, Fetch, FromRefText, ROPE, Size], RuntimeError, TIPUser USING [InstantiateNewTIPTable, TIPScreenCoords, TIPScreenCoordsRec, TIPTable], VFonts USING [defaultFont], ViewerClasses USING [ModifyProc, NotifyProc, PaintProc, Viewer, ViewerClass, ViewerClassRec], ViewerForkers USING [ForkPaint], ViewerLocks USING [CallUnderWriteLock, Wedged], ViewerOps USING [CreateViewer, MoveViewer, PaintViewer, RegisterViewerClass], ViewerPrivate USING [], -- exports messageWindow ViewerSpecs USING [bwScreenHeight, messageWindowHeight]; MessageWindowImpl: CEDAR MONITOR IMPORTS BasicTime, Feedback, FeedbackClasses, Imager, ImagerBackdoor, ImagerFont, IO, Process, Real, Rope, RuntimeError, TIPUser, VFonts, ViewerForkers, ViewerLocks, ViewerOps, ViewerSpecs EXPORTS MessageWindow, MessageWindowBackdoor, ViewerPrivate = BEGIN ROPE: TYPE ~ Rope.ROPE; messageWindow: PUBLIC ViewerClasses.Viewer ¬ NIL; replayPause: Process.Milliseconds ¬ 750; Replay: PROC [messages: LIST OF ROPE] ~ { prev: ROPE ¬ NIL; WHILE messages # NIL DO IF Rope.Size[messages.first] = 0 OR Rope.Equal[messages.first, prev] THEN NULL ELSE { IF prev # NIL THEN Process.Pause[Process.MsecToTicks[replayPause]]; Append[messages.first, TRUE]; prev ¬ messages.first; }; messages ¬ messages.rest; ENDLOOP; }; avail: LIST OF ROPE ¬ NIL; queue: LIST OF ROPE ¬ NIL; last: LIST OF ROPE ¬ NIL; postmasterActive: BOOL ¬ FALSE; queueSize: CARDINAL ¬ 0; pendingClears: CARDINAL ¬ 0; clearToken: ROPE ~ "clear"; blinkToken: ROPE ~ "blink"; dally: CONDITION; -- times out after 1 second. See initialization at the bottom KosherRope: PROC [ref: REF] RETURNS [ROPE] ~ { WITH ref SELECT FROM rope: ROPE => RETURN [rope]; text: REF TEXT => RETURN [Rope.FromRefText[text]]; ENDCASE => RETURN [NIL]; }; AddToQueue: ENTRY PROC [rope: ROPE] ~ { new: LIST OF ROPE ¬ NIL; IF rope = clearToken AND last#NIL AND last.first = clearToken THEN RETURN; IF avail = NIL THEN new ¬ LIST[rope] ELSE {new ¬ avail; avail ¬ new.rest; new.rest ¬ NIL; new.first ¬ rope}; IF queue = NIL THEN { queue ¬ last ¬ new } ELSE { last.rest ¬ new; last ¬ new }; queueSize ¬ queueSize + 1; IF rope = clearToken THEN pendingClears ¬ pendingClears + 1; IF NOT postmasterActive THEN TRUSTED { Process.Detach[FORK PostMaster[]]; postmasterActive ¬ TRUE }; NOTIFY dally; }; ReadQueue: ENTRY PROC RETURNS [rope: ROPE ¬ NIL] ~ { Try: INTERNAL PROC ~ { IF queue # NIL THEN { old: LIST OF ROPE ~ queue; rope ¬ old.first; queue ¬ old.rest; IF old.rest=NIL THEN last ¬ NIL; old.first ¬ NIL; old.rest ¬ avail; avail ¬ old; queueSize ¬ queueSize - 1; }; }; Try[]; IF rope = NIL THEN { WAIT dally; Try[] }; IF rope = NIL THEN { postmasterActive ¬ FALSE }; IF rope = clearToken THEN pendingClears ¬ pendingClears - 1; }; MoreText: ENTRY PROC RETURNS [BOOL] ~ { RETURN [queue # NIL AND (SELECT queue.first FROM clearToken, blinkToken => FALSE ENDCASE => TRUE)] }; Clear: PUBLIC PROC = { AddToQueue[clearToken] }; Append: PUBLIC PROC [message: ROPE, clearFirst: BOOL ¬ FALSE] = { IF clearFirst THEN AddToQueue[clearToken]; IF message # NIL THEN AddToQueue[KosherRope[message]]; }; Blink: PUBLIC PROC = { AddToQueue[blinkToken] }; shortQueue: CARDINAL ¬ 5; -- If there are more pending clears than this, use shortPause longQueue: CARDINAL ¬ 30; -- If there are more pending clears than this, don't pause shortPause: Process.Milliseconds ¬ 200; longerPause: Process.Milliseconds ¬ 500; -- normal persistence before a message dies PostMaster: PROC ~ { lastAppend: BasicTime.Pulses ¬ BasicTime.GetClockPulses[]-1000000; DO rope: ROPE ~ ReadQueue[]; SELECT rope FROM NIL => EXIT; -- No action for awhile, so kill this process. clearToken => { pulsesWaited: CARD ~ BasicTime.GetClockPulses[]-lastAppend; msWaited: CARDINAL ~ MIN[BasicTime.PulsesToMicroseconds[pulsesWaited]/1000, 10000]; msToWait: CARDINAL ~ IF queueSize > shortQueue THEN shortPause ELSE longerPause; IF queueSize < longQueue AND msToWait > msWaited THEN { Process.Pause[Process.MsecToTicks[msToWait-msWaited]]; }; ClearPrivate[]; }; blinkToken => {BlinkPrivate[]; lastAppend ¬ BasicTime.GetClockPulses[]}; ENDCASE => {AppendPrivate[rope]; lastAppend ¬ BasicTime.GetClockPulses[]}; ENDLOOP; }; mode: {none, confirm} ¬ none; ready: BOOL ¬ FALSE; confirm: BOOL ¬ FALSE; -- initial value to keep compiler happy inputReady: CONDITION; empty: BOOL ¬ TRUE; maxLength: INTEGER = 120; static: REF TEXT ¬ NEW[TEXT[maxLength]]; ringSize: NAT ~ 10; RingIndex: TYPE ~ [0..ringSize); RingArray: TYPE ~ ARRAY RingIndex OF REF TEXT; ringIndex: RingIndex ¬ 0; ring: REF RingArray ¬ CreateRingArray[]; CreateRingArray: PROC RETURNS [ring: REF RingArray] ~ { ring ¬ NEW[RingArray ¬ ALL[NIL]]; FOR i: RingIndex IN RingIndex DO ring[i] ¬ NEW[TEXT[maxLength]] ENDLOOP; }; GetHistory: PUBLIC PROC RETURNS [list: LIST OF ROPE ¬ NIL] ~ { FOR i: RingIndex IN RingIndex DO r: RingIndex ~ (ringIndex+ringSize-i) MOD ringSize; text: REF TEXT ~ ring[r]; list ¬ CONS[Rope.FromRefText[text], list]; ENDLOOP; }; ClearStatic: PROC ~ { IF static.length=0 THEN RETURN; ringIndex ¬ (ringIndex+1) MOD ringSize; static ¬ ring[ringIndex]; static.length ¬ 0; }; ClearInternal: PROC = { ClearStatic[]; empty ¬ TRUE; ViewerOps.PaintViewer[viewer: messageWindow, hint: client, whatChanged: $Clear]; }; ClearPrivate: PROC = { IF empty OR messageWindow=NIL THEN RETURN; ViewerLocks.CallUnderWriteLock[ClearInternal, messageWindow ! ViewerLocks.Wedged => CONTINUE]; }; AppendPrivate: PROC [message: ROPE] = { LockedAppend: PROC = { FOR n: INT IN [0..Rope.Size[message]) DO IF static.length >= maxLength THEN EXIT; static[static.length] ¬ Rope.Fetch[message, n]; static.length ¬ static.length + 1; ENDLOOP; IF NOT MoreText[] THEN ViewerOps.PaintViewer[messageWindow, client, TRUE, $Append]; empty ¬ FALSE; }; IF messageWindow=NIL THEN RETURN; ViewerLocks.CallUnderWriteLock[LockedAppend, messageWindow ! ViewerLocks.Wedged => CONTINUE]; }; BlinkPrivate: PROC = { Inner: PROC ~ { THROUGH [0..6) DO ViewerOps.PaintViewer[messageWindow, client, FALSE, $Invert]; Process.Pause[Process.MsecToTicks[120]]; ENDLOOP; }; IF messageWindow#NIL THEN ViewerLocks.CallUnderWriteLock[Inner, messageWindow ! ViewerLocks.Wedged => CONTINUE]; }; ReadFrom: PUBLIC PROC RETURNS [message: ROPE ¬ NIL] = {ERROR}; confirmPrompt: ROPE ¬ NIL; nextInLine: CONDITION; Confirm: PUBLIC ENTRY PROC [prompt: ROPE ¬ NIL] RETURNS [BOOL] = { WHILE mode#none DO WAIT nextInLine; ENDLOOP; -- wait in line confirmPrompt ¬ prompt; mode ¬ confirm; ready ¬ FALSE; ViewerForkers.ForkPaint[messageWindow, client]; UNTIL ready=TRUE DO WAIT inputReady; ENDLOOP; ready ¬ FALSE; mode ¬ none; confirmPrompt ¬ NIL; NOTIFY nextInLine; ViewerForkers.ForkPaint[messageWindow, client]; RETURN[confirm]; }; MessageWindowPaint: ViewerClasses.PaintProc = { IF whatChanged = $Invert THEN { Imager.SetColor[context, ImagerBackdoor.invert]; Imager.MaskRectangleI[context, 0, 0, messageWindow.cw, messageWindow.ch]; } ELSE { bottomOffset: INTEGER = 3; hOffset: INTEGER = 2; IF NOT clear THEN { Imager.SetColor[context, Imager.white]; Imager.MaskRectangleI[context, 0, 0, messageWindow.cw, messageWindow.ch]; }; Imager.SetColor[context, Imager.black]; Imager.SetXYI[context, hOffset, bottomOffset]; Imager.SetFont[context, VFonts.defaultFont]; Imager.ShowText[context, static]; IF mode = confirm THEN { x: REAL ¬ self.cw; a: ARRAY [0..8] OF REAL ¬ ALL[0.0]; a[8] ¬ x ¬ x-2*hOffset; a[7] ¬ x ¬ x-1; a[6] ¬ x ¬ x-hOffset-ImagerFont.RopeEscapement[VFonts.defaultFont, "No"].x; a[5] ¬ x ¬ x-hOffset; a[4] ¬ x ¬ x-1; a[3] ¬ x ¬ x-hOffset-ImagerFont.RopeEscapement[VFonts.defaultFont, "Yes"].x; a[2] ¬ x ¬ x-hOffset; a[1] ¬ x ¬ x-1; a[0] ¬ x ¬ x-hOffset-ImagerFont.RopeEscapement[VFonts.defaultFont, confirmPrompt].x; Imager.MaskRectangle[context, [a[0]-hOffset, 0, messageWindow.cw, messageWindow.ch]]; Imager.SetGray[context, 0]; Imager.MaskRectangle[context, [a[2], 0, a[7]-a[2], messageWindow.ch]]; Imager.SetXY[context, [a[0], bottomOffset]]; Imager.ShowRope[context, confirmPrompt]; Imager.SetGray[context, 1]; Imager.SetXY[context, [a[3], bottomOffset]]; Imager.ShowRope[context, "Yes"]; Imager.SetXY[context, [a[6], bottomOffset]]; Imager.ShowRope[context, "No"]; Imager.MaskRectangle[context, [a[4], 0, 1, messageWindow.ch]]; SetConfirmation[yes0: a[2], yes1: a[4], no0: a[5], no1: a[7]]; Imager.SetColor[context, ImagerBackdoor.invert]; Process.Pause[Process.MsecToTicks[100]]; Imager.MaskRectangle[context, [a[0], 0, messageWindow.cw, messageWindow.ch]]; Process.Pause[Process.MsecToTicks[100]]; Imager.MaskRectangle[context, [a[0], 0, messageWindow.cw, messageWindow.ch]]; }; }; }; yesMin: INTEGER ¬ 0; yesMax: INTEGER ¬ 0; noMin: INTEGER ¬ 0; noMax: INTEGER ¬ 0; SetConfirmation: ENTRY PROC [yes0, yes1, no0, no1: REAL] ~ { yesMin ¬ Real.Round[yes0]; yesMax ¬ Real.Round[yes1]; noMin ¬ Real.Round[no0]; noMax ¬ Real.Round[no1]; }; TryConfirmation: ENTRY PROC [x: INTEGER] RETURNS [BOOL] ~ { IF mode # confirm THEN RETURN [FALSE]; ready ¬ TRUE; SELECT x FROM IN [yesMin..yesMax) => {confirm ¬ TRUE}; IN [noMin..noMax) => {confirm ¬ FALSE}; ENDCASE => {ready ¬ FALSE}; IF ready THEN { yesMin ¬ yesMax ¬ noMin ¬ noMax ¬ 0; NOTIFY inputReady; }; RETURN [TRUE]; }; MessageWindowNotify: ViewerClasses.NotifyProc = { mouse: TIPUser.TIPScreenCoordsRec ¬ [mouseX: 0, mouseY: 0, color: FALSE]; FOR l: LIST OF REF ANY ¬ input, l.rest UNTIL l=NIL DO WITH l.first SELECT FROM z: TIPUser.TIPScreenCoords => mouse ¬ z­; atom: ATOM => SELECT atom FROM $ButtonHit => IF TryConfirmation[mouse.mouseX] THEN NULL ELSE Clear[]; $Replay => IF TryConfirmation[mouse.mouseX] THEN NULL ELSE TRUSTED { messages: LIST OF ROPE ~ GetHistory[]; Process.Detach[FORK Replay[messages]]; }; ENDCASE => NULL; ENDCASE => NULL; ENDLOOP; }; FindTIPTable: PROC RETURNS [tipTable: TIPUser.TIPTable ¬ NIL] ~ { tipTable ¬ TIPUser.InstantiateNewTIPTable["MessageWindow.tip" ! RuntimeError.UNCAUGHT => CONTINUE]; }; staticArea: INT ¬ 1024; -- monitored. This value does not need to be the actual size, since it us just used for startup. AllocateStaticArea: PUBLIC PROC [width: INTEGER] RETURNS [wx, wy, ww, wh: INTEGER] ~ { m: ViewerClasses.Viewer ~ messageWindow; width ¬ MAX[width, 0]; IF m = NIL THEN { Locked: ENTRY PROC ~ { staticArea ¬ staticArea - width; wx ¬ MAX[staticArea, 0]; wy ¬ ViewerSpecs.bwScreenHeight-ViewerSpecs.messageWindowHeight; ww ¬ width; wh ¬ ViewerSpecs.messageWindowHeight; }; Locked[]; } ELSE { ViewerOps.MoveViewer[m, m.wx, m.wy, m.ww-width, m.wh, FALSE]; wx ¬ m.wx + m.ww; wy ¬ m.wy; ww ¬ width; wh ¬ m.wh; }; }; CreateMessageWindowViewer: PROC ~ { wx, wy, ww, wh: INTEGER; [wx, wy, ww, wh] ¬ AllocateStaticArea[staticArea]; IF wx # 0 THEN ERROR; messageWindow ¬ ViewerOps.CreateViewer[$MessageWindow, [name: "MW", wx: wx, wy: wy, ww: ww, wh: wh, column: static ]]; }; CreateMessageWindow: PUBLIC PROC ~ { IF messageWindow = NIL THEN CreateMessageWindowViewer[]; StartFeedback[]; }; MWPutFL: PROC [mh: Feedback.MsgHandler, msgType: Feedback.MsgType, msgClass: Feedback.MsgClass, format: Rope.ROPE, list: LIST OF IO.Value ¬ NIL ] ~ { Append[IO.PutFLR[format, list], msgType=begin OR msgType=oneLiner]; }; MWClearHerald: PROC [mh: Feedback.MsgHandler, msgClass: Feedback.MsgClass ] ~ { Append[NIL, TRUE]; }; MWBlink: PROC [mh: Feedback.MsgHandler, msgClass: Feedback.MsgClass ] ~ { Blink[]; }; StartFeedback: PROC = { h1: Feedback.MsgHandler ~ FeedbackClasses.CreateHandler[MWPutFL, MWClearHerald, MWBlink, NIL]; h2: Feedback.MsgHandler ~ FeedbackClasses.CreateStoringHandler[2000]; h12: Feedback.MsgHandler ~ FeedbackClasses.CreateSplittingHandler[h1, h2]; [] ¬ Feedback.SetGlobalDefaultHandlersBehavior[h12]; }; messageWindowClass: ViewerClasses.ViewerClass ¬ NEW[ViewerClasses.ViewerClassRec ¬ [ paint: MessageWindowPaint, notify: MessageWindowNotify, tipTable: FindTIPTable[] ]]; ViewerOps.RegisterViewerClass[$MessageWindow, messageWindowClass]; TRUSTED{ Process.InitializeCondition[@dally, Process.SecondsToTicks[1]]; }; CreateMessageWindowViewer[]; END. ψ MessageWindowImpl.mesa Copyright Σ 1985, 1986, 1987, 1991, 1992 by Xerox Corporation. All rights reserved. McGregor on September 10, 1982 10:41 am Maxwell, February 14, 1983 10:22 am Paul Rovner, June 16, 1983 8:53 am Russ Atkinson, November 18, 1983 1:30 pm Doug Wyatt, February 2, 1987 5:34:30 pm PST Michael Plass, March 17, 1992 9:44 am PST Bier, December 8, 1988 12:16:00 pm PST Willie-s, May 24, 1991 1:47 pm PDT Christian Jacobi, February 14, 1992 1:49 pm PST Message Replay Message Queueing This section maintains a queue of unposted messages, and has a consumer process to post them. It is layered on the older mechanism; in the best of worlds it would all be better integrated, but it was done this way to avoid the risk of breaking what was there.  mfp In case somebody was trusting a REF TEXT to look like a ROPE. Returns true if there is a pending action to append more text to the window; good for punting provably redundant repaints. The idea here is to not clear the previous message until it has been around for a certain amount of time. Since ReadQueue times out with NIL if nothing happens for awhile, we know that pulsesWaited should not get too big, but we guard against overflow anyway. Old Stuff De-implemented. Feedback Initialization Κθ•NewlineDelimiter –(cedarcode) style™codešœ™Kšœ ΟeœI™TKšœ'™'Kšœ#™#Kšœ"™"K™(K™+K™)K™&K™"K™/—K˜šΟk ˜ Kšœ žœ0˜?K˜ K˜Kšœžœn˜zKšœžœ ˜Kšœ žœ˜"Kšžœ˜Kšœžœ˜Kšœžœ˜Kšœ˜Kšœžœ ˜Kšœžœžœ˜3K˜ KšœžœI˜VKšœžœ˜KšœžœJ˜]Kšœžœ ˜ Kšœ žœ˜/Kšœ žœ>˜MKšœžœΟc˜0Kšœ žœ'˜8—K˜KšΠblœžœž˜ KšžœKžœh˜ΌKšžœ4˜;šœž˜K˜Kšžœžœžœ˜K˜Kšœžœžœ˜1—head™šœ(˜(K˜—Kš Οnœžœ žœžœžœ˜)Kšœžœžœ˜šžœ žœž˜šžœžœ!˜DKšžœž˜ šžœ˜Kšžœžœžœ1˜CKšœžœ˜Kšœ˜Kšœ˜——Kšœ˜Kšžœ˜—Kšœ˜—™™ŠK˜—Kš œžœžœžœžœ˜Kš œžœžœžœžœ˜Kš œžœžœžœžœ˜Kšœžœžœ˜Kšœ žœ˜Kšœžœ˜Kšœ žœ ˜šœ žœ ˜K˜—šœž œŸ>˜PK˜—š ‘ œžœžœžœžœ˜.Kšœ žœžœžœ™=šžœžœž˜Kšœžœžœ˜Kšœžœžœžœ˜2Kšžœžœžœ˜—Kšœ˜K˜—š‘ œžœžœžœ˜'Kš œžœžœžœžœ˜Kš žœžœžœžœžœžœ˜Jšžœ ž˜Kšžœžœ˜Kšžœ,žœ˜G—šžœ ž˜Kšžœ˜Kšžœ!˜%—Kšœ˜Kšžœžœ#˜<šžœžœ˜Kšžœžœžœ#žœ˜L—Kšžœ˜ Kšœ˜K˜—š ‘ œžœžœžœžœžœ˜4š‘œžœžœ˜šžœ žœžœ˜Kšœžœžœžœ ˜Kšœ˜Kšœ˜Kšžœ žœžœžœ˜ Kšœ žœ˜Kšœ˜Kšœ ˜ Kšœ˜Kšœ˜—Kšœ˜—Kšœ˜Kšžœžœžœžœ˜)Kšžœžœžœžœ˜0Kšžœžœ#˜šžœžœ ž˜ Kšœ&žœ ˜3Kšœžœžœ ˜Kšœžœ˜*Kšžœ˜—K˜K˜—š‘ œžœ˜Kšžœžœžœ˜Kšœžœ ˜'Kšœ˜K˜K˜K˜—š‘ œžœ˜Kšœ˜Kšœžœ˜ KšœP˜PK˜K˜—š‘ œžœ˜Kš žœžœžœžœžœ˜*šœ=˜=Kšœžœ˜ —šœ˜K˜——š‘ œžœ žœ˜'š‘ œžœ˜šžœžœžœž˜(Kšžœžœžœ˜(K˜/K˜"Kšžœ˜—Kšžœžœ žœ.žœ ˜SKšœžœ˜Kšœ˜—Kšžœžœžœžœ˜!šœ:˜:Kšœžœ˜"—Kšœ˜K˜—š‘ œžœ˜š‘œžœ˜šžœž˜Kšœ-žœ ˜=K˜(Kšžœ˜—Kšœ˜—KšžœžœžœMžœ˜pKšœ˜K˜—š‘œžœžœžœ žœžœžœ˜>K™—K˜Kšœžœžœ˜Kšœ ž œ˜K˜š‘œžœžœžœ žœžœžœžœ˜BKš žœ žœžœ žœŸ˜˜>Kšœ>˜>Kšœ0˜0K˜(KšœM˜MK˜(KšœM˜MKšœ˜—Kšœ˜—Kšœ˜K˜—Kšœžœ˜Kšœžœ˜Kšœžœ˜šœžœ˜K˜—š‘œžœžœžœ˜