/*-------------------------------------------------------------------------*/
/* 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>

#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>

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

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

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

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

/*-------------------------------------------------------------------------*/
/* Global Variable definitions						   */
/*-------------------------------------------------------------------------*/

extern short eventmode;	/* keep track of the mode the screen is in */
extern Display	*dpy;   /* Works well to make this globally accessible */
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, short 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;
   }
}

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

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

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

   XSetForeground(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 */
/*--------------------------------------------*/

Boolean pathselect(genericptr *curgen, short class)
{
   /*----------------------------------------------------------------------*/
   /* 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 + 11.5 / (*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 */
/*--------------------------------------*/

short *genselectelement(short class, u_char mode, objectptr selobj, objinstptr selinst)
{
   short	*newselect, *foundtmp, *foundlist;

   genericptr	*curgen;
   XPoint 	newboxpts[4];
   XEvent 	event;
   XButtonEvent *buttonevent = (XButtonEvent *)(&event);
   Boolean	selected, unselect = False;
   short	found = 0;

   XDefineCursor(dpy, areastruct.areawin, QUESTION);

   /* Definition for unselecting an element */

   if (class < 0) {
      unselect = True;
      class = -class;
   }
   class &= areastruct.filter;		/* apply the selection filter */

   /* 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 != COPY2_MODE && eventmode !=
	    PRESS_MODE && curgen == EDITPART) continue;

      selected = False;

      /* check for position inside the bounding box of one of the objects */

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

	 objinstbbox(TOOBJINST(curgen), newboxpts);

         /* 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);
      }

      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);

         /* 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 & 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)) {
	       selected = True;
	       areastruct.editsubpart = (short)(pathp - TOPATH(curgen)->plist);
	       break;
	    }
      }

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

      else selected = pathselect(curgen, class);

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

      for (newselect = areastruct.selectlist; newselect <
             areastruct.selectlist + areastruct.selects; newselect++) 
         if (*newselect == (short)(curgen - selobj->plist)) {
	    if (!unselect) selected = False;
	    break;
	 }

      if (unselect && (newselect == areastruct.selectlist + areastruct.selects))
	 selected = False;

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

      if (selected && (!unselect)) {

         if (found == 0)
            foundlist = (short *) malloc(sizeof(short));
         else
            foundlist = (short *) realloc(foundlist, (found + 1) * sizeof(short));

         newselect = foundlist + found;
         found++;
     
         if (selected) *newselect = (short)(curgen - selobj->plist);
      }

      /* mechanism for unselecting elements under the cursor */

      else if (selected) {
         short *desel;

         XSetFunction(dpy, areastruct.gc, GXcopy);
         XTopSetForeground(GSELTOCOLOR(selobj, newselect));
         geneasydraw(*newselect, DEFAULTCOLOR, selobj, selinst);
         areastruct.selects--;
         for (desel = newselect; desel < areastruct.selectlist +
	        areastruct.selects; desel++)
	    *desel = *(desel + 1);
         if (areastruct.selects == 0) free (areastruct.selectlist);
      }
      else newselect = NULL;

   } /* for-loop over all parts */

   /* Various options depending on how many things were found */

   switch (found) {
      case 0: 
	 newselect = NULL;
	 break;

      case 1: 
	 newselect = allocselect();
	 *newselect = *foundlist;
	 free(foundlist);
	 gendrawselected(newselect, selobj, selinst);
	 break;

      default: {
	 newselect = NULL;
	 XSetFunction(dpy, areastruct.gc, GXcopy);
	 for (foundtmp = foundlist; foundtmp < foundlist + found; foundtmp++) {
            XSetForeground(dpy, areastruct.gc, QUERYCOLOR);
            geneasydraw(*foundtmp, DOFORALL, selobj, selinst);
	    sprintf(_STR, "Click to accept/reject: %d of %d",
		(int)(foundtmp - foundlist + 1), found);
	    Wprintf(_STR);

	    for(;;) {  	/* This flushes the X event queue */
	       XNextEvent(dpy, &event); 
	       if (event.type != ButtonPress && event.type != ButtonRelease
		    && event.type != MotionNotify && event.type != KeyPress)
	          xcDispatchEvent(&event);
	       else if (event.type == ButtonPress) break;
	    }
	    if (buttonevent->button == Button3) {
               XTopSetForeground(GSELTOCOLOR(selobj, foundtmp));
               geneasydraw(*foundtmp, DEFAULTCOLOR, selobj, selinst);
	    }
	    else {
	       newselect = allocselect();
               *newselect = *foundtmp;
               XSetForeground(dpy, areastruct.gc, SELECTCOLOR);
               geneasydraw(*newselect, DOFORALL, selobj, selinst);
	    }
         }
      }  break;
   }

   if (found > 1) {
      short *nselect;

      XDefineCursor(dpy, areastruct.areawin, CIRCLE);
      if (eventmode == PRESS_MODE)
	 Wprintf("Click and hold button 1 to continue move.");
      else
	 Wprintf("Selection complete");

      for(;;) {
	 XNextEvent(dpy, &event); 
	 if (event.type != ButtonPress && event.type != ButtonRelease &&
		event.type != MotionNotify && event.type != KeyPress)
	    xcDispatchEvent(&event);
	 else if ((event.type == ButtonPress) && (eventmode == PRESS_MODE))
	    break;
	 else if ((event.type == ButtonRelease) && (eventmode != PRESS_MODE))
	    break;
      }
      free(foundlist);

      /* make it work right for smooth xor'ing */

      XSetFunction(dpy, areastruct.gc, GXcopy);
      XSetForeground(dpy, areastruct.gc, BACKGROUND);
      for (nselect = areastruct.selectlist; nselect < areastruct.selectlist
	   + areastruct.selects; nselect++)
         geneasydraw(*nselect, DOFORALL, selobj, selinst);
      XSetFunction(dpy, areastruct.gc, GXxor);
      XSetForeground(dpy, areastruct.gc, SELECTCOLOR ^ BACKGROUND);
      for (nselect = areastruct.selectlist; nselect < areastruct.selectlist
	   + areastruct.selects; nselect++)
         geneasydraw(*nselect, DOFORALL, selobj, selinst);
   }

   setoptionmenu();

   if (eventmode == DELETE_MODE)
      XDefineCursor(dpy, areastruct.areawin, SCISSORS);
   else
      XDefineCursor(dpy, areastruct.areawin, CROSS);

   /* snap the coordinates */

   u2u_snap(&areastruct.save);

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

   return newselect;
}

/*----------------------------------------------------------------*/
/* 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 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(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();
      else {
         eventmode = DESEL_MODE;
         Wprintf("Click on element to deselect");
      }
   }
}

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

void draw_all_selected()
{
   int j;

   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;

   /* reset the graphics state */

   XSetFunction(dpy, areastruct.gc, GXcopy);

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

/*--------------------------------------------------------------*/
/* 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 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 objects and free memory from the	*/
/* selection list.						*/
/*--------------------------------------------------------------*/

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

/*----------------------------------------------------------------------*/
/* Select the nearest polygon, 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.							*/
/*----------------------------------------------------------------------*/

genericptr recurselect(short class, pushlistptr *seltop)
{
   genericptr rgen = NULL;
   short *selectobj;
   objectptr selobj;
   objinstptr selinst;
   XPoint savesave, tmppt;

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

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

   if ((selectobj = genselectelement(class, UNDO_DONE, selobj, selinst)) != NULL) {
      rgen = *(selobj->plist + (*selectobj));
      /* Fprintf(stdout, "%s found in object %s\n", (IS_LABEL(rgen)) ?
			"label" : "polygon", selobj->name); */
      draw_normal_selected(selobj, selinst);
      clearselects();
   }
   else if ((selectobj = genselectelement(OBJINST, UNDO_DONE, selobj, selinst)) != NULL) {
      pushlistptr selnew = (pushlistptr)malloc(sizeof(pushlist));

      selinst = TOOBJINST(selobj->plist + (*selectobj));	
      draw_normal_selected(selobj, selinst);
      clearselects();

      /* Link hierarchy information to the pushlist linked list */
      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);
      rgen = recurselect(class, &selnew);
      UPopCTM();
      areastruct.save.x = savesave.x;
      areastruct.save.y = savesave.y;
   }
   /* else
      Fprintf(stdout, "nothing found in object %s\n", selobj->name); */

   return rgen;
}

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

#ifdef TCL_WRAPPER

xcTimeOutProc startselect(caddr_t clientdata)
{
   if (eventmode != PENDING_MODE) return;
   eventmode = SELAREA_MODE;
   areastruct.origin.x = areastruct.save.x;
   areastruct.origin.y = areastruct.save.y;
   UDrawBox(areastruct.origin, areastruct.save);

   Tk_CreateEventHandler(areastruct.area, ButtonMotionMask,
		(Tk_EventProc *)xctk_drag, NULL);
}

#else

xcTimeOutProc startselect(caddr_t clientdata, xcIntervalId *id)

{
   if (eventmode != PENDING_MODE) return;
   eventmode = SELAREA_MODE;
   areastruct.origin.x = areastruct.save.x;
   areastruct.origin.y = areastruct.save.y;
   UDrawBox(areastruct.origin, areastruct.save);
}

#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;
}

/*-------------------------------------------------------------------------*/
