Copyright (C) 1994, Digital Equipment Corp.
by Steve Glassman and Stephen Harrison
<*PRAGMA LL*>
MODULE GenericTree;
IMPORT Animate, Axis, RefList, MG, MGPublic, MGV, Pts, RefListUtils,
R2, R2Box, Thread, VBT;
EXCEPTION Fatal;
<* FATAL Fatal, Thread.Alerted *>
REVEAL
V = PublicV BRANDED OBJECT
OVERRIDES
reshape := MGV.ReshapeLeaveOrigin;
setBorder := SetBorderV;
shape := ShapeV;
setRoot := SetRootV;
init := InitV;
END;
REVEAL
SubTree = PublicSubTree BRANDED OBJECT
OVERRIDES
init := InitSubTree;
addChild := AddChildDefault;
removeChild := RemoveChildDefault;
route := RouteDefault;
link := LinkError;
succ := SuccError;
pred := PredDefault;
nth := NthDefault;
iterate := IterateDefault;
bounds := BoundsError;
calculateSize := CalculateSizeError;
translate := TranslateError;
END;
REVEAL
GenericTree = GenericTreePublic BRANDED OBJECT
OVERRIDES
bounds := BoundsGeneric;
succ := SuccGeneric;
addChild := AddChildGeneric;
removeChild := RemoveChildGeneric;
link := LinkGeneric;
calculateSize := CalculateSizeGeneric;
translate := TranslateGeneric;
END;
REVEAL
Forest = ForestPublic BRANDED OBJECT
OVERRIDES
init := InitForest;
END;
PROCEDURE InitV (v: V): MG.V =
BEGIN
EVAL MG.V.init(v);
LOCK v.mu DO
IF v.root # NIL THEN
v.displayList.addBefore(v, v.root);
END;
END;
RETURN v;
END InitV;
PROCEDURE ShapeV (v: V; axis: Axis.T; <* UNUSED *>n: CARDINAL):
VBT.SizeRange =
VAR
sr : VBT.SizeRange;
size: REAL;
BEGIN
LOCK v.mu DO
IF v.root = NIL THEN
size := 0.0;
ELSIF axis = Axis.T.Hor THEN
size := v.root.width
ELSE
size := v.root.height
END;
sr.pref := Pts.ToScreenPixels(v, 2.0 * v.border[axis] + size, axis)
END;
sr.lo := 0;
sr.hi := MAX(sr.pref + 1, VBT.DefaultShape.hi);
RETURN sr;
END ShapeV;
PROCEDURE SetRootV (v: V; root: SubTree) =
VAR bounds: R2Box.T;
BEGIN
LOCK v.mu DO
IF v.root # NIL THEN v.displayList.remove(v, v.root) END;
v.root := root;
IF root # NIL THEN
bounds := root.graphic.appearance.boundingBox(root.graphic, v);
v.displayList.addBefore(v, v.root);
root.setVisible(v, 0.0);
END;
END;
VBT.NewShape(v);
VBT.Mark(v);
END SetRootV;
PROCEDURE SetBorderV (v: V; border: ARRAY Axis.T OF REAL) =
BEGIN
LOCK v.mu DO v.border := border END;
VBT.NewShape(v);
VBT.Mark(v);
END SetBorderV;
PROCEDURE RelayoutAncestors (node: SubTree; v: V) =
BEGIN
WHILE node # NIL DO
node.dirty := TRUE;
node.calculateSize(v);
node := node.parent;
END;
v.root.translate(
v, v.nw[1] - v.border[Axis.T.Ver], v.nw[0] + v.border[Axis.T.Hor]);
END RelayoutAncestors;
<* LL < v.mu *>
PROCEDURE SetRoot (root: SubTree; v: V; ) =
BEGIN
v.setRoot(root);
LOCK v.mu DO RelayoutAncestors(root, v); END;
VBT.NewShape(v);
MGV.Animation(v);
END SetRoot;
<* LL < v.mu *>
PROCEDURE AddChild (node: SubTree; v: V; pred, new: SubTree) =
BEGIN
LOCK v.mu DO
<* ASSERT((pred = NIL OR pred.parent = node) AND new.parent = NIL) *>
node.addChild(v, pred, new);
RelayoutAncestors(node, v);
END;
VBT.NewShape(v);
MGV.Animation(v);
END AddChild;
PROCEDURE RemoveChild (node: SubTree; v: V; child: SubTree) =
BEGIN
LOCK v.mu DO
<* ASSERT( child.parent = node) *>
node.removeChild(v, child);
RelayoutAncestors(node, v);
END;
VBT.NewShape(v);
MGV.Animation(v);
END RemoveChild;
PROCEDURE Route (ancestor: SubTree; v: V; descendant: SubTree): MG.Group =
BEGIN
LOCK v.mu DO RETURN ancestor.route(v, descendant); END;
END Route;
PROCEDURE Succ (node: SubTree; v: V; pred: SubTree): SubTree =
BEGIN
LOCK v.mu DO <* ASSERT (pred = NIL OR pred.parent = node) *>
RETURN node.succ(v, pred);
END;
END Succ;
PROCEDURE Pred(node: SubTree; v: V; succ: SubTree): SubTree =
BEGIN
LOCK v.mu DO <* ASSERT (succ = NIL OR succ.parent = node) *>
RETURN node.pred(v, succ);
END;
END Pred;
PROCEDURE Nth (node: SubTree; v: V; n: CARDINAL): SubTree =
BEGIN
LOCK v.mu DO RETURN node.nth(v, n); END;
END Nth;
PROCEDURE NumChildren (node: SubTree; v: V): INTEGER =
BEGIN
LOCK v.mu DO RETURN node.numChildren; END;
END NumChildren;
PROCEDURE Parent (node: SubTree; v: V): SubTree =
BEGIN
LOCK v.mu DO RETURN node.parent; END;
END Parent;
PROCEDURE Iterate (node: SubTree; v: V; iter: ChildrenIterator) =
BEGIN
LOCK v.mu DO node.iterate(v, iter); END;
END Iterate;
PROCEDURE InitSubTree (node: SubTree; v: V; graphic: MG.T): SubTree =
BEGIN
EVAL MG.Group.init(node);
IF node.id # MG.NoID THEN
MGPublic.Register(v, node.id, node);
END;
LOCK v.mu DO
IF node.linker = NIL THEN node.linker := linkerDefault END;
node.graphic := graphic;
node.addBefore(v, graphic);
node.calculateSize(v);
MG.TranslateToLocked(graphic, v, R2.Origin, TRUE);
MG.SetPosLocked(node, R2.Origin, v);
node.setVisible(v, 0.0);
END;
RETURN node
END InitSubTree;
PROCEDURE LinkerForest (<* UNUSED *> l : Linker;
<* UNUSED *> v : V;
<* UNUSED *> parent, child: SubTree ): LinkerRec =
BEGIN
RETURN LinkerRec{NIL, NIL};
END LinkerForest;
CONST
R2Epsilon = R2.T{0.001, 0.001};
PROCEDURE InitForest (node: Forest; v: V): Forest =
BEGIN
node.linker := NEW(Linker, new := LinkerForest);
RETURN GenericTree.init(node, v, NEW(MG.Rectangle, visible := 0.0,
weight := 0.0).init(
R2.Origin, R2Epsilon))
END InitForest;
PROCEDURE BoundsError (<*UNUSED *> node : SubTree;
<*UNUSED *> v : MG.V): R2Box.T =
BEGIN
RAISE Fatal
END BoundsError;
PROCEDURE BoundsGeneric ( node : SubTree;
v : MG.V): R2Box.T =
VAR
pos := MG.PosLocked(node, v);
bounds := node.graphic.appearance.boundingBox(node.graphic, v);
w := pos[0] - node.width / 2.0;
n := pos[1] + (bounds[1].hi - bounds[1].lo) / 2.0;
e := w + node.width;
s := n - node.height;
BEGIN
RETURN R2Box.FromEdges(w, e, s, n);
END BoundsGeneric;
PROCEDURE AddChildDefault ( node : SubTree;
v : V;
<* UNUSED *> pred : SubTree;
child: SubTree ) =
BEGIN
child.parent := node;
child.setVisible(v, 1.0);
INC(node.numChildren);
END AddChildDefault;
PROCEDURE Center (node: GenericTree; v: V): R2.T =
BEGIN
RETURN R2Box.Middle(node.graphic.bounds(v));
END Center;
PROCEDURE LinkerNewDefault (<* UNUSED *> l : Linker;
v : V;
parent, child: SubTree ):
LinkerRec =
VAR
link := NEW(MG.Line, weight := 2.0).init(
to := Center(parent, v), from := Center(child, v));
BEGIN
RETURN LinkerRec{parentLink := NEW(MG.LineEnd, line := link,
controlsFrom := FALSE).init(),
childLink := NEW(MG.LineEnd, line := link,
controlsFrom := TRUE).init()}
END LinkerNewDefault;
CAUTION: Don't change without also changing AddChildForest
PROCEDURE AddChildGeneric (node: GenericTree; v: V; pred, child: SubTree) =
VAR
predTail: RefList.T;
new := NARROW(child, GenericTree);
nlr := node.linker.new(v, node, child);
BEGIN
SubTree.addChild(node, v, pred, child);
IF pred = NIL THEN
node.children := RefList.Cons(child, node.children);
ELSE
predTail := FindGenericChild(node, pred);
predTail.tail := RefList.Cons(child, predTail.tail);
END;
(* assumes if one end is NIL then both are *)
IF nlr.parentLink # NIL THEN
new.linkEndParent := nlr.parentLink;
new.linkEnd := nlr.childLink;
new.linkEnd.setVisible(v, new.visible);
(* painting order should be node.graphic, new.graphic, new.linkend
linkEndParent doesn't get painted *)
node.addAfter(v, new.linkEndParent); (* bottom *)
new.addAfter(v, new.linkEnd); (* bottom *)
END;
node.addBefore(v, new, node.graphic); (* below graphic *)
END AddChildGeneric;
PROCEDURE RemoveChildDefault ( node : SubTree;
<* UNUSED *> v : V;
child: SubTree ) =
BEGIN
child.parent := NIL;
DEC(node.numChildren);
END RemoveChildDefault;
PROCEDURE RemoveChildGeneric (node: GenericTree; v: V; child: SubTree) =
VAR ch := NARROW(child, GenericTree);
BEGIN
SubTree.removeChild(node, v, child);
RefListUtils.DeleteQ(node.children, child);
(* assumes if one end is NIL then both are *)
IF ch.linkEndParent # NIL THEN
node.remove(v, ch.linkEndParent);
ch.remove(v, ch.linkEnd);
END;
node.remove(v, ch);
END RemoveChildGeneric;
PROCEDURE CalculateSizeError (<* UNUSED *> node: SubTree; <* UNUSED *> v: V) =
BEGIN RAISE Fatal
END CalculateSizeError;
PROCEDURE CalculateSizeGeneric (node: GenericTree; v: V) =
VAR
width, height := 0.0;
bounds: R2Box.T;
size: R2.T;
child := node.succ(v, NIL);
BEGIN
WHILE child # NIL DO
width := width + child.width;
height := MAX(height, child.height);
child := node.succ(v, child);
END;
IF node.numChildren > 0 THEN height := height + node.dyChildren END;
width := width + FLOAT(MAX(0, node.numChildren - 1)) * node.dxChildren;
bounds := node.graphic.appearance.boundingBox(node.graphic, v);
size := R2Box.Size(bounds);
node.width := MAX(size[0], width);
node.height := size[1] + height;
END CalculateSizeGeneric;
PROCEDURE TranslateError (<* UNUSED *> node : SubTree;
<* UNUSED *> v : V;
<* UNUSED *> north, west: REAL ) =
BEGIN RAISE Fatal
END TranslateError;
TYPE
FromOrigin = Animate.Linear OBJECT
OVERRIDES
length := FOLength;
doStep := FODoStep;
END;
PROCEDURE FOLength (<* UNUSED *> fo: FromOrigin;
<* UNUSED *> v : MG.V;
<* UNUSED *> mg: MG.T ): INTEGER =
BEGIN
RETURN 1
END FOLength;
PROCEDURE FODoStep (fo : FromOrigin;
time : REAL;
timePrev: REAL;
v : MG.V;
mg : MG.T ) =
BEGIN
IF timePrev = 0.0 AND time # 0.0 THEN
MG.RTranslateLocked(mg, v, fo.vector);
END;
IF time = 1.0 AND timePrev # 1.0 THEN
mg.setVisible(v, 1.0);
END;
END FODoStep;
CONST
Epsilon = 0.01;
PROCEDURE LinearAnimation (v: V; vector: R2.T; mg: SubTree): BOOLEAN =
VAR a: Animate.T;
BEGIN
IF ABS(vector[0]) > Epsilon OR ABS(vector[1]) > Epsilon THEN
IF v.animations = NIL THEN
v.animations := NEW(Animate.Group).init()
END;
IF MG.PosLocked(mg, v) = R2.Origin THEN
a := NEW(FromOrigin, vector := vector).init();
ELSE
a := NEW(Animate.Linear, vector := vector).init()
END;
v.animations.add(v, NEW(Animate.Composite, t := a, mg := mg));
RETURN TRUE;
ELSE
RETURN mg.dirty;
END;
END LinearAnimation;
PROCEDURE ParentPos (parent: SubTree; v: V): R2.T =
BEGIN
IF parent = NIL THEN
RETURN R2.Origin;
ELSE
RETURN MG.PosLocked(parent, v);
END;
END ParentPos;
We need to compute the vector which will move node to the correct north,
west (relative to the parent)
PROCEDURE TranslateGeneric (node: GenericTree; v: V; north, west: REAL) =
VAR
westCh, northCh: REAL;
child := node.succ(v, NIL);
ppos := ParentPos(node.parent, v);
bounds := node.graphic.appearance.boundingBox(node.graphic, v);
size := R2Box.Size(bounds);
middle := R2Box.Middle(bounds);
BEGIN
IF LinearAnimation(
v, R2.T{ppos[0] + west + node.width / 2.0 - middle[0],
ppos[1] + north + size[1] / 2.0 - bounds[1].hi}, node) THEN
(* translate each child so top is dyChildren below graphic's south
and left edge is dxChildren from prev's right edge *)
northCh := -size[1] - node.dyChildren;
westCh := -node.width / 2.0;
WHILE child # NIL DO
child.translate(v, northCh, westCh);
westCh := westCh + child.width + node.dxChildren;
child := node.succ(v, child);
END;
END;
END TranslateGeneric;
PROCEDURE RouteDefault (node: SubTree; v: V; descendant: SubTree):
MG.Group =
VAR group := NEW(MG.Group).init();
BEGIN
WHILE descendant # node DO
group.addBefore(v, descendant);
VAR link := descendant.link(v);
BEGIN
IF link # NIL THEN group.addAfter(v, descendant.link(v)); END;
END;
descendant := descendant.parent;
END;
group.addBefore(v, node);
RETURN group;
END RouteDefault;
PROCEDURE LinkError (<* UNUSED *> node: SubTree; <* UNUSED *> v: V): MG.T =
BEGIN RAISE Fatal
END LinkError;
PROCEDURE LinkGeneric (node: GenericTree; <* UNUSED *> v: V): MG.T =
BEGIN
RETURN node.linkEnd
END LinkGeneric;
PROCEDURE SuccError (<* UNUSED *> node: SubTree;
<* UNUSED *> v : V;
<* UNUSED *> pred: SubTree ): SubTree =
BEGIN RAISE Fatal;
END SuccError;
RefList.First(FindGenericChild(node: Generic; ch: SubTree)) = ch
PROCEDURE FindGenericChild (node: GenericTree; ch: SubTree): RefList.T =
VAR children := node.children;
BEGIN
WHILE children.head # ch DO
children := children.tail;
END;
RETURN children;
END FindGenericChild;
PROCEDURE SuccGeneric (node: GenericTree; <* UNUSED *>v: V; pred: SubTree):
SubTree =
VAR predTail: RefList.T;
BEGIN
IF pred = NIL THEN
predTail := node.children
ELSE
predTail := FindGenericChild(node, pred).tail;
END;
IF predTail = NIL THEN RETURN NIL ELSE RETURN predTail.head END;
END SuccGeneric;
PROCEDURE PredDefault (node: SubTree; v: V; succ: SubTree): SubTree =
VAR
pred: SubTree := NIL;
next: SubTree := node.succ(v, NIL);
BEGIN
WHILE next # succ DO pred := next; next := node.succ(v, pred); END;
RETURN pred
END PredDefault;
PROCEDURE NthDefault (node: SubTree; v: V; n: CARDINAL): SubTree =
VAR ch := node.succ(v, NIL);
BEGIN
FOR i := 1 TO n DO ch := node.succ(v, ch); END;
RETURN ch;
END NthDefault;
PROCEDURE IterateDefault (node: SubTree; v: V; iter: ChildrenIterator) =
VAR ch := node.succ(v, NIL);
BEGIN
WHILE ch # NIL AND iter.proc(ch) DO ch := node.succ(v, ch); END;
END IterateDefault;
BEGIN
linkerDefault := NEW(Linker, new := LinkerNewDefault);
END GenericTree.