ViewerPaintImpl.mesa
Copyright © 1983, 1984 Xerox Corporation. All rights reserved.
written by S. McGregor
Edited by McGregor on May 2, 1983 11:11 am
Edited by Maxwell, June 3, 1983 8:23 am
Russ Atkinson, September 1, 1983 11:47 am
Doug Wyatt, September 4, 1984 4:34:32 pm PDT
DIRECTORY
Atom USING [],
Carets USING [ResumeCarets, SuspendCarets],
Graphics USING [Context],
IconManager USING [selectedIcon],
Icons USING [DrawIcon],
Imager USING [black, ClipRectangleI, Context, DoSave, ExcludeRectangleI, MaskRectangleI, SetColor, SetXYI, ShowText, white],
ImagerOps USING [GraphicsFromImager, XOR],
InputFocus USING [],
Menus USING [],
MenusPrivate USING [DrawMenu],
Process USING [Detach, MsecToTicks, Pause, Ticks],
RefText USING [AppendRope, ObtainScratch, ReleaseScratch, TrustTextAsRope],
Rope USING [Length, ROPE, Run],
VFonts USING [defaultFont, StringWidth],
ViewerClasses USING [PaintRectangle, Viewer],
ViewerLocks USING [CallUnderReadLock, CallUnderWriteLock, Wedged],
ViewerOps USING [AddProp, ChangeColumn, EnumerateViewers, EnumProc, FetchProp, GreyScreen, PaintHint, UserToScreenCoords],
ViewerSpecs USING [captionHeight, iconHeight, iconWidth, menuBarHeight, menuHeight, windowBorderSize],
ViewersStallNotifier USING [],
ViewerTools USING [GetSelectedViewer];
ViewerPaintImpl: CEDAR MONITOR
IMPORTS Carets, Imager, ImagerOps, IconManager, Icons, MenusPrivate, Process, RefText, Rope, VFonts, ViewerLocks, ViewerTools, ViewerOps
EXPORTS ViewerOps, ViewersStallNotifier
SHARES Menus, ViewerClasses, ViewerLocks
= BEGIN OPEN ViewerClasses, ViewerOps, ViewerSpecs;
ROPE: TYPE = Rope.ROPE;
InvisiblePaint: PUBLIC SIGNAL = CODE;
PaintDefaultCaption:
PROC[viewer: Viewer, context: Imager.Context] ~ {
text: REF TEXT ~ RefText.ObtainScratch[100];
header: REF TEXT ← text;
headerW: INTEGER ← 0;
name: ROPE ~ viewer.name;
nameLen: INT ~ Rope.Length[name];
file: ROPE ~ viewer.file;
fileLen: INT ~ Rope.Length[file];
header ← RefText.AppendRope[to: header, from: name];
IF fileLen>nameLen
AND Rope.Run[s1: name, s2: file, case:
FALSE]=nameLen
THEN {
The intent of this crock is to show the file version for $Text viewers: if name is a prefix of file, show the remainder of file (presumably "!n") in parentheses
header ← RefText.AppendRope[to: header, from: " ("];
header ← RefText.AppendRope[to: header, from: file, start: nameLen];
header ← RefText.AppendRope[to: header, from: ")"];
};
IF viewer.saveInProgress
THEN
header ← RefText.AppendRope[to: header, from: " [Saving...]"]
ELSE
IF viewer.newFile
THEN
header ← RefText.AppendRope[to: header, from: " [New File]"]
ELSE
IF viewer.newVersion
THEN
header ← RefText.AppendRope[to: header, from: " [Edited]"];
IF viewer.link#
NIL
THEN
header ← RefText.AppendRope[to: header, from: " [Split]"];
headerW ← VFonts.StringWidth[RefText.TrustTextAsRope[header]];
headerW ← MIN[headerW, viewer.ww];
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-captionHeight+4];
Imager.ShowText[context, header];
RefText.ReleaseScratch[text];
};
PaintDocumentHeader:
PROC[viewer: Viewer, context: Imager.Context,
clear: BOOL, hint: PaintHint, whatChanged: REF ANY] ~ {
wbs: INTEGER = IF viewer.border THEN windowBorderSize ELSE 0;
IF hint#menu
THEN {
IF viewer.class.caption#
NIL
THEN {
-- client-painted caption
action:
PROC ~ {
Imager.ClipRectangleI[context: context,
x: 0, y: viewer.wh, w: viewer.ww, h: -captionHeight];
viewer.class.caption[viewer, ImagerOps.GraphicsFromImager[context]];
};
Imager.DoSave[context, action];
}
ELSE PaintDefaultCaption[viewer, context];
};
IF viewer.menu#
NIL
AND hint#caption
THEN {
top: INTEGER ~ viewer.wh-captionHeight;
height: INTEGER ~ viewer.menu.linesUsed*menuHeight;
IF whatChanged=
NIL
THEN {
IF ~clear
THEN {
Imager.SetColor[context, Imager.white];
Imager.MaskRectangleI[context, wbs, top, viewer.ww-(2*wbs), -height]; -- menu area
};
Imager.SetColor[context, Imager.black];
Imager.MaskRectangleI[context, 0, top-height, viewer.ww, -menuBarHeight]; -- bar below
};
MenusPrivate.DrawMenu[viewer.menu, ImagerOps.GraphicsFromImager[context],
wbs, top-menuHeight, whatChanged];
};
};
PaintIcon:
PROC [viewer: Viewer, hint: PaintHint, whatChanged:
REF
ANY ←
NIL] = {
label: Rope.ROPE;
action:
PROC[imager: Imager.Context] ~ {
context: Graphics.Context ~ ImagerOps.GraphicsFromImager[imager];
IF flavor is private then client wants to draw the icon
IF viewer.icon=private THEN viewer.class.paint[viewer, context, whatChanged, hint=all]
ELSE
IF hint=all
OR hint=caption
THEN {
draw: PROC ~ { Icons.DrawIcon[flavor: viewer.icon, context: context, label: label] };
IF viewer.icon=document
AND (viewer.newVersion
OR viewer.newFile)
THEN viewer.icon ← dirtyDocument
ELSE
IF viewer.icon=dirtyDocument
AND
NOT(viewer.newVersion
OR viewer.newFile)
THEN viewer.icon ← document;
label ← NARROW[ViewerOps.FetchProp[viewer, $IconLabel]];
IF label = NIL THEN label ← viewer.name;
Imager.DoSave[imager, draw];
IF IconManager.selectedIcon=viewer
THEN {
Imager.SetColor[imager, ImagerOps.XOR];
Imager.MaskRectangleI[imager, 0, 0, iconWidth, iconHeight];
};
};
};
IF viewer.parent=NIL THEN PaintClient[viewer, action];
};
SetIconLabel:
PUBLIC
PROC [viewer: Viewer, label: Rope.
ROPE] = {
ViewerOps.AddProp[viewer, $IconLabel, label];
};
RecursivelyPaintViewers:
PROC [viewer: Viewer, hint: PaintHint, clearClient:
BOOL,
clear: BOOL, rect: PaintRectangle, whatChanged: REF ANY] = {
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 {
windowPart:
PROC[context: Imager.Context] ~ {
IF clearClient
AND ~clear
AND hint=all
THEN {
Imager.SetColor[context, Imager.white];
Imager.MaskRectangleI[context, 0, 0, viewer.ww, viewer.wh];
clear ← TRUE;
};
IF viewer.border
AND hint=all
THEN {
wbs: INTEGER ~ ViewerSpecs.windowBorderSize;
Imager.SetColor[context, Imager.black];
Imager.MaskRectangleI[context: context, x: 0, y: 0, w: viewer.ww, h: wbs];
Imager.MaskRectangleI[context: context, x: 0, y: viewer.wh, w: viewer.ww, h: -wbs];
Imager.MaskRectangleI[context: context, x: 0, y: 0, w: wbs, h: viewer.wh];
Imager.MaskRectangleI[context: context, x: viewer.ww, y: 0, w: -wbs, h: viewer.wh];
};
IF viewer.parent=
NIL
AND viewer.column#static
THEN
PaintDocumentHeader[viewer, context, clear, hint, whatChanged];
};
IF ~viewer.visible OR viewer.destroyed THEN SIGNAL InvisiblePaint;
PaintWindow[viewer, windowPart];
};
IF hint=all
OR hint=client
THEN {
clientPart:
PROC[context: Imager.Context] ~ {
IF rect#NIL AND viewer.class.bltContents=none THEN clearClient ← TRUE;
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
viewer.class.paint[viewer, ImagerOps.GraphicsFromImager[context], whatChanged, clear];
};
IF ~viewer.visible OR viewer.destroyed THEN SIGNAL InvisiblePaint;
PaintClient[viewer, clientPart];
};
do we need to paint any of the children?
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};
recursively paint children
IF hint=all
OR hint=client
OR clear
THEN
these guys should be painted in reverse order for correct overlaps
FOR v: Viewer ← viewer.child, v.sibling
UNTIL v=
NIL
DO
IF rect #
NIL
THEN {
-- don't paint if it is completely within the blt
-- map to the coodinate system that rect uses
vx, vy: INTEGER;
IF viewer.class.coordSys = top
THEN [vx, vy] ← ViewerOps.UserToScreenCoords[viewer, v.wx, 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] = {
LockedPaintViewer:
PROC = {
ENABLE ABORTED => CONTINUE;
IF NOT viewer.visible OR viewer.destroyed THEN RETURN;
IF viewer.iconic THEN PaintIcon[viewer, hint, whatChanged]
ELSE {
action:
PROC ~ {
rect: PaintRectangle ← NIL;
WITH whatChanged SELECT FROM r: PaintRectangle => rect ← r; ENDCASE;
RecursivelyPaintViewers[viewer: viewer, hint: hint,
clearClient: clearClient AND ~(hint=menu OR hint=caption), clear: FALSE,
rect: rect, whatChanged: whatChanged ! InvisiblePaint => CONTINUE];
};
Carets.SuspendCarets[];
action[! UNWIND => Carets.ResumeCarets[]];
Carets.ResumeCarets[];
};
};
ViewerLocks.CallUnderReadLock[LockedPaintViewer, viewer ! ViewerLocks.Wedged => CONTINUE];
};
PaintVector: TYPE = REF PaintVectorRec;
PaintVectorRec: TYPE = RECORD [
next: PaintVector ← NIL,
viewer: Viewer ← NIL,
inUse: BOOL ← FALSE,
resetOnRelease: BOOL ← FALSE,
color: BOOL ← FALSE,
mark: Graphics.Mark ← nullMark,
process: UNSAFE PROCESS ← NIL,
context: Context ← NIL,
previous: PaintVector ← NIL];
nVectors: CARDINAL = 5;
usedVectors: CARDINAL ← 0;
PaintVectorAvailable: CONDITION;
paintingLock: CARDINAL ← 0;
vectorRoot: PaintVector ← NIL;
colorVector: PaintVector ← NIL;
Init:
ENTRY
PROC = {
new: PaintVector;
FOR n: CARDINAL IN [0..nVectors) DO
new ← NEW[PaintVectorRec];
new.context ← NewContext[];
SetDefaultFont[new.context, VFonts.defaultGFont];
new.next ← vectorRoot;
IF vectorRoot#NIL THEN vectorRoot.previous ← new;
vectorRoot ← new;
ENDLOOP;
colorVector ← NEW[PaintVectorRec];
colorVector.color ← TRUE;
};
colorInit: BOOL ← FALSE;
InitialiseColorPainting: PUBLIC PROC = {
ColorCriticalInit: ENTRY PROC = {
colorVector.context ← ColorWorld.NewContext[];
SetDefaultFont[colorVector.context, VFonts.defaultGFont];
colorInit ← TRUE;
};
ColorCriticalInit[];
ViewerOps.GreyScreen[0, 0, 9999, 9999, TRUE, FALSE];
};
ZapVector: INTERNAL PROC [p: PaintVector] = {
oldContext: Context ← p.context;
p.context ← IF p.color THEN ColorWorld.NewContext[] ELSE NewContext[];
Graphics.SetDefaultFont[p.context, VFonts.defaultGFont];
p.viewer ← NIL;
p.inUse ← FALSE;
p.process ← NIL;
p.mark ← nullMark;
IF ~p.color THEN usedVectors ← usedVectors - 1;
GraphicsOps.Disable[oldContext]; -- smash old
BROADCAST PaintVectorAvailable;
};
UnWedgePainter:
PUBLIC
ENTRY
PROC [process:
PROCESS, kind:
ATOM] = {
ENABLE UNWIND => NULL;
IF colorVector.inUse AND colorVector.process=process THEN ZapVector[colorVector]
ELSE FOR p: PaintVector ← vectorRoot, p.next UNTIL p=NIL DO
IF p.inUse AND p.process=process THEN ZapVector[p];
ENDLOOP;
};
UnWedge: ENTRY PROC [process: PROCESS, kind: ATOM] = {
call from debugger
IF colorVector.inUse THEN ZapVector[colorVector]
ELSE FOR p: PaintVector ← vectorRoot, p.next UNTIL p=NIL DO
IF p.inUse THEN ZapVector[p];
ENDLOOP;
usedVectors ← 0; -- make sure
paintingLock ← 0; -- make sure
BROADCAST PaintVectorAvailable;
};
DisablePainting: PUBLIC ENTRY PROC [wait: BOOL ← TRUE] = {
ENABLE UNWIND => NULL;
InternalDisablePainting[wait];
};
InternalDisablePainting: INTERNAL PROC [wait: BOOL ← TRUE] = INLINE {
IF wait THEN
WHILE usedVectors#0 OR colorVector.inUse DO WAIT PaintVectorAvailable; ENDLOOP;
paintingLock ← paintingLock+1;
};
EnablePainting: PUBLIC ENTRY PROC = {
ENABLE UNWIND => NULL;
InternalEnablePainting[];
};
InternalEnablePainting: INTERNAL PROC = INLINE {
IF paintingLock#0 THEN paintingLock ← paintingLock-1;
broadcast since all are likely to be free
IF paintingLock=0 THEN BROADCAST PaintVectorAvailable;
};
DisablePaintingUnWedged: INTERNAL PROC [wait: BOOL ← TRUE] = INLINE {
AllWedged: INTERNAL PROC RETURNS[BOOL] = INLINE {
IF colorVector.inUse THEN {
IF colorVector.viewer=NIL THEN RETURN[FALSE]; -- context = screen
IF ~ViewerLocks.ColumnWedged[color].wedged THEN RETURN[FALSE];
};
FOR p: PaintVector ← vectorRoot, p.next UNTIL p=NIL DO
IF ~p.inUse THEN LOOP;
IF p.viewer = NIL THEN RETURN[FALSE]; -- context = screen
IF ~ViewerLocks.ColumnWedged[ViewerColumn[p.viewer]].wedged THEN RETURN[FALSE];
ENDLOOP;
RETURN[TRUE];
};
IF wait THEN
WHILE usedVectors#0 OR colorVector.inUse DO
IF AllWedged[] THEN EXIT ELSE WAIT PaintVectorAvailable;
ENDLOOP;
paintingLock ← paintingLock+1;
};
WaitForPaintingToFinish: PUBLIC ENTRY PROC = {
assumes interesting paints already in progress
WHILE usedVectors#0 AND colorVector.inUse DO WAIT PaintVectorAvailable ENDLOOP;
};
ResetPaintCache: PUBLIC ENTRY PROC [viewer: Viewer ← NIL, wait: BOOL ← TRUE] = {
ENABLE UNWIND => NULL;
DisablePaintingUnWedged[wait];
FOR p: PaintVector ← vectorRoot, p.next UNTIL p=NIL DO
IF p.inUse THEN {
IF p.viewer = NIL OR ~ViewerLocks.ColumnWedged[ViewerColumn[p.viewer]].wedged
THEN IF wait THEN RETURN WITH ERROR fatalViewersError;
p.resetOnRelease ← TRUE;
}
ELSE IF p.viewer#NIL THEN {
ResetContext[p.context];
p.viewer ← NIL;
};
ENDLOOP;
IF colorVector.context#NIL THEN {
IF colorVector.inUse AND ~ViewerLocks.ColumnWedged[color].wedged THEN {
IF wait THEN RETURN WITH ERROR fatalViewersError;
colorVector.resetOnRelease ← TRUE;
}
ELSE IF colorVector.viewer#NIL THEN {
ResetContext[colorVector.context];
colorVector.viewer ← NIL;
};
};
InternalEnablePainting[];
};
fatalViewersError: ERROR = CODE;
ReleaseContext: PUBLIC ENTRY PROC [free: Context] = {
ENABLE UNWIND => NULL;
CleanUp: INTERNAL PROC [p: PaintVector] = {
Restore[free, p.mark]; -- throw away any client changes
p.inUse ← FALSE;
p.mark ← nullMark;
IF ~p.color THEN {IF usedVectors=0 THEN RETURN WITH ERROR fatalViewersError
ELSE usedVectors ← usedVectors-1};
IF p.resetOnRelease THEN {
ResetContext[p.context];
p.viewer ← NIL;
p.resetOnRelease ← FALSE;
};
p.process ← NIL;
BROADCAST PaintVectorAvailable;
};
IF free=NIL THEN RETURN WITH ERROR fatalViewersError;
IF free=colorVector.context THEN CleanUp[colorVector]
ELSE FOR p: PaintVector ← vectorRoot, p.next UNTIL p=NIL DO
IF free=p.context THEN {CleanUp[p]; EXIT};
ENDLOOP;
};
AcquireContext: PUBLIC ENTRY PROC [viewer: Viewer, color: BOOL ← FALSE]
RETURNS [allocated: Context] = {
ENABLE UNWIND => NULL;
bestFit: PaintVector;
nil: BOOL ← FALSE;
MakeFirst: INTERNAL PROC [pv: PaintVector] = INLINE { -- LRU caching scheme
IF pv=vectorRoot THEN RETURN;
pv.previous.next ← pv.next;
IF pv.next#NIL THEN pv.next.previous ← pv.previous;
pv.next ← vectorRoot;
vectorRoot.previous ← pv;
vectorRoot ← pv;
};
IF color THEN {
IF ~colorInit THEN ERROR fatalViewersError;
WHILE colorVector.inUse OR paintingLock#0 DO WAIT PaintVectorAvailable; ENDLOOP;
IF viewer#NIL AND (~viewer.visible OR viewer.destroyed) THEN
SIGNAL InvisiblePaint; -- someone got changed while waiting in the loop above
bestFit ← colorVector;
}
ELSE {
UNTIL usedVectors<nVectors AND paintingLock=0 DO WAIT PaintVectorAvailable; ENDLOOP;
IF viewer#NIL AND (~viewer.visible OR viewer.destroyed) THEN
SIGNAL InvisiblePaint; -- someone got changed while waiting in the loop above
FOR p: PaintVector ← vectorRoot, p.next UNTIL p=NIL DO
IF p.inUse THEN LOOP;
IF viewer=p.viewer THEN {bestFit ← p; EXIT}
ELSE IF p.viewer=NIL THEN {bestFit ← p; nil ← TRUE}
ELSE IF ~nil THEN bestFit ← p;
ENDLOOP;
usedVectors ← usedVectors+1;
MakeFirst[bestFit];
};
bestFit.inUse ← TRUE;
TRUSTED {bestFit.process ← Process.GetCurrent[]};
SetContext[viewer, bestFit];
bestFit.mark ← Save[allocated ← bestFit.context]; -- protect context
};
SetContext: INTERNAL PROC [viewer: Viewer, paintVector: PaintVector] = {
pViewer: Viewer = paintVector.viewer;
pContext: Context = paintVector.context;
RecursivelyPushParent: INTERNAL PROC [viewer: Viewer] = {
IF viewer.parent#NIL THEN RecursivelyPushParent[viewer.parent];
PushContext[viewer, pContext]
};
SELECT TRUE FROM -- try to efficiently set up client context
pViewer=viewer => NULL;
viewer=NIL => ResetContext[pContext];
pViewer=viewer.parent => PushContext[viewer, pContext];
pViewer=NIL => RecursivelyPushParent[viewer];
pViewer.destroyed => {ResetContext[pContext]; RecursivelyPushParent[viewer]};
pViewer.parent=viewer => PopContext[pContext];
pViewer.parent=viewer.parent => {PopContext[pContext]; PushContext[viewer, pContext]};
ENDCASE => {ResetContext[pContext]; RecursivelyPushParent[viewer]};
paintVector.viewer ← viewer;
};
PushContext: INTERNAL PROC [viewer: Viewer, context: Context] = { OPEN viewer;
invertCoords: BOOL = IF viewer.parent=NIL THEN viewer.class.coordSys=top
ELSE viewer.class.coordSys#viewer.parent.class.coordSys;
[] ← Save[context];
IF viewer.iconic THEN {
IF wy+wh > openBottomY THEN ClipIcon[context];
ClipBox[context, XYWH2Box[wx, wy, ww, wh]];
Translate[context, wx, wy];
}
ELSE IF invertCoords THEN {
ClipBox[context, XYWH2Box[cx, cy, cw, ch]];
Translate[context, cx, cy+ch];
Scale[context, 1, -1];
}
ELSE {
ClipBox[context, XYWH2Box[cx, cy, cw, ch]];
Translate[context, cx, cy];
};
};
PopContext: INTERNAL PROC [context: Context] = INLINE {
Restore[context];
};
ResetContext: INTERNAL PROC [context: Context] = INLINE {
screen: Mark = LOOPHOLE[0];
Restore[context, screen];
};
InvertForMenus:
PUBLIC
PROC[viewer: Viewer, x,y:
INTEGER, w,h:
INTEGER] = {
action:
PROC[context: Imager.Context] ~ {
Imager.SetColor[context, ImagerOps.XOR];
Imager.MaskRectangleI[context, x, y, MIN[w, viewer.ww-x], h];
};
PaintWindow[viewer, action];
};
ClipIcon:
PROC[context: Imager.Context] = {
clips for overlapping top level windows given valid Context and Viewer
ClipOpenViewer: ViewerOps.EnumProc = {
IF ~v.iconic
AND (v.column=right
OR v.column=left)
THEN
Imager.ExcludeRectangleI[context, v.wx, v.wy, v.ww, v.wh];
};
ViewerOps.EnumerateViewers[ClipOpenViewer];
};
BlinkIcon:
PUBLIC PROC [viewer: Viewer, count, interval:
NAT] = {
IF interval >= 1000 THEN interval ← interval/1000;
IF viewer.offDeskTop THEN ViewerOps.ChangeColumn[viewer, left];
IF count = 1 THEN BlinkProcess[viewer, count, interval]
ELSE TRUSTED {Process.Detach[FORK BlinkProcess[viewer, count, interval]]};
};
BlinkProcess:
PROC [viewer: Viewer, count, n:
NAT] = {
howOften: Process.Ticks = Process.MsecToTicks[500];
Blink:
PROC = {
IF viewer.visible
AND
NOT viewer.destroyed
THEN {
action:
PROC[context: Imager.Context] ~ {
Imager.SetColor[context, ImagerOps.XOR];
Imager.MaskRectangleI[context, 0, 0, viewer.ww, viewer.wh];
Process.Pause[Process.MsecToTicks[400]];
Imager.MaskRectangleI[context, 0, 0, viewer.ww, viewer.wh];
};
PaintWindow[viewer, action];
Process.Pause[Process.MsecToTicks[350]]; -- so can be called back-to-back
};
};
DO
ViewerLocks.CallUnderWriteLock[Blink, viewer];
IF count = 1 THEN RETURN;
IF count > 0 THEN count ← count - 1;
FOR i:
NAT
IN [0..2*n)
DO
-- blink every n seconds, but wake up every half second.
Process.Pause[howOften];
IF viewer.destroyed
OR ~viewer.iconic
OR viewer = ViewerTools.GetSelectedViewer[] THEN RETURN;
ENDLOOP;
ENDLOOP;
};
PaintWindow:
PUBLIC
PROC[viewer: Viewer, action:
PROC[Imager.Context]] ~ {
};
PaintClient:
PUBLIC
PROC[viewer: Viewer, action:
PROC[Imager.Context]] ~ {
};
Init[];
ViewerOps.GreyScreen[0, 0, 9999, 9999, FALSE, FALSE];
END.