ViewerPaintImpl.mesa; written by S. McGregor
Edited by McGregor on April 5, 1983 5:01 pm
Edited by Maxwell, February 7, 1983 1:37 pm
DIRECTORY
Carets USING [ResumeCarets, SuspendCarets],
ColorWorld USING [NewContext],
Graphics USING [black, ClipBox, Context, DrawBox, DrawRope,
FontRef, Mark, NewContext, nullMark, PaintMode, Restore, Save,
Scale, SetColor, SetCP, SetDefaultFont, SetPaintMode, SetStipple, Translate, white],
GraphicsColor USING [IntensityToColor],
GraphicsOps USING [Disable],
IconManager USING [selectedIcon],
Icons USING [DrawIcon],
Menus USING [],
MenusPrivate USING [DrawMenu],
Process USING [Detach, GetCurrent, MsecToTicks, Pause, Ticks],
Rope USING [ROPE],
VFonts USING [defaultFont, defaultGFont, StringWidth],
ViewerOps,
ViewerClasses,
ViewerLocks USING [CallUnderReadLock, CallUnderWriteLock, ColumnWedged, Wedged],
ViewerSpecs,
ViewersStallNotifier,
ViewerTools USING [GetSelectedViewer];
ViewerPaintImpl: CEDAR MONITOR
IMPORTS Carets, ColorWorld, Graphics, GraphicsColor, GraphicsOps, IconManager, Icons, MenusPrivate, Process, VFonts, ViewerLocks, ViewerTools, ViewerOps, ViewerSpecs
EXPORTS ViewerOps, ViewersStallNotifier
SHARES Menus, ViewerClasses, ViewerLocks =
BEGIN OPEN Graphics, ViewerClasses, ViewerOps, ViewerSpecs;
InvisiblePaint: PUBLIC SIGNAL = CODE;
PaintDocumentHeader: PROC [viewer: Viewer, context: Context, clear: BOOL, hint: PaintHint] =
BEGIN OPEN viewer;
wbs: INTEGER = IF viewer.border THEN windowBorderSize ELSE 0;
ClipBox[context, [wx, wy, wx+ww, wy+wh]];
IF hint#menu THEN BEGIN
IF viewer.class.caption#NIL THEN BEGIN-- client-painted caption
mark: Mark ← Save[context];
ClipBox[context, [wx+wbs, wy+wh-captionHeight-wbs, wx+ww-(2*wbs),
wy+wh-wbs]];
viewer.class.caption[viewer, context];
Restore[context, mark];
END
ELSE BEGIN
headerW: INTEGER ← VFonts.StringWidth[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, ww];
DrawBox[context, XYWH2Box[wx, wy+wh-captionHeight, ww, captionHeight]];
SetColor[context, white];
SetCP[context, wx+((ww-headerW)/2), wy+wh-captionHeight+4];
DrawRope[context, name];
IF viewer.saveInProgress THEN DrawRope[context, sipT]
ELSE IF viewer.newFile THEN DrawRope[context, newFT]
ELSE IF viewer.newVersion THEN DrawRope[context, newVT];
IF viewer.link#NIL THEN DrawRope[context, linkT];
END;
END;
IF menu#NIL AND hint#caption THEN BEGIN
height: INTEGER ← menu.linesUsed*menuHeight;
IF ~clear THEN BEGIN
IF ~clear THEN BEGIN
SetColor[context, white];
DrawBox[context, XYWH2Box[wx+wbs, wy+wh-captionHeight-height,
ww-(2*wbs), height]];
END;
SetColor[context, black];
DrawBox[context, XYWH2Box[wx, wy+wh-captionHeight-height-windowBorderSize,
ww, menuBarHeight]];
END;
MenusPrivate.DrawMenu[viewer.menu, context, wx+wbs,
wy+wh-menuHeight-captionHeight, whatChanged];
END;
END;
PaintIcon: PROC [viewer: Viewer, hint: PaintHint, whatChanged: REF ANYNIL] = BEGIN
context: Context;
label: Rope.ROPE;
IF viewer.parent#NIL THEN RETURN;
context ← AcquireContext[viewer, FALSE]; -- all icons on b&w display
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 BEGIN
mark: Mark ← Save[context]; -- since Icons.DrawIcon will set clipper
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;
Icons.DrawIcon[flavor: viewer.icon, context: context, label: label];
Restore[context, mark];
IF IconManager.selectedIcon=viewer THEN BEGIN
[] ← SetPaintMode[context, invert];
DrawBox[context, [0, 0, iconWidth, iconHeight]];
END;
END;
ReleaseContext[context];
END;
SetIconLabel: PUBLIC PROC [viewer: Viewer, label: Rope.ROPE] = BEGIN
ViewerOps.AddProp[viewer, $IconLabel, label];
END;
RecursivelyPaintViewers: PROC [viewer: Viewer, hint: PaintHint, clearClient: BOOL,
clear: BOOL, whatChanged: REF ANY] = BEGIN OPEN viewer;
context: Context ← NIL;
bitmapY: INTEGER ← 0;
FOR v: Viewer ← viewer, v.parent UNTIL v=NIL DO-- visibility test
IF v.parent#NIL THEN BEGIN
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;
END
ENDLOOP;
IF hint#client THEN BEGIN
ENABLE UNWIND => IF context#NIL THEN ReleaseContext[context];
context ← AcquireContext[parent, viewer.column=color];
IF ~viewer.visible OR viewer.destroyed THEN SIGNAL InvisiblePaint;
IF ~clear AND hint=all THEN BEGIN
SetColor[context, white];
DrawBox[context, XYWH2Box[wx, wy, ww, wh]];
SetColor[context, black];
clear ← TRUE;
END;
IF border AND hint=all THEN BEGIN
wbs: INTEGER ~ windowBorderSize;
DrawBox[context, XYWH2Box[wx, wy, ww, wbs]];
DrawBox[context, XYWH2Box[wx, wy, wbs, wh]];
DrawBox[context, XYWH2Box[wx+ww-wbs, wy, wbs, wh]];
DrawBox[context, XYWH2Box[wx, wy+wh-wbs, ww, wbs]];
END;
IF parent=NIL AND viewer.column#static
THEN PaintDocumentHeader[viewer, context, clear, hint];
ReleaseContext[context];
END;
IF hint=all OR hint=client THEN BEGIN
ENABLE UNWIND => IF context#NIL THEN ReleaseContext[context];
context ← NIL; context ← AcquireContext[viewer, viewer.column=color];
IF ~viewer.visible OR viewer.destroyed THEN SIGNAL InvisiblePaint;
IF clearClient AND ~clear THEN BEGIN
SetColor[context, white];
DrawBox[context, [0, 0, cw, ch]];
SetColor[context, black];
clear ← TRUE;
END;
-- SetCP[context, 0, 0]; ++ normalise things for client
IF class.paint#NIL THEN class.paint[viewer, context, whatChanged, clear];
ReleaseContext[context];
END;
now 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
RecursivelyPaintViewers[v, all, FALSE, clear, NIL];
ENDLOOP;
END;
PaintViewer: PUBLIC PROC [viewer: Viewer, hint: PaintHint ← client, clearClient: BOOLTRUE,
whatChanged: REF ANYNIL] = BEGIN
LocalPaintViewer: PROC = {
ENABLE ABORTED => {Carets.ResumeCarets[]; CONTINUE};
IF ~viewer.visible OR viewer.destroyed THEN RETURN;
IF viewer.iconic THEN {PaintIcon[viewer, hint, whatChanged]; RETURN};
Carets.SuspendCarets[];
RecursivelyPaintViewers[viewer, hint, clearClient AND ~(hint=menu OR hint=caption),
FALSE, whatChanged ! InvisiblePaint => CONTINUE];
Carets.ResumeCarets[]};
ViewerLocks.CallUnderReadLock[LocalPaintViewer, viewer ! ViewerLocks.Wedged => CONTINUE];
END;
PaintVector: TYPE = REF PaintVectorRec;
PaintVectorRec: TYPE = RECORD [
next: PaintVector ← NIL,
viewer: Viewer ← NIL,
inUse: BOOLFALSE,
resetOnRelease: BOOLFALSE,
color: BOOLFALSE,
mark: Graphics.Mark ← nullMark,
process: UNSAFE PROCESSNIL,
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 = BEGIN
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;
newFW ← VFonts.StringWidth[newFT];
newVW ← VFonts.StringWidth[newVT];
linkW ← VFonts.StringWidth[linkT];
sipW ← VFonts.StringWidth[sipT];
colorVector ← NEW[PaintVectorRec];
colorVector.color ← TRUE;
END;
colorInit: BOOLFALSE;
InitialiseColorPainting: PUBLIC PROC = BEGIN
ColorCriticalInit: ENTRY PROC = BEGIN
colorVector.context ← ColorWorld.NewContext[];
SetDefaultFont[colorVector.context, VFonts.defaultGFont];
colorInit ← TRUE;
END;
ColorCriticalInit[];
GreyScreen[0, 0, 9999, 9999, TRUE, FALSE];
END;
ZapVector: INTERNAL PROC [p: PaintVector] = BEGIN
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;
END;
UnWedgePainter: PUBLIC ENTRY PROC [process: PROCESS, kind: ATOM] = BEGIN
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;
END;
UnWedge: ENTRY PROC [process: PROCESS, kind: ATOM] = BEGIN
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;
END;
DisablePainting: PUBLIC ENTRY PROC [wait: BOOLTRUE] = BEGIN
ENABLE UNWIND => NULL;
InternalDisablePainting[wait];
END;
InternalDisablePainting: INTERNAL PROC [wait: BOOLTRUE] = INLINE BEGIN
IF wait THEN
WHILE usedVectors#0 OR colorVector.inUse DO WAIT PaintVectorAvailable;
ENDLOOP;
paintingLock ← paintingLock+1;
END;
EnablePainting: PUBLIC ENTRY PROC = BEGIN
ENABLE UNWIND => NULL;
InternalEnablePainting[];
END;
InternalEnablePainting: INTERNAL PROC = INLINE BEGIN
IF paintingLock#0 THEN paintingLock ← paintingLock-1;
broadcast since all are likely to be free
IF paintingLock=0 THEN BROADCAST PaintVectorAvailable;
END;
DisablePaintingUnWedged: INTERNAL PROC [wait: BOOLTRUE] = INLINE BEGIN
AllWedged: INTERNAL PROC RETURNS[BOOL] = INLINE BEGIN
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];
END;
IF wait THEN
WHILE usedVectors#0 OR colorVector.inUse DO
IF AllWedged[] THEN EXIT ELSE WAIT PaintVectorAvailable;
ENDLOOP;
paintingLock ← paintingLock+1;
END;
WaitForPaintingToFinish: PUBLIC ENTRY PROC = BEGIN
assumes interesting paints already in progress
WHILE usedVectors#0 AND colorVector.inUse DO WAIT PaintVectorAvailable ENDLOOP;
END;
ResetPaintCache: PUBLIC ENTRY PROC [viewer: Viewer ← NIL, wait: BOOLTRUE] = BEGIN
ENABLE UNWIND => NULL;
DisablePaintingUnWedged[wait];
FOR p: PaintVector ← vectorRoot, p.next UNTIL p=NIL DO
IF p.inUse THEN BEGIN
IF p.viewer = NIL OR ~ViewerLocks.ColumnWedged[ViewerColumn[p.viewer]].wedged
THEN IF wait THEN RETURN WITH ERROR fatalViewersError;
p.resetOnRelease ← TRUE;
END
ELSE IF p.viewer#NIL THEN BEGIN
ResetContext[p.context];
p.viewer ← NIL;
END;
ENDLOOP;
IF colorVector.context#NIL THEN BEGIN
IF colorVector.inUse AND ~ViewerLocks.ColumnWedged[color].wedged THEN BEGIN
IF wait THEN RETURN WITH ERROR fatalViewersError;
colorVector.resetOnRelease ← TRUE;
END
ELSE IF colorVector.viewer#NIL THEN BEGIN
ResetContext[colorVector.context];
colorVector.viewer ← NIL;
END;
END;
InternalEnablePainting[];
END;
fatalViewersError: ERROR = CODE;
ReleaseContext: PUBLIC ENTRY PROC [free: Context] = BEGIN
ENABLE UNWIND => NULL;
CleanUp: INTERNAL PROC [p: PaintVector] = BEGIN
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 BEGIN
ResetContext[p.context];
p.viewer ← NIL;
p.resetOnRelease ← FALSE;
END;
p.process ← NIL;
BROADCAST PaintVectorAvailable;
END;
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;
END;
AcquireContext: PUBLIC ENTRY PROC [viewer: Viewer, color: BOOLFALSE]
RETURNS [allocated: Context] = BEGIN
ENABLE UNWIND => NULL;
bestFit: PaintVector;
nil: BOOLFALSE;
MakeFirst: INTERNAL PROC [pv: PaintVector] = INLINE BEGIN-- 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;
END;
IF color THEN BEGIN
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;
END
ELSE BEGIN
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];
END;
bestFit.inUse ← TRUE;
TRUSTED {bestFit.process ← Process.GetCurrent[]};
SetContext[viewer, bestFit];
bestFit.mark ← Save[allocated ← bestFit.context]; -- protect context
END;
SetContext: INTERNAL PROC [viewer: Viewer, paintVector: PaintVector] = BEGIN
pViewer: Viewer = paintVector.viewer;
pContext: Context = paintVector.context;
RecursivelyPushParent: INTERNAL PROC [viewer: Viewer] = BEGIN
IF viewer.parent#NIL THEN RecursivelyPushParent[viewer.parent];
PushContext[viewer, pContext]
END;
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.parent=viewer => PopContext[pContext];
pViewer.parent=viewer.parent => {PopContext[pContext]; PushContext[viewer, pContext]};
ENDCASE     => {ResetContext[pContext]; RecursivelyPushParent[viewer]};
paintVector.viewer ← viewer;
END;
ClipIcon: PROC [context: Context] = BEGIN
clips for overlapping top level windows given valid Context and Viewer
ClipOpenViewer: ViewerOps.EnumProc = BEGIN
IF ~v.iconic AND (v.column=right OR v.column=left) THEN
Graphics.ClipBox[context, [v.wx, v.wy, v.wx+v.ww, v.wy+v.wh], TRUE];
END;
ViewerOps.EnumerateViewers[ClipOpenViewer];
END;
PushContext: INTERNAL PROC [viewer: Viewer, context: Context] = BEGIN 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 BEGIN
IF wy+wh > openBottomY THEN ClipIcon[context];
ClipBox[context, XYWH2Box[wx, wy, ww, wh]];
Translate[context, wx, wy];
END
ELSE IF invertCoords THEN BEGIN
ClipBox[context, XYWH2Box[cx, cy, cw, ch]];
Translate[context, cx, cy+ch];
Scale[context, 1, -1];
END
ELSE BEGIN
ClipBox[context, XYWH2Box[cx, cy, cw, ch]];
Translate[context, cx, cy];
END;
END;
PopContext: INTERNAL PROC [context: Context] = INLINE BEGIN
Restore[context];
END;
ResetContext: INTERNAL PROC [context: Context] = INLINE BEGIN
screen: Mark = LOOPHOLE[0];
Restore[context, screen];
END;
InvertForMenus: PUBLIC PROC [viewer: Viewer, x,y: INTEGER, w,h: INTEGER] = BEGIN
context: Context;
context ← AcquireContext[viewer.parent, viewer.column=color];
[] ← SetPaintMode[context, invert];
DrawBox[context, ViewerOps.XYWH2Box[viewer.wx+x, y, w, h]];
ReleaseContext[context];
END;
GreyScreen: PUBLIC PROC [x,y,w,h: INTEGER, color, icon: BOOL,
stipple: CARDINAL ← 104042B] = BEGIN
context: Context ← AcquireContext[NIL, color];
IF icon AND y+h > openBottomY THEN ClipIcon[context];
IF color THEN SetColor[context, GraphicsColor.IntensityToColor[.5]]
ELSE SetStipple[context, stipple];
DrawBox[context, ViewerOps.XYWH2Box[x, y, w, h]];
ReleaseContext[context];
END;
BlinkIcon: PUBLIC PROC [viewer: Viewer, count, interval: NAT] = BEGIN
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]]};
END;
BlinkProcess: PROC [viewer: Viewer, count, n: NAT] = BEGIN
howOften: Process.Ticks = Process.MsecToTicks[500];
Blink: PROC = BEGIN OPEN viewer;
context: Context;
IF NOT visible OR destroyed THEN RETURN;
context ← AcquireContext[viewer, IF iconic THEN FALSE ELSE column=color];
[] ← SetPaintMode[context, invert];
DrawBox[context, ViewerOps.XYWH2Box[0, 0, viewer.ww, viewer.wh]];
Process.Pause[Process.MsecToTicks[400]];
DrawBox[context, ViewerOps.XYWH2Box[0, 0, viewer.ww, viewer.wh]];
ReleaseContext[context];
Process.Pause[Process.MsecToTicks[350]]; -- so can be called back-to-back
END;
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;
END;
newVT: Rope.ROPE = " [New Version]";
newVW: INTEGER;
newFT: Rope.ROPE = " [New File]";
newFW: INTEGER;
linkT: Rope.ROPE = " [Split]";
linkW: INTEGER;
sipT: Rope.ROPE = " [Saving...]";
sipW: INTEGER;
Init[];
GreyScreen[0, 0, 9999, 9999, FALSE, FALSE];
END.