<> <> <> <> <> 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]; <<>> <> IF hint#menu THEN PaintCaption[viewer, context]; <<>> <> 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 { <> 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]; }; <> 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]; }; <> 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}; <> IF hint=all OR clear THEN <> 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 <> <> <> <> [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] = { <> 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; <> 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 = { <> 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> RecursivelyPushParent: INTERNAL PROC [v: Viewer] = { IF v.parent#NIL THEN RecursivelyPushParent[v.parent]; PushContext[v] }; PushContext: INTERNAL PROC [v: Viewer] = { <> <> x _ x + v.cx; <> y _ y + v.cy; Imager.ClipView[context, [x, y, v.cw, v.ch], FALSE]; <> }; 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]]; <> <> <> <<};>> } ELSE { RecursivelyPushParent[viewer]; <> <> <> <<} 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] = { <> 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.