<> <> <> <> <> <<>> DIRECTORY Carets USING [ResumeCarets, SuspendCarets], CaretsExtras USING [DoWithoutCarets], CedarProcess USING [SetPriority], Icons USING [DrawIcon], Imager USING [black, ClipRectangleI, Color, Context, DoSaveAll, MakeGray, MaskRectangleI, SetColor, SetFont, SetXYI, ShowText, Trans, white], ImagerBackdoor USING [invert, MakeStipple, ViewClipRectangleI, ViewReset, ViewTranslateI], ImagerTerminal USING [BWContext, ColorContext, SetStandardColorMap], InterminalBackdoor USING [terminal], Menus USING [], Process USING [Detach, MsecToTicks, Pause, SecondsToTicks, Ticks], RefText USING [AppendRope, ObtainScratch, ReleaseScratch, TrustTextAsRope], Rope USING [Length, ROPE, Run], Terminal, VFonts USING [defaultFont, Font, StringWidth], ViewerClasses USING [PaintRectangle, Viewer], ViewerLocks USING [CallUnderWriteLock, Wedged], ViewerGroupLocks USING [CallRootUnderWriteLock], ViewerOps USING [ChangeColumn, FetchProp, PaintHint, SaveAllEdits, UserToScreenCoords], ViewerPrivate, ViewerSpecs, ViewerTools USING [GetSelectedViewer]; ViewerPaintImpl: CEDAR MONITOR IMPORTS Carets, CaretsExtras, CedarProcess, Icons, Imager, ImagerBackdoor, ImagerTerminal, InterminalBackdoor, Process, RefText, Rope, Terminal, VFonts, ViewerGroupLocks, ViewerLocks, ViewerOps, ViewerPrivate, ViewerSpecs, ViewerTools EXPORTS ViewerOps, ViewerPrivate SHARES Menus, ViewerClasses, ViewerLocks ~ BEGIN OPEN ViewerClasses, ViewerOps, ViewerSpecs; ContextCreatorProc: TYPE ~ ViewerPrivate.ContextCreatorProc; ROPE: TYPE ~ Rope.ROPE; DoWithScratchText: PROC[len: NAT, action: PROC[REF TEXT]] ~ { scratch: REF TEXT ~ RefText.ObtainScratch[len]; action[scratch ! UNWIND => RefText.ReleaseScratch[scratch]]; RefText.ReleaseScratch[scratch]; }; captionAscent: NAT _ 9; PaintCaption: PROC [viewer: Viewer, context: Imager.Context] ~ { wbs: INTEGER ~ IF viewer.border THEN windowBorderSize ELSE 0; IF viewer.class.caption#NIL THEN { <> x: INTEGER ~ wbs; y: INTEGER ~ viewer.wh-captionHeight; w: INTEGER ~ viewer.ww-wbs*2; h: INTEGER ~ captionHeight-wbs; action: PROC ~ { Imager.SetXYI[context, x, y]; Imager.Trans[context]; Imager.ClipRectangleI[context, 0, 0, w, h]; viewer.class.caption[viewer, context]; }; Imager.DoSaveAll[context, action]; } ELSE { name: ROPE ~ viewer.name; nameLen: INT ~ Rope.Length[name]; file: ROPE ~ viewer.file; fileLen: INT ~ Rope.Length[file]; action: PROC[header: REF TEXT] ~ { font: VFonts.Font ~ VFonts.defaultFont; headerW: INTEGER _ 0; header _ RefText.AppendRope[to: header, from: name]; IF fileLen>nameLen AND Rope.Run[s1: name, s2: file, case: FALSE]=nameLen THEN { <> header _ RefText.AppendRope[to: header, from: " ("]; header _ RefText.AppendRope[to: header, from: file, start: nameLen]; header _ RefText.AppendRope[to: header, from: ")"]; }; SELECT TRUE FROM viewer.saveInProgress => header _ RefText.AppendRope[to: header, from: " [Saving...]"]; viewer.newFile => header _ RefText.AppendRope[to: header, from: " [New File]"]; viewer.newVersion => header _ RefText.AppendRope[to: header, from: " [Edited]"]; ENDCASE; IF viewer.link#NIL THEN header _ RefText.AppendRope[to: header, from: " [Split]"]; headerW _ VFonts.StringWidth[RefText.TrustTextAsRope[header], font]; headerW _ MIN[headerW, viewer.ww-wbs*2]; Imager.SetColor[context, Imager.black]; Imager.MaskRectangleI[context, 0, viewer.wh, viewer.ww, -captionHeight]; Imager.SetColor[context, Imager.white]; Imager.SetXYI[context, (viewer.ww-headerW)/2, viewer.wh-captionAscent]; Imager.SetFont[context, font]; Imager.ShowText[context, header]; }; DoWithScratchText[100, action]; }; }; PaintDocumentHeader: PROC [viewer: Viewer, context: Imager.Context, clear: BOOL, hint: PaintHint, whatChanged: REF ANY] = { IF hint#menu THEN PaintCaption[viewer, context]; IF hint#caption AND viewer.menu#NIL THEN { wbs: INTEGER ~ IF viewer.border THEN windowBorderSize ELSE 0; x: INTEGER ~ wbs; w: INTEGER ~ viewer.ww-wbs*2; h: INTEGER ~ viewer.menu.linesUsed*menuHeight; y: INTEGER ~ viewer.wh-captionHeight-h; IF whatChanged=NIL THEN { IF ~clear THEN { Imager.SetColor[context, Imager.white]; Imager.MaskRectangleI[context, x, y, w, h]; }; Imager.SetColor[context, Imager.black]; Imager.MaskRectangleI[context, x, y, w, -menuBarHeight]; }; ViewerPrivate.DrawMenu[viewer.menu, context, x, y+h, whatChanged]; }; }; PaintIcon: PROC [viewer: Viewer, hint: PaintHint, whatChanged: REF ANY _ NIL] = { iconAction: PROC [context: Imager.Context] ~ { SELECT TRUE FROM viewer.icon=private => [] _ viewer.class.paint[viewer, context, whatChanged, hint=all]; hint=all, hint=caption => { dirty: BOOL ~ viewer.newVersion OR viewer.newFile; label: ROPE ~ GetIconLabel[viewer]; drawIcon: PROC ~ { Icons.DrawIcon[flavor: viewer.icon, context: context, label: label] }; SELECT viewer.icon FROM document => IF dirty THEN viewer.icon _ dirtyDocument; dirtyDocument => IF NOT dirty THEN viewer.icon _ document; ENDCASE; Imager.DoSaveAll[context, drawIcon]; IF ViewerPrivate.selectedIcon=viewer THEN { Imager.SetColor[context, ImagerBackdoor.invert]; Imager.MaskRectangleI[context, 0, 0, iconWidth, iconHeight]; }; }; ENDCASE; }; IF viewer.parent=NIL THEN PaintWindow[viewer, iconAction]; }; GetIconLabel: PROC [viewer: Viewer] RETURNS [ROPE] = { IF viewer.label#NIL THEN RETURN [viewer.label]; WITH ViewerOps.FetchProp[viewer, $IconLabel] SELECT FROM label: ROPE => RETURN [label]; ENDCASE => RETURN [viewer.name]; }; InvisiblePaint: ERROR ~ CODE; RecursivelyPaintViewers: PROC [viewer: Viewer, hint: PaintHint, clearClient: BOOL, clear: BOOL, rect: PaintRectangle, whatChanged: REF] = { quit: BOOL _ FALSE; vx, vy: INTEGER; bitmapY: INTEGER _ 0; FOR v: Viewer _ viewer, v.parent UNTIL v=NIL DO -- visibility test IF v.parent#NIL THEN { IF (v.wy+v.wh < 0) OR (v.wy > v.parent.ch) THEN RETURN; IF (v.ww+v.ww < 0) OR (v.wx > v.parent.cw) THEN RETURN; } ENDLOOP; IF hint#client THEN { windowAction: PROC [context: Imager.Context] ~ { w: INTEGER ~ viewer.ww; h: INTEGER ~ viewer.wh; IF ~viewer.visible OR viewer.destroyed THEN ERROR InvisiblePaint; IF clearClient AND ~clear AND hint=all THEN { Imager.SetColor[context, Imager.white]; Imager.MaskRectangleI[context, 0, 0, w, h]; clear _ TRUE; }; IF viewer.border AND hint=all THEN { wbs: INTEGER ~ windowBorderSize; Imager.SetColor[context, Imager.black]; Imager.MaskRectangleI[context, 0, 0, windowBorderSize, h]; Imager.MaskRectangleI[context, w, 0, -windowBorderSize, h]; Imager.MaskRectangleI[context, 0, 0, w, windowBorderSize]; Imager.MaskRectangleI[context, 0, h, w, -windowBorderSize]; }; IF viewer.parent=NIL AND viewer.column#static THEN PaintDocumentHeader[viewer, context, clear, hint, whatChanged]; }; PaintWindow[viewer, windowAction]; }; IF hint=all OR hint=client THEN { clientAction: PROC [context: Imager.Context] ~ { IF ~viewer.visible OR viewer.destroyed THEN ERROR InvisiblePaint; IF rect#NIL AND viewer.class.bltH=none AND viewer.class.bltV=none THEN clearClient _ TRUE; -- if class doesn't care about blt, don't pass it a rectangle IF clearClient AND ~clear THEN { Imager.SetColor[context, Imager.white]; Imager.MaskRectangleI[context, 0, 0, viewer.cw, viewer.ch]; Imager.SetColor[context, Imager.black]; IF rect#NIL THEN whatChanged _ NIL; rect _ NIL; clear _ TRUE; }; IF viewer.class.paint#NIL THEN { quit _ viewer.class.paint[viewer, context, whatChanged, clear]; }; }; PaintClient[viewer, clientAction]; }; <> IF quit THEN RETURN; IF rect#NIL AND viewer.parent=NIL THEN { IF viewer.cx>=rect.x AND (viewer.cx+viewer.cw <= rect.x+rect.w) AND viewer.cy>=rect.y AND (viewer.cy+viewer.ch <= rect.y+rect.h) THEN RETURN; }; <> IF hint=all OR hint=client OR clear THEN <> FOR v: Viewer _ viewer.child, v.sibling UNTIL v=NIL DO IF rect # NIL THEN { <> <> IF viewer.class.topDownCoordSys THEN [vx, vy] _ ViewerOps.UserToScreenCoords[viewer, v.wx, viewer.ch-(v.wy+v.wh)] ELSE [vx, vy] _ ViewerOps.UserToScreenCoords[viewer, v.wx, v.wy]; IF vx >= rect.x AND (vx + v.ww <= rect.x + rect.w) AND vy >= rect.y AND (vy + v.wh <= rect.y + rect.h) THEN LOOP}; RecursivelyPaintViewers[v, all, FALSE, clear, rect, whatChanged]; ENDLOOP; }; PaintViewer: PUBLIC PROC [viewer: Viewer, hint: PaintHint _ client, clearClient: BOOL _ TRUE, whatChanged: REF ANY _ NIL] ~ { LocalPaintViewer: PROC ~ { rect: PaintRectangle _ NIL; IF (NOT viewer.visible) OR viewer.destroyed OR viewer.paintingWedged THEN RETURN; IF viewer.iconic THEN {PaintIcon[viewer, hint, whatChanged ! ABORTED => CONTINUE]; RETURN}; WITH whatChanged SELECT FROM pr: PaintRectangle => rect _ pr; ENDCASE; RecursivelyPaintViewers[viewer: viewer, hint: hint, clearClient: clearClient AND ~(hint=menu OR hint=caption), clear: FALSE, rect: rect, whatChanged: whatChanged ! ABORTED => CONTINUE; InvisiblePaint => CONTINUE ]; }; IF viewer = NIL OR viewer.destroyed OR viewer.paintingWedged THEN RETURN; <> ViewerGroupLocks.CallRootUnderWriteLock[LocalPaintViewer, viewer ! ViewerLocks.Wedged => {viewer.paintingWedged _ TRUE; CONTINUE}]; }; terminal: Terminal.Virtual _ InterminalBackdoor.terminal; Screen: TYPE ~ ViewerPrivate.Screen; CreateContext: PUBLIC PROC [screen: Screen] RETURNS [context: Imager.Context] ~ { RETURN [ContextCreator[screen]]; }; ContextCreator: PROC [screen: Screen] RETURNS [context: Imager.Context] _ DefaultCreateContext; DefaultCreateContext: PROC [screen: Screen] RETURNS [context: Imager.Context] ~ { SELECT screen FROM bw => context _ ImagerTerminal.BWContext[vt: terminal, pixelUnits: TRUE]; color => context _ ImagerTerminal.ColorContext[vt: terminal, pixelUnits: TRUE]; ENDCASE => ERROR; }; ContextList: TYPE ~ LIST OF Imager.Context; contextPool: ARRAY Screen OF ContextList _ ALL[NIL]; contextPoolOverflows: ARRAY Screen OF INT _ ALL[0]; Init: ENTRY PROC ~ { [] _ Terminal.SetBWBitmapState[terminal, allocated]; ViewerPrivate.SetBWScreenSize[w: terminal.bwWidth, h: terminal.bwHeight]; THROUGH [0..8) DO context: Imager.Context ~ CreateContext[bw]; contextPool[bw] _ CONS[context, contextPool[bw]]; ENDLOOP; }; AllocBWContexts: INTERNAL PROC ~ { contextPool[bw] _ NIL; THROUGH [0..8) DO context: Imager.Context ~ CreateContext[bw]; contextPool[bw] _ CONS[context, contextPool[bw]]; ENDLOOP; }; AllocColorContexts: INTERNAL PROC ~ { contextPool[color] _ NIL; THROUGH [0..4) DO context: Imager.Context ~ CreateContext[color]; contextPool[color] _ CONS[context, contextPool[color]]; ENDLOOP; }; SetCreator: PUBLIC PROC [creator: ContextCreatorProc] RETURNS [old: ContextCreatorProc] ~ { inner: ENTRY PROC ~ { old _ ContextCreator; ContextCreator _ IF creator=CreateContext OR creator=NIL THEN DefaultCreateContext ELSE creator; AllocBWContexts[]; IF colorEnabled THEN AllocColorContexts[]; }; DisablePainting[wait: TRUE]; inner[! UNWIND => EnablePainting[]]; EnablePainting[]; }; colorEnabled: BOOL _ FALSE; ColorModeFromBitsPerPixel: PROC [bitsPerPixel: NAT] RETURNS [Terminal.ColorMode] ~ { SELECT bitsPerPixel FROM IN[0..8] => RETURN[[bitsPerPixelChannelA: bitsPerPixel]]; 24 => RETURN[[full: TRUE]]; ENDCASE => RETURN[[FALSE, 0, 0]]; }; EnableColor: PUBLIC PROC [bitsPerPixel: NAT] RETURNS [ok: BOOL _ FALSE] ~ { inner: ENTRY PROC ~ { mode: Terminal.ColorMode ~ ColorModeFromBitsPerPixel[bitsPerPixel]; IF colorEnabled THEN RETURN; IF NOT Terminal.LegalColorMode[terminal, mode] THEN RETURN; [] _ Terminal.SetColorBitmapState[vt: terminal, newState: allocated, newMode: mode, newVisibility: all]; ViewerPrivate.SetColorScreenSize[w: terminal.colorWidth, h: terminal.colorHeight]; ImagerTerminal.SetStandardColorMap[terminal]; THROUGH [0..4) DO context: Imager.Context ~ CreateContext[color]; contextPool[color] _ CONS[context, contextPool[color]]; ENDLOOP; ok _ colorEnabled _ TRUE; }; DisablePainting[wait: TRUE]; inner[! UNWIND => EnablePainting[]]; EnablePainting[]; IF ok THEN { GreyScreen[color, 0, 0, 9999, 9999]; Terminal.TurnOnColorDisplay[terminal]; }; }; DisableColor: PUBLIC PROC ~ { inner: ENTRY PROC ~ { IF NOT colorEnabled THEN RETURN; [] _ Terminal.SetColorBitmapState[vt: terminal, newState: none, newMode: [FALSE, 0, 0], newVisibility: all]; THROUGH [0..3) DO contextPool[color] _ NIL; ENDLOOP; colorEnabled _ FALSE; }; DisablePainting[wait: TRUE]; inner[! UNWIND => EnablePainting[]]; EnablePainting[]; }; paintingCount: CARDINAL _ 0; -- number of paints in progress FinishedPainting: CONDITION; -- notified when a process finishes painting paintingLock: CARDINAL _ 0; -- incremented to disable painting PaintingEnabled: CONDITION; -- wait here for painting to be enabled DisablePainting: PUBLIC PROC [wait: BOOL _ TRUE] = { locked: ENTRY PROC ~ { <> paintingLock _ paintingLock+1; IF wait THEN WHILE paintingCount>0 DO WAIT FinishedPainting ENDLOOP; }; Carets.SuspendCarets[]; locked[]; Carets.ResumeCarets[]; }; EnablePainting: PUBLIC ENTRY PROC ~ { IF paintingLock>0 THEN paintingLock _ paintingLock-1; IF paintingLock=0 THEN BROADCAST PaintingEnabled; }; WaitForPaintingToFinish: PUBLIC ENTRY PROC ~ { WHILE paintingCount>0 DO WAIT FinishedPainting ENDLOOP; }; AcquireContext: ENTRY PROC [screen: Screen] RETURNS [Imager.Context] ~ { WHILE paintingLock>0 DO WAIT PaintingEnabled ENDLOOP; paintingCount _ paintingCount+1; FOR list: ContextList _ contextPool[screen], list.rest UNTIL list=NIL DO context: Imager.Context ~ list.first; IF context#NIL THEN { list.first _ NIL; RETURN[context] }; ENDLOOP; contextPoolOverflows[screen] _ contextPoolOverflows[screen]+1; RETURN[NIL]; }; ReleaseContext: ENTRY PROC [screen: Screen, context: Imager.Context] ~ { IF context#NIL THEN FOR list: ContextList _ contextPool[screen], list.rest UNTIL list=NIL DO IF list.first=NIL THEN { list.first _ context; EXIT }; ENDLOOP; paintingCount _ paintingCount-1; NOTIFY FinishedPainting; }; PaintScreen: PUBLIC PROC [screen: Screen, action: PROC [context: Imager.Context], suspendCarets: BOOL _ TRUE] ~ { acquired: Imager.Context ~ AcquireContext[screen]; context: Imager.Context _ acquired; saveAction: PROC ~ { ImagerBackdoor.ViewReset[context]; IF suspendCarets THEN Carets.SuspendCarets[]; action[context ! UNWIND => IF suspendCarets THEN Carets.ResumeCarets[]]; IF suspendCarets THEN Carets.ResumeCarets[]; }; IF context=NIL THEN context _ CreateContext[screen]; Imager.DoSaveAll[context, saveAction ! UNWIND => ReleaseContext[screen, acquired]]; ReleaseContext[screen, acquired]; }; DoWithoutCaretsVisible: PROC [viewer: Viewer, action: PROC [context: Imager.Context], context: Imager.Context] ~ { proc: PROC ~ {action[context]}; v: Viewer _ viewer; UNTIL v.parent = NIL DO v _ v.parent ENDLOOP; CaretsExtras.DoWithoutCarets[v.wx, v.wy, v.ww, v.wh, ViewerPrivate.ViewerScreen[viewer], proc]; }; PaintWindow: PUBLIC PROC [viewer: Viewer, action: PROC [context: Imager.Context]] ~ { windowAction: PROC [context: Imager.Context] ~ { IF viewer.iconic AND (viewer.wy+viewer.wh)>openBottomY THEN ClipIcon[context]; SetView[context: context, viewer: viewer, client: FALSE]; DoWithoutCaretsVisible[viewer, action, context]; }; PaintScreen[ViewerPrivate.ViewerScreen[viewer], windowAction, FALSE]; }; PaintClient: PUBLIC PROC [viewer: Viewer, action: PROC [context: Imager.Context]] ~ { <> clientAction: PROC [context: Imager.Context] ~ { SetView[context: context, viewer: viewer, client: TRUE]; DoWithoutCaretsVisible[viewer, action, context]; }; IF NOT viewer.iconic THEN PaintScreen[ViewerPrivate.ViewerScreen[viewer], clientAction, FALSE]; }; SetView: PROC [context: Imager.Context, viewer: Viewer, client: BOOL] ~ { wx, wy: INTEGER; [wx, wy] _ ClipView[context, viewer, client]; ImagerBackdoor.ViewTranslateI[context, wx, wy]; }; ClipView: PROC [context: Imager.Context, viewer: Viewer, client: BOOL] RETURNS [wx, wy: INTEGER] ~ { parent: Viewer ~ viewer.parent; topDown: BOOL ~ (parent#NIL AND parent.class.topDownCoordSys); ww: INTEGER _ viewer.ww; wh: INTEGER _ viewer.wh; wx _ viewer.wx; wy _ IF topDown THEN parent.ch-(viewer.wy+wh) ELSE viewer.wy; IF parent # NIL THEN SetView[context: context, viewer: parent, client: TRUE]; IF client THEN { wx _ wx + viewer.cx; wy _ wy + viewer.cy; ww _ viewer.cw; wh _ viewer.ch; }; ImagerBackdoor.ViewClipRectangleI[context, wx, wy, ww, wh]; }; ClipIcon: PROC [context: Imager.Context] ~ { y: NAT ~ ViewerSpecs.openBottomY; h: NAT ~ ViewerSpecs.openTopY-y; Exclude: PROC [x, w: NAT] ~ { ImagerBackdoor.ViewClipRectangleI[context: context, x: x, y: y, w: w, h: h, exclude: TRUE]; }; IF ViewerPrivate.rootViewerTree[left]#NIL THEN Exclude[ViewerSpecs.openLeftLeftX, ViewerSpecs.openLeftWidth]; IF ViewerPrivate.rootViewerTree[right]#NIL THEN Exclude[ViewerSpecs.openRightLeftX, ViewerSpecs.openRightWidth]; }; desktopGrey: Imager.Color _ ImagerBackdoor.MakeStipple[104042B]; uniformGrey: Imager.Color _ Imager.MakeGray[0.4]; GreyScreen: PUBLIC PROC [screen: Screen, x, y, w, h: INTEGER] ~ { greyAction: PROC [context: Imager.Context] ~ { Imager.SetColor[context, IF screen=bw THEN desktopGrey ELSE uniformGrey]; IF screen=bw THEN Imager.SetColor[context, desktopGrey] ELSE Imager.SetColor[context, uniformGrey]; Imager.MaskRectangleI[context, x, y, w, h]; }; PaintScreen[screen, greyAction]; }; GreyWindow: PUBLIC PROC [viewer: Viewer] ~ { screen: Screen ~ ViewerPrivate.ViewerScreen[viewer]; greyAction: PROC [context: Imager.Context] ~ { wx, wy: INTEGER; IF viewer.iconic AND (viewer.wy+viewer.wh)>openBottomY THEN ClipIcon[context]; [wx, wy] _ ClipView[context: context, viewer: viewer, client: FALSE]; IF screen=bw THEN Imager.SetColor[context, desktopGrey] ELSE Imager.SetColor[context, uniformGrey]; Imager.MaskRectangleI[context, wx, wy, viewer.ww, viewer.wh]; }; PaintScreen[screen, greyAction]; }; InvertForMenus: PUBLIC PROC [viewer: Viewer, x, y, w, h: INTEGER] = { invertAction: PROC [context: Imager.Context] ~ { Imager.SetColor[context, ImagerBackdoor.invert]; Imager.MaskRectangleI[context, x, y, w, h]; }; PaintWindow[viewer, invertAction]; }; BlinkOnce: PROC [viewer: Viewer, duration: Process.Ticks] ~ { LockedBlinkOnce: PROC ~ { blinkAction: PROC [context: Imager.Context] ~ { Imager.SetColor[context, ImagerBackdoor.invert]; Imager.MaskRectangleI[context, 0, 0, viewer.ww, viewer.wh]; Process.Pause[duration]; Imager.MaskRectangleI[context, 0, 0, viewer.ww, viewer.wh]; }; IF NOT viewer.visible OR viewer.destroyed THEN RETURN; PaintWindow[viewer, blinkAction]; }; ViewerLocks.CallUnderWriteLock[LockedBlinkOnce, viewer ! ViewerLocks.Wedged => CONTINUE]; }; BlinkViewer: PUBLIC PROC [viewer: Viewer, milliseconds: NAT _ 400] ~ { BlinkOnce[viewer, Process.MsecToTicks[milliseconds]]; }; BlinkIcon: PUBLIC PROC [viewer: Viewer, count: INT _ 1, secondsBetweenBlinks: NAT _ 2, millisecondsPerBlink: NAT _ 400] ~ { IF viewer.offDeskTop THEN ViewerOps.ChangeColumn[viewer, left]; TRUSTED { Process.Detach[ FORK BlinkProcess[viewer, count, secondsBetweenBlinks, millisecondsPerBlink]]; }; }; BlinkProcess: PROC [viewer: Viewer, count: INT, secondsBetweenBlinks, millisecondsPerBlink: NAT] ~ { ticksPerHalfSecond: Process.Ticks ~ Process.MsecToTicks[500]; ticksPerBlink: Process.Ticks ~ Process.MsecToTicks[millisecondsPerBlink]; DO BlinkOnce[viewer, ticksPerBlink]; IF count = 1 THEN RETURN; IF count > 0 THEN count _ count - 1; FOR i: NAT IN [0..2*secondsBetweenBlinks) DO -- check every half second. Process.Pause[ticksPerHalfSecond]; IF viewer.destroyed OR NOT viewer.iconic OR viewer=ViewerTools.GetSelectedViewer[] THEN RETURN; ENDLOOP; ENDLOOP; }; BlinkDisplay: PUBLIC PROC ~ { Terminal.BlinkBWDisplay[terminal]; }; EmergencySaveAllEdits: PROC ~ { CedarProcess.SetPriority[foreground]; DO -- forever keys: Terminal.KeyBits ~ terminal.GetKeys[]; IF keys[LeftShift]=down AND keys[RightShift]=down AND keys[Spare3]=down THEN ViewerOps.SaveAllEdits[]; Process.Pause[Process.SecondsToTicks[1]]; ENDLOOP; }; Init[]; GreyScreen[bw, 0, 0, 9999, 9999]; [] _ Terminal.SetBWBitmapState[terminal, displayed]; Terminal.Select[terminal]; TRUSTED {Process.Detach[FORK EmergencySaveAllEdits[]]}; END.