CDSatellitesImpl.mesa
Copyright © 1985, 1986, 1987 by Xerox Corporation. All rights reserved.
Written by: Christian Jacobi, August 29, 1986 9:55:02 pm PDT
This module is descended from the early implementation of satellites by Monier. It has been
rewritten a number of times since then by Sindhu and Serlet to find an implementation that
is comfortable to use both from programs and interactively. This last version is a complete
rewrite by Jacobi, one hopes for the last time!
Old version by: Pradeep Sindhu December 20, 1985 1:25:44 pm PST
Old version by: Pradeep Sindhu March 26, 1986 3:21:40 pm PST
Old version by: Bertrand Serlet August 17, 1986 0:49:00 am PDT
Old version by: Christian Jacobi, July 15, 1986 1:59:30 pm PDT
Old version by: Jean-Marc Frailong July 28, 1986 6:45:49 pm PDT
Last edited by: Christian Jacobi, June 2, 1987 6:09:49 pm PDT
Clients of this interface should be aware of the following set of invariants maintained for satellites, so they may avoid violating them inadvertently (the description below uses the notion of a world, which is a list of CD.Instance belonging to a cell or to the top level of the design):
The strong invariant
I1) A master must be a non-text CD.Instance with a non-nil groupIDProp.
I2) A satellite must be a text CD.Instance with a non-nil groupIDProp.
I3) Every satellite in a given world must have a corresponding master. That is, if n is the value of its groupIDProp (its groupID), then there must be a master in world that also has n as its groupID.
I4) A world may have at most one master with a given groupID.
I5) Each master must have a satellitesProp property that points to a list instances that are the master's satellites or, if the master is an object and has no satellites, a $none property value.
The weak invariant
hold I1, hold I2, dont care about I3, hold I4, dont care about I5
The almost invariant
A4) A design may have at most one master with a given groupID.
the almost invariant helps to remember stallite assiciations when a design is edited; however, the implementation tries but does not manage to keep it reliable.
DIRECTORY
Ascii, CD, CDCells, CDDirectory, CDEvents, CDInstances, CDOps, CDPrivate, CDProperties, CDSatellites, CDTexts, Rope;
CDSatellitesImpl: CEDAR MONITOR
IMPORTS Ascii, CD, CDCells, CDDirectory, CDEvents, CDInstances, CDOps, CDPrivate, CDProperties, CDTexts, Rope
EXPORTS CDSatellites =
BEGIN
OPEN CDSatellites;
ROPE: TYPE = Rope.ROPE;
satellitesProp: REF ATOM ~ NEW[ATOM←$CDSatellitesSatList];
This property hangs on a master.
Its value is the list of the master's satellites or the
atom $none if the master is an object or a design.
maxGroupIdProp: ATOM ~ $CDSatellitesMax;
This property hangs on a design.
Its value is the highest numbered satellite group in the design.
It is valid only while the design is saved on file.
iGroupIdProp: ATOM ~ $CDSatellitesGroupId;
This property hangs on each instance that is in a satellite group.
Its value is the group's groupId. The group's groupId is read-only
and can be reused.
oGroupIdProp: ATOM ~ $CDSatellitesOGroup;
This property hangs on objects and designs
Its value is the group's groupId. The group's groupId is read-only
and can be reused..
noGroup: INT = -1;
maxGroupId: INT ← noGroup+1;
GetSatellites: PUBLIC PROC [from: REF] RETURNS [sats: CD.InstanceList←NIL] = {
--external, does not assume but check invariants for objects and designs
--assumes invariants for instances
WITH from SELECT FROM
d: CD.Design => RETURN [EnforceInvariants[CDOps.RealTopCell[d]]]; --not cached
ob: CD.Object => IF CDCells.IsDummyCell[ob] THEN RETURN [EnforceInvariants[ob]];
ENDCASE => NULL;
WITH CDProperties.GetProp[from, satellitesProp] SELECT FROM
s: CD.InstanceList => RETURN [s];
a: ATOM => IF a=$none THEN RETURN [NIL];
ENDCASE => NULL;
WITH from SELECT FROM
ob: CD.Object => IF CDCells.IsCell[ob] THEN RETURN [EnforceInvariants[ob]];
i: CD.Instance => RETURN [NIL];
ENDCASE => ERROR;
};
InternalGetSatellites: PROC [x: REF] RETURNS [CD.InstanceList] = INLINE {
WITH CDProperties.GetProp[x, satellitesProp] SELECT FROM
list: CD.InstanceList => RETURN [list];
ENDCASE => RETURN [NIL];
};
InternalPutSatellites: PROC [x: REF, sats: REF] = INLINE {
WITH x SELECT FROM
i: CD.Instance => CDProperties.PutInstanceProp[i, satellitesProp, sats];
ENDCASE => CDProperties.PutProp[x, satellitesProp, IF sats=NIL THEN $none ELSE sats];
};
EnforceInvariants: ENTRY PROC [cell: CD.Object] RETURNS [oSats: CD.InstanceList←NIL] = {
--This is exagerated locking...
-- We could have a list of currently active objects and wait until our object is
-- removed from this list; however, that might be exagerated coding in our nearly
-- sequential DA world
ENABLE UNWIND => NULL;
Node: TYPE = REF NodeRec;
NodeRec: TYPE = RECORD [
id: INT ← -1,
list: CD.InstanceList ← NIL];
Table: TYPE = REF TableRec;
TableRec: TYPE = RECORD [
elements: SEQUENCE size: NAT OF LIST OF Node
];
masterTable: Table ← NEW[TableRec[67]];
iSats: CD.InstanceList;
oldObId: INT;
newObId: REF ← INewId[];
Hash: PROC [i: INT] RETURNS [INT] = INLINE {
RETURN[ABS[i] MOD masterTable.size]
};
StoreMaster: PROC [inst: CD.Instance] = {
id: INT ← IGroupId[inst];
hash: INT ← Hash[id];
FOR nl: LIST OF Node ← masterTable[hash], nl.rest WHILE nl#NIL DO
IF nl.first.id=id THEN {nl.first.list ← CONS[inst, nl.first.list]; RETURN};
ENDLOOP;
masterTable[hash] ← CONS[NEW[NodeRec ← [id: id, list: LIST[inst]]], masterTable[hash]];
};
FetchMaster: PROC [id: INT] RETURNS [CD.InstanceList] = {
hash: INT ← Hash[id];
FOR nl: LIST OF Node ← masterTable[hash], nl.rest WHILE nl#NIL DO
IF nl.first.id=id THEN RETURN [nl.first.list];
ENDLOOP;
RETURN [NIL];
};
TrustedMakeInstSat: PROC [master: CD.Instance, satellite: CD.Instance] = {
Internal proc! assumes invariant
sats: CD.InstanceList ← NIL;
WITH InternalGetSatellites[master] SELECT FROM
sats: CD.InstanceList => {
IF CDInstances.Member[satellite, sats] THEN ERROR;
sats.rest ← CONS[satellite, sats.rest];
RETURN
};
ENDCASE => sats ← LIST[satellite];
CDProperties.PutInstanceProp[master, satellitesProp, sats];
};
EachInst: CDCells.InstEnumerator = {
WITH inst.ob.specific SELECT FROM
tp: CDTexts.TextSpecific =>
IF IGroupId[inst]#noGroup THEN iSats ← CONS[inst, iSats];
ENDCASE =>
IF IGroupId[inst]#noGroup THEN {
StoreMaster[inst];
CDProperties.PutInstanceProp[inst, satellitesProp, NIL];
}
};
paranoid: BOOL ← CDCells.IsDummyCell[cell];
oldObId ← OGroupId[cell];
CDProperties.PutObjectProp[cell, oGroupIdProp, newObId];
--Make masterTable and iSats; at the same time strip masters of their satellite lists
[] ← CDCells.EnumerateInstances[cell, EachInst];
--For each satellite find its closest master and put it on that master's satellite list
FOR sl: CD.InstanceList ← iSats, sl.rest WHILE sl#NIL DO
Closer: PROC [newMaster: CD.Instance] RETURNS [BOOL] = INLINE {
accesses global master
mRect, newMRect, sRect: CD.Rect;
IF master=NIL THEN RETURN [TRUE];
mRect ← CDInstances.InstRectO[master];
newMRect ← CDInstances.InstRectO[newMaster];
sRect ← CDInstances.InstRectO[sl.first];
RETURN [RectMinDist[newMRect, sRect] < RectMinDist[mRect, sRect]];
};
master: CD.Instance ← NIL;
satGId: INT ← IGroupId[sl.first];
FOR ml: CD.InstanceList ← FetchMaster[satGId], ml.rest WHILE ml#NIL DO
IF satGId#IGroupId[ml.first] THEN ERROR;
IF Closer[ml.first] THEN master ← ml.first;
ENDLOOP;
IF master#NIL THEN --InstSat-- TrustedMakeInstSat[master, sl.first]
ELSE IF satGId=oldObId THEN --ObjectSat-- {
CDProperties.PutInstanceProp[sl.first, iGroupIdProp, newObId];
oSats ← CONS[sl.first, oSats];
}
ELSE --not a satellite-- CDProperties.PutInstanceProp[sl.first, iGroupIdProp, NIL]
ENDLOOP;
Now go through the masterTable and renumber groups with identical group Id's
FOR i: INT IN [0..masterTable.size) DO
FOR nl: LIST OF Node ← masterTable[i], nl.rest WHILE nl#NIL DO
FOR ml: CD.InstanceList ← nl.first.list.rest, ml.rest WHILE ml#NIL DO
id: REF ← INewId[];
CDProperties.PutInstanceProp[ml.first, iGroupIdProp, id];
FOR sl: CD.InstanceList ← InternalGetSatellites[ml.first], sl.rest WHILE sl#NIL DO
CDProperties.PutInstanceProp[sl.first, iGroupIdProp, id];
ENDLOOP;
ENDLOOP;
ENDLOOP;
ENDLOOP;
InternalPutSatellites[cell, oSats];
};
GetSatelliteRopes: PUBLIC PROC [from: REF, filter: PROC [CD.Instance] RETURNS [BOOL]] RETURNS [ropes: LIST OF ROPENIL] = {
FOR list: CD.InstanceList ← GetSatellites[from], list.rest WHILE list#NIL DO
IF filter=NIL OR filter[list.first] THEN {
rope: ROPENARROW[list.first.ob.specific, CDTexts.TextSpecific].text;
ropes ← CONS[rope, ropes];
}
ENDLOOP;
};
StandardFilter: PUBLIC PROC [inst: CD.Instance] RETURNS [BOOLFALSE] = {
WITH inst.ob.specific SELECT FROM
tp: CDTexts.TextSpecific =>
IF CD.LayerTechnology[inst.ob.layer]=NIL THEN
RETURN [NOT IsItalic[tp.cdFont.supposedName]];
ENDCASE => NULL;
};
Associate: PUBLIC PROC [master: REF, text: CD.Instance] = {
WITH master SELECT FROM
d: CD.Design => master ← CDOps.RealTopCell[d];
ENDCASE => NULL;
IF text=NIL THEN {
WITH master SELECT FROM
o: CD.Object => OPutGroup[o, NIL];
i: CD.Instance => IPutGroup[i, NIL];
ENDCASE => ERROR;
}
ELSE {
id: REFNIL;
IF ~CDTexts.IsText[text.ob] THEN ERROR;
WITH master SELECT FROM
o: CD.Object => {
id ← CDProperties.GetObjectProp[o, oGroupIdProp];
IF id=NIL THEN OPutGroup[o, id←XNewId[]];
IPutGroup[text, id];
};
i: CD.Instance => IF ~CDTexts.IsText[i.ob] THEN {
id ← CDProperties.GetInstanceProp[i, iGroupIdProp];
IF id=NIL THEN IPutGroup[i, id←XNewId[]];
IPutGroup[text, id];
} ELSE ERROR;
ENDCASE => IF master#NIL THEN ERROR;
IPutGroup[text, id];
};
};
IsAssociated: PUBLIC PROC [any: REF, inst: CD.Instance] RETURNS [BOOL] = {
id: INT ← IGroupId[inst];
IF id=noGroup THEN RETURN [any=NIL];
RETURN [
WITH any SELECT FROM
d: CD.Design => id=OGroupId[CDOps.RealTopCell[d]],
o: CD.Object => id=OGroupId[o],
i: CD.Instance => id=IGroupId[i],
ENDCASE => FALSE
]
};
GetMaster: PUBLIC PROC [cell: REF, text: CD.Instance] RETURNS [master: REFNIL] = {
id: INT;
CheckInst: CDCells.InstEnumerator = {
IF id=IGroupId[inst] AND ~CDTexts.IsText[inst.ob] THEN {
master ← inst; quit←TRUE
};
};
IF ~CDTexts.IsText[text.ob] THEN ERROR;
[] ← GetSatellites[cell]; --enforces invariants
id ← IGroupId[text];
IF id=noGroup THEN RETURN [NIL];
WITH cell SELECT FROM
ob: CD.Object => {
IF ~CDCells.IsCell[ob] THEN ERROR;
IF id=OGroupId[ob] THEN RETURN [ob];
[] ← CDCells.EnumerateInstances[ob, CheckInst];
};
d: CD.Design => {
ob: CD.Object ← CDOps.RealTopCell[d];
IF id=OGroupId[ob] THEN RETURN [d];
[] ← CDCells.EnumerateInstances[ob, CheckInst];
};
ENDCASE => NULL;
};
OPutGroup: PROC [ob: CD.Object, x: REF] = INLINE {
CDProperties.PutObjectProp[ob, oGroupIdProp, x]
};
IPutGroup: PROC [i: CD.Instance, x: REF] = INLINE {
CDProperties.PutInstanceProp[i, iGroupIdProp, x]
};
OGroupId: PROC [ob: CD.Object] RETURNS [INT] = {
WITH CDProperties.GetObjectProp[ob, oGroupIdProp] SELECT FROM
ri: REF INT => RETURN [ri^];
ENDCASE => RETURN [noGroup];
};
IGroupId: PROC [i: CD.Instance] RETURNS [INT←noGroup] = {
WITH CDProperties.GetInstanceProp[i, iGroupIdProp] SELECT FROM
ri: REF INT => RETURN [ri^];
ENDCASE => NULL;
};
XNewId: ENTRY PROC [] RETURNS [REF] = {
ENABLE UNWIND => NULL;
RETURN [INewId[]];
};
INewId: INTERNAL PROC [] RETURNS [REF] = {
IF maxGroupId=LAST[INT] THEN maxGroupId ← noGroup+1;
RETURN [NEW[INT ← maxGroupId ← maxGroupId+1]]
};
BeforeOutput: CDEvents.EventProc = {
IF design#NIL THEN CDProperties.PutProp[design, maxGroupIdProp, NEW[INT←maxGroupId]]
};
AfterInput: CDEvents.EventProc = {
[] ← CheckMaxGroupId[design];
};
Convert: PROC [design: CD.Design] = {
ConvertCell: CDDirectory.EachEntryAction = {
IF CDCells.IsCell[ob] THEN {
DoInst: CDCells.InstEnumerator = {
IF CDTexts.IsText[inst.ob] THEN
IF CDProperties.GetProp[inst, $CDSatellitesGroupId]=NIL THEN
Associate[ob, inst]
};
[] ← CDCells.EnumerateInstances[ob, DoInst];
};
};
CDProperties.PutProp[design, $CDSatellitesMaxGroupId, NIL];
[] ← CDDirectory.Enumerate[design, ConvertCell];
FOR pl: LIST OF CD.PushRec ← design^.actual, pl.rest WHILE pl#NIL DO
[] ← ConvertCell[NIL, pl.first.dummyCell.ob]
ENDLOOP
};
CheckMaxGroupId: PROC [design: CD.Design] RETURNS [BOOLFALSE] = {
MaxGroupId: PROC [design: CD.Design] RETURNS [n: INT ← -1] = {
IF design#NIL THEN
WITH CDProperties.GetProp[design, maxGroupIdProp] SELECT FROM
i: REF INT => n ← i^;
ENDCASE => NULL;
};
IF design#NIL THEN {
n: INT ← MaxGroupId[design];
IF n<LAST[INT]-10000 THEN maxGroupId ← MAX[maxGroupId, n];
IF CDProperties.GetProp[design, $CDSatellitesMaxGroupId]#NIL THEN Convert[design];
};
};
--general utilities
RectMinDist: PROC [rectA, rectB: CD.Rect] RETURNS [INT] = INLINE {
IntervalMinDist: PROC [i1min, i1max, i2min, i2max: INT] RETURNS [INT] = INLINE {
IF i1max<i2min THEN RETURN [i2min-i1max];
IF i1min>i2max THEN RETURN [i1min-i2max];
RETURN [0];
};
xMinDist: INT ~ IntervalMinDist[rectA.x1, rectA.x2, rectB.x1, rectB.x2];
yMinDist: INT ~ IntervalMinDist[rectA.y1, rectA.y2, rectB.y1, rectB.y2];
RETURN [xMinDist+yMinDist]
};
IsItalic: PROC [fontName: ROPE] RETURNS [BOOLFALSE] = {
pressFontLeng: INT = 17; pressFontName: ROPE = "Xerox/PressFonts/";
tiogaFontLeng: INT = 17; tiogaFontName: ROPE = "Xerox/TiogaFonts/";
leng: INT ← Rope.Length[fontName];
IF leng>tiogaFontLeng THEN
IF Rope.Equal[Rope.Substr[fontName, 0, tiogaFontLeng], tiogaFontName, FALSE] THEN
RETURN [Ascii.Upper[Rope.Fetch[fontName, leng-1]]='I];
IF leng>pressFontLeng THEN
IF Rope.Equal[Rope.Substr[fontName, 0, pressFontLeng], pressFontName, FALSE] THEN
RETURN [Ascii.Upper[Rope.Fetch[fontName, leng-2]]='I]
};
[] ← CDProperties.RegisterProperty[satellitesProp, $CDSatellites];
[] ← CDProperties.RegisterProperty[maxGroupIdProp, $CDSatellites];
[] ← CDProperties.RegisterProperty[oGroupIdProp, $CDSatellites];
[] ← CDProperties.RegisterProperty[iGroupIdProp, $CDSatellites];
CDProperties.InstallProcs[satellitesProp, [makeCopy: CDProperties.CopyVal, autoRem: TRUE]];
CDProperties.InstallProcs[maxGroupIdProp, [makeCopy: CDProperties.CopyVal]];
CDProperties.InstallProcs[oGroupIdProp, [makeCopy: CDProperties.CopyVal, autoRem: FALSE]];
CDProperties.InstallProcs[iGroupIdProp, [makeCopy: CDProperties.CopyVal, autoRem: FALSE]];
CDEvents.RegisterEventProc[$BeforeOutput, BeforeOutput];
CDEvents.RegisterEventProc[$AfterInput, AfterInput];
[] ← CDPrivate.EnumDesigns[CheckMaxGroupId];
END.