ViewerPaintImpl.mesa; written by S. McGregor
Edited by McGregor on August 8, 1983 4:00 pm
Edited by Maxwell, June 3, 1983 8:23 am
Last Edited by: Pausch, August 26, 1983 3:39 pm
Last Edited by: Wyatt, October 24, 1983 12:37 pm
DIRECTORY
Carets USING [ResumeCarets, SuspendCarets],
IconManager USING [selectedIcon],
Icons USING [DrawIcon],
Imager USING [black, ClipView, Context, Create, DoSave, IntegerClipRectangle, IntegerMaskRectangle, IntegerSetXY, Reset, SetColor, SetView, ShowCharacters, TranslateT, white, XOR],
MenusPrivate USING [DrawMenu, ViewerMenus],
Process USING [Detach, GetCurrent, MsecToTicks, Pause, Ticks],
Rope USING [ROPE],
VFonts USING [defaultFont, FONT, RopeWidth],
ViewerOps USING [AddProp, ChangeColumn, EnumerateViewers, EnumProc, FetchProp, GreyScreen, PaintHint, UserToScreenCoords, ViewerColumn],
ViewerClasses USING [PaintRectangle, Viewer],
ViewerLocks USING [CallUnderReadLock, CallUnderWriteLock, ColumnWedged, Wedged],
ViewerSpecs USING [captionDescent, captionHeight, iconHeight, iconWidth, menuBarHeight, openBottomY, windowBorderSize],
ViewersStallNotifier USING [],
ViewerTools USING [GetSelectedViewer];
ViewerPaintImpl: CEDAR MONITOR
IMPORTS Carets, Imager, IconManager, Icons, MenusPrivate, Process, VFonts, ViewerLocks, ViewerTools, ViewerOps, ViewerSpecs
EXPORTS ViewerOps, ViewersStallNotifier
SHARES ViewerClasses, ViewerLocks
= BEGIN OPEN ViewerSpecs;
Viewer: TYPE = ViewerClasses.Viewer;
PaintRectangle: TYPE = ViewerClasses.PaintRectangle;
PaintHint: TYPE = ViewerOps.PaintHint;
Context: TYPE = Imager.Context;
ROPE: TYPE = Rope.ROPE;
InvisiblePaint: PUBLIC SIGNAL = CODE;
PaintCaption:
PROC[viewer: Viewer, context: Context] = {
IF viewer.class.caption#
NIL
THEN {
-- client-painted caption
CallClient:
PROC = {
wbs: INTEGER = IF viewer.border THEN windowBorderSize ELSE 0;
Imager.IntegerClipRectangle[context,
viewer.wx+wbs, viewer.wy+viewer.wh-wbs-captionHeight,
viewer.ww-(2*wbs), captionHeight];
viewer.class.caption[viewer, context];
};
Imager.DoSave[context, CallClient];
}
ELSE {
font: VFonts.FONT = VFonts.defaultFont;
headerW: INTEGER ← VFonts.RopeWidth[viewer.name];
IF viewer.saveInProgress THEN headerW ← headerW + sipW
ELSE IF viewer.newFile THEN headerW ← headerW + newFW
ELSE IF viewer.newVersion THEN headerW ← headerW + newVW;
IF viewer.link#NIL THEN headerW ← headerW + linkW;
headerW ← MIN[headerW, viewer.ww];
Imager.IntegerMaskRectangle[context,
viewer.wx, viewer.wy+viewer.wh-captionHeight, viewer.ww, captionHeight];
Imager.SetColor[context, Imager.white];
Imager.IntegerSetXY[context,
viewer.wx+(viewer.ww-headerW)/2,
viewer.wy+viewer.wh-captionHeight+captionDescent];
Imager.ShowCharacters[context, viewer.name, font];
IF viewer.saveInProgress THEN Imager.ShowCharacters[context, sipT, font]
ELSE IF viewer.newFile THEN Imager.ShowCharacters[context, newFT, font]
ELSE IF viewer.newVersion THEN Imager.ShowCharacters[context, newVT, font];
IF viewer.link#NIL THEN Imager.ShowCharacters[context, linkT, font];
};
};
PaintDocumentHeader:
PROC[viewer: Viewer, context: Context, clear:
BOOL, hint: PaintHint, whatChanged:
REF
ANY] = {
Imager.IntegerClipRectangle[context, viewer.wx, viewer.wy, viewer.ww, viewer.wh];
Paint caption:
IF hint#menu THEN PaintCaption[viewer, context];
Paint menu:
IF viewer.menus#
NIL
AND hint#caption
THEN {
menus: MenusPrivate.ViewerMenus = NARROW[viewer.menus];
menuHeight: INTEGER = menus.h;
wbs: INTEGER = IF viewer.border THEN windowBorderSize ELSE 0;
IF whatChanged=
NIL
THEN {
Clear the menu area, if necessary
IF ~clear
THEN {
Imager.SetColor[context, Imager.white];
Imager.IntegerMaskRectangle[context,
viewer.wx+wbs, viewer.wy+viewer.wh-wbs-captionHeight-menuHeight,
viewer.ww-(2*wbs), menuHeight];
};
Draw the bar below the menu
Imager.SetColor[context, Imager.black];
Imager.IntegerMaskRectangle[context,
viewer.wx, viewer.wy+viewer.wh-wbs-captionHeight-menuHeight,
viewer.ww, menuBarHeight];
};
MenusPrivate.DrawMenu[viewer, menus, context, whatChanged];
};
};
PaintIcon:
PROC[viewer: Viewer, hint: PaintHint, whatChanged:
REF
ANY ←
NIL] = {
context: Context;
IF viewer.parent#NIL THEN RETURN;
context ← AcquireContext[viewer, FALSE]; -- all icons on b&w display
IF viewer.icon=private
THEN
-- client wants to draw the icon
viewer.class.paint[viewer, context, whatChanged, hint=all]
ELSE
IF hint=all
OR hint=caption
THEN {
label: ROPE = GetIconLabel[viewer];
DrawIcon: 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;
Imager.DoSave[context, DrawIcon];
IF IconManager.selectedIcon=viewer
THEN {
Imager.SetColor[context, Imager.XOR];
Imager.IntegerMaskRectangle[context, 0, 0, iconWidth, iconHeight];
};
};
ReleaseContext[context];
};
SetIconLabel:
PUBLIC
PROC[viewer: Viewer, label:
ROPE] = {
ViewerOps.AddProp[viewer, $IconLabel, label];
};
GetIconLabel:
PROC[viewer: Viewer]
RETURNS[label:
ROPE] = {
prop: REF ANY = ViewerOps.FetchProp[viewer, $IconLabel];
IF prop#NIL THEN RETURN[NARROW[prop]]
ELSE RETURN[viewer.name];
};
RecursivelyPaintViewers:
PROC [viewer: Viewer, hint: PaintHint, clearClient:
BOOL,
clear: BOOL, rect: PaintRectangle, whatChanged: REF ANY] = {
vx, vy: INTEGER;
context: Context ← NIL;
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 {
ENABLE UNWIND => IF context#NIL THEN ReleaseContext[context];
PaintHeader: PROC = {PaintDocumentHeader[viewer, context, clear, hint, whatChanged]};
context ← AcquireContext[viewer.parent, viewer.column=color];
IF ~viewer.visible OR viewer.destroyed THEN SIGNAL InvisiblePaint;
IF clearClient
AND ~clear
AND hint=all
THEN {
Imager.SetColor[context, Imager.white];
Imager.IntegerMaskRectangle[context, viewer.wx, viewer.wy, viewer.ww, viewer.wh];
Imager.SetColor[context, Imager.black];
clear ← TRUE;
};
IF viewer.border
AND hint=all
THEN {
wbs: INTEGER ~ windowBorderSize;
Imager.IntegerMaskRectangle[context, viewer.wx, viewer.wy, viewer.ww, wbs];
Imager.IntegerMaskRectangle[context, viewer.wx, viewer.wy, wbs, viewer.wh];
Imager.IntegerMaskRectangle[context, viewer.wx+viewer.ww-wbs, viewer.wy, wbs, viewer.wh];
Imager.IntegerMaskRectangle[context, viewer.wx, viewer.wy+viewer.wh-wbs, viewer.ww, wbs];
};
IF viewer.parent=NIL AND viewer.column#static THEN Imager.DoSave[context, PaintHeader];
ReleaseContext[context];
};
IF hint=all
OR hint=client
THEN {
ENABLE UNWIND => IF context#NIL THEN ReleaseContext[context];
PaintClient: PROC = {viewer.class.paint[viewer, context, whatChanged, clear]};
context ← NIL; context ← AcquireContext[viewer, viewer.column=color];
IF ~viewer.visible OR viewer.destroyed THEN SIGNAL InvisiblePaint;
IF rect#
NIL
AND rect.flavor=blt
AND viewer.class.bltContents = none
THEN
clearClient ← TRUE;
IF clearClient
AND ~clear
THEN {
Imager.SetColor[context, Imager.white];
Imager.IntegerMaskRectangle[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 {
IF viewer.clientContext THEN PaintClient[] ELSE Imager.DoSave[context, PaintClient];
};
ReleaseContext[context];
};
do we need to paint any of the children?
IF rect #
NIL
AND rect.flavor=blt
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 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
AND rect.flavor=blt
THEN {
-- don't paint if it is completely within the blt
map to the coodinate system that rect uses
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];
[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 = {
ENABLE ABORTED => {Carets.ResumeCarets[]; CONTINUE};
rect: PaintRectangle ← NIL;
IF ~viewer.visible OR viewer.destroyed THEN RETURN;
IF viewer.iconic THEN {PaintIcon[viewer, hint, whatChanged]; RETURN};
WITH whatChanged SELECT FROM r: PaintRectangle => rect ← r; ENDCASE;
Carets.SuspendCarets[];
RecursivelyPaintViewers[viewer, hint, clearClient
AND ~(hint=menu
OR hint=caption),
FALSE, rect, whatChanged ! InvisiblePaint => CONTINUE];
Carets.ResumeCarets[];
};
ViewerLocks.CallUnderReadLock[LocalPaintViewer, 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,
process: UNSAFE PROCESS ← NIL,
context: Context ← NIL,
previous: PaintVector ← NIL];
nVectors: CARDINAL = 5;
usedVectors: CARDINAL ← 0;
PaintVectorAvailable: CONDITION;
paintingLock: CARDINAL ← 0;
vectorRoot: PaintVector ← NIL;
Init:
ENTRY
PROC = {
new: PaintVector;
FOR n:
CARDINAL
IN [0..nVectors)
DO
new ← NEW[PaintVectorRec];
new.context ← Imager.Create[$LFDisplay];
new.next ← vectorRoot;
IF vectorRoot#NIL THEN vectorRoot.previous ← new;
vectorRoot ← new;
ENDLOOP;
newFW ← VFonts.RopeWidth[newFT];
newVW ← VFonts.RopeWidth[newVT];
linkW ← VFonts.RopeWidth[linkT];
sipW ← VFonts.RopeWidth[sipT];
};
colorInit: BOOL ← FALSE;
InitialiseColorPainting:
PUBLIC
PROC = {
colorInit ← TRUE;
ViewerOps.GreyScreen[0, 0, 9999, 9999, TRUE, FALSE];
};
ZapVector:
INTERNAL
PROC [p: PaintVector] = {
oldContext: Context ← p.context;
p.context ← Imager.Create[$LFDisplay];
p.viewer ← NIL;
p.inUse ← FALSE;
p.process ← NIL;
IF ~p.color THEN usedVectors ← usedVectors - 1;
Imager.SetView[oldContext, [0,0,0,0]]; -- smash old
BROADCAST PaintVectorAvailable;
};
UnWedgePainter:
PUBLIC
ENTRY
PROC [process:
PROCESS, kind:
ATOM] = {
ENABLE UNWIND => NULL;
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
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;
};
InternalDisablePainting:
INTERNAL
PROC [wait:
BOOL ←
TRUE] =
INLINE {
IF wait
THEN
WHILE usedVectors#0 DO WAIT PaintVectorAvailable;
ENDLOOP;
paintingLock ← paintingLock+1;
};
DisablePainting:
PUBLIC
ENTRY
PROC [wait:
BOOL ←
TRUE] = {
ENABLE UNWIND => NULL;
InternalDisablePainting[wait];
};
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;
};
EnablePainting:
PUBLIC
ENTRY
PROC = {
ENABLE UNWIND => NULL;
InternalEnablePainting[];
};
DisablePaintingUnWedged:
INTERNAL
PROC [wait:
BOOL ←
TRUE] =
INLINE {
AllWedged:
INTERNAL
PROC
RETURNS[
BOOL] =
INLINE {
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[ViewerOps.ViewerColumn[p.viewer]].wedged THEN RETURN[FALSE];
ENDLOOP;
RETURN[TRUE];
};
IF wait
THEN {
WHILE usedVectors#0
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 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[ViewerOps.ViewerColumn[p.viewer]].wedged
THEN IF wait THEN RETURN WITH ERROR fatalViewersError;
p.resetOnRelease ← TRUE;
}
ELSE
IF p.viewer#
NIL
THEN {
Imager.Reset[p.context];
p.viewer ← NIL;
};
ENDLOOP;
InternalEnablePainting[];
};
fatalViewersError: ERROR = CODE;
ReleaseContext:
PUBLIC
ENTRY
PROC [free: Context] = {
ENABLE UNWIND => NULL;
CleanUp:
INTERNAL
PROC [p: PaintVector] = {
p.inUse ← FALSE;
IF usedVectors=0
THEN
RETURN
WITH
ERROR fatalViewersError
ELSE usedVectors ← usedVectors-1;
IF p.resetOnRelease
THEN {
Imager.Reset[p.context];
p.viewer ← NIL;
p.resetOnRelease ← FALSE;
};
p.process ← NIL;
BROADCAST PaintVectorAvailable;
};
IF free=NIL THEN RETURN WITH ERROR fatalViewersError;
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 };
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
IF viewer#
NIL
AND viewer.clientContext
THEN {
-- private client context
allocated ← NARROW[ViewerOps.FetchProp[viewer, $ViewerClientContext]];
SetContext[viewer, allocated];
RETURN;
};
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;
bestFit.inUse ← TRUE;
TRUSTED {bestFit.process ← Process.GetCurrent[]};
IF viewer#bestFit.viewer THEN SetContext[viewer, bestFit.context];
bestFit.viewer ← viewer;
Imager.SetColor[bestFit.context, Imager.black]; -- since we don't save the color
RETURN[bestFit.context];
};
SetContext:
INTERNAL
PROC [viewer: Viewer, context: Context] = {
x, y: INTEGER ← 0;
invert: BOOL ← FALSE;
RecursivelyPushParent:
INTERNAL
PROC [v: Viewer] = {
IF v.parent#NIL THEN RecursivelyPushParent[v.parent];
PushContext[v]
};
PushContext:
INTERNAL
PROC [v: Viewer] = {
invertCoords: BOOL = IF v.parent=NIL THEN v.class.coordSys=top
ELSE v.class.coordSys#v.parent.class.coordSys;
x ← x + v.cx;
IF invert THEN y ← y + v.parent.ch - v.cy - v.ch ELSE
y ← y + v.cy;
Imager.ClipView[context, [x, y, v.cw, v.ch], FALSE];
IF invertCoords THEN invert ← ~invert;
};
Imager.Reset[context];
IF viewer=NIL THEN RETURN
ELSE
IF viewer.iconic
THEN {
IF viewer.wy+viewer.wh > openBottomY
THEN {
ClipIcon[context];
Imager.ClipView[context, [viewer.wx, viewer.wy, viewer.ww, viewer.wh], FALSE];
Imager.TranslateT[context, viewer.wx, viewer.wy]; --xlate since ClipView does no x,y translation
}
ELSE Imager.SetView[context, [viewer.wx, viewer.wy, viewer.ww, viewer.wh]];
}
ELSE
IF viewer.parent=
NIL
THEN {
Imager.SetView[context, [viewer.cx, viewer.cy, viewer.cw, viewer.ch]];
IF viewer.class.coordSys=top THEN {
Imager.TranslateT[context, 0, viewer.ch];
Imager.Scale2T[context, 1, -1];
};
}
ELSE {
RecursivelyPushParent[viewer];
IF invert THEN {
Imager.TranslateT[context, x, y+viewer.ch];
Imager.Scale2T[context, 1, -1];
} ELSE
Imager.TranslateT[context, x, y];
};
};
InvertForMenus:
PUBLIC
PROC [viewer: Viewer, x:
INTEGER, y:
INTEGER, w:
INTEGER, h:
INTEGER] = {
context: Context ← AcquireContext[viewer.parent, viewer.column=color];
ActuallyDoDrawing:
PROC = {
Imager.IntegerClipRectangle[context, viewer.wx, viewer.wy, viewer.ww, viewer.wh];
Imager.SetColor[context, Imager.XOR];
Imager.IntegerMaskRectangle[context, x, y, w, h];
};
Imager.DoSave[context, ActuallyDoDrawing];
ReleaseContext[context];
};
ClipIcon:
PROC [context: 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.ClipView[context, [v.wx, v.wy, v.ww, v.wh], TRUE];
};
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 = {
context: Context;
IF NOT viewer.visible OR viewer.destroyed THEN RETURN;
context ← AcquireContext[viewer, IF viewer.iconic THEN FALSE ELSE viewer.column=color];
Imager.SetColor[context, Imager.XOR];
Imager.IntegerMaskRectangle[context, 0, 0, viewer.ww, viewer.wh];
Process.Pause[Process.MsecToTicks[400]];
Imager.IntegerMaskRectangle[context, 0, 0, viewer.ww, viewer.wh];
ReleaseContext[context];
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;
};
newVT: ROPE = " [New Version]";
newVW: INTEGER;
newFT: ROPE = " [New File]";
newFW: INTEGER;
linkT: ROPE = " [Split]";
linkW: INTEGER;
sipT: ROPE = " [Saving...]";
sipW: INTEGER;
Init[];
ViewerOps.GreyScreen[0, 0, 9999, 9999, FALSE, FALSE];
END.