CDSatellitesImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Written by Pradeep Sindhu December 20, 1985 1:25:44 pm PST
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
works and is comfortable to use both from programs and interactively. This last version is a
complete rewrite, one hopes for the last time!
Pradeep Sindhu December 23, 1985 2:51:44 am PST
DIRECTORY
CD, CDCells, CDDirectory, CDEvents, CDCommandOps, CDInstances, CDMenus, CDOps, CDPanelFonts, CDProperties, CDSatellites, CDSequencer, CDTexts, IO, PW, TerminalIO;
CDSatellitesImpl: CEDAR PROGRAM
IMPORTS CDEvents, CDCommandOps, CDDirectory, CDInstances, CDMenus, CDOps, CDPanelFonts, CDProperties, CDTexts, IO, PW, TerminalIO
EXPORTS CDSatellites =
BEGIN OPEN CDSatellites;
satellitesProp: PUBLIC ATOMPW.RegisterProp[$CDSatellites, TRUE];
This property hangs on a master. Its value is the list of the master's satellites.
maxGroupIdProp: PUBLIC ATOMPW.RegisterProp[$CDSatellitesMaxGroupId, TRUE];
This property hangs on a design. Its value is the highest numbered satellite group in the design.
groupIdProp: PUBLIC ATOMPW.RegisterProp[$CDSatellitesGroupId, TRUE];
This property hangs on each instance that is in a satellite group. Its value is the group's groupId.
prevNumMasters: NAT ← 0;
Public functions
AddSatellite: PUBLIC PROC [design: CD.Design, master: CD.Instance, satellite: CD.Instance] = {
EnsureSatelliteOnList[master, satellite];
IF CDProperties.GetProp[master, groupIdProp]=NIL THEN {
ref: REF INTNARROW[CDProperties.GetProp[design, maxGroupIdProp]];
ref^ ← ref^+1;
CDProperties.PutProp[master, groupIdProp, NEW [INT ← ref^]];
};
CDProperties.PutProp[satellite, groupIdProp, NEW[INT ← GroupId[master]]];
};
RemoveSatellite: PUBLIC PROC [world: InstanceList, satellite: CD.Instance] = {
sats: InstanceList;
master: CD.Instance ← FindMaster[world, satellite];
CDProperties.PutProp[satellite, groupIdProp, NIL];
IF master=NIL OR CDProperties.GetProp[master, satellitesProp]=NIL THEN RETURN;
sats ← NARROW[CDProperties.GetProp[master, satellitesProp]];
FOR sl: InstanceList ← NARROW[CDProperties.GetProp[master, satellitesProp]], sl.rest WHILE sl#NIL DO
IF sl.first#satellite THEN sats ← CONS[sl.first, sats]
ENDLOOP;
CDProperties.PutProp[master, satellitesProp, sats]
};
GetSatellites: PUBLIC PROC [master: CD.Instance] RETURNS [satellites: InstanceList ← NIL] = {
satellites ← NARROW [CDProperties.GetPropFromInstance[master, satellitesProp]];
};
GetSatelliteRopes: PUBLIC PROC [masterProps: CD.PropList] RETURNS [ropes: LIST OF ROPENIL] = {
FOR list: InstanceList ← NARROW [CDProperties.GetPropFromList[masterProps, satellitesProp]], list.rest WHILE list#NIL DO
rope: ROPENARROW [list.first.ob.specificRef, CDTexts.TextPtr].text;
ropes ← CONS [rope, ropes];
ENDLOOP;
};
EnforceInvariants: PUBLIC PROC [design: CD.Design, world: InstanceList] RETURNS [ok: BOOLTRUE] = {
Node: TYPE = REF NodeRec;
NodeRec: TYPE = RECORD [
id: INT ← -1,
list: InstanceList ← NIL];
Table: TYPE = REF TableRec;
TableRec: TYPE = RECORD [
elements: SEQUENCE size: NAT OF LIST OF Node
];
masterTable: Table ← NEW[TableRec[TableSize[]]];
satelliteList: InstanceList;
maxGroupId: INT;
TableSize: PROC [] RETURNS [NAT] = INLINE {
FOR sizes: LIST OF NATLIST [7, 17, 31, 67, 253], sizes.rest WHILE sizes#NIL DO
IF prevNumMasters <= sizes.first THEN RETURN [sizes.first]
ENDLOOP;
RETURN [253]
};
Hash: PROC [i: INT] RETURNS [INT] = {
RETURN[i MOD masterTable.size]
};
Store: PROC [inst: CD.Instance] = {
id: INT ← GroupIdFast[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]];
};
Fetch: PROC [id: INT] RETURNS [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];
};
Check that maxGroupIdProp exists; if not, put it
IF CDProperties.GetProp[design, maxGroupIdProp]=NIL THEN CDProperties.PutProp[design, maxGroupIdProp, NEW [INT ← 0]];
Make masterTable and satelliteList; at the same time strip masters of their satellite lists
FOR w: InstanceList ← world, w.rest WHILE w#NIL DO
IF CDTexts.IsText[w.first.ob]
THEN {IF CDProperties.GetProp[w.first, groupIdProp]#NIL THEN satelliteList ← CONS [w.first, satelliteList]}
ELSE {IF CDProperties.GetProp[w.first, groupIdProp]#NIL THEN {Store[w.first]; CDProperties.PutProp[w.first, satellitesProp, NIL]}}
ENDLOOP;
For each satellite find its closest master and put it on that master's satellite list
FOR sl: InstanceList ← satelliteList, sl.rest WHILE sl#NIL DO
Closer: PROC [newMaster: CD.Instance] RETURNS [BOOL] = INLINE {
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 ← GroupIdFast[sl.first];
FOR ml: InstanceList ← Fetch[satGId], ml.rest WHILE ml#NIL DO
IF satGId#GroupIdFast[ml.first] THEN LOOP;
IF Closer[ml.first] THEN master ← ml.first;
ENDLOOP;
IF master=NIL
THEN CDProperties.PutProp[sl.first, groupIdProp, NIL]
ELSE EnsureSatelliteOnList[master, sl.first];
ENDLOOP;
Now go through the masterTable and renumber groups with identical group Id's
maxGroupId ← NARROW [CDProperties.GetProp[design, maxGroupIdProp], REF INT]^;
prevNumMasters ← 0;
FOR i: INT IN [0..masterTable.size) DO
FOR nl: LIST OF Node ← masterTable[i], nl.rest WHILE nl#NIL DO
prevNumMasters ← prevNumMasters+1;
FOR ml: InstanceList ← nl.first.list.rest, ml.rest WHILE ml#NIL DO
maxGroupId ← maxGroupId+1;
CDProperties.PutProp[ml.first, groupIdProp, NEW [INT ← maxGroupId]];
FOR sl: InstanceList ← NARROW[CDProperties.GetProp[ml.first, satellitesProp]], sl.rest WHILE sl#NIL DO
CDProperties.PutProp[sl.first, groupIdProp, NEW [INT ← maxGroupId]];
ENDLOOP;
ENDLOOP;
ENDLOOP;
ENDLOOP;
CDProperties.PutProp[design, maxGroupIdProp, NEW [INT ← maxGroupId]]
};
EnforceInvariants: PUBLIC PROC [design: CD.Design, world: InstanceList] RETURNS [ok: BOOLTRUE] = {
masterList, satelliteList: InstanceList;
maxGroupId: INT;
Check that maxGroupIdProp exists; if not, put it
IF CDProperties.GetProp[design, maxGroupIdProp]=NIL THEN CDProperties.PutProp[design, maxGroupIdProp, NEW [INT ← 0]];
Make masterList and satelliteList, at the same time stripping masters of their satellite lists
FOR w: InstanceList ← world, w.rest WHILE w#NIL DO
IF CDTexts.IsText[w.first.ob]
THEN {IF CDProperties.GetProp[w.first, groupIdProp]#NIL THEN satelliteList ← CONS [w.first, satelliteList]}
ELSE {IF CDProperties.GetProp[w.first, groupIdProp]#NIL THEN {masterList ← CONS [w.first, masterList]; CDProperties.PutProp[w.first, satellitesProp, NIL]}}
ENDLOOP;
For each satellite find its master and put it on that master's satellite list
FOR sl: InstanceList ← satelliteList, sl.rest WHILE sl#NIL DO
Closer: PROC [newMaster: CD.Instance] RETURNS [BOOL] = INLINE {
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 ← GroupIdFast[sl.first];
FOR ml: InstanceList ← masterList, ml.rest WHILE ml#NIL DO
IF satGId#GroupIdFast[ml.first] THEN LOOP;
IF Closer[ml.first] THEN master ← ml.first;
ENDLOOP;
IF master=NIL
THEN CDProperties.PutProp[sl.first, groupIdProp, NIL]
ELSE EnsureSatelliteOnList[master, sl.first];
ENDLOOP;
Now go through and renumber groups with identical group Id's
maxGroupId ← NARROW [CDProperties.GetProp[design, maxGroupIdProp], REF INT]^;
FOR ml: InstanceList ← masterList, ml.rest WHILE ml#NIL DO
IF ~GroupMember[ml.rest, ml.first] THEN LOOP;
maxGroupId ← maxGroupId+1;
CDProperties.PutProp[ml.first, groupIdProp, NEW [INT ← maxGroupId]];
FOR sl: InstanceList ← NARROW[CDProperties.GetProp[ml.first, satellitesProp]], sl.rest WHILE sl#NIL DO
CDProperties.PutProp[sl.first, groupIdProp, NEW [INT ← maxGroupId]];
ENDLOOP;
ENDLOOP;
CDProperties.PutProp[design, maxGroupIdProp, NEW [INT ← maxGroupId]]
};
Event Procs for reading and writing satellites from files
BeforeOutput: CDEvents.EventProc = {
[] ← EnforceInvariants[design, CDOps.InstList[design]]
};
AfterInput: CDEvents.EventProc = {
EnforceForEachCell: CDDirectory.EachEntryAction = {
[] ← EnforceInvariants[design, NARROW[ob.specificRef, CD.CellPtr].contents]
};
[] ← CDDirectory.Enumerate[design, EnforceForEachCell];
[] ← EnforceInvariants[design, CDOps.InstList[design]]
};
Event Proc for processing a cell change
AfterCellReplacement: CDEvents.EventProc = {
ob: CD.Object ← NARROW [x];
[] ← EnforceInvariants[design, NARROW[ob.specificRef, CD.CellPtr].contents];
};
Command Procs
Given a satellite or a master select the whole group.
SelectGroupCommand: PROC [comm: CDSequencer.Command] = {
world: InstanceList ← CDOps.InstList[comm.design];
multiple: BOOL;
selected: CD.Instance;
numSats: INT;
[] ← EnforceInvariants[comm.design, world];
[selected, multiple] ← CDOps.SelectedInstance[comm.design];
IF ~SingleSelected[selected, multiple] THEN RETURN;
IF CDProperties.GetProp[selected, groupIdProp]=NIL THEN {
TerminalIO.WriteRope["Selected instance is not in any group.\n"];
RETURN
};
IF CDTexts.IsText[selected.ob]
THEN numSats ← SelectGroup[comm.design, world, FindMaster[world, selected]]
ELSE numSats ← SelectGroup[comm.design, world, selected];
TerminalIO.WriteF["Group selected: %g satellites(s) in group.\n", IO.int[numSats]];
};
Adds a single satellite to those already existing for a master.
AddSatelliteCommand: PROC [comm: CDSequencer.Command] = {
world: InstanceList ← CDOps.InstList[comm.design];
multiple: BOOL;
selected: CD.Instance;
name: ROPE;
font: CDTexts.CDFont ← CDPanelFonts.CurrentFont[comm.design];
text: CD.Instance;
[] ← EnforceInvariants[comm.design, world];
[selected, multiple] ← CDOps.SelectedInstance[comm.design];
IF ~SingleSelected[selected, multiple] THEN RETURN;
IF CDTexts.IsText[selected.ob] THEN {
TerminalIO.WriteRope["\n** Selected instance is text--can't put a satellite on it.\n"];
RETURN;
};
name ← TerminalIO.RequestRope["Type Satellite: "];
IF name=NIL THEN {TerminalIO.WriteRope["\n** Empty name--can't do it.\n"]; RETURN};
IF font=NIL THEN {TerminalIO.WriteRope["\n** No current font--can't do it.\n"]; RETURN};
text ← NEW [CD.InstanceRep ← [ob: CDTexts.CreateText[name, font], location: comm.pos]];
CDOps.IncludeInstance[comm.design, text];
AddSatellite[comm.design, selected, text];
TerminalIO.WriteRope["Satellite added\n"];
};
Given zero or more text instances and exactly one non-text instance (the master) remove any previous satellites and add the ones currently selected.
SetSatellitesCommand: PROC [comm: CDSequencer.Command] = {
world: InstanceList ← CDOps.InstList[comm.design];
satellites: InstanceList ← NIL;
master: CD.Instance ← NIL;
[] ← EnforceInvariants[comm.design, world];
Find the master and put selected text onto the list of satellites
FOR w: InstanceList ← world, w.rest WHILE w#NIL DO
inst: CD.Instance ← w.first;
IF ~inst.selected THEN LOOP;
IF CDTexts.IsText[inst.ob]
THEN satellites ← CONS[inst, satellites]
ELSE {
IF master#NIL THEN {TerminalIO.WriteRope["\n** More than one master selected--can't do it.\n"]; RETURN};
master ← inst}
ENDLOOP;
IF master=NIL THEN {TerminalIO.WriteRope["\n** No master selected--can't do it.\n"]; RETURN};
Remove master's old satellites, if any
IF CDProperties.GetProp[master, satellitesProp]#NIL THEN
FOR sl: InstanceList ← NARROW [CDProperties.GetProp[master, satellitesProp]], sl.rest WHILE sl#NIL DO
RemoveSatellite[world, sl.first];
ENDLOOP;
Remove each satellite from its previous master, if any, and put it on to the new one
FOR sl: InstanceList ← satellites, sl.rest WHILE sl#NIL DO
RemoveSatellite[world, sl.first];
AddSatellite[comm.design, master, sl.first];
ENDLOOP;
TerminalIO.WriteRope["Satellites set\n"];
};
Internal Utilities
GroupId: PROC [inst: CD.Instance] RETURNS [INT] = INLINE {
IF CDProperties.GetProp[inst, groupIdProp]#NIL
THEN RETURN[NARROW[CDProperties.GetProp[inst, groupIdProp], REF INT]^]
ELSE RETURN[-1];
};
GroupIdFast: PROC [inst: CD.Instance] RETURNS [INT] = INLINE {
RETURN[NARROW[CDProperties.GetProp[inst, groupIdProp], REF INT]^]
};
EnsureSatelliteOnList: PROC [master: CD.Instance, satellite: CD.Instance] = {
sats: InstanceList;
IF CDProperties.GetPropFromInstance[master, satellitesProp]=NIL
THEN sats ← LIST[satellite]
ELSE {
sats ← NARROW[CDProperties.GetPropFromInstance[master, satellitesProp], InstanceList];
IF ~Member[sats, satellite] THEN sats ← CONS[satellite, sats];
};
CDProperties.PutPropOnInstance[master, satellitesProp, sats];
};
Returns true if inst is in the list insances
Member: PROC [insances: InstanceList, inst: CD.Instance] RETURNS [BOOLFALSE] = {
WHILE insances#NIL DO
IF insances.first=inst THEN RETURN [TRUE]; insances ← insances.rest;
ENDLOOP;
};
Returns true if some instance in intances has the same group Id as inst.
GroupMember: PROC [instances: InstanceList, inst: CD.Instance] RETURNS [BOOLFALSE] = {
instGId: INT ← GroupId[inst];
WHILE instances#NIL DO
IF GroupId[instances.first]=instGId THEN RETURN [TRUE]; instances ← instances.rest;
ENDLOOP;
};
FindMaster: PROC [world: InstanceList, satellite: CD.Instance] RETURNS [master: CD.Instance ← NIL] = {
satGId: INT ← GroupId[satellite];
FOR w: InstanceList ← world, w.rest WHILE w#NIL DO
IF satGId=GroupId[w.first] AND ~CDTexts.IsText[w.first.ob] THEN RETURN [w.first];
ENDLOOP;
};
SingleSelected: PROC [selected: CD.Instance, multiple: BOOL] RETURNS [BOOL] = {
IF selected=NIL THEN {
TerminalIO.WriteRope["\n** No current selection--can't do it.\n"];
RETURN[FALSE];
};
IF multiple THEN {
TerminalIO.WriteRope["\n** Multiple instances selected--can't do it.\n"];
RETURN[FALSE];
};
RETURN[TRUE];
};
Select the satellite group for master
SelectGroup: PROC [design: CD.Design, world: LIST OF CD.Instance, master: CD.Instance] RETURNS [numSats: INT] = {
satellites: InstanceList;
numSats ← 0;
IF master=NIL THEN RETURN ELSE satellites ← GetSatellites[master];
master.selected ← TRUE;
CDCommandOps.RedrawInstance[design, master];
FOR w: InstanceList ← world, w.rest WHILE w#NIL DO
IF ~Member[satellites, w.first] THEN LOOP;
w.first.selected ← TRUE; numSats ← numSats + 1;
CDCommandOps.RedrawInstance[design, w.first];
ENDLOOP;
};
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];
};
RectMinDist: PROC [rectA, rectB: CD.Rect] RETURNS [INT] = INLINE {
xMinDist: INT ← IntervalMinDist[rectA.x1, rectA.x2, rectB.x1, rectB.x2];
yMinDist: INT ← IntervalMinDist[rectA.y1, rectA.y2, rectB.y1, rectB.y2];
IF xMinDist=0
THEN IF yMinDist=0 THEN RETURN [0] ELSE RETURN [yMinDist]
ELSE IF yMinDist=0 THEN RETURN [xMinDist] ELSE RETURN [xMinDist+yMinDist]
};
Initialization
CDMenus.ImplementEntryCommand[menu: $OtherProgramMenu, entry: "Select Group (O-Left)", p: SelectGroupCommand, key: $SelectGroup];
CDMenus.ImplementEntryCommand[menu: $OtherProgramMenu, entry: "Add Satellite (O-Middle)", p: AddSatelliteCommand, key: $AddSatellite, queue: doQueueAndMark];
CDMenus.ImplementEntryCommand[menu: $OtherProgramMenu, entry: "Set Satellites (O-Right)", p: SetSatellitesCommand, key: $SetSatellites, queue: doQueueAndMark];
CDEvents.RegisterEventProc[$BeforeOutput, BeforeOutput];
CDEvents.RegisterEventProc[$AfterInput, AfterInput];
CDEvents.RegisterEventProc[$AfterCellReplacement, AfterCellReplacement];
END.