-- Compiler ObjectOps/n;bind griffin;griffin
-- mstone November 10, 1980  5:29 PM

DIRECTORY
	GriffinDefs: FROM "GriffinDefs",
	GriffinStartDefs: FROM "GriffinStartDefs",
	StringDefs: FROM "StringDefs",
	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: FROM "ScreenDefs" USING [ClipPointToScreen],
	RelationDefs: FROM "RelationDefs";

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

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: BOOLEAN ← FALSE;
IsSelected: PROCEDURE[obj:  ObjectHandle]=BEGIN
	anyselected ← TRUE;
	END;
ForAllSelectedDo[IsSelected];
RETURN[anyselected];
END;


Delete: 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: 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: 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: POINTER TO SelObj, item: ObjectHandle];

ChangeOverlap: MenuDefs.MenuProc =
BEGIN OPEN StringDefs;
string: STRING ← MenuDefs.MenuString[item];
topObj,bottomObj: POINTER TO SelObj ← NIL;
ptr,next: POINTER TO SelObj ← NIL;

ListSelected: PROCEDURE[obj:  ObjectHandle]=BEGIN
	selObj: POINTER TO SelObj ←Allocate[SIZE[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: POINTER TO SelObj ←topObj;
	FOR selObj ← topObj, selObj.down UNTIL selObj=NIL DO
		FlipUpObject[selObj.item];
		ENDLOOP;
	END;
Down: PROCEDURE=BEGIN
	selObj: POINTER TO SelObj ;
	FOR selObj ← bottomObj, selObj.up UNTIL selObj=NIL DO
		FlipDownObject[selObj.item];
		ENDLOOP;
	END;
Bottom: PROCEDURE=BEGIN
	selObj: POINTER TO SelObj ←topObj;
	FOR selObj ← topObj, selObj.down UNTIL selObj=NIL DO
		SinkObject[selObj.item];
		ENDLOOP;
	END;
Top: PROCEDURE=BEGIN
	selObj: POINTER TO 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
	EquivalentStrings[string,"Top"] => top,
	EquivalentStrings[string,"Bottom"] => bottom,
	EquivalentStrings[string,"Up One"] => up1,
	EquivalentStrings[string,"Down One"] => 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;
	Free[ptr];
	ptr ← next;
	ENDLOOP;
END;

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

string: STRING ← MenuDefs.MenuString[item];
cps: DESCRIPTOR FOR ARRAY OF  ObjPt ← ReadCPs[];
mspace: XFormDefs.XFormMatrix;
matrix: XFormDefs.XFMDescriptor ← DESCRIPTOR[mspace];
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: UNSPECIFIED] =
	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 LENGTH[cps] <=1 THEN BEGIN
	Free[BASE[cps]];
	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
	EquivalentStrings[string,"Map"] => map,
	EquivalentStrings[string,"Scale"] => scale,
	EquivalentStrings[string,"ScaleX"] => scalex,
	EquivalentStrings[string,"ScaleY"] => scaley,
	EquivalentStrings[string,"Rotate"] => 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 LENGTH[cps]=2  AND xftype=map THEN BEGIN
	ForAllSelectedDo[MoveObjs];
	END
ELSE BEGIN
	ForAllSelectedDo[XFormObjs];
	END;
Free[BASE[cps]];
ForAllSelectedDo[ PlotAndMark];
RestoreScreen[];
END;

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

Rotate: PROCEDURE[matrix: XFormDefs.XFMDescriptor] = 
BEGIN OPEN XFormDefs;
cps: DESCRIPTOR FOR ARRAY OF  ObjPt ← ReadCPs[];
npts: INTEGER ← LENGTH[cps];
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: DESCRIPTOR FOR ARRAY OF  ObjPt ← ReadCPs[];
npts: INTEGER ← LENGTH[cps];
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: DESCRIPTOR FOR ARRAY OF  ObjPt ← ReadCPs[];
npts: INTEGER ← LENGTH[cps];
pts: DESCRIPTOR FOR ARRAY OF  ObjPt;
free: BOOLEAN ← FALSE;
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 ← DESCRIPTOR[Allocate[4*SIZE[ ObjPt]],4];
		free ← TRUE;
		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 ← DESCRIPTOR[Allocate[6*SIZE[ ObjPt]],6];
		free ← TRUE;
		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;
	=6 => pts ← cps;
	ENDCASE=> pts ← DESCRIPTOR[BASE[cps],6];
Translate[dorig,matrix];
SELECT npts FROM
	=4 => XForm4Pts[pts,matrix];
	>=6 => XForm6Pts[pts,matrix];
	ENDCASE;
Translate[negdorig,matrix];
Free[BASE[cps]];
IF free THEN Free[BASE[pts]];
END;

Cluster: MenuDefs.MenuProc =
BEGIN
id: ClusterID ← GetNextClusterID[];
called: BOOLEAN ← FALSE;
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: MenuDefs.MenuProc =
BEGIN
called: BOOLEAN ← FALSE;
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: MenuDefs.MenuProc =
BEGIN
DS: ObjectProc = BEGIN
	EraseAndSave[ReturnSelected[obj]];
	DeSelectObject[obj];
	END;
ObjectDefs.ForAllSelectedDo[DS];
RestoreScreen[];
END;

SelectAll: 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];
			[]←DeleteObject[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;

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.