<*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 *>
Insertch
as a new child ofv
with domaindom
, and markv
for 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 *>
Insertch
at the top ifalt = Altitude.Top
; insertch
just 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 *>
Insertch
with its preferred shape and its northwest corner atnw
. Thealt
andalsoMap
parameters 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 ofch
to bedom
and 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 *>
Liftch
to the top or lower it to be just above the background, depending onalt
.Lift
is 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 *>
ReturnTRUE
if ch ismapped
andFALSE
ifch
is 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.Empty
ifv.domain
has 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 childch
to 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.