<> <> <> <<>> 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 ANY _ NIL] = BEGIN context: Context; label: Rope.ROPE; IF viewer.parent#NIL THEN RETURN; context _ AcquireContext[viewer, FALSE]; -- all icons on b&w display <> 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; <> IF hint=all OR clear THEN <> 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: BOOL _ TRUE, whatChanged: REF ANY _ NIL] = 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: 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 = 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: BOOL _ FALSE; 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 <> 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: BOOL _ TRUE] = BEGIN ENABLE UNWIND => NULL; InternalDisablePainting[wait]; END; InternalDisablePainting: INTERNAL PROC [wait: BOOL _ TRUE] = 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; <> IF paintingLock=0 THEN BROADCAST PaintVectorAvailable; END; DisablePaintingUnWedged: INTERNAL PROC [wait: BOOL _ TRUE] = 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 <> WHILE usedVectors#0 AND colorVector.inUse DO WAIT PaintVectorAvailable ENDLOOP; END; ResetPaintCache: PUBLIC ENTRY PROC [viewer: Viewer _ NIL, wait: BOOL _ TRUE] = 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: BOOL _ FALSE] RETURNS [allocated: Context] = BEGIN ENABLE UNWIND => NULL; bestFit: PaintVector; nil: BOOL _ FALSE; 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 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 <> 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.