AbuttersImpl.Mesa
Last Edited by: Spreitzer, April 14, 1985 12:14:51 pm PST
DIRECTORY Abutters, Graphics, MessageWindow, MJSContainers, Process, Rope, ViewerClasses, ViewerLocks, ViewerOps;
AbuttersImpl: CEDAR MONITOR
LOCKS a USING a: Abutter
IMPORTS MessageWindow, MJSContainers, Process, Rope, ViewerLocks, ViewerOps
EXPORTS Abutters
INVARIANT
cn.scheduled iff we're planning to consider moving/stretching n.
n.scheduled iff n reachable from a.queue.
n.scheduled if n.predecessor.scheduled.
a.cw & a.ch describe shape to which a last layed out.
cn.x, y, w, h describe where cn last put.
Viewer LOCKs are never requested inside Abutter LOCKs.
= {OPEN Abutters;
Abutter: TYPE = REF AbutterRep;
AbutterRep: PUBLIC TYPE = MONITORED RECORD [
v: Viewer,
cw, ch: INTEGER ← 0,
cr: CompiledRules ← ALL[NIL],
queue: Queue ← ALL[NIL]
];
CompiledRules: TYPE = ARRAY Edge OF RootNode;
Queue: TYPE = ARRAY Edge OF NodeList;
NodeList: TYPE = LIST OF Node;
Node: TYPE = REF NodeRep;
ChildNodeList: TYPE = LIST OF ChildNode;
ChildNode: TYPE = REF NodeRep[child];
RootNode: TYPE = REF NodeRep[root];
NodeRep: TYPE = RECORD [
e: Edge,
scheduled: BOOLFALSE,
successors: ChildNodeList ← NIL,
predecessor: Node ← NIL,
variant: SELECT kind: * FROM
root => [],
child => [
v: Viewer,
spaceBefore: INTEGER,
stretch: BOOL,
x, y, w, h: INTEGER ← 0
],
ENDCASE
];
Axis: TYPE = {horizontal, vertical};
axisKeys: ARRAY Axis OF ATOM = [
horizontal: $horizontalAbutterNode,
vertical: $verticalAbutterNode
];
esgn: ARRAY Edge OF [-1 .. 1] = [
left: 1,
right: -1,
top: 1,
bottom: -1];
Next: ARRAY Edge OF Edge;
edgeAxis: ARRAY Edge OF Axis = [
left: horizontal,
right: horizontal,
top: vertical,
bottom: vertical];
flavor: ATOM = $Abutter;
mjsClass: MJSContainers.MJSContainerClass ← NEW [MJSContainers.MJSContainerClassRep ← [
paint: Paint,
NoteSizeChanged: NoteSizeChanged
]];
Create: PUBLIC PROC [info: ViewerClasses.ViewerRec ← [], paint: BOOLTRUE] RETURNS [abutter: Abutter] = {
info.data ← abutter ← NEW [AbutterRep ← [v: NIL]];
abutter.cr[left] ← NEW [NodeRep.root ← [e: left, variant: root[] ]];
abutter.cr[right] ← NEW [NodeRep.root ← [e: right, variant: root[] ]];
abutter.cr[top] ← NEW [NodeRep.root ← [e: top, variant: root[] ]];
abutter.cr[bottom] ← NEW [NodeRep.root ← [e: bottom, variant: root[] ]];
abutter.v ← MJSContainers.Create[flavor, info, paint];
};
Paint: PROC [self: Viewer, context: Graphics.Context, whatChanged: REF ANY, clear: BOOL] --ViewerClasses.PaintProc-- = {
a: Abutter ← NARROW[MJSContainers.GetClientData[self]];
Inner: ENTRY PROC [a: Abutter] = {ENABLE UNWIND => {};
CheckUnion: INTERNAL PROC [nl: ChildNodeList] = {
FOR nl ← nl, nl.rest WHILE nl # NIL DO
n: ChildNode ← nl.first;
SELECT n.e FROM
left, right => IF n.v.ww # n.w OR n.v.wx # n.x THEN Sched[a, n];
top, bottom => IF n.v.wh # n.h OR n.v.wy # n.y THEN Sched[a, n];
ENDCASE => ERROR;
CheckUnion[n.successors];
ENDLOOP;
};
[] ← CheckEdges[a];
FOR e: Edge IN Edge DO CheckUnion[a.cr[e].successors] ENDLOOP;
IF a.queue # ALL[NIL] THEN TRUSTED {Process.Detach[FORK DeQueue[a]]};
};
Inner[a];
};
NoteSizeChanged: PROC [container: MJSContainers.MJSContainer, cw, ch: BOOL] RETURNS [paint: BOOLTRUE] --MJSContainers.SizeChangeNotifyProc-- = {
a: Abutter ← NARROW[MJSContainers.GetClientData[container]];
Inner: ENTRY PROC [a: Abutter] = {[] ← CheckEdges[a]};
Inner[a];
paint ← ReallyDeQueue[a, FALSE];
};
CheckEdges: INTERNAL PROC [a: Abutter] RETURNS [diff: BOOL] = {
diff ← FALSE;
IF a.v.cw # a.cw THEN {Sched[a, a.cr[right]]; diff ← TRUE};
IF a.v.ch # a.ch THEN {Sched[a, a.cr[bottom]]; diff ← TRUE};
};
Sched: INTERNAL PROC [a: Abutter, n: Node] = {
Mark: PROC [n: Node] = {
n.scheduled ← TRUE;
FOR nl: ChildNodeList ← n.successors, nl.rest WHILE nl # NIL DO
Mark[nl.first];
ENDLOOP;
};
IF n.scheduled THEN RETURN;
a.queue[n.e] ← CONS[n, a.queue[n.e]];
Mark[n];
};
Find: PROC [v: Viewer, axis: Axis] RETURNS [n: Node] =
{n ← NARROW[ViewerOps.FetchProp[v, axisKeys[axis]]]};
DeQueue: PROC [a: Abutter] =
{[] ← ReallyDeQueue[a, TRUE]};
ReallyDeQueue: PROC [a: Abutter, mayPaint: BOOL] RETURNS [needPaint: BOOL] = {
WithViewerLock: PROC = {needPaint ← DeQueueWithAbutterLock[a, mayPaint]};
ViewerLocks.CallUnderWriteLock[WithViewerLock, a.v];
};
itLimit: INTEGER ← 20;
DeQueueWithAbutterLock: ENTRY PROC [a: Abutter, mayPaint: BOOL] RETURNS [needPaint: BOOL] = {
Adjust: INTERNAL PROC [n: ChildNode, org: INTEGER] = {
near: INTEGER ← org + esgn[n.e]*n.spaceBefore;
far: INTEGER;
To: INTERNAL PROC [x, y, w, h: INTEGER] = {
ow: INTEGER ← n.v.ww;
oh: INTEGER ← n.v.wh;
dw: BOOL ← w # ow;
dh: BOOL ← h # oh;
diff--erence on other axis--: BOOL;
axis: Axis;
on: Node ← NIL;
needPaint ← TRUE;
ViewerOps.MoveViewer[n.v, x, y, w, h, FALSE];
IF (dw OR dh) AND MJSContainers.IsMJSContainer[n.v] THEN [] ← MJSContainers.NoteSize[n.v, FALSE];
SELECT n.e FROM
left, right => {diff ← n.v.wh # oh; axis ← vertical};
top, bottom => {diff ← n.v.ww # ow; axis ← horizontal};
ENDCASE => ERROR;
IF diff AND (on ← Find[n.v, axis]) # NIL THEN Sched[a, on];
};
d: INTEGER;
IF n.stretch AND n.successors # NIL THEN ERROR;
n.scheduled ← FALSE;
SELECT n.e FROM
left => IF (d ← near - n.v.wx) # 0 THEN {
IF n.stretch
THEN To[near, n.v.wy, n.v.ww-d, n.v.wh]
ELSE To[near, n.v.wy, n.v.ww, n.v.wh]};
right => IF (d ← near - (n.v.wx+n.v.ww)) # 0 THEN {
IF n.stretch
THEN To[n.v.wx, n.v.wy, n.v.ww+d, n.v.wh]
ELSE To[n.v.wx+d, n.v.wy, n.v.ww, n.v.wh]};
top => IF (d ← near - n.v.wy) # 0 THEN {
IF n.stretch
THEN To[n.v.wx, near, n.v.ww, n.v.wh-d]
ELSE To[n.v.wx, near, n.v.ww, n.v.wh]};
bottom => IF (d ← near - (n.v.wy+n.v.wh)) # 0 THEN {
IF n.stretch
THEN To[n.v.wx, n.v.wy, n.v.ww, n.v.wh+d]
ELSE To[n.v.wx, n.v.wy+d, n.v.ww, n.v.wh]};
ENDCASE => ERROR;
n.x ← n.v.wx; n.y ← n.v.wy; n.w ← n.v.ww; n.h ← n.v.wh;
far ← SELECT n.e FROM
left => n.v.wx+n.v.ww,
right => n.v.wx,
top => n.v.wy+n.v.wh,
bottom => n.v.wy,
ENDCASE => ERROR;
FOR succs: ChildNodeList ← n.successors, succs.rest WHILE succs # NIL DO
Adjust[succs.first, far];
ENDLOOP;
};
empties: [0 .. 4] ← 0;
itCount: INTEGER ← 0;
needPaint ← FALSE;
FOR e: Edge ← FIRST[Edge], Next[e] WHILE empties < 4 DO
IF itLimit <= (itCount ← itCount + 1) THEN {
TRUSTED {Process.Detach[FORK TooMany[a]]};
EXIT;
};
IF a.queue[e] # NIL THEN {
scroll: INTEGER ← MJSContainers.ScrollOffset[a.v];
rootOrg: INTEGER = SELECT e FROM
left => 0,
right => a.v.cw,
top => 0 + scroll,
bottom => a.v.ch + scroll,
ENDCASE => ERROR;
nl: NodeList ← a.queue[e];
a.queue[e] ← NIL;
empties ← 0;
FOR nl ← nl, nl.rest WHILE nl # NIL DO
n: Node ← nl.first;
IF n.e # e THEN ERROR;
IF n.scheduled THEN {
WITH n SELECT FROM
cn: ChildNode => {
org: INTEGERWITH cn.predecessor SELECT FROM
rn: RootNode => rootOrg,
cn: ChildNode => SELECT e FROM
left => cn.v.wx+cn.v.ww,
right => cn.v.wx,
top => cn.v.wy+cn.v.wh,
bottom => cn.v.wy,
ENDCASE => ERROR,
ENDCASE => ERROR;
Adjust[cn, org];
};
rn: RootNode => {
SELECT e FROM
left, top => NULL;
right => a.cw ← a.v.cw;
bottom => a.ch ← a.v.ch;
ENDCASE => ERROR;
n.scheduled ← FALSE;
FOR cnl: ChildNodeList ← n.successors, cnl.rest WHILE cnl # NIL DO
Adjust[cnl.first, rootOrg];
ENDLOOP;
};
ENDCASE => ERROR;
};
ENDLOOP;
}
ELSE {
empties ← empties + 1
};
ENDLOOP;
IF mayPaint AND needPaint THEN {
needPaint ← FALSE;
TRUSTED {Process.Detach[FORK ViewerOps.PaintViewer[a.v, all]]};
};
};
TooMany: PROC [a: Abutter] = {
MessageWindow.Append[
message: Rope.Cat["Too many iterations solving layout of ", a.v.name],
clearFirst: TRUE];
};
QuaViewer: PUBLIC PROC [a: Abutter] RETURNS [v: Viewer] =
{v ← a.v};
QuaAbutter: PUBLIC PROC [v: Viewer] RETURNS [a: Abutter] =
{a ← NARROW[v.data]};
ViewerIsAbutter: PUBLIC PROC [v: Viewer] RETURNS [b: BOOL] =
{b ← v.data # NIL AND ISTYPE[v.data, Abutter]};
IsAbutter: PUBLIC PROC [ra: REF ANY] RETURNS [b: BOOL] =
{b ← ra # NIL AND ISTYPE[ra, Abutter]};
Narrow: PUBLIC PROC [ra: REF ANY] RETURNS [a: Abutter] =
{a ← NARROW[ra]};
ScrollOffset: PUBLIC PROC [a: Abutter] RETURNS [offTop: INTEGER] =
{offTop ← MJSContainers.ScrollOffset[a.v]};
SetLayout: PUBLIC ENTRY PROC [a: Abutter, rules: Rules, paint: BOOLTRUE] = {
DoSeries: INTERNAL PROC [e: Edge, s: Series, parent: Node] = {
FOR sl: LIST OF SeriesElement ← s.rigid, sl.rest WHILE sl # NIL DO
parent ← AddChild[a, parent, sl.first.viewer, e, sl.first.spaceBefore, FALSE];
ENDLOOP;
WITH s SELECT FROM
x: Series[none] => NULL;
x: Series[parallel] => {
FOR sl: Parallel ← x.p, sl.rest WHILE sl # NIL DO
DoSeries[e, sl.first, parent];
ENDLOOP;
};
x: Series[stretch] => parent ← AddChild[a, parent, x.se.viewer, e, x.se.spaceBefore, TRUE];
ENDCASE => ERROR;
};
FOR e: Edge IN Edge DO a.cr[e].successors ← NIL ENDLOOP;
a.queue ← ALL[NIL];
FOR e: Edge IN Edge DO DoSeries[e, rules[e], a.cr[e]] ENDLOOP;
IF paint THEN TRUSTED {Process.Detach[FORK DeQueue[a]]};
};
AddChild: INTERNAL PROC [a: Abutter, parent: Node, v: Viewer, e: Edge, space: INTEGER, stretch: BOOL] RETURNS [child: ChildNode] = {
axis: Axis ← edgeAxis[e];
IF Find[v, axis] # NIL THEN ERROR;
child ← NEW [NodeRep.child ← [
e: e,
scheduled: parent.scheduled,
predecessor: parent,
variant: child[
v: v,
spaceBefore: space,
stretch: stretch,
x: 0, y: 0, w: 0, h: 0
]
]];
parent.successors ← CONS[child, parent.successors];
Sched[a, child];
IF NOT stretch THEN ViewerOps.AddProp[v, axisKeys[axis], child];
};
Abut: PUBLIC ENTRY PROC [a: Abutter, child1, child2: Viewer, edge: Edge, space: INTEGER ← 0, stretch: BOOLFALSE, paint: BOOLTRUE] = {
parent: Node ← Find[child1, edgeAxis[edge]];
IF parent = NIL THEN ERROR;
[] ← AddChild[a, parent, child2, edge, space, stretch];
IF paint THEN TRUSTED {Process.Detach[FORK DeQueue[a]]};
};
Start: PROC = {
MJSContainers.RegisterClass[flavor, mjsClass];
FOR e: Edge IN [FIRST[Edge] .. LAST[Edge]) DO
Next[e] ← e.SUCC;
ENDLOOP;
Next[LAST[Edge]] ← FIRST[Edge];
};
Start[];
}.