-- Copyright (C) 1982, 1984  by Xerox Corporation. All rights reserved. 
-- WindowImplA.mesa - last edited by
-- Daniels	16-Mar-84 14:18:04
-- Bruce	20-Dec-82 15:35:50

DIRECTORY
  BitBlt: TYPE USING [BitAddress, BITBLT],
  Display: TYPE USING [boxFlags, replaceFlags],
  Inline: TYPE USING [DBITSHIFT, LongMult],
  RecOps: TYPE USING [
    Bite, Blt, CheckForLeaks, CleanInvalid, Coalesce, Convert, ConvertBox,
    Copy, Disjoint, Free, FreeRecList, Intersect, RecList, ScreenToWindowBox,
    Shift, SimpleAppend, Visible],
  SpecialDisplay USING [defaultContext, Special, SpecialContext],
  Window: TYPE USING [Box, Clarity, ErrorCode, GetBitmapUnder, Gravity, Place],
  WindowOps: TYPE USING [
    bbPtr, Bounds, DIVMOD16, GetContext, GetBpl, Handle, lock, nullGray,
    Object, rootWindow, ScreenBox, SpecialTimesWpl];

WindowImplA: MONITOR LOCKS WindowOps.lock
  IMPORTS BitBlt, Inline, RecOps, SpecialDisplay, Window, WindowOps
  EXPORTS Display, Window, WindowOps =
  
  BEGIN

  Handle: TYPE = LONG POINTER TO Object;
  Object: PUBLIC TYPE = WindowOps.Object;

  Error: PUBLIC ERROR [code: Window.ErrorCode] = CODE;

  EnumerateInvalidBoxes: PUBLIC PROC [
    window: Handle, proc: PROC [Handle, Window.Box]] = {
    invalidList: RecOps.RecList;
    CopyInvalidBoxes: ENTRY PROC RETURNS [RecOps.RecList] = {
      ENABLE UNWIND => NULL;
      RETURN RecOps.Copy[window.badPhosphor]};
    IF ~window.inTree THEN RETURN;
    invalidList ← CopyInvalidBoxes[];
    FOR r: RecOps.RecList ← invalidList, r.link UNTIL r = NIL DO
      box: Window.Box ← RecOps.ScreenToWindowBox[r.box];
      box.place.x ← box.place.x - window.place.x;
      box.place.y ← box.place.y - window.place.y;
      proc[window, box];
      ENDLOOP;
    RecOps.FreeRecList[invalidList]};

  EnumerateTree: PUBLIC PROC [root: Handle, proc: PROC [window: Handle]] = {
    proc[root];
    FOR child: Handle ← root.child, child.sibling UNTIL child = NIL DO
      EnumerateTree[child, proc] ENDLOOP};

  FreeBadPhosphorList: PUBLIC ENTRY PROC [window: Handle] = {
    ENABLE UNWIND => NULL;
    IF ~window.inTree THEN RETURN;
    RecOps.FreeRecList[window.badPhosphor];
    window.badPhosphor ← NIL};

  InvalidateBox: PUBLIC ENTRY PROC [
    window: Handle, box: Window.Box, clarity: Window.Clarity] = {
    ENABLE UNWIND => NULL;
    -- clarity ignored: any whitening is done @ Validate time
    InvalidateList[window, RecOps.ConvertBox[window, box]]};
  
  InvalidateList: INTERNAL PROC [window: Handle, list: RecOps.RecList] = {
    -- Consumes list.
    IF list = NIL THEN RETURN;
    IF ~window.inTree THEN {RecOps.FreeRecList[list]; RETURN};
    <<FOR r: RecOps.RecList ← list, r.link UNTIL r = NIL DO
      r.src ← r.dst ← NIL ENDLOOP;>>
    window.invalid ← RecOps.SimpleAppend[window.invalid, list]};

  InsertIntoTree: PUBLIC PROC [window: Handle] = {
    IF window.parent.inTree AND window.underNow THEN 
      ValidateTree[WindowOps.rootWindow];
    InsertIntoTreeLocked[window]};

  InsertIntoTreeLocked: ENTRY PROC [window: Handle] = {
    ENABLE UNWIND => NULL;
    invalidList: RecOps.RecList;
    whiten: BOOLEAN = window.child # NIL OR window.clearingRequired;
    MarkWindowInTree: PROC [window: Handle] = {
      window.place.x ← window.parent.place.x + window.box.place.x;
      window.place.y ← window.parent.place.y + window.box.place.y;
      window.inTree ← TRUE};
    IF window.sibling = window.parent.child THEN window.parent.child ← window
    ELSE
      FOR ww: Handle ← window.parent.child, ww.sibling UNTIL ww = NIL DO
        IF ww.sibling = window.sibling THEN {ww.sibling ← window; EXIT};
        REPEAT FINISHED => ERROR Error[noSuchSibling];
        ENDLOOP;
    IF ~window.parent.inTree THEN RETURN;
    ResetCache[];
    EnumerateTree[window, MarkWindowInTree];
    IF window.underNow THEN {
      ctx: SpecialDisplay.Special = SpecialDisplay.defaultContext;
      offset, bit: INTEGER;
      AssertUnobscured[window, illegalBitmap];
      [offset, bit] ← WindowOps.DIVMOD16[CARDINAL[window.place.x]];
      WindowOps.bbPtr↑ ← [
        dst: [word: Window.GetBitmapUnder[window], bit: bit], 
	dstBpl: WindowOps.GetBpl[window], flags: Display.replaceFlags, 
	src: [word: 
	  ctx.bmAddress + WindowOps.SpecialTimesWpl[window.place.y, ctx] 
	  + offset, bit: bit], 
	srcDesc: [srcBpl[ctx.bpl]], width: window.box.dims.w, 
	height: window.box.dims.h];
      BitBlt.BITBLT[WindowOps.bbPtr]};
    invalidList ← RecOps.Visible[window, RecOps.Convert[window], dst];
    IF invalidList # NIL THEN {
      IF whiten THEN WhitenRecList[invalidList];
      BadPhosphorToInvalid[window.parent];
      InvalidateTree[window, window.child, invalidList]}};

  RemoveFromTree: PUBLIC ENTRY PROC [window: Handle] = {
    ENABLE UNWIND => NULL;
    MarkWindowNotInTree: PROC [window: Handle] = {
      window.inTree ← FALSE;
      IF window.invalid # NIL THEN {
        RecOps.FreeRecList[window.invalid]; window.invalid ← NIL};
      IF window.badPhosphor # NIL THEN {
        RecOps.FreeRecList[window.badPhosphor]; window.badPhosphor ← NIL}};
    justPlaying: BOOLEAN = ~window.inTree;
    parent: Handle = window.parent;
    sibling: Handle = window.sibling;
    box: Window.Box = window.box;
    invalidList: RecOps.RecList;
    ww: Handle;
    IF justPlaying THEN invalidList ← NIL
    ELSE {
      ResetCache[];
      invalidList ← RecOps.Visible[window, RecOps.Convert[window], dst, FALSE];
      IF invalidList # NIL AND ~window.underNow THEN WhitenRecList[invalidList]};
    EnumerateTree[window, MarkWindowNotInTree];
    SELECT TRUE FROM
      window.parent.child = window => window.parent.child ← window.sibling;
      window.parent.child = NIL => ERROR Error[windowNotChildOfParent];
      ENDCASE =>
        FOR ww ← window.parent.child, ww.sibling UNTIL ww.sibling = NIL DO
          IF ww.sibling = window THEN {ww.sibling ← window.sibling; EXIT};
          REPEAT FINISHED => ERROR Error[windowNotChildOfParent];
          ENDLOOP;
    IF window.underNow THEN {
      -- A clone of Display.Bitmap...
      OPEN WindowOps;
      abs: ScreenBox = Bounds[window];
      src: BitBlt.BitAddress = [
        word: Window.GetBitmapUnder[window],
	bit: CARDINAL[window.place.x] MOD 16];
      srcBpl: INTEGER = GetBpl[window];
      absOffset, absBit: INTEGER;
      [absOffset, absBit] ← WindowOps.DIVMOD16[CARDINAL[abs.left]];
      FOR r: RecOps.RecList ← invalidList, r.link UNTIL r = NIL DO
	clipLeft: INTEGER = r.box.left - abs.left;
	clipTop: INTEGER = r.box.top - abs.top;
	clipRight: INTEGER = abs.right - r.box.right;
	clipBottom: INTEGER = abs.bottom - r.box.bottom;
	ctx: SpecialDisplay.Special = GetContext[
	  r, SpecialDisplay.defaultContext];
	bbPtr↑ ← [
	  dst: [word: ctx.bmAddress + absOffset + SpecialTimesWpl[
	    abs.top, ctx], bit: absBit], src: src, srcDesc: [srcBpl[srcBpl]],
	  dstBpl: ctx.bpl, flags: Display.replaceFlags,
	  height: window.box.dims.h, width: window.box.dims.w];
	IF clipTop > 0 THEN {
	  bbPtr.src.word ← bbPtr.src.word +
	    Inline.DBITSHIFT[Inline.LongMult[clipTop, srcBpl], -4];
	  bbPtr.height ← bbPtr.height - clipTop;
	  bbPtr.dst.word ← bbPtr.dst.word + SpecialTimesWpl[clipTop, ctx]};
	IF clipBottom > 0 THEN bbPtr.height ← bbPtr.height - clipBottom;
	IF clipRight > 0 THEN bbPtr.width ← bbPtr.width - clipRight;
        IF clipLeft > 0 THEN {
          wordOffset, bitOffset, bit: INTEGER;
          [wordOffset, bitOffset] ← WindowOps.DIVMOD16[CARDINAL[clipLeft]];
          bbPtr.src.word ← bbPtr.src.word + wordOffset;
          bbPtr.dst.word ← bbPtr.dst.word + wordOffset;
          [wordOffset, bit] ← WindowOps.DIVMOD16[
	    CARDINAL[bbPtr.src.bit + bitOffset]];
          bbPtr.src ← [word: bbPtr.src.word + wordOffset, bit: bit];
          [wordOffset, bit] ← WindowOps.DIVMOD16[
	    CARDINAL[bbPtr.dst.bit + bitOffset]];
          bbPtr.dst ← [word: bbPtr.dst.word + wordOffset, bit: bit];
          bbPtr.width ← bbPtr.width - clipLeft};
	IF INTEGER[bbPtr.width] <= 0 OR INTEGER[bbPtr.height] <= 0 THEN LOOP;
	BitBlt.BITBLT[bbPtr];
	ENDLOOP;
      TakeMyNameOffBadPhosphors[window, sibling];
      RecOps.FreeRecList[invalidList]}
    ELSE InvalidateTree[parent, sibling, invalidList]};

  TakeMyNameOffBadPhosphors: INTERNAL PROC [bmu, sib: Handle] = {
    -- trim bad phosphor lists that used to point to bmu
    CheckBadPhosphor: INTERNAL PROC [window: Handle] = {
      newBad, invalid: RecOps.RecList ← NIL;
      oldBad, next: RecOps.RecList ← TRASH;
      IF window.badPhosphor = NIL THEN RETURN;
      next ← window.badPhosphor;
      UNTIL (oldBad ← next) = NIL DO
        next ← oldBad.link;
	IF oldBad.dst = bmu THEN {
	  oldBad.dst ← NIL; oldBad.link ← invalid; invalid ← oldBad}
	ELSE {oldBad.link ← newBad; newBad ← oldBad};
        ENDLOOP;
      window.badPhosphor ← newBad;
      InvalidateList[window, invalid]};
    FOR w: Handle ← sib, w.sibling UNTIL w = NIL DO
      EnumerateTree[w, CheckBadPhosphor] ENDLOOP};
  
  BadPhosphorToInvalid: INTERNAL PROC [sib: Handle] = {
    BPTI: INTERNAL PROC [w: Handle] = {
      IF w.badPhosphor # NIL THEN {
        InvalidateList[w, w.badPhosphor]; w.badPhosphor ← NIL}};
    FOR w: Handle ← sib, w.sibling UNTIL w = NIL DO
      EnumerateTree[w, BPTI] ENDLOOP};
    
  Shift: PUBLIC ENTRY PROC [
    window: Handle, box: Window.Box, newPlace: Window.Place] = {
    ENABLE UNWIND => NULL;
    << We need to blt from the visible part of what was visible to the
       visible part of where it goes.  Boxes to blt by dx, dy are:  
       Unshift of VP(Shift of VP(old)).  
       The invalid list becomes the boxes we were unable to paint at the 
       destination place; it is: BiteOut Shift of VP(oldBox) from VP(newBox) >>
    IF window.inTree THEN {
      dx: INTEGER = newPlace.x - box.place.x;
      dy: INTEGER = newPlace.y - box.place.y;
      oldBox: RecOps.RecList = RecOps.ConvertBox[window, box];
      vpOldShifted: RecOps.RecList = RecOps.Shift[
        RecOps.Bite[
          list: RecOps.Visible[window, RecOps.Copy[oldBox], src],
	  biter: RecOps.Copy[RecOps.CleanInvalid[window]]],
	dx, dy];
      list: RecOps.RecList = RecOps.Shift[
        RecOps.Visible[window, RecOps.Copy[vpOldShifted], dst], -dx, -dy];
      vpNew: RecOps.RecList = RecOps.Visible[
        window, RecOps.Shift[oldBox, dx, dy], dst];
      invalid: RecOps.RecList = RecOps.Bite[list: vpNew, biter: vpOldShifted];
      RecOps.Blt[list, dx, dy];
      IF invalid # NIL THEN InvalidateList[window, invalid]}};

  Slide: PUBLIC PROC [window: Handle, newPlace: Window.Place] = {
    SlideAndSizeAndStack[
      window, [newPlace, window.box.dims], window.sibling, window.parent, nw]};

  Stack: PUBLIC PROC [window: Handle, newSibling, newParent: Handle] = {
    SlideAndSizeAndStack[window, window.box, newSibling, newParent, nw]};

  SlideAndStack: PUBLIC PROCEDURE [
    window: Handle, newPlace: Window.Place, newSibling: Handle,
    newParent: Handle] = {
    SlideAndSizeAndStack[
      window: window, newBox: [newPlace, window.box.dims], newSibling: newSibling,
      newParent: newParent, gravity: nil]};

  SlideAndSize: PUBLIC PROCEDURE [
    window: Handle, newBox: Window.Box, gravity: Window.Gravity] = {
    SlideAndSizeAndStack[window, newBox, window.sibling, window.parent, gravity]};

  SlideAndSizeAndStack: PUBLIC ENTRY PROC [
    window: Handle, newBox: Window.Box, newSibling: Handle, newParent: Handle,
    gravity: Window.Gravity] = {
    ENABLE UNWIND => NULL;
    << Boxes of interest are (shifts are move + scrolling):
         ValidBoxes ← VP(newBox) Intersect Shift of VP(old)
         BoxesToBlt ← Unshift of ValidBoxes
         invalid ← Bite ValidBoxes from (VP(newBox) union VP(old))
       Note that VP(newBox) can't be taken until the stack is done. >>
    MoveChildren: INTERNAL PROC [w: Handle] = {
      IF w # window THEN ShiftWindow[w, shiftX, shiftY]};
    dx: INTEGER ← newBox.place.x - window.box.place.x;
    dy: INTEGER ← newBox.place.y - window.box.place.y;
    purePromotion: BOOLEAN ← dx = 0 AND dy = 0 AND newBox = window.box;
    demotion: BOOLEAN ← TRUE;
    oldParent, oldSibling: Handle;
    shiftX, shiftY: INTEGER;
    vpNew, vpOld, validBoxes, boxesToBlt, invalid, temp, oldBox: RecOps.RecList;
    IF window.underNow AND window.box.dims # newBox.dims THEN
      ERROR Error[sizingWithBitmapUnder];
    IF newParent = NIL THEN newParent ← window.parent;
    IF newBox = window.box AND newSibling = window.sibling
      AND newParent = window.parent THEN RETURN;
    IF ~window.inTree THEN {
      window.box ← newBox; StackInternal[window, newSibling, newParent]; RETURN};
    oldParent ← window.parent;
    oldSibling ← window.sibling;
    dx ← dx + newParent.place.x - oldParent.place.x;
    dy ← dy + newParent.place.y - oldParent.place.y;
    IF newParent # oldParent THEN {purePromotion ← FALSE; demotion ← TRUE}
    ELSE FOR sib: Handle ← window.parent.child, sib.sibling DO
      IF sib = newSibling THEN {demotion ← FALSE; EXIT};
      IF sib = window.sibling THEN {purePromotion ← FALSE; EXIT};
      ENDLOOP;
    BEGIN
    scrollX, scrollY: INTEGER;
    SELECT gravity FROM
      nw, xxx => scrollX ← scrollY ← 0;
      n => {
	scrollX ← CARDINAL[newBox.dims.w]/2 - CARDINAL[window.box.dims.w]/2;
	scrollY ← 0};
      ne => {scrollX ← newBox.dims.w - window.box.dims.w; scrollY ← 0};
      e => {
	scrollX ← newBox.dims.w - window.box.dims.w;
	scrollY ← CARDINAL[newBox.dims.h]/2 - CARDINAL[window.box.dims.h]/2};
      se => {
	scrollX ← newBox.dims.w - window.box.dims.w;
	scrollY ← newBox.dims.h - window.box.dims.h};
      s => {
	scrollX ← CARDINAL[newBox.dims.w]/2 - CARDINAL[window.box.dims.w]/2;
	scrollY ← newBox.dims.h - window.box.dims.h};
      sw => {
	scrollX ← 0;
	scrollY ← newBox.dims.h - window.box.dims.h};
      w => {
	scrollX ← 0;
	scrollY ← CARDINAL[newBox.dims.h]/2 - CARDINAL[window.box.dims.h]/2};
      c => {
	scrollX ← CARDINAL[newBox.dims.w]/2 - CARDINAL[window.box.dims.w]/2;
	scrollY ← CARDINAL[newBox.dims.h]/2 - CARDINAL[window.box.dims.h]/2};
      nil => {scrollX ← -dx; scrollY ← -dy};
      ENDCASE;
    FOR child: Handle ← window.child, child.sibling UNTIL child = NIL DO
      child.box.place.x ← child.box.place.x + scrollX;
      child.box.place.y ← child.box.place.y + scrollY;
      ENDLOOP;
    shiftX ← dx + scrollX;
    shiftY ← dy + scrollY;
    END;
    oldBox ← RecOps.Convert[window];
    vpOld ← RecOps.Visible[window, RecOps.Convert[window], src];
    ResetCache[];
    window.box ← newBox;
    StackInternal[window, newSibling, newParent];
    window.place.x ← window.place.x + dx;
    window.place.y ← window.place.y + dy;
    window.invalid ← RecOps.Shift[window.invalid, shiftX, shiftY];
    window.badPhosphor ← RecOps.Shift[window.badPhosphor, shiftX, shiftY];
    EnumerateTree[window, MoveChildren];
    vpNew ← RecOps.Visible[window, RecOps.Convert[window], dst];
    temp ← RecOps.Shift[vpOld, shiftX, shiftY];
    validBoxes ← IF gravity = xxx THEN NIL ELSE RecOps.Intersect[vpNew, temp];
    RecOps.FreeRecList[temp];
    boxesToBlt ←
      IF shiftX = 0 AND shiftY = 0 THEN NIL
      ELSE RecOps.Shift[RecOps.Copy[validBoxes], -shiftX, -shiftY];
    invalid ← RecOps.Bite[list: vpNew, biter: validBoxes];
    RecOps.Blt[boxesToBlt, shiftX, shiftY];
    BadPhosphorToInvalid[window.sibling];
    IF window.underNow THEN TakeMyNameOffBadPhosphors[window, oldSibling];
    InvalidateTree[window, window.child, invalid];
    IF NOT purePromotion THEN {
      InvalidateTree[
        window: oldParent, dirtyChild: IF demotion THEN oldSibling ELSE window,
        invalid: oldBox, butNot: window];
      BadPhosphorToInvalid[window.child]}
    ELSE RecOps.Free[oldBox]};

  ShiftWindow: INTERNAL PROC [w: Handle, dx, dy: INTEGER] = INLINE {
    w.place.x ← w.place.x + dx; 
    w.place.y ← w.place.y + dy;
    w.invalid ← RecOps.Shift[w.invalid, dx, dy];
    w.badPhosphor ← RecOps.Shift[w.badPhosphor, dx, dy]};
    
  ValidateTree: PUBLIC PROC [window: Handle] = {
    EnumerateTree[window, Validate]; 
    IF window = WindowOps.rootWindow THEN RecOps.CheckForLeaks[FALSE, cache.r]};

  Validate: PUBLIC PROC [window: Handle] = {
    IF ~window.inTree OR ~ShouldDisplay[window] THEN RETURN;
    DO
      SetUpBadPhosphorList[window];
      IF window.badPhosphor = NIL THEN EXIT;
      << Three things can happen to the window while the following procedure is 
         running.  They are:
           nothing  (the common case).
           more of the window is obscured.  
             In this case the badPhosphor list is trimmed when that happens. 
           more of the window is exposed or marked invalid by the client
             In this case the badPhosphor list remains unchanged, but boxes
             are added to the invalid list.  They get displayed the next
             time around this loop. 
         If the client looks at the reclist through EnumerateInvalidBoxes, it 
         may become "stale" in these uncommon cases; i.e., it will not 
         reflect additional trimming of the badPhosphor list as the result of an
         asynchronous action.  However, this is not "dangerous" in that all the 
         painting routines clip to the badPhosphor list. >>
      IF window.display # NIL THEN window.display[window];
      ENDLOOP;
    DoneDisplay[window];
    RETURN};

  DoneDisplay: ENTRY PROC [window: Handle] = {window.beingDisplayed ← FALSE};

  SetUpBadPhosphorList: ENTRY PROC [window: Handle] = {
    ENABLE UNWIND => NULL;
    RecOps.FreeRecList[window.badPhosphor];
    window.badPhosphor ← NIL;
    window.useBadPhosphor ← TRUE;
    IF window.invalid # NIL THEN {
      vp: RecOps.RecList = RecOps.Visible[
        window, RecOps.Convert[window], dst, TRUE];
      window.badPhosphor ← RecOps.Coalesce[
        RecOps.Intersect[RecOps.CleanInvalid[window], vp]];
      RecOps.FreeRecList[window.invalid];
      window.invalid ← NIL;
      RecOps.FreeRecList[vp];
      IF window.clearingRequired THEN WhitenRecList[window.badPhosphor]}};

  ShouldDisplay: ENTRY PROC [window: Handle] RETURNS [BOOLEAN] = {
    IF window.beingDisplayed THEN RETURN[FALSE];
    RETURN[window.beingDisplayed ← TRUE]};

  InvalidateTree: PUBLIC INTERNAL PROC [
    window, dirtyChild: Handle, invalid: RecOps.RecList, butNot: Handle ← NIL] = {
    SELECT TRUE FROM
      invalid = NIL => NULL;
      NOT window.inTree => RecOps.FreeRecList[invalid];
      dirtyChild = NIL => InvalidateList[window, invalid];
      ENDCASE => {
        childBox: RecOps.RecList = RecOps.Convert[dirtyChild];
	invalidInChild: RecOps.RecList;
        IF RecOps.Disjoint[invalid, childBox] THEN {
	  RecOps.FreeRecList[childBox];
	  InvalidateTree[window, dirtyChild.sibling, invalid, butNot];
	  RETURN};
	invalidInChild ← RecOps.Intersect[childBox, invalid];
	invalid ← RecOps.Bite[list: invalid, biter: childBox];
	IF invalid # NIL THEN
	  IF dirtyChild.sibling = NIL THEN InvalidateList[window, invalid]
	  ELSE InvalidateTree[window, dirtyChild.sibling, invalid, butNot];
	-- If we got here, invalidInChild is guaranteed non-NIL
	IF butNot = dirtyChild THEN RecOps.FreeRecList[invalidInChild]
	ELSE InvalidateTree[
	  dirtyChild, dirtyChild.child, invalidInChild, butNot]}};
    
  StackInternal: INTERNAL PROC [window, newSibling, newParent: Handle] = {
    parent: Handle = window.parent;
    IF window = newSibling THEN newSibling ← window.sibling;
    IF window.sibling = newSibling AND parent = newParent THEN RETURN;
    IF window.inTree # newParent.inTree THEN ERROR Error[illegalStack];
    -- first delink
    IF parent.child = window THEN parent.child ← window.sibling
    ELSE {
      FOR sib: Handle ← parent.child, sib.sibling UNTIL sib.sibling = NIL DO
        IF sib.sibling = window THEN {sib.sibling ← window.sibling; EXIT};
        REPEAT FINISHED => ERROR Error[windowNotChildOfParent];
        ENDLOOP};
    -- then enlink
    window.parent ← newParent;
    IF newSibling = newParent.child THEN {
      window.sibling ← newParent.child; newParent.child ← window}
    ELSE 
      FOR sib: Handle ← newParent.child, sib.sibling UNTIL sib = NIL DO
	IF sib.sibling = newSibling THEN {
	  window.sibling ← sib.sibling; sib.sibling ← window; EXIT};
	REPEAT FINISHED => ERROR Error[noSuchSibling];
	ENDLOOP};

  white: CARDINAL ← 0;
  whiteSrc: BitBlt.BitAddress = [word: @white, bit: 0];

  WhitenRecList: PUBLIC INTERNAL PROC [boxes: RecOps.RecList] = {
    FOR r: RecOps.RecList ← boxes, r.link UNTIL r = NIL DO
      top: INTEGER = r.box.top;
      left: INTEGER = r.box.left;
      width: INTEGER = r.box.right - left;
      height: INTEGER = r.box.bottom - top;
      ctx: SpecialDisplay.Special = 
        WindowOps.GetContext[r, SpecialDisplay.defaultContext];
      offset, bit: INTEGER;
      IF height <= 0 OR width <= 0 THEN LOOP;
      [offset, bit] ← WindowOps.DIVMOD16[CARDINAL[left]];
      WindowOps.bbPtr↑ ← [
        dst: [
          word: ctx.bmAddress + WindowOps.SpecialTimesWpl[top, ctx] + offset,
          bit: bit], 
	dstBpl: ctx.bpl, src: whiteSrc, srcDesc: WindowOps.nullGray,
        width: width, height: height, flags: Display.boxFlags];
      BitBlt.BITBLT[WindowOps.bbPtr];
      ENDLOOP};

  globalCtx: SpecialDisplay.SpecialContext ← [
    bmAddress: NIL, wpl: 0, bpl: 0, alloc: NIL, free: NIL];
  
  MakeContext: PUBLIC INTERNAL PROC [r: RecOps.RecList] 
    RETURNS [SpecialDisplay.Special] = {
    lp: LONG ORDERED POINTER = LOOPHOLE[Window.GetBitmapUnder[r.dst]];
    IF lp = NIL THEN ERROR Error[illegalBitmap];
    globalCtx.wpl ← (CARDINAL[r.dst.box.dims.w] + 31)/16;
    globalCtx.bpl ← globalCtx.wpl*16;
    globalCtx.bmAddress ← 
      lp - CARDINAL[r.dst.place.x]/16 - Inline.LongMult[globalCtx.wpl,
      r.dst.place.y];
    RETURN[@globalCtx]};

  cache: RECORD [w: Handle, r: RecOps.RecList] ← [NIL, NIL];

  ResetCache: INTERNAL PROC = {RecOps.FreeRecList[cache.r]; cache ← [NIL, NIL]};

  GetRecList: PUBLIC INTERNAL PROC [window: Handle] RETURNS [RecOps.RecList] = {
    IF cache.w # window THEN {
      RecOps.FreeRecList[cache.r];
      cache ← IF ~window.inTree THEN [NIL, NIL]
      ELSE [window, RecOps.Visible[window, RecOps.Convert[window], dst, TRUE]]};
    RETURN[cache.r]};

  AssertUnobscured: PUBLIC PROC [w: Handle, code: Window.ErrorCode] = {
    wList: RecOps.RecList = RecOps.Convert[w];
    vp: RecOps.RecList = RecOps.Visible[w, RecOps.Copy[wList], null];
    obscured: BOOLEAN ← wList.box # vp.box;
    RecOps.FreeRecList[wList];
    RecOps.FreeRecList[vp];
    IF obscured THEN ERROR Error[code]};

  END.