/*----------------------------------------------------------------------*/
/* parameter.c								*/
/* Copyright (c) 2002  Tim Edwards, Johns Hopkins University        	*/
/*----------------------------------------------------------------------*/

/*----------------------------------------------------------------------*/
/*      written by Tim Edwards, 10/26/99    				*/
/*	revised for segmented strings, 3/8/01				*/
/*----------------------------------------------------------------------*/

#include <stdio.h>
#include <stdlib.h>
#include <string.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 "menudep.h"

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

/*----------------------------------------------------------------------*/
/* Externally declared global variables					*/
/*----------------------------------------------------------------------*/

#ifdef TCL_WRAPPER
extern Tcl_Interp *xcinterp;
#endif
extern Globaldata xobjs;
extern Clientdata areastruct;
extern Widget     menuwidgets[];
extern short textpos, textend;
extern char _STR[150];

/*----------------------------------------------------------------------*/
/* The following u_char array matches parameterization types to element	*/
/* types which are able to accept the given parameterization.		*/
/*----------------------------------------------------------------------*/

u_char param_select[] = {
   ALL_TYPES,					/* P_NUMERIC */
   LABEL,					/* P_SUBSTRING */
   POLYGON | SPLINE | LABEL | OBJINST | ARC,	/* P_POSITION_X */
   POLYGON | SPLINE | LABEL | OBJINST | ARC,	/* P_POSITION_Y */
   POLYGON | SPLINE | ARC | PATH,		/* P_STYLE */
   LABEL,					/* P_JUSTIFY */
   ARC,						/* P_ANGLE1 */
   ARC,						/* P_ANGLE2 */
   ARC,						/* P_RADIUS */
   ARC,						/* P_MINOR_AXIS */
   LABEL | OBJINST,				/* P_ROTATION */
   LABEL | OBJINST,				/* P_SCALE */
   POLYGON | SPLINE | ARC | PATH,		/* P_LINEWIDTH */
   ALL_TYPES, 					/* P_COLOR */
   ALL_TYPES, 					/* P_EXPRESSION */
   POLYGON | SPLINE | LABEL | OBJINST | ARC 	/* P_POSITION */
};

#ifdef TCL_WRAPPER
xcWidget *param_buttons[] = {
   /* To be done---map buttons to Tk_Windows! */
};
#else
Widget *param_buttons[] = {
   &ParametersNumericButton,		/* P_NUMERIC */
   &ParametersSubstringButton,		/* P_SUBSTRING */
   &ParametersPositionButton,		/* P_POSITION_X */
   &ParametersPositionButton,		/* P_POSITION_Y */
   &ParametersStyleButton,		/* P_STYLE */
   &ParametersJustificationButton,	/* P_JUSTIFY */
   &ParametersStartAngleButton,		/* P_ANGLE1 */
   &ParametersEndAngleButton,		/* P_ANGLE2 */
   &ParametersRadiusButton,		/* P_RADIUS */
   &ParametersMinorAxisButton,		/* P_MINOR_AXIS */
   &ParametersRotationButton,		/* P_ROTATION */
   &ParametersScaleButton,		/* P_SCALE */
   &ParametersLinewidthButton,		/* P_LINEWIDTH */
   &ParametersColorButton,		/* P_COLOR */
   &ParametersPositionButton,		/* P_POSITION */
};
#endif

/*----------------------------------------------------------------------*/
/* Basic routines for matching parameters by key values.  Note that	*/
/* this really, really ought to be replaced by a hash table search!	*/
/*----------------------------------------------------------------------*/

/*----------------------------------------------------------------------*/
/* Check for the existance of a parameter with key "key" in object	*/
/* "thisobj".   Return true if the parameter exists.			*/
/*----------------------------------------------------------------------*/

Boolean check_param(objectptr thisobj, char *key)
{
   oparamptr tops;

   for (tops = thisobj->params; tops != NULL; tops = tops->next)
      if (!strcmp(tops->key, key))
	 return TRUE;

   return FALSE;
}

/*----------------------------------------------------------------------*/
/* Create a new parameter;  allocate memory for the parameter and the	*/
/* key.									*/
/*----------------------------------------------------------------------*/

oparamptr make_new_parameter(char *key)
{
   oparamptr newops;

   newops = (oparamptr)malloc(sizeof(oparam));
   newops->next = NULL;
   newops->key = (char *)malloc(1 + strlen(key));
   strcpy(newops->key, key);
   return newops;
}

/*----------------------------------------------------------------------*/
/* Create a new element (numeric) parameter.  Fill in essential values	*/
/*----------------------------------------------------------------------*/

eparamptr make_new_eparam(char *key)
{
   eparamptr newepp;

   newepp = (eparamptr)malloc(sizeof(eparam));
   newepp->next = NULL;
   newepp->key = (char *)malloc(1 + strlen(key));
   strcpy(newepp->key, key);
   newepp->pdata.refkey = NULL;		/* equivalently, sets pointno=0 */

   return newepp;
}

/*----------------------------------------------------------------------*/
/* Determine if a parameter is indirectly referenced.  If so, return	*/
/* the reference key.  If not, return NULL.				*/
/*----------------------------------------------------------------------*/

char *find_indirect_param(objinstptr thisinst, char *key)
{
   eparamptr epp;

   for (epp = thisinst->passed; epp != NULL; epp = epp->next) {
      if (!strcmp(epp->key, key))
	 return epp->pdata.refkey;
   }
   return NULL;
}

/*----------------------------------------------------------------------*/
/* Find the parameter in the indicated object by key			*/
/*----------------------------------------------------------------------*/

oparamptr match_param(objectptr thisobj, char *key)
{
   oparamptr fparam;

   for (fparam = thisobj->params; fparam != NULL; fparam = fparam->next)
      if (!strcmp(fparam->key, key))
	 return fparam;

   return NULL;		/* No parameter matched the key---error condition */
}

/*----------------------------------------------------------------------*/
/* Find the parameter in the indicated instance by key.  If no such 	*/
/* instance value exists, return NULL.					*/
/*----------------------------------------------------------------------*/

oparamptr match_instance_param(objinstptr thisinst, char *key)
{
   oparamptr fparam;

   for (fparam = thisinst->params; fparam != NULL; fparam = fparam->next)
      if (!strcmp(fparam->key, key))
	 return fparam;

   return NULL;		/* No parameter matched the key---error condition */
}

/*----------------------------------------------------------------------*/
/* Find the parameter in the indicated instance by key.  If no such 	*/
/* instance value exists, return the object (default) parameter.	*/
/*----------------------------------------------------------------------*/

oparamptr find_param(objinstptr thisinst, char *key)
{
   oparamptr fparam;
   fparam = match_instance_param(thisinst, key);
   if (fparam == NULL)
      fparam = match_param(thisinst->thisobject, key);
   return fparam;
}

/*----------------------------------------------------------------------*/
/* Find the total number of parameters in an object			*/
/*----------------------------------------------------------------------*/

int get_num_params(objectptr thisobj)
{
   oparamptr fparam;
   int nparam = 0;

   for (fparam = thisobj->params; fparam != NULL; fparam = fparam->next)
      nparam++;
   return nparam;
}

/*----------------------------------------------------------------------*/
/* Remove an element parameter (eparam) and free memory associated with	*/
/* the parameter key.							*/
/*----------------------------------------------------------------------*/

void free_element_param(genericptr thiselem, eparamptr thisepp)
{
   eparamptr epp, lastepp = NULL;

   for (epp = thiselem->passed; epp != NULL; epp = epp->next) {
      if (epp == thisepp) {
	 if (lastepp != NULL)
	    lastepp->next = epp->next;
	 else
	    thiselem->passed = epp->next;

	 /* If object is an instance and the pdata record is not NULL,	*/
	 /* then this is an indirect reference with the reference key	*/
	 /* stored as an allocated string in pdata.refkey, which needs	*/
	 /* to be free'd.						*/

	 if (IS_OBJINST(thiselem) && (epp->pdata.refkey != NULL))
	    free(epp->pdata.refkey);

	 free(epp->key);
	 free(epp);
	 break;
      }
      lastepp = epp; 
   }
}

/*----------------------------------------------------------------------*/
/* Free an instance parameter.  Note that this routine does not free	*/
/* any strings associated with string parameters!			*/
/*----------------------------------------------------------------------*/

void free_instance_param(objinstptr thisinst, oparamptr thisparam)
{
   oparamptr ops, lastops = NULL;

   for (ops = thisinst->params; ops != NULL; ops = ops->next) {
      if (ops == thisparam) {
	 if (lastops != NULL)
	    lastops->next = ops->next;
	 else
	    thisinst->params = ops->next;
	 free(ops->key);
	 free(ops);
	 break;
      }
      lastops = ops; 
   }
}

/*----------------------------------------------------------------------*/
/* Convenience function used by files.c to set a color parameter.	*/
/*----------------------------------------------------------------------*/

void std_eparam(genericptr gen, char *key)
{
   eparamptr epp;

   if (key == NULL) return;

   epp = make_new_eparam(key);
   epp->next = gen->passed;
   gen->passed = epp;
}

/*----------------------------------------------*/
/* Draw a circle at all parameter positions	*/
/*----------------------------------------------*/

void indicateparams(genericptr thiselem)
{
   int i, j, k, which;
   oparamptr ops;
   eparamptr epp;

   if (thiselem != NULL) {
      for (epp = thiselem->passed; epp != NULL; epp = epp->next) { 
	 ops = match_param(topobject, epp->key);
	 if (ops == NULL) continue;	/* error condition */
	 k = epp->pdata.pointno;
	 switch(ops->which) {
	    case P_POSITION: case P_POSITION_X: case P_POSITION_Y:
	       switch(thiselem->type) {
		  case ARC:
	             UDrawCircle(&TOARC(&thiselem)->position, ops->which);
		     break;
		  case LABEL:
	             UDrawCircle(&TOLABEL(&thiselem)->position, ops->which);
		     break;
		  case OBJINST:
	             UDrawCircle(&TOOBJINST(&thiselem)->position, ops->which);
		     break;
		  case POLYGON:
	             UDrawCircle(TOPOLY(&thiselem)->points + k, ops->which);
		     break;
		  case SPLINE:
	             UDrawCircle(&TOSPLINE(&thiselem)->ctrl[k], ops->which);
		     break;
	       }
	       break;
	 }
      }
   }
}

/*----------------------------------------------*/
/* Set the menu marks according to properties	*/
/* which are parameterized.  Unmanage the	*/
/* buttons which do not apply.			*/
/*						*/
/* pgen = NULL returns menu to default settings */
/*----------------------------------------------*/

#ifdef TCL_WRAPPER
void setparammarks(genericptr thiselem)
{
   /* Set GUI variables associated with the "parameter" menu.	*/

   int i, j, paramno;
   oparamptr ops;
   eparamptr epp;

   /* These match the order of parameter definitions in xcircuit.h */
   const char *paramvars[] = {"positionparam", "substringparam", "xparam",
	"yparam", "styleparam", "justparam", "startparam", "endparam",
	"radiusparam", "minorparam", "rotationparam", "scaleparam",
	"linewidthparam", "colorparam"};

   /* Reset all variables to false */
   for (i = 0; i < 14; i++)
      Tcl_SetVar2(xcinterp, "XCOps", (char *)paramvars[i], "false", TCL_NAMESPACE_ONLY);

   /* For each parameter declared, set the corresponding Tcl variable */
   if (thiselem != NULL) {
      for (epp = thiselem->passed; epp != NULL; epp = epp->next) {
	 ops = match_param(topobject, epp->key);
	 if (ops == NULL) continue;	/* error condition */
         Tcl_SetVar2(xcinterp, "XCOps", (char *)paramvars[ops->which], "true",
		TCL_NAMESPACE_ONLY);
      }
   }
}
#else

void setparammarks(genericptr thiselem)
{
   Widget w;
   Arg	wargs[1];
   const int rlength = sizeof(param_buttons) / sizeof(Widget *);
   int i, j, paramno;
   oparamptr ops;
   eparamptr epp;

   /* Clear all checkmarks */

   for (i = 0; i < rlength; i++) {
      XtSetArg(wargs[0], XtNsetMark, False);
      XtSetValues(*param_buttons[i], wargs, 1);
   }

   /* Check those properties which are parameterized in the element */

   if (thiselem != NULL) {
      for (epp = thiselem->passed; epp != NULL; epp = epp->next) {
         ops = match_param(topobject, epp->key);
         w = *param_buttons[ops->which];
         XtSetArg(wargs[0], XtNsetMark, True);
         XtSetValues(w, wargs, 1);
      }
   }

   /* Unmanage widgets which do not apply to the element type */

   for (i = 0; i < rlength; i++) {
      if ((thiselem == NULL) || (param_select[i] & thiselem->type))
	 XtManageChild(*param_buttons[i]);
      else
	 XtUnmanageChild(*param_buttons[i]);
   }
}

#endif

/*------------------------------------------------------*/
/* Make numerical parameter substitutions into an	*/
/* element.  "thisinst" may be NULL, in which case all	*/
/* default values are used in the substitution.		*/ 
/*							*/
/* Return values:					*/
/*   -1 if the instance declares no parameters		*/
/*    0 if parameters do not change the instance's bbox */
/*    1 if parameters change instance's bbox		*/
/*    2 if parameters change instance's netlist		*/
/*------------------------------------------------------*/

int opsubstitute(objectptr thisobj, objinstptr pinst)
{
   genericptr *eptr, thiselem;
   int i, j, k, ival;
   char *key;
   float fval;
   oparamptr dps, ops;
   eparamptr epp;
   XPoint *setpt;
   Boolean needrecalc;	/* for arcs and splines */
   int retval = -1;
   char *promoted;

   for (eptr = thisobj->plist; eptr < thisobj->plist + thisobj->parts; eptr++) {

      needrecalc = False;
      thiselem = *eptr;
      if (thiselem->passed == NULL) continue;	/* Nothing to substitute */

      for (epp = thiselem->passed; epp != NULL; epp = epp->next) {

	 /* Use the parameter from the instance, if available.	*/
	 /* Otherwise, revert to the type of the object.	*/
	 /* Normally they will be the same.			*/

         ops = match_param(thisobj, epp->key);
	 dps = (pinst != NULL) ?  find_param(pinst, epp->key) : ops;

	 if (dps != NULL) {

	    /* Get integer and float values.  Promote types if necessary */

	    switch(dps->type) {
	       case XC_INT:
		  ival = dps->parameter.ivalue;
		  fval = (float)(ival);
		  break;
	       case XC_FLOAT:
		  fval = dps->parameter.fvalue;
		  ival = (int)(fval + 0.5);
		  break;
	       case XC_STRING:
		  promoted = textprint(dps->parameter.string, pinst);
		  if (sscanf(promoted, "%g", &fval) == 1)
		     ival = (int)(fval + 0.5);
		  else
		     ival = 0;
		  free(promoted);
		  break;
	       case XC_EXPR:
		  if ((promoted = evaluate_expr(dps)) == NULL) continue;
		  if (sscanf(promoted, "%g", &fval) == 1)
		     ival = (int)(fval + 0.5);
		  free(promoted);
		  break;
	    }
	 }
	 else if (ops == NULL)
	    continue;

	 if (IS_OBJINST(thiselem)) {
	    key = epp->pdata.refkey;
	    if (key != NULL) {
	       objinstptr thisinst;
	       oparamptr refop, newop;

	       thisinst = TOOBJINST(eptr);

	       /* Sanity check: refkey must exist in object */
	       refop = match_param(thisinst->thisobject, key);
	       if (refop == NULL) {
		  Fprintf(stderr, "Error:  Reference key %s does not"
				" exist in object %s\n",
				key, thisinst->thisobject->name);
		  continue;
	       }

	       /* If an instance value already exists, remove it */
	       newop = match_instance_param(thisinst, refop->key);
	       if (newop != NULL)
		  free_instance_param(thisinst, newop);
	       
	       /* Create a new instance parameter */
	       newop = copyparameter(dps);
	       newop->next = thisinst->params;
	       thisinst->params = newop;

	       /* Change the key from the parent to the child */
	       if (strcmp(ops->key, refop->key)) {
		  free(newop->key);
		  newop->key = strdup(refop->key);
	       }
	       continue;
	    }
	 }

	 k = epp->pdata.pointno;
         switch(ops->which) {
	    case P_POSITION_X:
	       retval = max(retval, 1);
	       switch(thiselem->type) {
	          case POLYGON:
	             setpt = TOPOLY(&thiselem)->points + k;
		     setpt->x = ival;
		     break;
	          case SPLINE:
		     TOSPLINE(&thiselem)->ctrl[k].x = ival;
		     needrecalc = True;
		     break;
	          case LABEL:
		     TOLABEL(&thiselem)->position.x = ival;
		     break;
	          case OBJINST:
		     TOOBJINST(&thiselem)->position.x = ival;
		     break;
	          case ARC:
		     TOARC(&thiselem)->position.x = ival;
		     break;
	       }
	       break;
	    case P_POSITION_Y:
	       retval = max(retval, 1);
	       switch(thiselem->type) {
	          case POLYGON:
	             setpt = TOPOLY(&thiselem)->points + k;
		     setpt->y = ival;
		     break;
	          case SPLINE:
		     TOSPLINE(&thiselem)->ctrl[k].y = ival;
		     needrecalc = True;
		     break;
	          case LABEL:
		     TOLABEL(&thiselem)->position.y = ival;
		     break;
	          case OBJINST:
		     TOOBJINST(&thiselem)->position.y = ival;
		     break;
	          case ARC:
		     TOARC(&thiselem)->position.y = ival;
		     break;
	       }
	       break;
	    case P_STYLE:
	       retval = max(retval, 0);
	       switch(thiselem->type) {
	          case POLYGON:
		     TOPOLY(&thiselem)->style = ival;
		     break;
	          case SPLINE:
		     TOSPLINE(&thiselem)->style = ival;
		     break;
	          case ARC:
		     TOARC(&thiselem)->style = ival;
		     break;
	          case PATH:
		     TOPATH(&thiselem)->style = ival;
		     break;
	       }
	       break;
	    case P_JUSTIFY:
	       retval = max(retval, 1);
	       switch(thiselem->type) {
	          case LABEL:
		     TOLABEL(&thiselem)->justify = ival;
		     break;
	       }
	       break;
	    case P_ANGLE1:
	       retval = max(retval, 1);
	       switch(thiselem->type) {
	          case ARC:
		     TOARC(&thiselem)->angle1 = fval;
		     needrecalc = True;
		     break;
	       }
	       break;
	    case P_ANGLE2:
	       retval = max(retval, 1);
	       switch(thiselem->type) {
	          case ARC:
		     TOARC(&thiselem)->angle1 = fval;
		     needrecalc = True;
		     break;
	       }
	       break;
	    case P_RADIUS:
	       retval = max(retval, 1);
	       switch(thiselem->type) {
	          case ARC:
		     TOARC(&thiselem)->radius = ival;
		     TOARC(&thiselem)->yaxis = ival;
		     needrecalc = True;
		     break;
	       }
	       break;
	    case P_MINOR_AXIS:
	       retval = max(retval, 1);
	       switch(thiselem->type) {
	          case ARC:
		     TOARC(&thiselem)->yaxis = ival;
		     needrecalc = True;
		     break;
	       }
	       break;
	    case P_ROTATION:
	       retval = max(retval, 1);
	       switch(thiselem->type) {
	          case LABEL:
		     TOLABEL(&thiselem)->rotation = ival;
		     break;
	          case OBJINST:
		     TOOBJINST(&thiselem)->rotation = ival;
		     break;
	       }
	       break;
	    case P_SCALE:
	       retval = max(retval, 1);
	       switch(thiselem->type) {
	          case LABEL:
		     TOLABEL(&thiselem)->scale = fval;
		     break;
	          case OBJINST:
		     TOOBJINST(&thiselem)->scale = fval;
		     break;
	       }
	       break;
	    case P_LINEWIDTH:
	       retval = max(retval, 0);
	       switch(thiselem->type) {
	          case POLYGON:
		     TOPOLY(&thiselem)->width = fval;
		     break;
	          case SPLINE:
		     TOSPLINE(&thiselem)->width = fval;
		     break;
	          case ARC:
		     TOARC(&thiselem)->width = fval;
		     break;
	          case PATH:
		     TOPATH(&thiselem)->width = fval;
		     break;
	       }
	       break;
	    case P_COLOR:
	       retval = max(retval, 0);
	       thiselem->color = ival;
	       break;
         }
      }
      /* substitutions into arcs and splines require that the	*/
      /* line segments be recalculated.				*/

      if (needrecalc) {
	 switch(thiselem->type) {
	    case ARC:
	       calcarc((arcptr)thiselem);
	       break;
	    case SPLINE:
	       calcspline((splineptr)thiselem);
	       break;
	 }
      }
   }
   return retval;
}

/*------------------------------------------------------*/
/* Same as above, but determines the object from the	*/
/* current page hierarchy.				*/
/*------------------------------------------------------*/

int psubstitute(objinstptr thisinst)
{
   objinstptr pinst;
   objectptr thisobj;

   pinst = (thisinst == areastruct.topinstance) ? areastruct.topinstance : thisinst;
   if (pinst == NULL) return -1;		/* there is no instance */
   else if (pinst->thisobject->params == NULL)
      return -1;			    /* object has no parameters */

   thisobj = (pinst == NULL) ? topobject : pinst->thisobject;
   return opsubstitute(thisobj, pinst);
}

/*----------------------------------------------*/
/* Check if an element contains a parameter.	*/
/*----------------------------------------------*/

Boolean has_param(genericptr celem)
{
   if (IS_LABEL(celem) == LABEL) {
      stringpart *cstr;
      labelptr clab = (labelptr)celem;
      for (cstr = clab->string; cstr != NULL; cstr = cstr->nextpart)
	 if (cstr->type == PARAM_START)
	    return TRUE;
   }
   if (celem->passed != NULL) return TRUE;
   return FALSE;
}

/*------------------------------------------------------*/
/* Find "current working values" in the element list of	*/
/* an object, and write them into the instance's	*/
/* parameter list.					*/
/* This is just the opposite of "psubstitute()", except	*/
/* that instance values are created prior to writeback, */
/* and resolved afterward.				*/
/*------------------------------------------------------*/

void pwriteback(objinstptr thisinst)
{
   genericptr *eptr, thiselem;
   objectptr thisobj;
   objinstptr pinst;
   eparamptr epp;
   oparamptr ops, ips;
   int i, j, k, *destivalptr, found;
   XPoint *setpt;
   Boolean changed, need_redraw = FALSE;
   union {
      int ival;
      float fval;
   } wtemp;

   pinst = thisinst;
   thisobj = (pinst == NULL) ? topobject : pinst->thisobject;

   /* Make sure that all instance values exist */
   if (pinst != NULL) copyparams(pinst, pinst);

   /* Because more than one element can point to the same parameter, we search	*/
   /* through each (numerical) parameter declared in the object.  If any	*/
   /* element has a different value, the parameter is changed to match.   This	*/
   /* operates on the assumption that no more than one element will change the	*/
   /* value of any one parameter on a single call to pwriteback().		*/

   for (ops = thisobj->params; ops != NULL; ops = ops->next) {
      /* handle pre-assigned numeric parameters only */
      if ((ops->which == P_SUBSTRING) || (ops->which == P_EXPRESSION) ||
		(ops->which == P_NUMERIC))
	 continue;
      found = 0;
      changed = FALSE;
      ips = (pinst != NULL) ? match_instance_param(pinst, ops->key) : NULL;
      for (eptr = thisobj->plist; eptr < thisobj->plist + thisobj->parts; eptr++) {
         thiselem = *eptr;
         if (thiselem->passed == NULL) continue;	/* Nothing to write back */
         for (epp = thiselem->passed; epp != NULL; epp = epp->next) {
	    if (!strcmp(epp->key, ops->key)) {
	       found++;
	       k = epp->pdata.pointno;
               switch(ops->which) {
	          case P_POSITION_X:
	             switch(thiselem->type) {
	                case OBJINST:
		           wtemp.ival = TOOBJINST(&thiselem)->position.x;
		           break;
	                case LABEL:
		           wtemp.ival = TOLABEL(&thiselem)->position.x;
		           break;
	                case POLYGON:
	                   setpt = TOPOLY(&thiselem)->points + k;
		           wtemp.ival = setpt->x;
		           break;
	                case ARC:
		           wtemp.ival = TOARC(&thiselem)->position.x;
		           break;
	                case SPLINE:
		           wtemp.ival = TOSPLINE(&thiselem)->ctrl[k].x;
		           break;
	             }
	             break;
	          case P_POSITION_Y:
	             switch(thiselem->type) {
	                case OBJINST:
		           wtemp.ival = TOOBJINST(&thiselem)->position.y;
		           break;
	                case LABEL:
		           wtemp.ival = TOLABEL(&thiselem)->position.y;
		           break;
	                case POLYGON:
	                   setpt = TOPOLY(&thiselem)->points + k;
		           wtemp.ival = setpt->y;
		           break;
	                case ARC:
		           wtemp.ival = TOARC(&thiselem)->position.y;
		           break;
	                case SPLINE:
		           wtemp.ival = TOSPLINE(&thiselem)->ctrl[k].y;
		           break;
	             }
	             break;
	          case P_STYLE:
	             switch(thiselem->type) {
	                case POLYGON:
		           wtemp.ival = TOPOLY(&thiselem)->style;
		           break;
	                case ARC:
		           wtemp.ival = TOARC(&thiselem)->style;
		           break;
	                case SPLINE:
		           wtemp.ival = TOSPLINE(&thiselem)->style;
		           break;
	                case PATH:
		           wtemp.ival = TOPATH(&thiselem)->style;
		           break;
	             }
	             break;
	          case P_JUSTIFY:
	             switch(thiselem->type) {
	                case LABEL:
		           wtemp.ival = TOLABEL(&thiselem)->justify;
		           break;
	             }
	             break;
	          case P_ANGLE1:
	             switch(thiselem->type) {
	                case ARC:
		           wtemp.fval = TOARC(&thiselem)->angle1;
		           break;
	             }
	             break;
	          case P_ANGLE2:
	             switch(thiselem->type) {
	                case ARC:
		           wtemp.fval = TOARC(&thiselem)->angle1;
		           break;
	             }
	             break;
	          case P_RADIUS:
	             switch(thiselem->type) {
	                case ARC:
		           wtemp.ival = TOARC(&thiselem)->radius;
		           break;
	             }
	             break;
	          case P_MINOR_AXIS:
	             switch(thiselem->type) {
	                case ARC:
		           wtemp.ival = TOARC(&thiselem)->yaxis;
		           break;
	             }
	             break;
	          case P_ROTATION:
	             switch(thiselem->type) {
	                case OBJINST:
		           wtemp.ival = TOOBJINST(&thiselem)->rotation;
		           break;
	                case LABEL:
		           wtemp.ival = TOLABEL(&thiselem)->rotation;
		           break;
	             }
	             break;
	          case P_SCALE:
	             switch(thiselem->type) {
	                case OBJINST:
		           wtemp.fval = TOOBJINST(&thiselem)->scale;
		           break;
	                case LABEL:
		           wtemp.fval = TOLABEL(&thiselem)->scale;
		           break;
	             }
	             break;
	          case P_LINEWIDTH:
	             switch(thiselem->type) {
	                case POLYGON:
		           wtemp.fval = TOPOLY(&thiselem)->width;
		           break;
	                case ARC:
		           wtemp.fval = TOARC(&thiselem)->width;
		           break;
	                case SPLINE:
		           wtemp.fval = TOSPLINE(&thiselem)->width;
		           break;
	                case PATH:
		           wtemp.fval = TOPATH(&thiselem)->width;
		           break;
	             }
	             break;
		  case P_COLOR:
		     wtemp.ival = thiselem->color;
		     break;
               }
	       destivalptr = (ips != NULL) ? &ips->parameter.ivalue
			: &ops->parameter.ivalue;
	       if ((!changed) && (wtemp.ival != *destivalptr)) {
		  *destivalptr = wtemp.ival;
		  changed = TRUE;
	       }
	       else if (found > 1) need_redraw = TRUE;
	       break;
	    }
	 }
      }
   }

   /* Any instance values which are identical to the default value	*/
   /* get erased (so they won't be written to the output unnecessarily) */

   if (pinst != NULL) resolveparams(pinst);

   /* Because more than one element may use the same parameter,		*/
   /* pwriteback checks for cases in which a change in one element	*/
   /* precipitates a change in another.  If so, force a redraw.		*/

   if (need_redraw && (thisinst == areastruct.topinstance))
      drawarea(NULL, NULL, NULL);
}

/*------------------------------------------------------*/
/* If the instance comes from the library, replace the	*/
/* default value with the instance value.		*/
/*------------------------------------------------------*/

void replaceparams(objinstptr thisinst)
{
   objectptr thisobj;
   oparamptr ops, ips;
   int i, nullparms = 0;

   thisobj = thisinst->thisobject;

   for (ops = thisobj->params; ops != NULL; ops = ops->next) {
      ips = match_instance_param(thisinst, ops->key);
      if (ips == NULL) continue;  /* this parameter is already default */

      switch(ops->type) {
	 case XC_STRING:
	    if (stringcomp(ops->parameter.string, ips->parameter.string)) {
	       freelabel(ops->parameter.string);
	       ops->parameter.string = ips->parameter.string;
	       free_instance_param(thisinst, ips);
	    }
	    break;
	 case XC_EXPR:
	    if (strcmp(ops->parameter.expr, ips->parameter.expr)) {
	       free(ops->parameter.expr);
	       ops->parameter.expr = ips->parameter.expr;
	       free_instance_param(thisinst, ips);
	    }
	 case XC_INT: case XC_FLOAT:
	    if (ops->parameter.ivalue != ips->parameter.ivalue) {
	       ops->parameter.ivalue = ips->parameter.ivalue;
	       free_instance_param(thisinst, ips);
	    }
	    break;
      }
   }
}

/*------------------------------------------------------*/
/* Resolve differences between the object instance	*/
/* parameters and the default parameters.  If they	*/
/* are the same for any parameter, delete that instance	*/
/* such that the instance reverts to the default value.	*/
/*------------------------------------------------------*/

void resolveparams(objinstptr thisinst)
{
   objectptr thisobj;
   liblistptr spec;
   oparamptr ops, ips;
   int i;

   /* If the instance has no parameters itself, ignore it. */
   if (thisinst == NULL || thisinst->params == NULL) return;

   /* If the object was pushed into from a library, we want to change	*/
   /* the default, not the instanced, parameter values.  However, this	*/
   /* is not true for "virtual" library objects (in the instlist)	*/

   if ((i = checklibtop()) >= 0) {
      for (spec = xobjs.userlibs[i].instlist; spec != NULL;
                spec = spec->next)
         if (spec->thisinst == thisinst)
	    break;

      if ((spec == NULL) || (spec->virtual == FALSE)) {
         /* Fprintf(stdout, "Came from library:  changing default value\n"); */
         replaceparams(thisinst);
         return;
      }
   }

   /* Parameters which are changed on a top-level page must also change	*/
   /* the default value; otherwise, the instance value shadows the page	*/
   /* object's value but the page object's value is the one written to	*/
   /* the output file.							*/

   else if (is_page(thisinst->thisobject) >= 0) {
      replaceparams(thisinst);
      return;
   }

   thisobj = thisinst->thisobject;

   for (ops = thisobj->params; ops != NULL; ops = ops->next) {
      ips = match_instance_param(thisinst, ops->key);
      if (ips == NULL) continue;  /* this parameter is already default */

      /* If type or which fields do not match, then we don't need to look */
      /* any further;  object and instance have different parameters.	  */
      if ((ips->type != ops->type) || (ips->which != ops->which)) continue;

      switch(ops->type) {
	 case XC_STRING:
	    if (!stringcomp(ops->parameter.string, ips->parameter.string)) {
	       freelabel(ips->parameter.string);
	       free_instance_param(thisinst, ips);
	    }
	    break;
	 case XC_EXPR:
	    if (!strcmp(ops->parameter.expr, ips->parameter.expr)) {
	       free(ips->parameter.expr);
	       free_instance_param(thisinst, ips);
	    }
	    break;
	 case XC_INT: case XC_FLOAT:
	    if (ops->parameter.ivalue == ips->parameter.ivalue) {
	       free_instance_param(thisinst, ips);
	    }
	    break;
      }
   }

   if (thisinst->params != NULL) {
      /* Object must recompute bounding box if any instance	*/
      /* uses a non-default parameter.				*/

      calcbboxvalues(thisinst, NULL);
   }
}

/*--------------------------------------------------------------*/
/* Return a copy of the single eparameter "cepp"		*/
/*--------------------------------------------------------------*/

eparamptr copyeparam(eparamptr cepp, genericptr thiselem)
{
   eparamptr newepp;

   newepp = make_new_eparam(cepp->key);
   if (IS_OBJINST(thiselem) && (cepp->pdata.refkey != NULL))
      newepp->pdata.refkey = strdup(cepp->pdata.refkey);
   else
      newepp->pdata.pointno = cepp->pdata.pointno;
   return newepp;
}

/*------------------------------------------------------*/
/* Copy all element parameters from source to dest	*/
/*------------------------------------------------------*/

void copyalleparams(genericptr destinst, genericptr sourceinst)
{
   eparamptr cepp, newepp;

   for (cepp = sourceinst->passed; cepp != NULL; cepp = cepp->next) {
      newepp = copyeparam(cepp, sourceinst);
      newepp->next = destinst->passed;
      destinst->passed = newepp;
   }
}

/*--------------------------------------------------------------*/
/* Return a copy of the single parameter "cops"			*/
/*--------------------------------------------------------------*/

oparamptr copyparameter(oparamptr cops)
{
   oparamptr newops;

   newops = make_new_parameter(cops->key);
   newops->type = cops->type;
   newops->which = cops->which;
   switch(cops->type) {
      case XC_STRING:
	 newops->parameter.string = stringcopy(cops->parameter.string);
	 break;
      case XC_EXPR:
	 newops->parameter.expr = strdup(cops->parameter.expr);
	 break;
      case XC_INT: case XC_FLOAT:
	 newops->parameter.ivalue = cops->parameter.ivalue;
	 break;
      default:
	 Fprintf(stderr, "Error:  bad parameter\n");
	 break;
   }
   return newops;
}

/*------------------------------------------------------*/
/* Fill any NULL instance parameters with the values	*/
/* from the calling instance, or from the instance	*/
/* object's defaults if destinst = sourceinst.		*/ 
/*------------------------------------------------------*/

void copyparams(objinstptr destinst, objinstptr sourceinst)
{
   oparamptr psource, cops, newops;

   if (sourceinst == NULL) return;
   if (destinst == sourceinst)
      psource = sourceinst->thisobject->params;
   else
      psource = sourceinst->params;

   for (cops = psource; cops != NULL; cops = cops->next) {
      if (match_instance_param(destinst, cops->key) == NULL) {
         newops = copyparameter(cops);
	 newops->next = destinst->params;
	 destinst->params = newops;
      }
   }
}

/*--------------------------------------------------------------*/
/* Make an unreferenced parameter expression in the object	*/
/* refobject.							*/
/* The expression may have either a numeric or string result.	*/
/* the proper "which" value is passed as an argument.		*/
/*--------------------------------------------------------------*/

void makeexprparam(objectptr refobject, char *key, char *value, int which)
{
   oparamptr newops;
   char *newkey;

   /* Check against postscript reserved names */

   newkey = checkpostscriptname(key, NULL, NULL);

   /* Ensure that no two parameters have the same name! */

   if (check_param(refobject, newkey)) {
      Wprintf("There is already a parameter with that key!");
   }

   newops = make_new_parameter(newkey);
   newops->next = refobject->params;
   refobject->params = newops;
   newops->type = XC_EXPR;		/* expression requiring evaluation */
   newops->which = which;
   newops->parameter.expr = strdup(value);
   incr_changes(refobject);
   free(newkey);
}

/*------------------------------------------------------------------*/
/* Make an unreferenced numerical parameter in the object refobject */
/*------------------------------------------------------------------*/

void makefloatparam(objectptr refobject, char *key, float value)
{
   oparamptr newops;
   char *newkey;

   /* Check against postscript reserved names */

   newkey = checkpostscriptname(key, NULL, NULL);

   /* Ensure that no two parameters have the same name! */

   if (check_param(refobject, newkey)) {
      Wprintf("There is already a parameter with that key!");
   }

   newops = make_new_parameter(key);
   newops->next = refobject->params;
   refobject->params = newops;
   newops->type = XC_FLOAT;		/* general-purpose numeric */
   newops->which = P_NUMERIC;
   newops->parameter.fvalue = value;
   incr_changes(refobject);
   free(newkey);
}

/*----------------------------------------------------------------*/
/* Make an unreferenced string parameter in the object refobject. */
/*----------------------------------------------------------------*/

void makestringparam(objectptr refobject, char *key, stringpart *strptr)
{
   oparamptr newops;
   char *newkey;

   /* Check against postscript reserved names */

   newkey = checkpostscriptname(key, NULL, NULL);

   /* Ensure that no two parameters have the same name! */

   if (check_param(refobject, newkey)) {
      Wprintf("There is already a parameter with that key!");
   }

   newops = make_new_parameter(newkey);
   newops->next = refobject->params;
   refobject->params = newops;
   newops->type = XC_STRING;
   newops->which = P_SUBSTRING;
   newops->parameter.string = strptr;
   incr_changes(refobject);
   free(newkey);
}

/*--------------------------------------------------------------*/
/* Make a numerical (integer or float) parameter.		*/
/* If "key" is non-NULL, then the parameter key will be set	*/
/* from this rather than from the list "param_keys".  If the	*/
/* key is an existing key with the same type as "mode", then	*/
/* the new parameter will be linked to the existing one.	*/
/*--------------------------------------------------------------*/

void makenumericalp(genericptr *gelem, u_int mode, char *key)
{
   oparamptr ops, newops;
   eparamptr epp;
   XPoint *pptr;
   char new_key[7], *keyptr;
   int pidx;

   /* Numerical parameters have designated keys to avoid the necessity	*/
   /* of having to specify a key, to avoid conflicts with PostScript	*/
   /* predefined keys, and other good reasons.				*/

   /* The order of these type names must match the enumeration in xcircuit.h */

   static char *param_keys[] = {
	"p_gps", "p_str", "p_xps", "p_yps", "p_sty", "p_jst", "p_an1",
	"p_an2", "p_rad", "p_axs", "p_rot", "p_scl", "p_wid", "p_col"
   };

   /* Parameterized strings are handled by makeparam() */

   if (IS_LABEL(*gelem) && mode == P_SUBSTRING) {
      Fprintf(stderr, "Error: String parameter passed to makenumericalp()\n");
      return;
   }

   /* Cannot form this type of parameter on a top-level page */
   if (is_page(topobject) != -1) {
      Wprintf("Cannot form a parameter in a top-level page!");
      return;
   }

   /* Make sure the parameter doesn't already exist.	   */

   for (epp = (*gelem)->passed; epp != NULL; epp = epp->next) {
      ops = match_param(topobject, epp->key);
      if (ops->which == (u_char)mode) {
	 Fprintf(stderr, "Cannot duplicate a parameter!\n");
	 return;
      }
   }

   /* Ensure that no two parameters have the same name! */

   if (key) {
      keyptr = checkpostscriptname(key, NULL, NULL);
   }
   else {
      strcpy(new_key, param_keys[mode]);
      pidx = 0;
      while (check_param(topobject, new_key)) {
         pidx++;
         sprintf(new_key, "%s%d", param_keys[mode], pidx);
      }
      keyptr = new_key;
   }

   /* Add the parameter to the element's parameter list */

   epp = make_new_eparam(keyptr);
   epp->next = (*gelem)->passed;
   (*gelem)->passed = epp;

   /* If keyptr does not point to an existing parameter, then we need	*/
   /* to create it in the object's parameter list and set the default	*/
   /* value to the existing value of the element.			*/

   ops = match_param(topobject, keyptr);
   if (ops == NULL) {
      newops = make_new_parameter(keyptr);
      newops->next = topobject->params;
      topobject->params = newops;
      newops->type = XC_INT;		/* most commonly used value */
      newops->which = (u_char)mode;	/* what kind of parameter */
      incr_changes(topobject);
   }
   else {
      if (ops->which != (u_char)mode) {
	 free_element_param(*gelem, epp);
	 Fprintf(stderr, "Error: Attempt to link a parameter to "
			"a parameter of a different type\n");
	 goto param_done;
      }
      if ((newops = match_instance_param(areastruct.topinstance, keyptr)) == NULL) {
         newops = make_new_parameter(keyptr);
         newops->next = areastruct.topinstance->params;
         areastruct.topinstance->params = newops;
         newops->type = ops->type;
         newops->which = ops->which;
      }
      else {
         /* If the parameter exists and the instance has a non-default	*/
         /* value for it, we will not change the instance record.  If	*/
         /* the element value is different, then it will change, so we	*/
	 /* should redraw.						*/
         drawarea(NULL, NULL, NULL);
         newops = NULL;
	 goto param_done;
      }
   }

   if (newops) {
      if (mode == P_COLOR)
	 newops->parameter.ivalue = (int)((*gelem)->color);

      switch((*gelem)->type) {
         case LABEL:
	    switch(mode) {
	       case P_POSITION_X:
	          newops->parameter.ivalue = (int)TOLABEL(gelem)->position.x;
	          break;
	       case P_POSITION_Y:
	          newops->parameter.ivalue = (int)TOLABEL(gelem)->position.y;
	          break;
	       case P_JUSTIFY:
	          newops->parameter.ivalue = (int)TOLABEL(gelem)->justify;
	          break;
	       case P_ROTATION:
	          newops->parameter.ivalue = (int)TOLABEL(gelem)->rotation;
	          break;
	       case P_SCALE:
	          newops->type = XC_FLOAT;
	          newops->parameter.fvalue = TOLABEL(gelem)->scale;
	          break;
	    }
	    break;
         case ARC:
	    switch(mode) {
	       case P_POSITION_X:
	          newops->parameter.ivalue = (int)TOARC(gelem)->position.x;
	          break;
	       case P_POSITION_Y:
	          newops->parameter.ivalue = (int)TOARC(gelem)->position.y;
	          break;
	       case P_ANGLE1:
	          newops->type = XC_FLOAT;
	          newops->parameter.fvalue = TOARC(gelem)->angle1;
	          break;
	       case P_ANGLE2:
	          newops->type = XC_FLOAT;
	          newops->parameter.fvalue = TOARC(gelem)->angle2;
	          break;
	       case P_RADIUS:
	          newops->parameter.ivalue = (int)TOARC(gelem)->radius;
	          break;
	       case P_MINOR_AXIS:
	          newops->parameter.ivalue = (int)TOARC(gelem)->yaxis;
	          break;
	       case P_STYLE:
	          newops->parameter.ivalue = (int)TOARC(gelem)->style;
	          break;
	       case P_LINEWIDTH:
	          newops->type = XC_FLOAT;
	          newops->parameter.fvalue = TOARC(gelem)->width;
	          break;
	    }
	    break;
         case OBJINST:
	    switch(mode) {
	       case P_POSITION_X:
	          newops->parameter.ivalue = (int)TOOBJINST(gelem)->position.x;
	          break;
	       case P_POSITION_Y:
	          newops->parameter.ivalue = (int)TOOBJINST(gelem)->position.y;
	          break;
	       case P_ROTATION:
	          newops->parameter.ivalue = (int)TOOBJINST(gelem)->rotation;
	          break;
	       case P_SCALE:
	          newops->type = XC_FLOAT;
	          newops->parameter.fvalue = TOOBJINST(gelem)->scale;
	          break;
	    }
	    break;
         case POLYGON:
	    switch(mode) {
	       case P_POSITION_X:
	          pptr = TOPOLY(gelem)->points + areastruct.editcycle; 
	          newops->parameter.ivalue = (int)pptr->x;
	          epp->pdata.pointno = areastruct.editcycle;
	          break;
	       case P_POSITION_Y:
	          pptr = TOPOLY(gelem)->points + areastruct.editcycle; 
	          newops->parameter.ivalue = (int)pptr->y;
	          epp->pdata.pointno = areastruct.editcycle;
	          break;
	       case P_STYLE:
	          newops->parameter.ivalue = (int)TOPOLY(gelem)->style;
	          break;
	       case P_LINEWIDTH:
	          newops->type = XC_FLOAT;
	          newops->parameter.fvalue = TOPOLY(gelem)->width;
	          break;
	    }
	    break;
         case SPLINE:
	    switch(mode) {
	       case P_POSITION_X:
	          pptr = TOSPLINE(gelem)->ctrl + areastruct.editcycle; 
	          newops->parameter.ivalue = (int)pptr->x;
	          epp->pdata.pointno = areastruct.editcycle;
	          break;
	       case P_POSITION_Y:
	          pptr = TOSPLINE(gelem)->ctrl + areastruct.editcycle; 
	          newops->parameter.ivalue = (int)pptr->y;
	          epp->pdata.pointno = areastruct.editcycle;
	          break;
	       case P_STYLE:
	          newops->parameter.ivalue = (int)TOSPLINE(gelem)->style;
	          break;
	       case P_LINEWIDTH:
	          newops->type = XC_FLOAT;
	          newops->parameter.fvalue = TOSPLINE(gelem)->width;
	          break;
	    }
	    break;
         case PATH:
	    switch(mode) {
	       case P_STYLE:
	          newops->parameter.ivalue = (int)TOPATH(gelem)->style;
	          break;
	       case P_LINEWIDTH:
	          newops->type = XC_FLOAT;
	          newops->parameter.fvalue = TOPATH(gelem)->width;
	          break;
	    }
	    break;
      }
   }

param_done:
   if (keyptr != new_key) free(keyptr);
}

/*--------------------------------------------------------------*/
/* Remove a numerical (integer or float) parameter.  Remove by	*/
/* type, rather than key.  There may be several keys associated	*/
/* with a particular type, so we want to remove all of them.	*/
/*--------------------------------------------------------------*/

void unmakenumericalp(genericptr *gelem, u_int mode)
{
   genericptr *pgen;
   eparamptr epp;
   oparamptr ops;
   char *key;
   Boolean done = False, is_last = True;
   
   /* Parameterized strings are handled by makeparam() */
   if (mode == P_SUBSTRING) {
      Fprintf(stderr, "Error: Unmakenumericalp called on a string parameter.\n");
      return;
   }

   /* Avoid referencing the object by only looking at the element.	*/
   /* But, avoid dereferencing the pointer!				*/

   while (!done) {
      key = NULL;
      done = True;
      for (epp = (*gelem)->passed; epp != NULL; epp = epp->next) {
	 ops = match_param(topobject, epp->key);
	 if (ops == NULL) break;	/* Error---no such parameter */
         else if (ops->which == (u_char)mode) {
	    key = ops->key;
	    free_element_param(*gelem, epp);

	    /* check for any other references to the parameter.  If there */
	    /* are none, remove all instance records of the eparam, then  */
	    /* remove the parameter itself from the object.		  */

	    for (pgen = topobject->plist; pgen < topobject->plist
			+ topobject->parts; pgen++) {
	       if (*pgen == *gelem) continue;
	       for (epp = (*pgen)->passed; epp != NULL; epp = epp->next) {
		  if (!strcmp(epp->key, key)) {
		     is_last = False;
		     break;
		  }
	       }
	       if (!is_last) break;
	    }
	    if (is_last)
	       free_object_param(topobject, ops);

	    done = False;
	    break;
	 }
      }
   }
}

/*--------------------------------------------------------------*/
/* Insert an existing parameter into a string.			*/
/*--------------------------------------------------------------*/

void insertparam()
{
   labelptr tlab;
   oparamptr ops;
   int result, nparms;
   char *selparm;

   /* Don't allow nested parameters */

   tlab = TOLABEL(EDITPART);
   if (paramcross(topobject, tlab)) {
      Wprintf("Parameters cannot be nested!");
      return;
   }

#ifdef TCL_WRAPPER

   /* The Tcl version uses a GUI selection mechanism. */

   result = Tcl_Eval(xcinterp, "xcircuit::promptselectparam");
   if (result != TCL_OK) {
      Tcl_SetResult(xcinterp, "Error in executing promptselectparam", NULL);
      return;
   }
   selparm = Tcl_GetString(Tcl_GetObjResult(xcinterp));

   ops = match_param(topobject, selparm);
   if (ops == NULL)
   {
       Tcl_SetResult(xcinterp, "No such parameter.", NULL);
       selparm = NULL;
   }

#else

   /* First pass is to check how many string parameters there are:	*/
   /* If only one, then automatically use it.  Otherwise, prompt	*/
   /* for which parameter to use.					*/

   for (ops = topobject->params; ops != NULL; ops = ops->next)
      if (ops->type == XC_STRING)
	 nparms++;

   if (nparms > 1) {
      char *newstr, *sptr;
      char *sstart = (char *)malloc(1024);
      nparms = 0;
      strcpy(sstart, "Choose: ");
      sptr = sstart + 8;
      for (ops = topobject->params; ops != NULL; ops = ops->next) {
	 if (ops->type == XC_STRING) {
	    nparms++;
	    if (nparms != 1) {
	       strcat(sptr, ", ");
	       sptr += 2;
	    }
	    sprintf(sptr, "%s = <", ops->key);
	    newstr = stringprint(ops->parameter.string, NULL);
	    strcat(sptr, newstr);
	    free(newstr);
	    newstr = NULL;
            sptr += strlen(sptr);
	 }
      }
      Wprintf(sstart);
      free(sstart);

      /* Need a new non-Tcl version method . . . */
      ops = NULL;	/* placeholder */
   }

#endif

   if (ops != NULL)
      labeltext(PARAM_START, selparm);
   else
      Wprintf("No such parameter.");
}

/*--------------------------------------------------------------*/
/* Parameterize a label string.					*/
/*--------------------------------------------------------------*/

void makeparam(labelptr thislabel, char *key)
{
   oparamptr newops;
   stringpart *begpart, *endpart;
   char *newkey;

   /* cannot form a parameter on a top-level page */

   if (is_page(topobject) != -1) {
      Wprintf("Cannot form a parameter in a top-level page!");
      return;
   }

   /* make sure this does not overlap another parameter */

   if (paramcross(topobject, thislabel)) {
      Wprintf("Parameters cannot be nested!");
      textend = 0;
      return;
   }

   /* check parameter against reserved PostScript names */

   newkey = checkpostscriptname(key, NULL, NULL);

   /* First, place PARAM_START and PARAM_END structures at the	*/
   /* intended parameter boundaries				*/

   if (textend > 0 && textend < textpos) {  	/* partial string */
      splitstring(textend, &thislabel->string, areastruct.topinstance);
      splitstring(textpos, &thislabel->string, areastruct.topinstance);

      /* Because "splitstring" changes all the pointers, find the    */
      /* stringpart structures at textend and textpos positions.     */

      begpart = findstringpart(textend, NULL, thislabel->string,
		areastruct.topinstance);
      endpart = findstringpart(textpos, NULL, thislabel->string,
		areastruct.topinstance);

      /* Make the new segments for PARAM_START and PARAM_END.	*/

      begpart = makesegment(&thislabel->string, begpart);
      endpart = makesegment(&thislabel->string, endpart);
   }
   else {				/* full string */
      makesegment(&thislabel->string, thislabel->string);
      begpart = thislabel->string;
      endpart = makesegment(&thislabel->string, NULL);
   }
   begpart->type = PARAM_START;
   begpart->data.string = (char *)malloc(1 + strlen(newkey));
   strcpy(begpart->data.string, newkey);
   endpart->type = PARAM_END;
   endpart->data.string = (u_char *)NULL;

   /* Now move the sections of string to the object parameter */

   newops = make_new_parameter(newkey);
   newops->next = topobject->params;
   topobject->params = newops;
   newops->type = XC_STRING;
   newops->which = P_SUBSTRING;
   newops->parameter.string = begpart->nextpart;
   begpart->nextpart = endpart->nextpart;
   endpart->nextpart = NULL;

   textend = 0;
   incr_changes(topobject);
   free(newkey);
}

/*--------------------------------------------------------------*/
/* Destroy the selected parameter in the indicated instance 	*/
/*--------------------------------------------------------------*/

void destroyinst(objinstptr tinst, objectptr refobj, char *key)
{
   oparamptr ops;
   short k;

   if (tinst->thisobject == refobj) {
      ops = match_instance_param(tinst, key);
      if (ops != NULL) {
	 if (ops->type == XC_STRING)
	    freelabel(ops->parameter.string);
	 else if (ops->type == XC_EXPR)
	    free(ops->parameter.expr);
	 free_instance_param(tinst, ops);
      }
   }
}
	
/*--------------------------------------------------------------*/
/* Search and destroy the selected parameter in all instances 	*/
/* of the specified object.					*/
/*--------------------------------------------------------------*/

void searchinst(objectptr topobj, objectptr refobj, char *key)
{
   objinstptr tinst;
   genericptr *pgen;

   if (topobj == NULL) return;

   for (pgen = topobj->plist; pgen < topobj->plist + topobj->parts; pgen++) {
      if (IS_OBJINST(*pgen)) {
	 tinst = TOOBJINST(pgen);
	 destroyinst(tinst, refobj, key);
      }
   }
}

/*--------------------------------------------------------------*/
/* Destroy the object parameter with key "key" in the object	*/
/* "thisobj".  This requires first tracking down and removing	*/
/* all instances of the parameter which may exist anywhere in	*/
/* the database.						*/
/*--------------------------------------------------------------*/

void free_object_param(objectptr thisobj, oparamptr thisparam)
{
   int k, j, l = -1;
   liblistptr spec;
   oparamptr ops, lastops = NULL;
   char *key = thisparam->key;

   /* Find all instances of this object and remove any parameter */
   /* substitutions which may have been made.		         */

   for (k = 0; k < xobjs.pages; k++) {
      if (xobjs.pagelist[k]->pageinst != NULL)
         searchinst(xobjs.pagelist[k]->pageinst->thisobject, thisobj, key);
   }
   for (j = 0; j < xobjs.numlibs; j++) {
      for (k = 0; k < xobjs.userlibs[j].number; k++) {
	 if (*(xobjs.userlibs[j].library + k) == thisobj)
	    l = j;
	 else
            searchinst(*(xobjs.userlibs[j].library + k), thisobj, key);
      }
   }

   /* Ensure that this parameter is not referred to in the undo records */
   /* We could be kinder and gentler to the undo record here. . . */
   flush_undo_stack();

   /* Also check through all instances on the library page */
   if (l >= 0)
      for (spec = xobjs.userlibs[l].instlist; spec != NULL; spec = spec->next) 
         destroyinst(spec->thisinst, thisobj, key);

   /* Remove the parameter from the object itself, tidying up	*/
   /* the linked list after it.					*/

   for (ops = thisobj->params; ops != NULL; ops = ops->next) {
      if (ops == thisparam) {
	 if (lastops != NULL)
	    lastops->next = ops->next;
	 else
	    thisobj->params = ops->next;
	 free(ops->key);
	 free(ops);
	 break;
      }
      lastops = ops; 
   }
   
   incr_changes(thisobj);
}

/*--------------------------------------------------------------*/
/* Check if this string contains a parameter			*/
/*--------------------------------------------------------------*/

stringpart *searchparam(stringpart *tstr)
{
   stringpart *rval = tstr;
   for (rval = tstr; rval != NULL; rval = rval->nextpart)
      if (rval->type == PARAM_START)
	 break;
   return rval;
}

/*--------------------------------------------------------------*/
/* Remove parameterization from a label string or substring.	*/
/*--------------------------------------------------------------*/

void unmakeparam(labelptr thislabel, stringpart *thispart)
{
   genericptr *pgen;
   labelptr plab;
   oparamptr ops;
   stringpart *strptr, *lastpart, *nextpart, *endpart, *newstr, *subs;
   char *key;
   Boolean is_last = True;
   
   /* make sure there is a parameter here */

   if (thispart->type != PARAM_START) {
      Wprintf("There is no parameter here.");
      return;
   }
   key = thispart->data.string;

   /* Unparameterizing can cause a change in the string */
   undrawtext(thislabel);

   /* Now we need to see if there are other places in the object where	*/
   /* this parameter is used.  If so, we delete only this particular	*/
   /* instance of the parameter.  Otherwise, we delete the parameter	*/
   /* and also remove it from the parameters list.			*/

   for (pgen = topobject->plist; pgen < topobject->plist + topobject->parts;
		pgen++) {
      if (IS_LABEL(*pgen)) {
	 plab = TOLABEL(pgen);
	 for (strptr = plab->string; strptr != NULL; strptr = strptr->nextpart) {
	    if (strptr->type == PARAM_START) {
	       if ((strptr != thispart) && (!strcmp(strptr->data.string, key))) {
		  is_last = False;
		  break;
	       }
	    }
	 }
	 if (!is_last) break;
      }
   }

   ops = match_param(topobject, key);
   subs = ops->parameter.string;

   /* Copy the default parameter into the place we are unparameterizing */

   newstr = NULL;
   newstr = stringcopy(subs);

   /* Delete the "PARAM_END" off of the copied string and link it into	*/
   /* the existing string.						*/

   for (endpart = newstr; endpart->nextpart->type != PARAM_END;
	endpart = endpart->nextpart);
   free(endpart->nextpart);
   endpart->nextpart = thispart->nextpart;

   /* Find the stringpart before the parameter call (if any) */

   lastpart = NULL;
   for (strptr = thislabel->string; strptr != NULL && strptr != thispart;
		strptr = strptr->nextpart) {
      lastpart = strptr;
   }
   if (lastpart == NULL)
      thislabel->string = newstr;
   else
      lastpart->nextpart = newstr;
   free(strptr);

   /* Merge strings at boundaries, if possible. */
   mergestring(endpart);
   mergestring(lastpart);

   redrawtext(thislabel);
   if (is_last) {
      freelabel(subs);
      free_object_param(topobject, ops);
   }
}

/*------------------------------------------------------*/
/* Wrapper for unmakeparam()				*/
/*------------------------------------------------------*/

void unparameterize(int mode)
{
   short *fselect, ptype;
   int locpos;
   stringpart *strptr, *tmpptr, *lastptr;
   labelptr settext;

   if (mode >= 0) {
      ptype = (short)param_select[mode];
      if (!checkselect(ptype)) select_element(ptype);
      if (!checkselect(ptype)) return;
   }
   else
      ptype = ALL_TYPES;

   if ((areastruct.selects == 1) && (mode == P_SUBSTRING) && textend > 0
		&& textend < textpos) {
      if (SELECTTYPE(areastruct.selectlist) != LABEL) return;	 /* Not a label */
      settext = SELTOLABEL(areastruct.selectlist);
      strptr = findstringpart(textend, &locpos, settext->string, areastruct.topinstance);
      while (strptr != NULL && strptr->type != PARAM_END)
	 strptr = strptr->nextpart;
      if (strptr == NULL) return;	/* No parameters */
      tmpptr = settext->string;
      lastptr = NULL;

      /* Search for parameter boundary, in case selection doesn't include */
      /* the whole parameter or the parameter start marker.		  */

      for (tmpptr = settext->string; tmpptr != NULL && tmpptr != strptr;
		tmpptr = nextstringpart(tmpptr, areastruct.topinstance))
	 if (tmpptr->type == PARAM_START) lastptr = tmpptr;
      /* Finish search, unlinking any parameter we might be inside */
      for (; tmpptr != NULL; tmpptr = nextstringpart(tmpptr, areastruct.topinstance));

      if (lastptr != NULL) unmakeparam(settext, lastptr);
   }
   else {
      for (fselect = areastruct.selectlist; fselect < areastruct.selectlist +
            areastruct.selects; fselect++) {
         if ((mode == P_SUBSTRING) && SELECTTYPE(fselect) == LABEL) {
            settext = SELTOLABEL(fselect);
            strptr = settext->string;
            while (strptr != NULL && strptr->type != PARAM_START)
	       strptr = strptr->nextpart;
	    if (strptr != NULL) unmakeparam(settext, strptr);
	 }
	 else if (mode == P_POSITION) {
	    unmakenumericalp(topobject->plist + (*fselect), P_POSITION_X);
	    unmakenumericalp(topobject->plist + (*fselect), P_POSITION_Y);
	 }
	 else
	    unmakenumericalp(topobject->plist + (*fselect), mode);
      }
      setparammarks(NULL);
   }
}

/*--------------------------------------------------------------*/
/* Wrapper for makeparam()					*/
/*--------------------------------------------------------------*/

void parameterize(int mode, char *key)
{
   short *fselect, ptype;
   labelptr settext;

   if (mode >= 0) {
      ptype = (short)param_select[mode];
      if (!checkselect(ptype)) select_element(ptype);
      if (!checkselect(ptype)) return;
   }
   else
      ptype = ALL_TYPES;

   for (fselect = areastruct.selectlist; fselect < areastruct.selectlist +
            areastruct.selects; fselect++) {
      if ((mode == P_SUBSTRING) && (areastruct.selects == 1) &&
		(SELECTTYPE(fselect) == LABEL)) {
         settext = SELTOLABEL(fselect);
         makeparam(settext, key);
      }
      else if (mode == P_POSITION) {
	 makenumericalp(topobject->plist + (*fselect), P_POSITION_X, key);
	 makenumericalp(topobject->plist + (*fselect), P_POSITION_Y, key);
      }
      else
	 makenumericalp(topobject->plist + (*fselect), mode, key);
   }
   unselect_all();
   setparammarks(NULL);
}

/*----------------------------------------------------------------------*/
/* Looks for a parameter overlapping the textend <--> textpos space.	*/
/* Returns True if there is a parameter in this space.			*/
/*----------------------------------------------------------------------*/

Boolean paramcross(objectptr tobj, labelptr tlab)
{
   stringpart *firstptr, *lastptr;
   int locpos;

   lastptr = findstringpart(textpos, &locpos, tlab->string, areastruct.topinstance);

   /* This text position can't be inside another parameter */
   for (firstptr = lastptr; firstptr != NULL; firstptr = firstptr->nextpart)
      if (firstptr->type == PARAM_END) return True;

   /* The area between textend and textpos cannot contain a parameter */
   if (textend > 0)
      for (firstptr = findstringpart(textend, &locpos, tlab->string,
		areastruct.topinstance); firstptr != lastptr;
		firstptr = firstptr->nextpart)
         if (firstptr->type == PARAM_START || firstptr->type == PARAM_END)
	    return True;

   return False;
}

/*----------------------------------------------------------------------*/
/* Check whether this page object was entered via a library page	*/
/*----------------------------------------------------------------------*/

int checklibtop()
{
   int i;
   pushlistptr thispush;

   for (thispush = areastruct.stack; thispush != NULL; thispush = thispush->next)
      if ((i = is_library(thispush->thisinst->thisobject)) >= 0)
	 return i;

   return -1;
}

/*----------------------------------------------------------------------*/
/* Remove all parameters from an object	instance			*/
/* (Reverts all parameters to default value)				*/
/*----------------------------------------------------------------------*/

void removeinstparams(objinstptr thisinst)
{
   oparamptr ops;

   while (thisinst->params != NULL) {
      ops = thisinst->params;
      thisinst->params = ops->next;
      free(ops->key);
      if (ops->type == XC_STRING)
	 freelabel(ops->parameter.string);
      else if (ops->type == XC_EXPR)
	 free(ops->parameter.expr);
      free(ops);
   }
}

/*----------------------------------------------------------------------*/
/* Remove all parameters from an object.				*/
/*----------------------------------------------------------------------*/

void removeparams(objectptr thisobj)
{
   oparamptr ops;

   while (thisobj->params != NULL) {
      ops = thisobj->params;
      thisobj->params = ops->next;
      free(ops->key);
      if (ops->type == XC_STRING)
	 freelabel(ops->parameter.string);
      else if (ops->type == XC_EXPR)
	 free(ops->parameter.expr);
      free(ops);
   }
}

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