/*-------------------------------------------------------------------------*/
/* selection.c --- xcircuit routines handling element selection etc.	   */
/* Copyright (c) 2002  Tim Edwards, Johns Hopkins University       	   */
/*-------------------------------------------------------------------------*/

/*-------------------------------------------------------------------------*/
/*      written by Tim Edwards, 8/13/93    				   */
/*-------------------------------------------------------------------------*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#ifndef XC_WIN32
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#endif

#ifdef TCL_WRAPPER 
#include <tk.h>
#else
#ifndef XC_WIN32
#include "Xw/Xw.h"
#endif
#endif

/*-------------------------------------------------------------------------*/
/* Local includes							   */
/*-------------------------------------------------------------------------*/

#include "xcircuit.h"
#include "colordefs.h"

/*----------------------------------------------------------------------*/
/* Function prototype declarations                                      */
/*----------------------------------------------------------------------*/
#include "prototypes.h"

/*----------------------------------------------------------------------*/
/* Exported Variable definitions					*/
/*----------------------------------------------------------------------*/

extern short eventmode;	/* keep track of the mode the screen is in */
extern Display	*dpy;
extern int *appcolors;
extern Cursor	appcursors[NUM_CURSORS];
extern Clientdata areastruct;
extern char _STR[150];
extern short attachto;
extern short textpos, textend;

/*----------------------------------------------------------------------*/
/* Change filter to determine what types can be selected		*/
/*----------------------------------------------------------------------*/

void selectfilter(xcWidget w, pointertype value, caddr_t calldata)
{
   short bitwise = (short)value;
   Boolean bval = (areastruct.filter & bitwise) ? True : False;

   if (bval)
      areastruct.filter &= ~bitwise;
   else
      areastruct.filter |= bitwise;

#ifndef TCL_WRAPPER
   toggle(w, &bval, calldata);
#endif
}

/*----------------------------------------------------------------------------*/
/* Look at select stack to see if there are any selects; call select, if not. */
/*----------------------------------------------------------------------------*/

Boolean checkselect(short value)
{
   short *check;
 
   value &= areastruct.filter;	/* apply the selection filter */

   if (areastruct.selects == 0) select_element(value);
   if (areastruct.selects == 0) return False;
   for (check = areastruct.selectlist; check < areastruct.selectlist +
	areastruct.selects; check++)
      if (SELECTTYPE(check) & value) break;
   if (check == areastruct.selectlist + areastruct.selects) return False;
   else return True;
}

/*--------------------------------------------------------------*/
/* Select list numbering revision when an element is deleted	*/
/* from an object. 						*/
/*--------------------------------------------------------------*/

void reviseselect(short *slist, int selects, short *removed)
{
   short *chkselect;

   for (chkselect = slist; chkselect < slist + selects; chkselect++)
      if (*chkselect > *removed) (*chkselect)--;
}

/*----------------------*/
/* Draw a selected item */
/*----------------------*/

void geneasydraw(short instance, int mode, objectptr curobj, objinstptr curinst)
{
   genericptr elementptr = *(curobj->plist + instance);

   switch (ELEMENTTYPE(*(curobj->plist + instance))) {
      case ARC:
         UDrawArc((arcptr)elementptr);
	 break;
      case POLYGON:
         UDrawPolygon((polyptr)elementptr);
	 break;
      case SPLINE:
	 UDrawSpline((splineptr)elementptr);
	 break;
      case PATH:
	 UDrawPath((pathptr)elementptr);
	 break;
      case LABEL:
         UDrawString((labelptr)elementptr, mode, curinst);
	 break;
      case OBJINST:
         UDrawObject((objinstptr)elementptr, SINGLE, mode, NULL);
         break;
      case GRAPHIC:
         UDrawGraphic((graphicptr)elementptr);
	 break;
   }
}

/*-------------------------------------------------*/
/* Draw a selected item, including selection color */
/*-------------------------------------------------*/

void gendrawselected(short *newselect, objectptr curobj, objinstptr curinst)
{
   SetFunction(dpy, areastruct.gc, GXcopy);
   SetForeground(dpy, areastruct.gc, BACKGROUND);
   geneasydraw(*newselect, DOFORALL, curobj, curinst);
   indicateparams(*(curobj->plist + *newselect));

   SetFunction(dpy, areastruct.gc, GXxor);
   SetForeground(dpy, areastruct.gc, SELECTCOLOR ^ BACKGROUND);
   geneasydraw(*newselect, DOFORALL, curobj, curinst);

   SetForeground(dpy, areastruct.gc, AUXCOLOR ^ BACKGROUND);
   indicateparams(*(curobj->plist + *newselect));
}

/*---------------------------------------------------*/
/* Allocate or reallocate memory for a new selection */
/*---------------------------------------------------*/

short *allocselect()
{
   short *newselect;

   if (areastruct.selects == 0)
      areastruct.selectlist = (short *) malloc(sizeof(short));
   else
      areastruct.selectlist = (short *) realloc(areastruct.selectlist,
	 (areastruct.selects + 1) * sizeof(short));

   newselect = areastruct.selectlist + areastruct.selects;
   areastruct.selects++;

   return newselect;
}

/*-------------------------------------------------*/
/* Set Options menu according to 1st selection	   */
/*-------------------------------------------------*/

void setoptionmenu()
{
   short      *mselect;
   labelptr   mlabel;

   if (areastruct.selects == 0) {
      setallstylemarks(areastruct.style);
      setcolormark(areastruct.color);
      setdefaultfontmarks();
      setparammarks(NULL);
      return;
   }

   for (mselect = areastruct.selectlist; mselect < areastruct.selectlist +
	areastruct.selects; mselect++) {
      setcolormark(SELTOCOLOR(mselect));
      setparammarks(SELTOGENERIC(mselect));
      switch(SELECTTYPE(mselect)) {
	 case ARC:
	    setallstylemarks(SELTOARC(mselect)->style);
	    return;
	 case POLYGON:
	    setallstylemarks(SELTOPOLY(mselect)->style);
	    return;
	 case SPLINE:
            setallstylemarks(SELTOSPLINE(mselect)->style);
	    return;
	 case PATH:
            setallstylemarks(SELTOPATH(mselect)->style);
	    return;
	 case LABEL:
	    mlabel = SELTOLABEL(mselect);
	    setfontmarks(mlabel->string->data.font, mlabel->justify);
	    return;
      }
   }
}

/*-------------------------------------*/
/* Test of point being inside of a box */
/*-------------------------------------*/

int test_insideness(int tx, int ty, XPoint *boxpoints)
{
   int i, stval = 0; 
   XPoint *pt1, *pt2;
   int stdir;   

   for (i = 0; i < 4; i++) {
      pt1 = boxpoints + i;
      pt2 = boxpoints + ((i + 1) % 4);
      stdir = (pt2->x - pt1->x) * (ty - pt1->y)
		- (pt2->y - pt1->y) * (tx - pt1->x);
      stval += sign(stdir);
   }
   return (abs(stval) == 4) ? 1 : 0;
}

/*--------------------------------------------*/
/* Search for selection among path components */
/*--------------------------------------------*/

#define RANGE_NARROW 11.5
#define RANGE_WIDE 50

Boolean pathselect(genericptr *curgen, short class, float range)
{
   /*----------------------------------------------------------------------*/
   /* wirelim is the distance, in user-space units, at which an element is */
   /* considered for selection.						   */
   /*									   */
   /* wirelim = A + B / (scale + C)					   */
   /*									   */
   /* where A = minimum possible distance (expands range at close scale)   */
   /*       C = minimum possible scale    (contract range at far scale)	   */
   /*	    B   makes wirelim approx. 25 at default scale of 0.5, which	   */
   /*		is an empirical result.					   */
   /*----------------------------------------------------------------------*/

   float	wirelim = 2 + range / (*areastruct.vscale + 0.05);
   long		sqrwirelim = (int)(wirelim * wirelim);

   long		newdist;
   Boolean	selected = False;

   class &= areastruct.filter;	/* apply the selection filter */

   if ((*curgen)->type == (class & ARC)) {

      /* look among the arcs */

      fpointlist currentpt;
      XPoint nearpt[3];

      nearpt[2].x = nearpt[0].x = (short)(TOARC(curgen)->points[0].x);
      nearpt[2].y = nearpt[0].y = (short)(TOARC(curgen)->points[0].y);
      for (currentpt = TOARC(curgen)->points + 1; currentpt < TOARC(curgen)->points
              + TOARC(curgen)->number; currentpt++) {
	 nearpt[1].x = nearpt[0].x;
	 nearpt[1].y = nearpt[0].y;
	 nearpt[0].x = (short)(currentpt->x);
	 nearpt[0].y = (short)(currentpt->y);
	 newdist = finddist(&nearpt[0], &nearpt[1], &areastruct.save);
         if (newdist <= sqrwirelim) break;
      }
      if ((!(TOARC(curgen)->style & UNCLOSED)) && (newdist > sqrwirelim))
	 newdist = finddist(&nearpt[0], &nearpt[2], &areastruct.save);

      if (newdist <= sqrwirelim) selected = True;
   }

   else if ((*curgen)->type == (class & SPLINE)) {

      /* look among the splines --- look at polygon representation */

      fpointlist currentpt;
      XPoint nearpt[2];

      nearpt[0].x = (short)(TOSPLINE(curgen)->points[0].x);
      nearpt[0].y = (short)(TOSPLINE(curgen)->points[0].y);
      newdist = finddist(&(TOSPLINE(curgen)->ctrl[0]), &(nearpt[0]),
		   &areastruct.save);
      if (newdist > sqrwirelim) {
         for (currentpt = TOSPLINE(curgen)->points; currentpt <
		  TOSPLINE(curgen)->points + INTSEGS; currentpt++) {
	    nearpt[1].x = nearpt[0].x;
	    nearpt[1].y = nearpt[0].y;
	    nearpt[0].x = (short)(currentpt->x);
	    nearpt[0].y = (short)(currentpt->y);
	    newdist = finddist(&nearpt[0], &nearpt[1], &areastruct.save);
            if (newdist <= sqrwirelim) break;
	 }
	 if (newdist > sqrwirelim) {
	    newdist = finddist(&nearpt[0], &(TOSPLINE(curgen)->ctrl[3]),
			&areastruct.save);
            if ((!(TOSPLINE(curgen)->style & UNCLOSED)) && (newdist > sqrwirelim))
	       newdist = finddist(&(TOSPLINE(curgen)->ctrl[0]),
		     &(TOSPLINE(curgen)->ctrl[3]), &areastruct.save);
	 }
      }

      if (newdist <= sqrwirelim) selected = True;
   }

   else if ((*curgen)->type == (class & POLYGON)) {

      /* finally, look among the polygons */

      pointlist currentpt;

      for (currentpt = TOPOLY(curgen)->points; currentpt < TOPOLY(curgen)
		->points + TOPOLY(curgen)->number - 1; currentpt++) {
	 newdist = finddist(currentpt, currentpt + 1, &areastruct.save);
	 if (newdist <= sqrwirelim) break;
      }
      if ((!(TOPOLY(curgen)->style & UNCLOSED)) && (newdist > sqrwirelim))
	 newdist = finddist(currentpt, TOPOLY(curgen)->points,
		&areastruct.save);

      if (newdist <= sqrwirelim) selected = True;
   }
   return selected;
}

/*--------------------------------------*/
/* Select one of the elements on-screen */
/*--------------------------------------*/

selection *genselectelement(short class, u_char mode, objectptr selobj,
		objinstptr selinst)
{
   selection	*rselect = NULL;
   short	*newselect;
   genericptr	*curgen;
   XPoint 	newboxpts[4];
   Boolean	selected;
   float	range = RANGE_NARROW;

   if (mode == MODE_RECURSE_WIDE)
      range = RANGE_WIDE;

   /* Loop through all elements found underneath the cursor */

   for (curgen = selobj->plist; curgen < selobj->plist + selobj->parts; curgen++) {

      /* when selection element to attach to, don't select the element */
      /* to be attached. . .					       */

      if (attachto == 1 && eventmode != COPY_MODE && eventmode !=
	    MOVE_MODE && curgen == EDITPART) continue;

      selected = False;

      /* Check among polygons, arcs, and curves */

      if (((*curgen)->type == (class & POLYGON)) ||
		((*curgen)->type == (class & ARC)) ||
		((*curgen)->type == (class & SPLINE))) {
	  selected = pathselect(curgen, class, range);
      }

      else if ((*curgen)->type == (class & LABEL)) {

         /* Look among the labels */

	 labelptr curlab = TOLABEL(curgen);

	 /* Don't select temporary labels from schematic capture system */
	 if (curlab->string->type != FONT_NAME) continue;

	 labelbbox(curlab, newboxpts, selinst);

	 /* Need to check for zero-size boxes or test_insideness()	*/
	 /* fails.  Zero-size boxes happen when labels are parameters	*/
	 /* set to a null string.					*/

	 if ((newboxpts[0].x != newboxpts[1].x) || (newboxpts[0].y !=
		newboxpts[1].y)) {
	
            /* check for point inside bounding box, as for objects */

	    selected = test_insideness(areastruct.save.x, areastruct.save.y,
		newboxpts);
	    if (selected) textpos = textend = 0;
	 }
      }

      else if ((*curgen)->type == (class & GRAPHIC)) {

         /* Look among the graphic images */

	 graphicptr curg = TOGRAPHIC(curgen);
	 graphicbbox(curg, newboxpts);

         /* check for point inside bounding box, as for objects */
	 selected = test_insideness(areastruct.save.x, areastruct.save.y,
		newboxpts);
      }

      else if ((*curgen)->type == (class & PATH)) {

         /* Check among the paths */

	 genericptr *pathp;

	 /* Accept path if any subcomponent of the path is accepted */
	 /* Record which part was selected in the "subeditpart" variable */

 	 for (pathp = TOPATH(curgen)->plist; pathp < TOPATH(curgen)->plist +
		TOPATH(curgen)->parts; pathp++)
	    if (pathselect(pathp, SPLINE|ARC|POLYGON, range)) {
	       selected = True;
	       areastruct.editsubpart = (short)(pathp - TOPATH(curgen)->plist);
	       break;
	    }
      }

      else if ((*curgen)->type == (class & OBJINST)) {

	 objinstbbox(TOOBJINST(curgen), newboxpts, True);

         /* Look for an intersect of the boundingbox and pointer position. */
         /* This is a simple matter of rotating the pointer position with  */
         /*  respect to the origin of the bounding box segment, as if the  */
         /*  segment were rotated to 0 degrees.  The sign of the resulting */
         /*  point's y-position is the same for all bbox segments if the   */
         /*  pointer position is inside the bounding box.		   */

	 selected = test_insideness(areastruct.save.x, areastruct.save.y,
		newboxpts);
      }

      /* Add this object to the list of things found under the cursor */

      if (selected) {
         if (rselect == NULL) {
	    rselect = (selection *)malloc(sizeof(selection));
	    rselect->selectlist = (short *)malloc(sizeof(short));
	    rselect->selects = 0;
	    rselect->thisinst = selinst;
	    rselect->next = NULL;
	 }
         else {
            rselect->selectlist = (short *)realloc(rselect->selectlist,
			(rselect->selects + 1) * sizeof(short));
	 }
	 *(rselect->selectlist + rselect->selects) = (short)(curgen -
		selobj->plist);
	 rselect->selects++;
      }
   }
   return rselect;
}

/*----------------------------------------------------------------*/
/* select arc, curve, and polygon objects from a defined box area */
/*----------------------------------------------------------------*/

Boolean areaelement(genericptr *curgen)
{
   Boolean selected;
   pointlist    currentpt;

   switch(ELEMENTTYPE(*curgen)) {

      case(ARC):
	   /* check center of arcs */

      	   selected = (TOARC(curgen)->position.x < areastruct.save.x) &&
	  	(TOARC(curgen)->position.x > areastruct.origin.x) &&
		(TOARC(curgen)->position.y < areastruct.save.y) &&
		(TOARC(curgen)->position.y > areastruct.origin.y);
	   break;

      case(POLYGON):
	   /* check each point of the polygons */

	   selected = False;
           for (currentpt = TOPOLY(curgen)->points; currentpt <
		TOPOLY(curgen)->points + TOPOLY(curgen)->number; currentpt++) {
              if ((currentpt->x < areastruct.save.x) && (currentpt->x >
	           areastruct.origin.x) && (currentpt->y < areastruct.save.y) &&
	           (currentpt->y > areastruct.origin.y)) {
	         selected = True;
		 break;
	      }
           }
	   break;

      case(SPLINE):
	   /* check each control point of the spline */
	
           selected = ((TOSPLINE(curgen)->ctrl[0].x < areastruct.save.x) && 
	   	(TOSPLINE(curgen)->ctrl[0].x > areastruct.origin.x) &&
		(TOSPLINE(curgen)->ctrl[0].y < areastruct.save.y) &&
		(TOSPLINE(curgen)->ctrl[0].y > areastruct.origin.y))
		|| ((TOSPLINE(curgen)->ctrl[3].x < areastruct.save.x) &&
		(TOSPLINE(curgen)->ctrl[3].x > areastruct.origin.x) &&
		(TOSPLINE(curgen)->ctrl[3].y < areastruct.save.y) &&
		(TOSPLINE(curgen)->ctrl[3].y > areastruct.origin.y));
	   break;
   }
   return selected;
}

/*--------------------------------------------*/
/* select all objects from a defined box area */
/*--------------------------------------------*/

void selectarea()
{
   short	*newselect;
   genericptr   *curgen, *pathgen;
   Boolean	selected;
   stringpart	*strptr;
   int		locpos;

   /* put points of area bounding box into proper order */

   if (areastruct.origin.y > areastruct.save.y) {
      short tmp;
      tmp = areastruct.origin.y;
      areastruct.origin.y = areastruct.save.y;
      areastruct.save.y = tmp;
   }
   if (areastruct.origin.x > areastruct.save.x) {
      short tmp;
      tmp = areastruct.origin.x;
      areastruct.origin.x = areastruct.save.x;
      areastruct.save.x = tmp;
   }

   textpos = textend = 0;
   for (curgen = topobject->plist; curgen < topobject->plist
	+ topobject->parts; curgen++) {

      /* apply the selection filter */
      if (!((*curgen)->type & areastruct.filter)) continue;

      switch(ELEMENTTYPE(*curgen)) {
	case(OBJINST):
           /* check for instance of object center point inside area box */
           selected = (TOOBJINST(curgen)->position.x < areastruct.save.x) &&
		(TOOBJINST(curgen)->position.x > areastruct.origin.x) &&
		(TOOBJINST(curgen)->position.y < areastruct.save.y) &&
		(TOOBJINST(curgen)->position.y > areastruct.origin.y);
	   break;

	case(GRAPHIC):
           /* check for graphic image center point inside area box */
           selected = (TOGRAPHIC(curgen)->position.x < areastruct.save.x) &&
		(TOGRAPHIC(curgen)->position.x > areastruct.origin.x) &&
		(TOGRAPHIC(curgen)->position.y < areastruct.save.y) &&
		(TOGRAPHIC(curgen)->position.y > areastruct.origin.y);
	   break;

        case(LABEL): {
	   XPoint bboxpts[4], newboxpts[4], adj;
	   labelptr slab = TOLABEL(curgen);
	   short j, state, isect, tmpl1, tmpl2;
	   TextExtents tmpext;

	   selected = False;

	   /* Ignore temporary labels created by the netlist generator */
	   if (slab->string->type != FONT_NAME) break;

	   /* create a 4-point polygon out of the select box information */
	   bboxpts[0].x = bboxpts[3].x = areastruct.origin.x;
	   bboxpts[0].y = bboxpts[1].y = areastruct.origin.y;
	   bboxpts[1].x = bboxpts[2].x = areastruct.save.x;
	   bboxpts[2].y = bboxpts[3].y = areastruct.save.y;

   	   /* translate select box into the coordinate system of the label */
	   InvTransformPoints(bboxpts, newboxpts, 4, slab->position,
		  slab->scale, slab->rotation);

	   if (slab->pin) {
	      if (!areastruct.schemon) continue;
	      for (j = 0; j < 4; j++) 
		 pinadjust(slab->justify, &(newboxpts[j].x),
			&(newboxpts[j].y), -1);
	   }

	   tmpext = ULength(slab->string, areastruct.topinstance, 0.0, 0, NULL);
	   adj.x = (slab->justify & NOTLEFT ? (slab->justify & RIGHT ? 
			tmpext.width : tmpext.width >> 1) : 0);
	   adj.y = (slab->justify & NOTBOTTOM ? (slab->justify & TOP ? 
			tmpext.ascent : (tmpext.ascent + tmpext.base) >> 1)
			: tmpext.base);
	   
	   /* Label selection:  For each character in the label string, */
	   /* do an insideness test with the select box.		*/

	   state = tmpl2 = 0;
	   for (j = 0; j < stringlength(slab->string, True, areastruct.topinstance); j++) {
	      strptr = findstringpart(j, &locpos, slab->string, areastruct.topinstance);
	      if (locpos < 0) continue;	  /* only look at printable characters */
	      if (strptr->type == RETURN) tmpl2 = 0;
	      tmpl1 = tmpl2;
	      tmpext = ULength(slab->string, areastruct.topinstance, 0.0, j + 1, NULL);
	      tmpl2 = tmpext.width;
	      isect = test_insideness(((tmpl1 + tmpl2) >> 1) - adj.x,
			(tmpext.base + (BASELINE >> 1)) - adj.y, newboxpts);

	      /* tiny state machine */
	      if (state == 0) {
		 if (isect) {
		    state = 1;
		    selected = True;
		    textend = j;
		    if ((textend > 1) && strptr->type != TEXT_STRING)
		       textend--;
		 }
	      }
	      else {
		 if (!isect) {
		    textpos = j;
		    state = 2;
		    break;
		 }
	      }
	   }
	   if (state == 1) textpos = j;   /* selection goes to end of string */
	   } break;
	   
	case(PATH):
	   /* check position point of each subpart of the path */

	   selected = False;
	   for (pathgen = TOPATH(curgen)->plist; pathgen < TOPATH(curgen)->plist
		  + TOPATH(curgen)->parts; pathgen++) {
	      if (areaelement(pathgen)) selected = True;
	   }
	   break;

	default:
	   selected = areaelement(curgen);
	   break;
      }

      /* check if this part has already been selected */

      if (selected)
         for (newselect = areastruct.selectlist; newselect <
              areastruct.selectlist + areastruct.selects; newselect++)
            if (*newselect == (short)(curgen - topobject->plist))
                  selected = False;

      /* add to list of selections */

      if (selected) {
         newselect = allocselect();
         *newselect = (short)(curgen - topobject->plist);
      }
   }
   setoptionmenu();

   /* if none or > 1 label has been selected, cancel any textpos placement */

   if (!checkselect(LABEL) || areastruct.selects != 1 ||
	(areastruct.selects == 1 && SELECTTYPE(areastruct.selectlist) != LABEL)) {
      textpos = textend = 0;
   }

   /* Register the selection as an undo event */
   register_for_undo(XCF_Select, UNDO_DONE, areastruct.topinstance,
		areastruct.selectlist, areastruct.selects);

   /* Drawing of selected objects will take place when drawarea() is */
   /* executed after the button release.			     */
}

/*------------------------*/
/* start deselection mode */
/*------------------------*/

void startdesel(xcWidget w, caddr_t clientdata, caddr_t calldata)
{
   if (eventmode == NORMAL_MODE) {
      if (areastruct.selects == 0)
	 Wprintf("Nothing to deselect!");
      else if (areastruct.selects == 1)
	 unselect_all();
   }
}

/*------------------------------------------------------*/
/* Redraw all the selected objects in the select color.	*/
/*------------------------------------------------------*/

void draw_all_selected()
{
   int j;

   if (areastruct.hierstack != NULL) return;
   for (j = 0; j < areastruct.selects; j++)
      gendrawselected(areastruct.selectlist + j, topobject, areastruct.topinstance);
}

/*---------------------------------------------------------*/
/* Redraw all the selected objects in their normal colors. */
/*---------------------------------------------------------*/

void draw_normal_selected(objectptr thisobj, objinstptr thisinst)
{
   short *lastselect;

   if (areastruct.selects == 0) return;
   else if (areastruct.hierstack != NULL) return;

   /* reset the graphics state */

   SetFunction(dpy, areastruct.gc, GXcopy);

   for (lastselect = areastruct.selectlist; lastselect < areastruct.selectlist 
	+ areastruct.selects; lastselect++) {
      XTopSetForeground(SELTOCOLOR(lastselect)); 
      geneasydraw(*lastselect, SELTOCOLOR(lastselect), thisobj, thisinst);
   }
}

/*----------------------------------------------------------------------*/
/* Free a selection linked-list structure				*/
/* (don't confuse with freeselects)					*/
/*----------------------------------------------------------------------*/

static void freeselection(selection *rselect)
{
   selection *nextselect;

   while (rselect != NULL) {
      nextselect = rselect->next;
      free(rselect->selectlist);
      free(rselect);
      rselect = nextselect;
   }
}

/*--------------------------------------------------------------*/
/* Free memory from the previous selection list, copy the	*/
/* current selection list to the previous selection list, and	*/
/* zero out the current selection list.				*/
/* Normally one would use clearselects();  use freeselects()	*/
/* only if the menu/toolbars are going to be updated later in	*/
/* the call.							*/
/*--------------------------------------------------------------*/

void freeselects()
{
   if (areastruct.selects > 0)
      free(areastruct.selectlist);
   areastruct.selects = 0;
   free_stack(&areastruct.hierstack);
}

/*--------------------------------------------------------------*/
/* Free memory from the selection list and set menu/toolbar	*/
/* items back to default values.				*/
/*--------------------------------------------------------------*/

void clearselects_noundo()
{
   if (areastruct.selects > 0) {
      freeselects();
      setallstylemarks(areastruct.style);
      setcolormark(areastruct.color);
      setdefaultfontmarks();
   }
}

/*--------------------------------------------------------------*/
/* Same as above, but registers an undo event.			*/
/*--------------------------------------------------------------*/

void clearselects()
{
   if (areastruct.selects > 0) {
      register_for_undo(XCF_Select, UNDO_DONE, areastruct.topinstance,
		NULL, 0);
      clearselects_noundo();
   }
}

/*--------------------------------------------------------------*/
/* Unselect all the selected elements and free memory from the	*/
/* selection list.						*/
/*--------------------------------------------------------------*/

void unselect_all()
{
   draw_normal_selected(topobject, areastruct.topinstance);
   clearselects();
}

/*----------------------------------------------------------------------*/
/* Select the nearest element, searching the hierarchy if necessary.	*/
/* Return an pushlist pointer to a linked list containing the 		*/
/* hierarchy of objects, with the topmost pushlist also containing a	*/
/* pointer to the polygon found.					*/
/* Allocates memory for the returned linked list which must be freed by	*/
/* the calling routine.							*/
/*----------------------------------------------------------------------*/

selection *recurselect(short class, u_char mode, pushlistptr *seltop)
{
   selection *rselect, *rcheck, *lastselect;
   genericptr rgen;
   short i, *selectobj, numselect, origselect;
   short rclass;
   objectptr selobj;
   objinstptr selinst;
   XPoint savesave, tmppt;
   pushlistptr selnew;
   short j, unselects;
   u_char locmode = (mode == MODE_CONNECT) ? UNDO_DONE : mode;
   u_char recmode = (mode != MODE_CONNECT) ? MODE_RECURSE_WIDE : MODE_RECURSE_NARROW;

   if (*seltop == NULL) {
      Fprintf(stderr, "Error: recurselect called with NULL pushlist pointer\n");
      return NULL;
   }

   selinst = (*seltop)->thisinst;
   selobj = selinst->thisobject;

   class &= areastruct.filter;		/* apply the selection filter */

   unselects = 0;
   rselect = genselectelement(class, locmode, selobj, selinst);
   if (rselect == NULL) return NULL;

   for (i = 0; i < rselect->selects; i++) {
      rgen = *(selobj->plist + (*(rselect->selectlist + i)));
      if (rgen->type == OBJINST) {
         selinst = TOOBJINST(selobj->plist + (*(rselect->selectlist + i)));	

         /* Link hierarchy information to the pushlist linked list */
         selnew = (pushlistptr)malloc(sizeof(pushlist));
         selnew->thisinst = selinst;
         selnew->next = NULL;
         (*seltop)->next = selnew;
	 
         /* Translate areastruct.save into object's own coordinate system */
         savesave.x = areastruct.save.x;
         savesave.y = areastruct.save.y;
         InvTransformPoints(&areastruct.save, &tmppt, 1, selinst->position,
			selinst->scale, selinst->rotation);
         areastruct.save.x = tmppt.x;
         areastruct.save.y = tmppt.y;
         /* Fprintf(stdout, "objinst %s found in object %s; searching recursively\n",
			selinst->thisobject->name, selobj->name); */
         /* Fprintf(stdout, "cursor position originally (%d, %d); "
			"in new object is (%d, %d)\n",
			savesave.x, savesave.y,
			areastruct.save.x, areastruct.save.y); */
         UPushCTM();
         UPreMultCTM(DCTM, selinst->position, selinst->scale, selinst->rotation);
	 rclass = class;
	 if ((class & (~OBJINST)) == 0) class = ALL_TYPES;
         rcheck = recurselect(class, recmode, &selnew);
         UPopCTM();
         areastruct.save.x = savesave.x;
         areastruct.save.y = savesave.y;

	 /* If rgen is NULL, remove selected object from the list, and	*/
	 /* remove the last entry from the pushlist stack.		*/

	 if (rcheck == NULL) {
	    *(rselect->selectlist + i) = -1;
	    unselects++;
	    (*seltop)->next = NULL;
	    if (selnew->next != NULL)
	       Fprintf(stderr, "Error: pushstack was freed, but was not empty!\n");
	    free(selnew);
	 }
	 else {
	    for (lastselect = rselect; lastselect->next != NULL; lastselect =
			lastselect->next);
	    lastselect->next = rcheck;
	 }
      }
   }

   /* Modify the selection list */

   for (i = 0, j = 0; i < rselect->selects; i++) {
      if (*(rselect->selectlist + i) >= 0) {
	 if (i != j)
	    *(rselect->selectlist + j) = *(rselect->selectlist + i);
	 j++;
      }
   }
   rselect->selects -= unselects;
   if (rselect->selects == 0) {
      freeselection(rselect);
      rselect = NULL;
   }
   return rselect;
}

/*----------------------------------*/
/* Start drawing a select area box. */
/*----------------------------------*/

void startselect()
{
   eventmode = SELAREA_MODE;
   areastruct.origin.x = areastruct.save.x;
   areastruct.origin.y = areastruct.save.y;
   UDrawBox(areastruct.origin, areastruct.save);

#ifdef TCL_WRAPPER
   Tk_CreateEventHandler(areastruct.area, ButtonMotionMask,
		(Tk_EventProc *)xctk_drag, NULL);
#else
   xcAddEventHandler(areastruct.area, ButtonMotionMask, False,
		(xcEventHandler)xlib_drag, NULL);
#endif

}

/*-------------------------*/
/* Track a select area box */
/*-------------------------*/

void trackselarea()
{
   XPoint newpos;
   u_int  nullui;

   newpos = UGetCursorPos();
   if (newpos.x == areastruct.save.x && newpos.y == areastruct.save.y) return;

   UDrawBox(areastruct.origin, areastruct.save);
   UDrawBox(areastruct.origin, newpos);

   areastruct.save.x = newpos.x;
   areastruct.save.y = newpos.y;
}

/*----------------------*/
/* Track a rescale box	*/
/*----------------------*/

void trackrescale()
{
   XPoint newpos;
   u_int  nullui;

   newpos = UGetCursorPos();
   if (newpos.x == areastruct.save.x && newpos.y == areastruct.save.y) return;

   UDrawRescaleBox(&areastruct.save);
   UDrawRescaleBox(&newpos);

   areastruct.save.x = newpos.x;
   areastruct.save.y = newpos.y;
}

/*----------------------------------------------------------------------*/
/* Polygon distance comparison function for qsort			*/
/*----------------------------------------------------------------------*/

int dcompare(const void *a, const void *b)
{
   XPoint cpt;
   genericptr agen, bgen;
   short j, k, adist, bdist;

   cpt.x = areastruct.save.x;
   cpt.y = areastruct.save.y;

   j = *((short *)a);
   k = *((short *)b);

   agen = *(topobject->plist + j);
   bgen = *(topobject->plist + k);

   if (agen->type != POLYGON || bgen->type != POLYGON) return 0;

   adist = closedistance((polyptr)agen, &cpt);
   bdist = closedistance((polyptr)bgen, &cpt);

   if (adist == bdist) return 0;
   return (adist < bdist) ? 1 : -1;
}

/*----------------------------------------------------------------------*/
/* Compare two selection linked lists					*/
/*----------------------------------------------------------------------*/

Boolean compareselection(selection *sa, selection *sb)
{
   int i, j, match;
   short n1, n2;

   if ((sa == NULL) || (sb == NULL)) return False;
   if (sa->selects != sb->selects) return False;
   match = 0;
   for (i = 0; i < sa->selects; i++) {
      n1 = *(sa->selectlist + i);
      for (j = 0; j < sb->selects; j++) {
         n2 = *(sb->selectlist + j);
	 if (n1 == n2) {
	    match++;
	    break;
	 }
      }
   }
   return (match == sa->selects) ? True : False;
}

/*----------------------------------------------------------------------*/
/* Recursive selection mechanism					*/
/*----------------------------------------------------------------------*/

short *recurse_select_element(short class, u_char mode) {
   pushlistptr seltop, nextptr;
   selection *rselect;
   short *newselect, *desel, localpick;
   static short pick = 0;
   static selection *saveselect = NULL;
   int i, j, k, ilast, jlast;
   Boolean unselect = False;

   seltop = (pushlistptr)malloc(sizeof(pushlist));
   seltop->thisinst = areastruct.topinstance;
   seltop->next = NULL;

   /* Definition for unselecting an element */

   if (class < 0) {
      unselect = True;
      class = -class;
   }
   rselect = recurselect(class, mode, &seltop);

   if (rselect) {
      /* Order polygons according to nearest point distance. */
      qsort((void *)rselect->selectlist, (size_t)rselect->selects,
		sizeof(short), dcompare);

      if (compareselection(rselect, saveselect))
	 pick++;
      else
	 pick = 0;

      localpick = pick % rselect->selects;
   }

   /* Mechanism for unselecting elements under the cursor	*/
   /* (Unselect all picked objects)				*/

   if (rselect && unselect) {

      ilast = -1;
      k = 0;
      for (i = 0; i < rselect->selects; i++) {
	 for (j = 0; j < areastruct.selects; j++) {
	    if (*(areastruct.selectlist + j) == *(rselect->selectlist + i)) {
	       jlast = j;
	       ilast = i;
	       if (++k == localpick)
	          break;
	    }
	 }
	 if (j < areastruct.selects) break;
      }
      if (ilast >= 0) {
	 newselect = rselect->selectlist + ilast;
         SetFunction(dpy, areastruct.gc, GXcopy);
         XTopSetForeground(SELTOCOLOR(newselect));
         geneasydraw(*newselect, DEFAULTCOLOR, topobject, areastruct.topinstance);
         areastruct.selects--;
         for (k = jlast; k < areastruct.selects; k++)
	    *(areastruct.selectlist + k) = *(areastruct.selectlist + k + 1);

         if (areastruct.selects == 0) freeselects();

         /* Register the selection as an undo event */
         register_for_undo(XCF_Select, mode, areastruct.topinstance,
		areastruct.selectlist, areastruct.selects);
      }
   }

   else if (rselect) {

      /* Mechanism for selecting objects:			*/
      /* Count all elements from rselect that are part of	*/
      /* the current selection.  Pick the "pick"th item (modulo	*/
      /* total number of items).				*/

      ilast = -1;
      k = 0;
      for (i = 0; i < rselect->selects; i++) {
	 for (j = 0; j < areastruct.selects; j++) {
	    if (*(areastruct.selectlist + j) == *(rselect->selectlist + i))
	       break;
	 }
	 if (j == areastruct.selects) {
	    ilast = i;
	    if (++k == localpick)
	       break;
	 }
      }

      if (ilast >= 0) {
         newselect = allocselect();
         *newselect = *(rselect->selectlist + ilast);
         gendrawselected(newselect, topobject, areastruct.topinstance);
         setoptionmenu();
         u2u_snap(&areastruct.save);

         /* Register the selection as an undo event	*/
	 /* (only if selection changed)			*/

         register_for_undo(XCF_Select, mode, areastruct.topinstance,
		areastruct.selectlist, areastruct.selects);
      }
   }

   /* Cleanup */

   while (seltop != NULL) {
      nextptr = seltop->next;
      free(seltop);
      seltop = nextptr;
   }

   freeselection(saveselect);
   saveselect = rselect;

   return areastruct.selectlist;
}
