-- Compiler ObjectOps/n;bind griffin;griffin
-- mstone November 10, 1980 5:29 PM
-- Last Edited by: Stone, November 14, 1983 3:08 pm

DIRECTORY
 GriffinDefs: FROM "GriffinDefs",
 Rope USING [ROPE, Equal],
 MenuDefs: FROM "MenuDefs",
 ObjectDefs: FROM "ObjectDefs",
 RealFns: FROM "RealFns",
 StyleDefs: FROM "StyleDefs",
 PointDefs: FROM "PointDefs",
 OpDefs: FROM "OpDefs",
 XFormDefs: FROM "XFormDefs",
 GriffinMemoryDefs: FROM "GriffinMemoryDefs",
 RefreshDefs: FROM "RefreshDefs",
 ScreenDefs USING [ClipPointToScreen],
 GriffinMenusInterface,
 RelationDefs: FROM "RelationDefs";

ObjectOps: PROGRAM
IMPORTS GriffinDefs, MenuDefs, ObjectDefs, Rope, GriffinMemoryDefs, RealFns, XFormDefs, PointDefs, RefreshDefs, RelationDefs, ScreenDefs
EXPORTS GriffinDefs, OpDefs, GriffinMenusInterface =
BEGIN OPEN ObjectDefs,PointDefs,GriffinMemoryDefs,RefreshDefs;
xftype: {map,scale,scalex,scaley,rotate};
oltype: {top,bottom,up1,down1};
TL,BR: ScrPt;
ROPE: TYPE = Rope.ROPE;

OverallMBB: PROCEDURE[obj: ObjectHandle]=
BEGIN
IF obj.tl[X]<TL[X] THEN TL[X] ← obj.tl[X];
IF obj.tl[Y]<TL[Y] THEN TL[Y] ← obj.tl[Y];
IF obj.br[X] >BR[X] THEN BR[X] ← obj.br[X];
IF obj.br[Y] >BR[Y] THEN BR[Y] ← obj.br[Y];
END;

AnySelected: PROCEDURE RETURNS[BOOLEAN] =
BEGIN
anyselected: BOOLEANFALSE;
IsSelected: PROCEDURE[obj: ObjectHandle]=BEGIN
 anyselected ← TRUE;
END;
ForAllSelectedDo[IsSelected];
RETURN[anyselected];
END;


Delete: PUBLIC MenuDefs.MenuProc =
BEGIN
DeleteObjs: PROCEDURE[obj: ObjectHandle]=BEGIN
 DeSelectObject[obj];
 EraseAndSave[obj];
 obj.deleted ← TRUE;
END;
IF AnySelected[] THEN BEGIN
 ExpungeObjects[];
 ForAllSelectedDo[DeleteObjs];
 RestoreScreen[];
END;
END;

Undo: PUBLIC MenuDefs.MenuProc =
BEGIN
Undelete: ObjectProc = BEGIN
IF ~obj.deleted THEN RETURN;
 obj.deleted ← FALSE;
IF obj.cluster # 0 THEN [] ← SelectCluster[obj.cluster]
  ELSE [] ← SelectObject[obj];
 MarkBox[obj.tl,obj.br,obj];
END;
ForAllObjects[Undelete];
RestoreScreen[];
END;

Transfer: PUBLIC MenuDefs.MenuProc =
BEGIN
view: View ← IF objectMenu.view=main THEN alternate ELSE main;
token: ObjectHandle ← NIL;
TransferObjs: PROCEDURE[obj: ObjectHandle]=BEGIN
 EraseAndSave[obj];
IF (token ← ReturnSelected[obj]) # NIL
  THEN BEGIN
   EraseAndSave[token];
   token.view ← view;
   END;
 obj.view ← view;
END;
IF ~AnySelected[] THEN GriffinDefs.UserMessage["Please make a selection"];
ForAllSelectedDo[TransferObjs];
RestoreScreen[];
END;

SelObj: TYPE = RECORD[up,down: REF SelObj, item: ObjectHandle];

ChangeOverlap: PUBLIC MenuDefs.MenuProc =
BEGIN
string: ROPE ← MenuDefs.MenuString[item];
topObj,bottomObj: REF SelObj ← NIL;
ptr,next: REF SelObj ← NIL;

ListSelected: PROCEDURE[obj: ObjectHandle]=BEGIN
 selObj: REF SelObj 𡤌Zone.NEW[SelObj];
 selObj^ ← [up: NIL,down: topObj,item: obj];
IF bottomObj=NIL THEN bottomObj ← topObj ← selObj
ELSE BEGIN topObj.up ← selObj; topObj ← selObj; END;
END;
Up: PROCEDURE=BEGIN
 selObj: REF SelObj ←topObj;
FOR selObj ← topObj, selObj.down UNTIL selObj=NIL DO
  FlipUpObject[selObj.item];
  ENDLOOP;
END;
Down: PROCEDURE=BEGIN
 selObj: REF SelObj ;
FOR selObj ← bottomObj, selObj.up UNTIL selObj=NIL DO
  FlipDownObject[selObj.item];
  ENDLOOP;
END;
Bottom: PROCEDURE=BEGIN
 selObj: REF SelObj ←topObj;
FOR selObj ← topObj, selObj.down UNTIL selObj=NIL DO
  SinkObject[selObj.item];
  ENDLOOP;
END;
Top: PROCEDURE=BEGIN
 selObj: REF SelObj ;
FOR selObj ← bottomObj, selObj.up UNTIL selObj=NIL DO
  FloatObject[selObj.item];
  ENDLOOP;
END;
--make a list of all items affected. Work in different order for different cases
IF ~AnySelected[] THEN GriffinDefs.UserMessage["Please make a selection"];
ForAllSelectedDo[ListSelected];
IF bottomObj=NIL THEN RETURN; --none found
oltype ← SELECT TRUE FROM
 Rope.Equal[string,"Top",FALSE] => top,
 Rope.Equal[string,"Bottom",FALSE] => bottom,
 Rope.Equal[string,"Up One",FALSE] => up1,
 Rope.Equal[string,"Down One",FALSE] => down1,
ENDCASE => ERROR;
SELECT oltype FROM
 top => Top[];
 bottom => Bottom[];
 up1 => Up[];
 down1 => Down[];
ENDCASE;

ForAllSelectedDo[ PlotAndMark];
RestoreScreen;
ptr ← bottomObj;
UNTIL ptr=NIL DO
 next ←ptr.up;
 ptr.up ← NIL;
 ptr ← next;
ENDLOOP;
END;

XForm: PUBLIC MenuDefs.MenuProc =
BEGIN
ENABLE XFormDefs.ProblemWithXForms => BEGIN
  IF Rope.Equal[string,"Singular transform",FALSE] THEN
   SIGNAL GriffinDefs.UserMessage["Transform will flatten picture"]
  ELSE SIGNAL GriffinDefs.UserMessage["ProblemWith Transform"];
  END;

string: ROPE ← MenuDefs.MenuString[item];
cps: PointDefs.ObjPtSequence ← ReadCPs[];
matrix: XFormDefs.XFMDescriptor ← CZone.NEW[XFormDefs.XFormMatrix];
clusterRelation: RelationDefs.Relation;
MoveObjs: PROCEDURE[obj: ObjectHandle]=BEGIN
 MoveObject[obj,ObjToScr[cps[0]],ObjToScr[cps[1]]];
END;
CopyObjs: PROCEDURE[obj: ObjectHandle]=BEGIN
OPEN RelationDefs;
 newobj: ObjectHandle;
 newcluster: ClusterID;
IF NOT obj.selected OR NOT Visible[obj] THEN RETURN;
 newobj ← CopyObject[obj];
IF obj.cluster # 0 THEN
  BEGIN
  IF Right[clusterRelation,obj.cluster] = notFound
   THEN BEGIN
   newcluster ← GetNextClusterID[];
   AddPair[clusterRelation, obj.cluster,newcluster];
   END
  ELSE newcluster ← Right[clusterRelation,obj.cluster];
  EraseAndSave[ReturnSelected[obj]];
  newobj.cluster ← newcluster;
  END
ELSE BEGIN
  [] ← SelectObject[newobj];
  EraseAndSave[ReturnSelected[obj]];
  DeSelectObject[obj];
  END; 
END;
SelectNewClusters: PROCEDURE[leftPart, rightPart: CARDINAL] =
BEGIN
-- selects new clusters formed after XForm with copy
 [] ← SelectCluster[rightPart];
 DeSelectCluster[leftPart];
END;

XFormObjs: PROCEDURE[obj: ObjectHandle]=BEGIN
 ObjectDefs.XFormObject[obj,matrix];
END;

IF cps=NIL OR cps.length <=1 THEN BEGIN
 GriffinDefs.UserMessage["All transforms need at least two points"];
RETURN;
END;
IF ~AnySelected[] THEN GriffinDefs.UserMessage["Please make a selection"];

XFormDefs.InitXForms[matrix];
xftype ← SELECT TRUE FROM
 Rope.Equal[string,"Map",FALSE] => map,
 Rope.Equal[string,"Scale",FALSE] => scale,
 Rope.Equal[string,"ScaleX",FALSE] => scalex,
 Rope.Equal[string,"ScaleY",FALSE] => scaley,
 Rope.Equal[string,"Rotate",FALSE] => rotate,
ENDCASE => ERROR;
SELECT xftype FROM
 map => Map[matrix];
 scale => Scale[matrix,both];
 scalex => Scale[matrix,xonly];
 scaley => Scale[matrix,yonly];
 rotate => Rotate[matrix];
ENDCASE;

EraseAndSaveAllCPs[];
DeleteAllCPs[];
--will use the selection
IF MenuDefs.IsSelected[copy] THEN
BEGIN
 clusterRelation ← RelationDefs.CreateRelation[];
 ForAllObjectsThroughObject[CopyObjs, GetTopPictureObj[]];
 RelationDefs.ForAllPairs[clusterRelation,SelectNewClusters];
 RelationDefs.DestroyRelation[clusterRelation];
END
ELSE ForAllSelectedDo[ EraseAndSave];
IF cps.length=2 AND xftype=map THEN BEGIN
 ForAllSelectedDo[MoveObjs];
END
ELSE BEGIN
 ForAllSelectedDo[XFormObjs];
END;
ForAllSelectedDo[ PlotAndMark];
RestoreScreen[];
END;

Toggle: PUBLIC MenuDefs.MenuProc =
BEGIN OPEN MenuDefs;
IF IsSelected[item] THEN Deselect[item] ELSE Select[item];
END;

Rotate: PROCEDURE[matrix: XFormDefs.XFMDescriptor] =
BEGIN OPEN XFormDefs;
cps: PointDefs.ObjPtSequence ← ReadCPs[];
npts: INTEGER ← (IF cps=NIL THEN 0 ELSE cps.length);
dorig: ObjPt ← cps[0];
negdorig: ObjPt ← [-cps[0][X],-cps[0][Y]];
dx,dy,theta: REAL;
IF npts>3 THEN GriffinDefs.UserMessage["Only 2 and 3 point rotate is valid"];
dx ← cps[1][X]-cps[0][X];
dy ← cps[1][Y]-cps[0][Y];
theta ← RealFns.ArcTan[dy,dx];
IF npts>=3 THEN BEGIN
 theta1: REAL;
 dx ← cps[2][X]-cps[0][X];
 dy ← cps[2][Y]-cps[0][Y];
 theta1 ← RealFns.ArcTan[dy,dx];
 theta ← theta1-theta;
END;
Translate[dorig,matrix];
Rotate[theta,z,matrix];
Translate[negdorig,matrix];
END;

--will mirror. Imagine a pinned point and a new point
Scale: PROCEDURE[matrix: XFormDefs.XFMDescriptor,direction: {both,xonly,yonly}] =
BEGIN OPEN XFormDefs;
cps: PointDefs.ObjPtSequence ← ReadCPs[];
npts: INTEGER ← (IF cps=NIL THEN 0 ELSE cps.length);
sx,sy: REAL;
dorig: ObjPt ← cps[0];
tl,br: ObjPt;
negdorig: ObjPt ← [-cps[0][X],-cps[0][Y]];
dx1,dy1,dx2,dy2: REAL;
IF npts>3 THEN GriffinDefs.UserMessage["Only 2 and 3 point scale is valid"];
--scale max dimension into 2 pt span
IF npts=2 THEN BEGIN
TL ← [77777B,77777B]; --init for OverallMBB
BR ← [0,0];
 ForAllSelectedDo[OverallMBB];
 tl ← ScrToObj[TL];
 br ← ScrToObj[BR];
 dx1 ← br[X]-tl[X];
 dy1 ← tl[Y]-br[Y]; --object space
 dx2 ← ABS[cps[1][X]-cps[0][X]];
 dy2 ← ABS[cps[1][Y]-cps[0][Y]];
END
-- assume 3 points
ELSE BEGIN
 dx2 ← cps[2][X]-cps[0][X];
 dy2 ← cps[2][Y]-cps[0][Y];
 dx1 ← cps[1][X]-cps[0][X];
 dy1 ← cps[1][Y]-cps[0][Y];
END;
Translate[dorig,matrix];
SELECT direction FROM
 both => BEGIN
  IF dx1=0 OR dy1=0 THEN GriffinDefs.UserMessage["Transform will flatten picture"];
  sx ← dx2/dx1;
  sy ← dy2/dy1;
  Scale[[sx,sy],matrix];
  END;
 xonly => BEGIN
  IF dx1=0 THEN GriffinDefs.UserMessage["Transform will flatten picture"];
  sx ← dx2/dx1;
  Scale[[sx,1],matrix];
  END;
 yonly => BEGIN
  IF dy1=0 THEN GriffinDefs.UserMessage["Transform will flatten picture"];
  sy ← dy2/dy1;
  Scale[[1,sy],matrix];
  END;
ENDCASE;
Translate[negdorig,matrix];
END;

Map: PROCEDURE[matrix: XFormDefs.XFMDescriptor] =
BEGIN OPEN XFormDefs;
cps: PointDefs.ObjPtSequence ← ReadCPs[];
npts: INTEGER ← (IF cps=NIL THEN 0 ELSE cps.length);
pts: PointDefs.ObjPtSequence;
dorig: ObjPt ← cps[0];
negdorig: ObjPt ← [-cps[0][X],-cps[0][Y]];
IF npts>6 THEN GriffinDefs.UserMessage["Maximum of 6 points for map"];
SELECT npts FROM
--0,1 NOP, 2 is a move, which is done above
 =3 => BEGIN
  pts ← CZone.NEW[ObjPtSequenceRec[4]];
  pts[0] ← cps[0];
  pts[1] ← cps[1];
  pts[2] ← cps[0];
  pts[3] ← cps[2];
  npts ← 4;
  END;
 =4 => pts ← cps;
 =5 => BEGIN
  pts ← CZone.NEW[ObjPtSequenceRec[6]];
  pts[0] ← cps[0];
  pts[1] ← cps[1];
  pts[2] ← cps[2];
  pts[3] ← cps[0];
  pts[4] ← cps[3];
  pts[5] ← cps[4];
  npts ← 6;
  END;
ENDCASE=> pts ← cps;
Translate[dorig,matrix];
SELECT npts FROM
 =4 => XForm4Pts[pts,matrix];
 >=6 => XForm6Pts[pts,matrix];
ENDCASE;
Translate[negdorig,matrix];
END;

Cluster: PUBLIC MenuDefs.MenuProc =
BEGIN
id: ClusterID ← GetNextClusterID[];
called: BOOLEANFALSE;
token: ObjectHandle ← NIL;
clu: ObjectProc=BEGIN
 obj.cluster ← id;
 called ← TRUE;
IF token # NIL
THEN token ← DeleteObject[token];
 token ← ReturnSelected[obj];
 EraseAndSave[token];
END;
IF ~AnySelected[] THEN GriffinDefs.UserMessage["Please make a selection"];
ForAllSelectedDo[clu];
IF called THEN BEGIN
 GriffinDefs.ShowUserMessage["New cluster made"];
 RestoreScreen[];
END;
END;

UnCluster: PUBLIC MenuDefs.MenuProc =
BEGIN
called: BOOLEANFALSE;
ctoken: ObjectHandle;
clu: ObjectProc=BEGIN
IF obj.cluster IN [0..OpenCluster] THEN RETURN;
 obj.cluster ← 0;
 called ← TRUE;
IF (ctoken ← ReturnSelected[obj]) # NIL THEN
  BEGIN
  EraseAndSave[ctoken];
  RETURN;
  END;
 obj.selected ← FALSE;
 [] ← SelectObject[obj];
END;
IF ~AnySelected[] THEN GriffinDefs.UserMessage["Please make a selection"];
ForAllSelectedDo[clu];
IF called THEN GriffinDefs.ShowUserMessage["Cluster unmade"];
END;

DeselectAll: PUBLIC MenuDefs.MenuProc =
BEGIN
DS: ObjectProc = BEGIN
 EraseAndSave[ReturnSelected[obj]];
 DeSelectObject[obj];
END;
ObjectDefs.ForAllSelectedDo[DS];
RestoreScreen[];
END;

SelectAll: PUBLIC MenuDefs.MenuProc =
BEGIN OPEN ObjectDefs;
SelectObjs: ObjectProc=BEGIN
 token: ObjectHandle ← IF obj.cluster = 0
   THEN SelectObject[obj]
   ELSE SelectCluster[obj.cluster];
IF token#NIL THEN PlotAndMark[token];
END;
ForAllVisibleObjects[SelectObjs];
RestoreScreen[];
END;

--make a translated copy of indicated object or set, given xy,xy
CopyObjects: PUBLIC PROCEDURE[Down,Up: ScrPt] =
BEGIN OPEN ObjectDefs;
new: ObjectHandle ← GetObjectHandle[Down];
selected: BOOLEAN;
id: ClusterID ← 0;
co: ObjectProc = BEGIN
IF selected THEN BEGIN
  EraseAndSave[ReturnSelected[obj]];
  IF id = 0 THEN DeSelectObject[obj];
  END;
 obj ← CopyObject[obj] ;
 obj.cluster ← id;
 MoveObject[obj,Down,Up];
 PlotAndMark[obj];
IF selected AND id = 0 THEN [] ← SelectObject[obj];
  -- clusters are selected below.
END;
IF new=NIL THEN RETURN;
selected ← new.selected;
IF new.objectType=menu OR new.objectType=token OR new.cluster=OpenCluster
THEN RETURN;
--click doesn't got thru move/refresh stuff
IF ABS[Down[X]-Up[X]]<1 AND ABS[Down[Y]-Up[Y]]<1 THEN RETURN;
IF new.cluster=0
THEN co[new] -- not a clustered object
ELSE BEGIN
  id ← GetNextClusterID[];
  ForAllInCluster[new.cluster,co];
  IF selected THEN BEGIN
   DeSelectCluster[new.cluster];
   [] ← SelectCluster[id];
   END;
  END;
RestoreScreen[];
END;

MoveObjects: PUBLIC PROCEDURE[Down,Up: ScrPt] =
BEGIN
object: ObjectDefs.ObjectHandle ← ObjectDefs.GetObjectHandle[Down] ;
move: ObjectProc = BEGIN
 EraseAndSave[obj];
 ObjectDefs.MoveObject[obj,Down,Up];
 PlotAndMark[obj];
END;
IF object=NIL THEN RETURN;
--click doesn't got thru move/refresh stuff
IF ABS[Down[X]-Up[X]]<1 AND ABS[Down[Y]-Up[Y]]<1 THEN RETURN;
IF object.cluster=OpenCluster THEN RETURN; --unfortuneate
IF object.cluster=0 THEN move[object] ELSE ForAllInCluster[object.cluster,move];
RestoreScreen[];
END;

PlaceControlPoint: PUBLIC PROCEDURE[pt: ScrPt] =
BEGIN
top: ObjectHandle ← GetObjectHandle[pt];
IF top=NIL OR top.objectType#menu
THEN ObjectDefs.AddToken[ScreenDefs.ClipPointToScreen[pt], CP]
END;

DeleteControlPoint: PUBLIC PROCEDURE[Down,Up: ScrPt] =
BEGIN OPEN ObjectDefs;
DelCP: ObjectProc = BEGIN
IF obj=NIL THEN RETURN;
WITH obj SELECT FROM
 token=>
  IF tokenType = CP THEN BEGIN
   EraseAndSave[obj];
   []�leteObject[obj];
  END;
ENDCASE;
END;
IF NOT ForAllInBoxDo[Down, Up, DelCP] THEN
 ForObjectPointedAtDo[Down, Up, DelCP];
RestoreScreen[];
END;

DeselectObjects: PUBLIC PROCEDURE[Down,Up: ScrPt] =
BEGIN
Desel: ObjectProc = BEGIN
IF obj=NIL OR obj.objectType=token OR obj.objectType=menu THEN RETURN;
IF obj.cluster = 0 THEN DS[obj]
  ELSE ForAllInCluster[obj.cluster, DS];
END;
DS: ObjectProc = BEGIN
 EraseAndSave[ReturnSelected[obj]];
 DeSelectObject[obj];
END;
IF NOT ForAllPictureObjectsInBoxDo[Down, Up, Desel]
THEN ForObjectPointedAtDo[Down, Up, Desel];
RestoreScreen[];
END;

SelectObjects: PUBLIC PROCEDURE[Down,Up: ScrPt] =
BEGIN OPEN ObjectDefs,RefreshDefs;
Sel: ObjectProc = BEGIN
IF obj=NIL OR obj.objectType=token OR obj.objectType=menu OR obj.cluster=OpenCluster
THEN RETURN;
S[obj];
END;
S: ObjectProc = BEGIN
 obj ← IF obj.cluster = 0 THEN SelectObject[obj]
  ELSE SelectCluster[obj.cluster];
  --get the token, may return NIL if already selected
  -- if clustered, will select whole cluster
IF obj#NIL THEN PlotAndMark[obj];
END;
IF NOT ForAllPictureObjectsInBoxDo[Down, Up, Sel]
THEN ForObjectPointedAtDo[Down, Up, Sel];
RestoreScreen[];
END;

ForObjectPointedAtDo: PROCEDURE[Down,Up: ScrPt,proc: ObjectProc] =
BEGIN
upObj,downObj: ObjectHandle;
upObj ← GetObjectHandle[Up];
IF upObj=NIL THEN RETURN;
downObj ← GetObjectHandle[Down];
IF upObj=downObj OR downObj=NIL THEN proc[upObj] ;
END;

objectMenu, xformMenu, overlapMenu: PUBLIC MenuDefs.MenuHandle←NIL;

copy: MenuDefs.MenuItemHandle←NIL;

StartObjectMenus
: PUBLIC PROC = {
 objectMenu ← MenuDefs.CreateMenu[horizontal, [100,600], NIL];
 []←MenuDefs.AddMenuItem[objectMenu,"Delete",Delete];
 []←MenuDefs.AddMenuItem[objectMenu,"Undo",Undo];
 []←MenuDefs.AddMenuItem[objectMenu,"Select all",SelectAll];
 []←MenuDefs.AddMenuItem[objectMenu,"Deselect all",DeselectAll];
 []←MenuDefs.AddMenuItem[objectMenu,"Cluster",Cluster];
 []←MenuDefs.AddMenuItem[objectMenu,"Uncluster",UnCluster];
 []←MenuDefs.AddMenuItem[objectMenu,"Transfer",Transfer];

 xformMenu ← MenuDefs.CreateMenu[vertical, [400,400], NIL];
 []←MenuDefs.AddMenuItem[xformMenu,"Map",XForm];
 []←MenuDefs.AddMenuItem[xformMenu,"Scale",XForm];
 []←MenuDefs.AddMenuItem[xformMenu,"ScaleX",XForm];
 []←MenuDefs.AddMenuItem[xformMenu,"ScaleY",XForm];
 []←MenuDefs.AddMenuItem[xformMenu,"Rotate",XForm];
 copy ← MenuDefs.AddMenuItem[xformMenu,"Use Copy",Toggle];

 overlapMenu ← MenuDefs.CreateMenu[vertical, [200,250], NIL];
 []←MenuDefs.AddMenuItem[overlapMenu,"Top",ChangeOverlap];
 []←MenuDefs.AddMenuItem[overlapMenu,"Bottom",ChangeOverlap];
 []←MenuDefs.AddMenuItem[overlapMenu,"Up One",ChangeOverlap];
 []←MenuDefs.AddMenuItem[overlapMenu,"Down One",ChangeOverlap];
 };

END.