Copyright (C) 1994, Digital Equipment Corp.
ZSplit.i3, coded Fri Oct 31 11:24:53 1986 by cgn
<*PRAGMA LL*>A
ZSplit.T is a parent window with overlapping child windows.
Each child has a stacking order given (conceptually) by a z
coordinate. A pixel of the parent's screen that is in the domain
of more than one child is controlled by whichever of these children
is highest in the z coordinate. The portions of the domains of
the children that extend outside the parent domain will be clipped.
Split.Succ enumerates the children from top to bottom.
The bottom child is called the {\it background}. An initial
background can be specified when the ZSplit is created; usually
it remains the background throughout the life of the ZSplit.
Usually the background has the same domain as the parent, and
therefore controls all pixels that are not controlled by any other
child. In the unusual case that the background child has a domain
different from the parent domain, there may be some parent pixels
that are not controlled by any child. The ZSplit will ignore these
pixels when asked to repaint.
The shape of a ZSplit is the shape of its background child (if
it has no children its shape is the default shape for a VBT). When
the preferred shape of a non-background child changes, the ZSplit
reshapes the child to its new preferred shape, preserving its
{\it offset}, which is the vector between the northwest corners
of the parent and child.
INTERFACEThe callZSplit ; IMPORT VBT, Rect, Split, Point; TYPE T <: Public; Private <: Split.T; Public = Private OBJECT METHODS <* LL <= VBT.mu *> init(bg: VBT.T := NIL; saveBits := FALSE; parlim: INTEGER := -1): T END;
v.init(...) initializes v as a ZSplit.
It is only legal to call the init method for a newly-allocated
ZSplit (as in the definition of the procedure New below) or from
the init method of a subclass. This restriction applies to all
the init methods in Trestle, although it will not be repeated for
each one. \index{init method, rules for calling}
The ZSplit will be given the initial background child bg if
bg#NIL; it will be given no children if bg=NIL. If bg is
non-NIL it will be mapped initially. If saveBits is TRUE,
the split will try to save the children's old bits when reformatting;
if the children don't use them anyway, it is faster to let saveBits
default to FALSE. The value of parlim is the minimum area of
a child for which a separate thread will be forked to reshape or
repaint it; if it is -1, it is set to an appropriate default (see
the VBTTuning interface).
PROCEDURE New(
bg: VBT.T := NIL;
saveBits := FALSE;
parlim: INTEGER := -1)
: T; <* LL <= VBT.mu *>
New(...)is equivalent toNEW(T).init(...).
\subsubsection{Inserting children}
The default Split.Insert call is rarely useful for a ZSplit: it
inserts the new child at the parent's northwest corner, unmapped.
Split.AddChild is even less useful, since it adds children as the
background, which is almost certainly not what you want. The
following procedures are more useful for inserting children into a
ZSplit:
PROCEDURE InsertAfter(
v: T;
pred, ch: VBT.T;
READONLY dom: Rect.T;
alsoMap: BOOLEAN := TRUE) RAISES {Split.NotAChild};
<* LL.sup = VBT.mu *>
Insertchas a new child ofvwith domaindom, and markvfor redisplay.
The new child is inserted immediately after (that is, below)
pred;
if pred=NIL the new child is inserted first (that is, on top).
If the height or width of dom does not satisfy ch's size
contraints, then the height and width of the child are projected
into range; its offset is preserved. This is a checked runtime error
if ch is not detached. If alsoMap is TRUE, ch is mapped,
otherwise it is unmapped.
It is occasionally useful to insert a new child below all existing children except the background, in which case the following procedure is handy:
TYPE Altitude = {Top, Bot};
PROCEDURE Insert(
v: T;
ch: VBT.T;
READONLY dom: Rect.T;
alt := Altitude.Top;
alsoMap: BOOLEAN := TRUE); <* LL.sup = VBT.mu *>
Insertchat the top ifalt = Altitude.Top; insertchjust above the background ifalt = Altitude.Bot.
That is,
Insert is equivalent to
IF alt = Altitude.Top THEN
pred := NIL
ELSE
pred := Split.Pred(v, Split.Pred(v, NIL))
END;
InsertAfter(v, pred, ch, dom, alsoMap)
Finally, instead of providing the new child's domain it can be useful to provide only the northwest corner and let the child's domain be determined by its shape constraints:
PROCEDURE InsertAt(
v: T;
ch: VBT.T;
nw: Point.T;
alt := Altitude.Top;
alsoMap: BOOLEAN := TRUE); <* LL.sup = VBT.mu *>
Insertchwith its preferred shape and its northwest corner atnw. ThealtandalsoMapparameters are interpreted as inInsert.
\subsubsection{Moving, lifting, and lowering children}
PROCEDURE Move(ch: VBT.T; READONLY dom: Rect.T); <* LL.sup = VBT.mu *>
Change the domain ofchto bedomand markch's parent for redisplay.
If the height or width of
dom do not satisfy ch's size
constraints, then they are projected into range, preserving the
northwest corner of dom. The stacking order of ch is not
changed. Move is a checked runtime error if ch's parent is not
a ZSplit. Note that this has nothing to do with Split.Move,
unlike the next procedure.
PROCEDURE Lift(ch: VBT.T; alt := Altitude.Top); <* LL.sup = VBT.mu *>
Liftchto the top or lower it to be just above the background, depending onalt.Liftis equivalent to:
v := VBT.Parent(ch); IF alt = Altitude.Top THEN pred := NIL ELSE pred := Split.Pred(v, Split.Pred(v, NIL)) END; Split.Move(v, pred, ch)
\subsubsection{Mapping and unmapping children}
You can {\it unmap} a child of a ZSplit, which reshapes the child
to be empty after recording the child's shape and offset. When you
later {\it map} the child, the recorded shape and offset are
restored. An unmapped child is rescreened when the parent is
rescreened, and its recorded shape and offset are updated when the
parent is reshaped, just like the domains of the mapped children.
PROCEDURE Unmap(ch: VBT.T); <* LL.sup = VBT.mu *>
If ch is mapped, unmap it and mark its parent for redisplay. PROCEDURE Map(ch: VBT.T); <* LL.sup = VBT.mu *>
If ch is unmapped, map it and mark its parent for redisplay. PROCEDURE IsMapped(ch: VBT.T): BOOLEAN; <* LL.sup = VBT.mu *>
ReturnTRUEif ch ismappedandFALSEifchis unmapped.
Map, Unmap, and IsMapped are checked runtime errors if ch's
parent is not a ZSplit.
\subsubsection{Getting domains}
PROCEDURE GetDomain(ch: VBT.T): Rect.T; <* LL.sup = VBT.mu *>
Return the effective domain of ch. The effective domain is the same as the normal domain, except (1) if the parent has been marked for redisplay,
GetDomain returns
the domain that ch will receive when the redisplay happens, or
(2) if the domain of the parent is Rect.Empty, GetDomain returns
the domain ch would receive if the parent were reshaped to its
last non-empty domain, or (3) if the child is unmapped, GetDomain
returns the domain the child would have if it were mapped.
GetDomain is a checked runtime error if the parent of ch is not
a ZSplit.
PROCEDURE GetParentDomain(v: T): Rect.T; <* LL.sup = VBT.mu *>
Return the last non-empty value ofv.domain, orRect.Emptyifv.domainhas always been empty.
\subsubsection{Moving children when the parent is reshaped}
You can supply procedures to control what happens to the children
when a ZSplit is reshaped. If you don't supply a procedure, the
default behavior is as follows: the initial background child is
always reshaped to have the same domain as the parent. The other
children are reshaped so as to preserve their shape and their offsets
(even if this makes them extend outside the parent domain). The rule
is different if the parent is reshaped to Rect.Empty: in this case
the ZSplit records its children's shapes and offsets and reshapes
them all to Rect.Empty. When the ZSplit is later reshaped to
a non-empty domain, it reshapes the initial background child to have
the same domain as the parent, and restores the saved dimensions
and offsets of the other children.
In the unusual case that the initial background child is deleted, subsequent background children do not automatically inherit the special reshaping behavior of the initial background child.
To override the default behavior, use SetReshapeControl:
PROCEDURE SetReshapeControl(
ch: VBT.T;
rc: ReshapeControl); <* LL.sup = VBT.mu *>
Set the reshape control object for the childchto berc.
TYPE ReshapeControl = OBJECT METHODS apply(ch:VBT.T; READONLY old, new, prev: Rect.T) : Rect.T <* LL.sup = VBT.mu.ch *> END;
SetReshapeControl arranges that whenever the ZSplit parent v
of ch is reshaped from domain old to domain new, then if the
previous domain of ch is prev, the new domain of ch will become
rc.apply(ch, old, new, prev) (if this rectangle doesn't satisfy
ch's size constraints, its height and width will be projected into
range, preserving its offset).
These methods of the ReshapeControl objects may be called concurrently
for different children. (This is why the apply method has only a
share of VBT.mu.) The stacking order is not changed by
reshaping.
When a ZSplit child is replaced by Split.Replace, the new child
inherits the old child's reshape control object.
SetReshapeControl is a checked runtime error if the parent of ch
is not a ZSplit.
If the ZSplit is reshaped to Rect.Empty, it will reshape its
children to Rect.Empty without calling their reshape control
methods. Similarly, if the parent is subsequently reshaped to its
original rectangle, it will restore the children's previous domains
without calling the methods.
By default, the background is chained absolutely to the parent
domain, using Background:
VAR (*CONST*)
Background: ReshapeControl;
(* One useful reshape control method provided by this interface is
"ChainReshape", in which some set of the child's west, east, north,
and south edges are ``chained'' to the corresponding edges of the
parent. Chaining an edge means that the distance between the child
edge and the corresponding parent edge will be preserved. For
example, if both the west and east edges are chained, then the child's
horizontal extent will be inset into the parent's horizontal extent
by fixed amounts on both sides. For another example, suppose that
the the east edge is chained and the west edge is not. In this case
the distance between the east edges of the child and parent will
be preserved, but the west edge of the child will move so as to
preserve the width of the child. The north and south edges control
the vertical extent in a similar manner. *)
TYPE
Ch = {W, E, N, S};
ChainSet = SET OF Ch;
ChainReshapeControl = ReshapeControl OBJECT
chains: ChainSet
OVERRIDES
apply := ChainedReshape
END;
VAR (*CONST*)
NoChains, WChains, EChains, WEChains, NChains,
WNChains, ENChains, WENChains, SChains,
WSChains, ESChains, WESChains, NSChains,
WNSChains, ENSChains, WENSChains: ChainReshapeControl;
The ``variables'' above are constants for the following reshape
control objects:
NEW(ChainReshapeControl, chains := ChainSet{}),
NEW(ChainReshapeControl, chains := ChainSet{Ch.W}),
...
NEW(ChainReshapeControl,
chains := ChainSet{Ch.W,Ch.E,Ch.N,Ch.S})
PROCEDURE ChainedReshape(
self: ChainReshapeControl;
ch: VBT.T;
READONLY oldParentDomain, newParentDomain,
oldChildDomain: Rect.T): Rect.T;
Return the rectangle that results from chaining each edge in
self.chains to the corresponding edge of the parent domain, and
leaving the other edges unconstrained. If both edges in a dimension are chained, the offset and extent of the child will both vary to satisfy the chain constraints; if one edge is chained, the offset will vary and the extent will be fixed; if both edges are unchained, the offset and the extent will both be fixed.
The default behavior for the initial background child
is Background, and the default behavior for all other children
is WNChains.
One final reshape control method is sometimes useful:
PROCEDURE ScaledReshape(
self: ReshapeControl;
ch: VBT.T;
READONLY oldParentDomain, newParentDomain,
oldChildDomain: Rect.T) : Rect.T;
Return the integer approximation to the rectangle that results from scaling the old child domain to occupy the same relative position of the changing parent domain.
VAR (*CONST*) Scaled: ReshapeControl;This ``variable'' is really a constant for the following reshape control object:
NEW(ReshapeControl, apply := ScaledReshape)
END ZSplit.