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: BOOL ← FALSE,
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:
BOOL ←
TRUE]
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:
BOOL ←
TRUE]
--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:
INTEGER ←
WITH 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:
BOOL ←
TRUE] = {
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:
BOOL ←
FALSE, paint:
BOOL ←
TRUE] = {
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[];
}.