/* 
Copyright (c) 1991-2000 UserLand Software, Inc. 

Permission is hereby granted, free of charge, to any person obtaining a 
copy of this software and associated documentation files (the 
"Software"), to deal in the Software without restriction, including 
without limitation the rights to use, copy, modify, merge, publish, 
distribute, sublicense, and/or sell copies of the Software, and to 
permit persons to whom the Software is furnished to do so, subject to the 
following conditions: 

The above copyright notice and this permission notice shall be 
included in all copies or substantial portions of the Software. 

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
*/ 


#include "iowacore.h"
#include <applet.h>
#include <appletquickdraw.h>
#include <appletops.h>
#include "iowafile.h"
#include "iowaobject.h"

#ifdef flstatusbar

	#include "iowastatusbar.h"

#endif

	
typedef struct tymoveinfo { /*info that makes moving stuff look smoother*/
	
	boolean firstsource, firstdest;
	
	Rect sourcerect, destrect;
	} tymoveinfo;
	
tymoveinfo move;


typedef struct typutobjectrectundo {
	
	hdlobject hobject;
	
	Rect rorig;
	} typutobjectrectundo, **hdlputobjectrectundo;


typedef struct tyunlinkobjectundo {
	
	hdlobject hprev, hunlink;
	} tyunlinkobjectundo, **hdlunlinkobjectundo;


static hdlobject heditable = nil;

	

void putchildobjectlist (hdlobject h, hdlobject childobjectlist) {
	
	(**h).childobjectlist = childobjectlist;
	} /*putchildobjectlist*/


static boolean undoputobjectrect (Handle hdata, boolean flundo) {
	
	hdlputobjectrectundo h = (hdlputobjectrectundo) hdata;
	
	if (flundo)
		putobjectrect ((**h).hobject, (**h).rorig); /*putobjectinfo will create opposite undo*(/
	
	disposehandle ((Handle) h); /*we're done with it*/
	
	return (true);
	} /*undoputobjectrect*/
	
	
boolean newobject (tyobject obj, hdlobject *hnew) {
	
	hdlcard hc = iowadata;
	hdlappwindow ha = app.appwindow;
	hdlobject h;
	
	obj.nextobject = obj.nextselectedobject = obj.childobjectlist = obj.nextinthread = nil; 
	
	obj.objecteditrecord = nil; 
	
	obj.owningcard = hc;
	
	if (!newfilledhandle (&obj, longsizeof (tyobject), (Handle *) hnew))
		return (false);
		
	h = *hnew; /*copy into register*/
	
	if ((**hc).objectlist == nil) { /*inserting into an empty list*/
		
		(**hc).objectlist = h;
		
		(**h).nextobject = nil;
		}
	else { /*insert at end of list*/
		hdlobject lastobject;
		
		getlastinlist ((**hc).objectlist, &lastobject);
		
		(**lastobject).nextobject = h;
		}
	
	if ((**hc).flautoname)
		autonameobject (h);
	
	sortobjectlist (); /*new object added, the sort order might change*/
	
	#ifdef flstatusbar

		invalstatusbar (); /*re-display name and script*/
	
	#endif
	
	return (true);
	} /*newobject*/
	
	
void defaultobject (tyobject *obj) {
	
	clearbytes (obj, longsizeof (tyobject));
	
	(*obj).objectfont = systemFont; 
	
	(*obj).objectfontsize = 12;
	
	(*obj).objectjustification = leftjustified;
	
	(*obj).objectfillcolor = whitecolor;
	
	(*obj).objecttextcolor = blackcolor;
	
	(*obj).objectframecolor = blackcolor;
	
	(*obj).objectvisible = true;
	
	(*obj).objectenabled = true;
	
	(*obj).objectlanguage = idUserTalk;
	
	(*obj).objectrecalcstatus = neverrecalc;
	} /*defaultobject*/
	
	
boolean newgroupobject (hdlobject *h) {
	
	tyobject obj;
	
	defaultobject (&obj);

	obj.objecttype = grouptype;
	
	/*
	clearbytes (&obj, longsizeof (obj));
	
	obj.objecttransparent = true;
	
	obj.objectfont = systemFont; 
	
	obj.objectfontsize = 12;
	
	obj.objectstyle = 0;
	
	obj.objectfillcolor = whiteindex;
	
	obj.objecttextcolor = blackindex;

	obj.objectframecolor = blackindex;
	*/

	return (newobject (obj, h));
	} /*newgroupobject*/
	

boolean getnthsortedobject (short n, hdlobject *h) {

	hdlcard hc = iowadata;
	hdlobject nomad = (**hc).objectlist;
	short maxtag = 0;
	hdlobject lastnomad;
	
	while (nomad != nil) {
		
		short sorttag = (**nomad).sorttag;
		
		if (sorttag == n) {
			
			*h = nomad;
			
			return (true);
			}
			
		if (sorttag > maxtag) {
			
			maxtag = sorttag;
			
			lastnomad = nomad;
			}
		
		nomad = (**nomad).nextobject;
		} /*while*/
		
	if (n == 0) {
		
		*h = lastnomad; /*wrap around*/
		
		return (true);
		}
	
	*h = nil;
	
	return (false);
	} /*getnthsortedobject*/
	

static boolean redounlinkobject (Handle, boolean); /*forward*/


static boolean undounlinkobject (Handle hdata, boolean flundo) {
	
	if (flundo) {
		
		tyunlinkobjectundo undoinfo = **(hdlunlinkobjectundo) hdata;
		
		(**undoinfo.hunlink).nextobject = (**undoinfo.hprev).nextobject;
		
		(**undoinfo.hprev).nextobject = undoinfo.hunlink;
		
		pushundostep (&redounlinkobject, (Handle) undoinfo.hunlink);
		}
	
	disposehandle (hdata);
	
	return (true);
	} /*undounlinkobject*/


boolean unlinkoperation (hdlobject x); /*forward declaration*/


static boolean redounlinkobject (Handle hdata, boolean flundo) {
	
	if (flundo)
		unlinkoperation ((hdlobject) hdata);
	
	return (true);
	} /*undounlinkobject*/


#if 1 /***dmb to DW: see item #4 at the top of this file***/

static boolean unlinkoperation (hdlobject x) {
	
	/*
	unlink the indicated object from the object list.
	*/

	hdlcard hc = iowadata;
	hdlobject nomad = (**hc).objectlist;
	hdlobject prevnomad = (hdlobject) hc; /*treat list as an object for linking*/
	hdlobject nextnomad;
	tyunlinkobjectundo undoinfo;
	Handle hundodata;
	
	while (nomad != nil) {
		
		nextnomad = (**nomad).nextobject;
		
		if (nomad == x) {
			
			(**prevnomad).nextobject = nextnomad;
			
			undoinfo.hunlink = nomad;
			
			undoinfo.hprev = prevnomad;
			
			if (newfilledhandle (&undoinfo, sizeof (undoinfo), &hundodata))
				pushundostep (&undounlinkobject, hundodata);
			
			return (true);
			}
		
		prevnomad = nomad;
		
		nomad = nextnomad;
		} /*while*/
	
	return (false);
	} /*unlinkoperation*/

#else

boolean unlinkoperation (hdlobject x) {
	
	/*
	unlink the indicated object from the object list.
	*/

	hdlcard hc = iowadata;
	hdlobject nomad = (**hc).objectlist;
	hdlobject prevnomad = nil, nextnomad;
	
	while (nomad != nil) {
		
		nextnomad = (**nomad).nextobject;
		
		if (nomad == x) {
			
			if (prevnomad == nil)
				(**hc).objectlist = nextnomad;
			else
				(**prevnomad).nextobject = nextnomad;
			
			return (true);
			}
		
		prevnomad = nomad;
		
		nomad = nextnomad;
		} /*while*/
	
	return (false);
	} /*unlinkoperation*/

#endif


void addtoselection (hdlobject x) {
	
	hdlcard hc = iowadata;
	
	(**x).objectselected = true;
	
	(**x).nextselectedobject = (**hc).selectionlist;
	
	(**hc).selectionlist = x;
	
	invalobject (x);
	
	selectiondirty ();
	
	setnubonborder ();
	} /*addtoselection*/
	
	
boolean unlinkfromselection (hdlobject x) {
	
	/*
	unlink the indicated object from the selection list.
	*/

	hdlcard hc = iowadata;
	hdlobject nomad = (**hc).selectionlist;
	hdlobject prevnomad = nil, nextnomad;
	
	while (nomad != nil) {
		
		nextnomad = (**nomad).nextselectedobject;
		
		if (nomad == x) {
			
			if (prevnomad == nil)
				(**hc).selectionlist = nextnomad;
			else
				(**prevnomad).nextselectedobject = nextnomad;
				
			(**nomad).objectselected = false;
			
			invalobject (nomad); /*1.0b15*/
			
			setnubonborder ();
				
			return (true);
			}
		
		prevnomad = nomad;
		
		nomad = nextnomad;
		} /*while*/
	
	return (false);
	} /*unlinkfromselection*/
	
	
boolean nothingselected (void) {
	
	hdlcard hc = iowadata;
	
	return ((**hc).selectionlist == nil);
	} /*nothingselected*/


void makeselectionempty (void) {
	
	hdlcard hc = iowadata;
	hdlobject nomad = (**hc).selectionlist;
	hdlobject nextnomad;
	
	(**hc).selectionlist = nil; /*it's now officially empty*/
	
	while (nomad != nil) { /*update all the newly non-selected objects*/
		
		invalobject (nomad);
		
		(**nomad).objectselected = false;
		
		nextnomad = (**nomad).nextselectedobject;
		
		(**nomad).nextselectedobject = nil; /*my house is dirty, code is clean*/
		
		nomad = nextnomad;
		} /*while*/
		
	selectiondirty ();
	
	setnubonborder ();
	} /*makeselectionempty*/
	
	
void makefrontobject (hdlobject x) {
	
	hdlcard hc = iowadata;
	hdlobject lastobject;
	
	if ((**x).nextobject == nil) /*already the front object*/
		return;
		
	unlinkoperation (x);
	
	getlastinlist ((**hc).objectlist, &lastobject);
	
	(**lastobject).nextobject = x;
	
	(**x).nextobject = nil;
	} /*makefrontobject*/
	
	
void sendobjecttoback (hdlobject x) {
	
	hdlcard hc = iowadata;
	hdlobject firstobject = (**hc).objectlist;
	
	if (firstobject == x) /*already at the bottom of the pile*/
		return;
		
	unlinkoperation (x);
	
	(**x).nextobject = firstobject;
	
	(**hc).objectlist = x;
	} /*sendobjecttoback*/
	
	
boolean getsingleselectedobject (hdlobject *h) {
	
	/*
	the caller wants exactly one selected object for the operation he's about
	to perform. return true if the number of selected objects == 1 and return
	a handle to that object.
	*/

	hdlcard hc = iowadata;
	hdlobject x = (**hc).selectionlist;
	
	*h = nil;
	
	if (x == nil) /*nothing selected*/
		return (false);
	
	if ((**x).nextselectedobject != nil) /*more than one object selected*/
		return (false);
	
	*h = x;
	
	return (true); /*exactly one selected object*/
	} /*getsingleselectedobject*/
	
	
void selectallobjects (void) {

	hdlcard hc = iowadata;
	hdlobject x = (**hc).objectlist;

	while (x != nil) {
		
		if (!inselection (x))
			addtoselection (x);
		
		x = (**x).nextobject; 
		} /*while*/
	} /*selectallobjects*/
	
	
void selectinrect (Rect rselect) {
	
	/*
	select all objects contained within the rectangle. used to implement
	dragging to select a range of objects.
	*/
	
	hdlcard hc = iowadata;
	hdlobject nomad = (**hc).objectlist;
	Rect rnomad, rintersection;
	
	while (nomad != nil) { 
		
		if (getobjectrect (nomad, &rnomad)) {
		
			if (SectRect (&rnomad, &rselect, &rintersection))
				addtoselection (nomad);
			}
		
		nomad = (**nomad).nextobject;
		} /*while*/
	} /*selectinrect*/
	

void setselectionto (hdlobject x) {
	
	/*
	DW 11/9/92: introduced this to optimize setting the selection to
	a single object. we check to see if x is already the single selected
	object.
	*/
	
	hdlobject hselected;
	
	if (getsingleselectedobject (&hselected)) {
		
		if (hselected == x) /*nothing to do, saved some burnt braincells*/
			return;
		}
	
	makeselectionempty (); 
	
	addtoselection (x);
	} /*setselectionto*/
	
	
boolean visitselectedobjects (tyobjectvisitroutine visit) {
	
	/*
	this traversal includes all selected objects, and all non-group
	objects nested inside groups.
	*/
	
	hdlcard hc = iowadata;
	hdlobject x = (**hc).selectionlist;
	hdlobject nextx;
	
	while (x != nil) {
		
		nextx = (**x).nextselectedobject;
		
		if ((**x).objecttype == grouptype) {
		
			if (!visitobjects ((**x).childobjectlist, visit))
				return (false);
			}
		else {
			if (!(*visit) (x))
				return (false);
			}
		
		x = nextx;
		} /*while*/
	
	return (true);
	} /*visitselectedobjects*/
	
	
boolean flatvisitselectedobjects (tyobjectvisitroutine visit) {
	
	/*
	this traversal includes all selected objects, but does not
	dive into groups.
	*/
	
	hdlcard hc = iowadata;
	hdlobject x = (**hc).selectionlist;
	hdlobject nextx;
	
	while (x != nil) {
		
		nextx = (**x).nextselectedobject;
		
		if (!(*visit) (x))
			return (false);
		
		x = nextx;
		} /*while*/
	
	return (true);
	} /*flatvisitselectedobjects*/
	
	
boolean firstselectedobject (hdlobject *x) {
	
	/*
	the object list is in reverse order, so we shoot to the end of 
	the list and report back the object that's last in the list, hence
	the first object.
	*/
		
	hdlcard hc = iowadata;
	hdlobject nomad = (**hc).selectionlist;
	hdlobject lastnomad = nil;
	
	while (nomad != nil) { 
		
		lastnomad = nomad;
		
		nomad = (**nomad).nextselectedobject;
		} /*while*/
		
	*x = lastnomad;
	
	return (lastnomad != nil);
	} /*firstselectedobject*/
	

boolean caneditobjecttext (hdlobject h) {
	
	Rect r;
	
	return (geteditrect (h, &r));
	} /*caneditobjecttext*/
	

static boolean findeditableobjectvisit (hdlobject h) {
	
	if (caneditobjecttext (h)) {
		
		heditable = h;
		
		return (false); /*halt traversal*/
		}
		
	return (true); /*continue traversing*/
	} /*findeditableobjectvisit*/
	

hdlobject findeditableobject (void) {

	hdlcard hc = iowadata;
	
	heditable = nil;
	
	flatvisitselectedobjects (&findeditableobjectvisit); 
	
	/*
	DW 12/14/92: turned feature off -- we were very aggressive about finding you
	something to edit when you press the enter key. the problem is that this 
	aggressive approach gets you inside of groups, and that's awkward. 
	
	if (heditable == nil)	
		visitobjects ((**hc).objectlist, &findeditableobjectvisit);
	*/
	
	return (heditable);
	} /*findeditableobject*/
	
	
void initmoveinfo (void) {
	
	move.firstsource = true;
	
	move.firstdest = true;
	
	zerorect (&move.sourcerect);
	
	zerorect (&move.destrect);
	
	madechanges (); /*all ops that do this dirty the window*/
	} /*initmoveinfo*/
	
	
void addtomoveinfo (hdlobject h, boolean flsource) {
	
	Rect r;
	
	getobjectinvalrect (h, &r);
	
	if (flsource) {
		
		if (move.firstsource)
			move.sourcerect = r;
		else
			UnionRect (&r, &move.sourcerect, &move.sourcerect);
			
		move.firstsource = false;
		}
	else {
		if (move.firstdest)
			move.destrect = r;
		else
			UnionRect (&r, &move.destrect, &move.destrect);
			
		move.firstdest = false;
		}
	} /*addtomoveinfo*/


static boolean undomoveinval (Handle hdata, boolean flundo) {
	
	/*
	invals are the same when we undo/redo steps involving invalfrommoveinfo; 
	no big deal.
	*/
	
	if (flundo) {
		
		move = **(tymoveinfo **) hdata;
		
		invalfrommoveinfo ();
		}
	
	disposehandle (hdata);
	
	return (true);
	} /*undomoveinval*/


void invalfrommoveinfo (void) {
	
	Handle hundodata;
	
	iowainvalrect (&move.sourcerect); /*force update where we moved stuff from*/
	
	iowainvalrect (&move.destrect); /*and where we moved to*/
	
	sortobjectlist (); /*if objects changed position, the sort order might change*/
	
	if (newfilledhandle (&move, sizeof (move), &hundodata))
		pushundostep (&undomoveinval, hundodata);
	} /*invalfrommoveinfo*/


void adjustforgrid (Rect *rmove) {
	
	hdlcard hc = iowadata;
	short x = (**hc).gridunits;
	short hdiff, vdiff;
	Rect r;
	
	if (!(**hc).flgrid) /*nothing to do*/
		return;
		
	r = *rmove;
	
	hdiff = r.left - ((r.left / x) * x);
	
	vdiff = r.top - ((r.top / x) * x);
	
	OffsetRect (rmove, -hdiff, -vdiff);
	} /*adjustforgrid*/
	
	
void adjustbottomrightforgrid (Rect *rmove) {
	
	hdlcard hc = iowadata;
	short x = (**hc).gridunits;
	short hdiff, vdiff;
	Rect r;
	
	if (!(**hc).flgrid) /*nothing to do*/
		return;
		
	r = *rmove;
	
	hdiff = r.right - ((r.right / x) * x);
	
	vdiff = r.bottom - ((r.bottom / x) * x);
	
	OffsetRect (rmove, -hdiff, -vdiff);
	} /*adjustbottomrightforgrid*/
	
	
void frameselectedobjects (short hdiff, short vdiff) {

	hdlcard hc = iowadata;
	hdlobject nomad = (**hc).selectionlist;
	Rect r;
	
	while (nomad != nil) { 
		
		getobjectrect (nomad, &r);
		
		OffsetRect (&r, hdiff, vdiff);
		
		adjustforgrid (&r);
		
		FrameRect (&r);
	
		nomad = (**nomad).nextselectedobject;
		} /*while*/
	} /*frameselectedobjects*/
	
	
void unionselectedrects (short hdiff, short vdiff, Rect *runion) {

	hdlcard hc = iowadata;
	hdlobject nomad = (**hc).selectionlist;
	boolean firstloop = true;
	Rect r;
	
	zerorect (runion);
	
	while (nomad != nil) { 
		
		getobjectrect (nomad, &r);
		
		OffsetRect (&r, hdiff, vdiff);
		
		adjustforgrid (&r);
		
		if (firstloop)
			*runion = r;
		else
			UnionRect (&r, runion, runion);
		
		firstloop = false;
		
		nomad = (**nomad).nextselectedobject;
		} /*while*/
	} /*unionselectedrects*/


void moveselectedobjects (short hdiff, short vdiff) {

	hdlcard hc = iowadata;
	hdlobject nomad = (**hc).selectionlist;
	Rect r;
	
	if ((hdiff == 0) && (vdiff == 0))
		return;
	
	pushundoaction (undomovestring);
	
	initmoveinfo ();
	
	while (nomad != nil) { 
		
		getobjectrect (nomad, &r);
		 
		addtomoveinfo (nomad, true);
		
		OffsetRect (&r, hdiff, vdiff);
		
		adjustforgrid (&r);
		
		putobjectrect (nomad, r);
		
		addtomoveinfo (nomad, false);
		
		nomad = (**nomad).nextselectedobject;
		} /*while*/
	
	invalfrommoveinfo (); /*force update where we moved stuff from*/
	} /*moveselectedobjects*/ 
	

boolean groupselectedobjects (void) {
	
	/*
	turn the selected object into a group. first, we travel thru the
	selection list, unlinking every item from the object list. as we
	go thru, we change each node's next object pointer to point at
	the next guy in the selection list.
	
	return false if there wasn't enough memory to allocate the new
	group object or if there were no selected objects.
	
	1.0b15 -- used to visit the selected objects in the order that
	they were selected. this would screw with the front-to-back order
	of the objects. unexpected by the user! now we loop over the whole
	object list in its current front-to-back order, and unlink those
	objects with their objectselected bit turned on. 
	*/
	
	hdlcard hc = iowadata;
	hdlobject nomad = (**hc).objectlist;
	hdlobject lastunlinkedobject = nil;
	hdlobject firstunlinkedobject = nil;
	hdlobject hgroup;
	
	if (nomad == nil) /*defensive driving*/
		return (false);
	
	if (!newgroupobject (&hgroup))
		return (false);
		
	while (nomad != nil) {
		
		hdlobject nextnomad = (**nomad).nextobject;
		
		if ((**nomad).objectselected) {
		
			unlinkoperation (nomad); /*unlink from object list*/
			
			if (firstunlinkedobject == nil)
				firstunlinkedobject = nomad;
				
			if (lastunlinkedobject != nil)
				(**lastunlinkedobject).nextobject = nomad;
				
			lastunlinkedobject = nomad;
			}
		
		nomad = nextnomad;
		} /*while*/
		
	if (firstunlinkedobject == nil) /*nothing was selected*/
		return (false);
	
	(**lastunlinkedobject).nextobject = nil; /*terminate the child list*/
	
	putchildobjectlist (hgroup, firstunlinkedobject);
	
	makeselectionempty (); /*also displays all previously selected objects*/
	
	addtoselection (hgroup); /*the group replaces the selected objects*/
	
	turnonfirstexclusiveobject ((**hgroup).childobjectlist); /*1.0b8*/
	
	schedulerecalc (nil, 0);

	return (true);
	} /*groupselectedobjects*/
	

void ungroupobject (hdlobject h) {
	
	hdlcard hc = iowadata;
	hdlobject firstchild, lastchild;
	hdlobject nomad;
	Rect r;
	
	pushundoaction (0); /*not completely undoable yet*/
	
	initmoveinfo ();
	
	getobjectrect (h, &r);
	
	addtomoveinfo (h, true);
	
	unlinkoperation (h);
	
	unlinkfromselection (h); 
	
	getchildobjectlist (h, &firstchild);
	
	getlastinlist (firstchild, &lastchild);
	
	turnoffallexclusiveobjects (firstchild);
	
	(**lastchild).nextobject = (**hc).objectlist;
	
	(**hc).objectlist = firstchild;
	
	(**h).childobjectlist = nil; /*don't dispose of children*/
	
	disposeobject (h);
	
	nomad = firstchild;
	
	while (true) {
		
		addtoselection (nomad);
		
		getobjectrect (nomad, &r);
		
		addtomoveinfo (nomad, true);
		
		if (nomad == lastchild)
			break;
			
		nomad = (**nomad).nextobject;
		} /*while*/
		
	invalfrommoveinfo ();
	
	schedulerecalc (nil, 0);
	} /*ungroupobject*/
	
	
boolean ungroupselectedobjects (void) {

	hdlcard hc = iowadata;
	hdlobject nomad = (**hc).selectionlist;
	hdlobject nextnomad;
	
	if (nomad == nil) /*nothing selected*/
		return (false);
		
	while (nomad != nil) { 
		
		nextnomad = (**nomad).nextselectedobject; /*it might get deleted*/
		
		if ((**nomad).objecttype == grouptype) 
			ungroupobject (nomad);
		
		nomad = nextnomad;
		} /*while*/
	
	return (true);
	} /*ungroupselectedobjects*/ 


static boolean redodeleteobject (Handle, boolean); /*forward*/


static boolean undodeleteobject (Handle hdata, boolean flundo) {
	
	if (flundo)
		pushundostep (&redodeleteobject, hdata);
	else
		disposeobject ((hdlobject) hdata); /*not undoing deletion; dispose now*/
	
	return (true);
	} /*undodeleteobject*/


static boolean redodeleteobject (Handle hdata, boolean flundo) {
	
	if (flundo)
		pushundostep (&undodeleteobject, hdata);
	else
		; /*not redoing deletion; nothing to do*/
	
	return (true);
	} /*undodeleteobject*/


boolean deleteselectedobjects (void) {

	hdlobject nomad = (**iowadata).selectionlist;
	hdlobject nextnomad;
	Rect r;
	
	pushundoaction (undodeletionstring);
	
	initmoveinfo ();
	
	while (nomad != nil) { 
		
		nextnomad = (**nomad).nextselectedobject; /*it gets deleted*/
		
		getobjectrect (nomad, &r);
		
		addtomoveinfo (nomad, true);
		
		unlinkoperation (nomad);
		
		pushundostep (&undodeleteobject, (Handle) nomad);
		
		nomad = nextnomad;
		} /*while*/
		
	invalfrommoveinfo ();
		
	(**iowadata).selectionlist = nil;
	
	return (true);
	} /*deleteselectedobjects*/ 
	
	
void cleanobject (hdlobject h) {
	
	hdlcard hc = iowadata;
	short gridunits = (**hc).gridunits;
	short objecttype = (**h).objecttype;
	Rect r, rorig;
	short height, width;

	getobjectrect (h, &rorig);
	
	r = rorig;
	
	if (emptyrect (r)) {
		
		r.right = r.left + gridunits;
		
		r.bottom = r.top + gridunits;
		}
		
	pushobjectstyle (h);
	
	getobjectsize (h, &height, &width);
	
	switch (objecttype) {
		
		case clonetype: { /*make the clone the exact size of target*/
			hdlobject htarget = h;
			Rect rtarget;
			
			recalcclone (h);
			
			derefclone (&htarget);
			
			getobjectrect (htarget, &rtarget);
			
			r.right = r.left + (rtarget.right - rtarget.left);

			r.bottom = r.top + (rtarget.bottom - rtarget.top);
			
			break;
			}
		
		case grouptype: { 
			short rectwidth;
			
			rectwidth = makemultiple (r.right - r.left, gridunits);
			
			if ((**h).objecthasframe)
				rectwidth = max (width + 32, rectwidth);
				
			r.right = r.left + rectwidth;
			
			height = makemultiple (r.bottom - r.top, gridunits);
			
			r.bottom = r.top + height;
			
			break;
			}
			
		default:
			callcleanobject (h, height, width, &r);
			
			break;
		} /*switch*/
	
	popobjectstyle ();
	
	/*align the rectangle to the grid*/ {
		
		short newtop, newleft;
		
		newtop = closestmultiple (r.top, gridunits);
		
		newleft = closestmultiple (r.left, gridunits);
		
		r.bottom = newtop + (r.bottom - r.top);
		
		r.right = newleft + (r.right - r.left);
		
		r.top = newtop;
		
		r.left = newleft;
		}
		
	if (!equalrects (r, rorig)) {
	
		addtomoveinfo (h, true);
		
		putobjectrect (h, r);
	
		addtomoveinfo (h, false);
		}
	
	(**h).objectautosize = true; /*1/23/93 DW*/
	} /*cleanobject*/
	
	
static void cleanchildren (hdlobject h) {

	if ((**h).objecttype == grouptype) {
	
		invalobject (h);
		
		/*zerorect (&(**h).objectrect);*/ /*reset*/
		
		cleanobjectlist ((**h).childobjectlist);
		}
	} /*cleanchildren*/

	
void cleanobjectlist (hdlobject listhead) {

	hdlcard hc = iowadata;
	hdlobject nomad = listhead;
	
	while (nomad != nil) {
		
		cleanchildren (nomad);
				
		cleanobject (nomad);
		
		nomad = (**nomad).nextobject;		
		} /*while*/
	
	/*interesting place to add more cleanup code*/
	} /*cleanobjectlist*/
	

void cleanselectedobjects (void) {
	
	/*
	this is the center of this piece of software. we travel over all
	the selected objects, and do things to them to make their display
	and appearance absolutely perfect. ambitious business plan!
	*/

	hdlcard hc = iowadata;
	hdlobject nomad = (**hc).selectionlist;
	
	pushundoaction (undocleanstring);
	
	initmoveinfo ();
	
	while (nomad != nil) { 
		
		cleanchildren (nomad);
		
		cleanobject (nomad);
		
		nomad = (**nomad).nextselectedobject;
		} /*while*/		
		
	invalfrommoveinfo ();
	} /*cleanselectedobjects*/ 
	
	
short countselectedobjects (void) {

	hdlcard hc = iowadata;
	hdlobject nomad = (**hc).selectionlist;
	short ct = 0;
	
	while (nomad != nil) { 
		
		ct++;
		
		nomad = (**nomad).nextselectedobject;
		} /*while*/
		
	return (ct);
	} /*countselectedobjects*/
	

short groupinselection (void) {
	
	/*
	return true if at least one of the selected objects is a group.
	*/

	hdlcard hc = iowadata;
	hdlobject nomad = (**hc).selectionlist;
	
	while (nomad != nil) { 
		
		if ((**nomad).objecttype == grouptype)
			return (true);
			
		nomad = (**nomad).nextselectedobject;
		} /*while*/
		
	return (false);
	} /*groupinselection*/
	

boolean alignselectedobjects (tydirection dir) {
	
	hdlcard hc = iowadata;
	hdlobject nomad = (**hc).selectionlist;
	hdlobject x;
	Rect rstandard, r;
	short height, width;
	
	pushundoaction (undoalignstring);
	
	if (!firstselectedobject (&x)) /*nothing selected*/
		return (false);
		
	initmoveinfo ();
	
	getobjectrect (x, &rstandard);
	
	while (nomad != nil) {
		
		getobjectrect (nomad, &r);
		
		addtomoveinfo (nomad, true);
		
		height = r.bottom - r.top;
		
		width = r.right - r.left;
		
		switch (dir) {
			
			case up: /*align tops*/
				r.top = rstandard.top;
				
				r.bottom = r.top + height;
				
				break;
				
			case down: /*align bottoms*/
				r.bottom = rstandard.bottom;
				
				r.top = r.bottom - height;
				
				break;
				
			case left: /*align left edges*/
				r.left = rstandard.left;
				
				r.right = r.left + width;
				
				break;
				
			case right: /*align right edges*/
				r.right = rstandard.right;
				
				r.left = r.right - width;
				
				break;
				
			case flatup: /*align centers horizontally*/
				r.left = rstandard.left + ((rstandard.right - rstandard.left - width) / 2);
				
				r.right = r.left + width;
				
				break;
				
			case flatdown: /*align centers vertically*/
				r.top = rstandard.top + ((rstandard.bottom - rstandard.top - width) / 2);
				
				r.bottom = r.top + height;
				
				break;
				
			} /*switch*/
			
		putobjectrect (nomad, r);
		
		addtomoveinfo (nomad, false);
		
		nomad = (**nomad).nextselectedobject;
		} /*while*/
	
	invalfrommoveinfo (); /*force update where we moved stuff from*/
	
	return (true);
	} /*alignselectedobjects*/
	

void growselectedobjects (Rect rnew) {

	hdlcard hc = iowadata;
	hdlobject nomad = (**hc).selectionlist;
	short height, width;
	Rect r;
	
	pushundoaction (0); /*not completely undoable yet*/
	
	initmoveinfo ();
	
	height = rnew.bottom - rnew.top;
	
	width = rnew.right - rnew.left;
	
	while (nomad != nil) { 
		
		getobjectrect (nomad, &r);
		
		addtomoveinfo (nomad, true);
		
		r.right = r.left + width;
		
		r.bottom = r.top + height;
		
		putobjectrect (nomad, r);
		
		addtomoveinfo (nomad, false);
		
		(**nomad).objectautosize = false; /*1/23/93 DW*/
	
		nomad = (**nomad).nextselectedobject;
		} /*while*/
	
	invalfrommoveinfo ();
	} /*growselectedobjects*/
	
	
void resizeactivetextobject (void) {
	
	/*
	resize the active text object so that it is exactly large enough to
	display the text inside it.
	*/

	hdlcard hc = iowadata;
	hdlobject h = (**hc).activetextobject;
	hdleditrecord hedit;
	Rect rorig, rnew, redit;
	
	pushundoaction (undoresizestring);
	
	if (h == nil) /*no text object is active*/
		return;
		
	if (!(**h).objectautosize) /*auto resizing is only for new objects*/
		return;
		
	getobjectrect (h, &rorig);
		
	initmoveinfo (); /*in case things change*/
	
	cleanobject (h);
	
	getobjectrect (h, &rnew);
	
	if (equalrects (rorig, rnew))
		return;
	
	invalfrommoveinfo (); /*finish process started by cleanobject*/
	
	geteditrect (h, &redit);
	
	hedit = (hdleditrecord) (**h).objecteditrecord;
	
	editsetrect (hedit, redit);
	
	editupdate (hedit);
	} /*resizeactivetextobject*/
	
	
boolean editobject (hdlobject h) { 
	
	clearactivetextobject ();
	
	if (!setactivetextobject (h))
		return (false);
	
	resizeactivetextobject ();
	
	return (true);
	} /*editobject*/
	
	
void moveobjectcursor (boolean flbackward) {

	hdlcard hc = iowadata;
	hdlobject h = (**hc).selectionlist;
	hdlobject hnext;
	
	if (h == nil) /*nothing selected*/
		hnext = (**hc).objectlist;
	else {
		if (flbackward)
			getnthsortedobject ((**h).sorttag - 1, &hnext);
		else {
			if (!getnthsortedobject ((**h).sorttag + 1, &hnext))
				getnthsortedobject (1, &hnext);
			}
		}
	
	if (hnext != nil) {
	
		makeselectionempty ();
	
		addtoselection (hnext);
		}
	} /*moveobjectcursor*/
	

static void emptyobjectstrings (tyobject *obj) {
	
	(*obj).objectname = nil;

	(*obj).objectvalue = nil;

	(*obj).objectscript = nil;
	} /*emptyobjectstrings*/
	
	
boolean newreturnkeyobject (void) {
	
	/*
	when the user hits the return key with a single selected object that's
	the right type, we create a new object down from there with the same
	type and attributes as the original. makes it easy to create a whole
	slew of objects of the same type.
	*/
	
	hdlcard hc = iowadata;
	hdlobject horig, hnew;
	tyobject obj;
	boolean fledit;
	
	horig = (**hc).activetextobject;
	
	if (horig == nil) { /*there's no active text object*/
	
		if (!getsingleselectedobject (&horig))
			return (false);
		}
	
	if (!callcanreplicate (horig))
		return (false);
		
	clearactivetextobject ();
	
	obj = **horig; /*inherit all attributes of the original*/
	
	obj.objectname = obj.objectvalue = obj.objectscript = nil;
	
	/*place the new object at the next grid stop down from the original*/ {
		
		short newtop = makemultiple (obj.objectrect.bottom + 1, (**iowadata).gridunits);
		
		obj.objectrect.bottom = newtop + (obj.objectrect.bottom - obj.objectrect.top);
		
		obj.objectrect.top = newtop;
		}
	
	obj.objectautosize = true;
	
	fledit = callinitobject (&obj);
	
	if (!newobject (obj, &hnew))	
		return (false);
	
	makeselectionempty ();
			
	madechanges ();
	
	if (fledit)
		editobject (hnew);
	else 
		setselectionto (hnew);
	
	return (true);
	} /*newreturnkeyobject*/
	
	
boolean newdraggedobject (tyobjecttype objecttype, boolean flautosize, Rect r) {
	
	hdlobject hnew;
	tyobject obj;
	boolean fledittext = true;
	
	clearactivetextobject (); /*if we're editing an object, get out*/
	
	if (objecttype == -1) /*DW 3/18/93*/
		return (true);
	
	defaultobject (&obj);
	
	obj.objectrect = r;
	
	obj.objecttype = objecttype;
	
	obj.objectautosize = flautosize;
	
	switch (objecttype) {
		
		case clonetype:			
			obj.objecthasframe = false;
			
			fledittext = false;
			
			break;
		
		default:
			fledittext = callinitobject (&obj);
			
			break;
		} /*switch*/
					
	if (!newobject (obj, &hnew))	
		return (false);
		
	if ((**hnew).objecttmpbit) {
		
		recalcbottleneck (hnew, true);
		
		(**hnew).objecttmpbit = false;
		}
	
	madechanges ();
	
	makeselectionempty ();
	
	if (fledittext) 
		editobject (hnew);
	else {
		addtoselection (hnew);
		
		if (obj.objectautosize)
			cleanselectedobjects ();
		}

	return (true);
	} /*newdraggedobject*/
	
	
boolean addclone (Handle hscript) {

	hdlcard hc = iowadata;
	hdlappwindow ha = app.appwindow;
	hdlobject hnew;
	tyobject obj;
	Rect r;
	
	/*
	clearbytes (&obj, longsizeof (obj)); 
		
	obj = (**hc).defaults;
	
	obj.objectfont = (**ha).defaultfont; 
	
	obj.objectfontsize = (**ha).defaultsize;
	
	obj.objectstyle = (**ha).defaultstyle;
	
	obj.objectjustification = (**ha).defaultjustification;
	*/
	
	defaultobject (&obj); /*1.0b9*/
	
	obj.objecttype = clonetype;
	
	obj.objecthasframe = false;
	
	r.top = r.left = 0;
	
	r.right = 100;
	
	r.bottom = 50;
	
	obj.objectrect = r;
	
	if (!newobject (obj, &hnew))	
		return (false);
		
	madechanges ();
	
	makeselectionempty ();
	
	if (hscript != nil)
		setobjectscript (hnew, hscript);
		
	addtoselection (hnew);
	
	cleanselectedobjects ();
	
	return (true);
	} /*addclone*/