Logo Search packages:      
Sourcecode: xcircuit version File versions

events.c

/*-------------------------------------------------------------------------*/
/* events.c --- xcircuit routines handling Xevents and Callbacks     */
/* 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 <ctype.h>

#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#define  XK_MISCELLANY
#define  XK_LATIN1
#include <X11/keysymdef.h>

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

#ifdef TCL_WRAPPER 
#include <tk.h>
#endif

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

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

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

stringpart *labelbuf = NULL;
short saverot; 
short   eventmode;            /* keep track of the mode the screen is in */
short   textpos, textend;     /* keep track of the cursor position in text */
u_char      curbutton = 0;          /* current working button */
short   refselect;
short attachto = 0;

extern XtAppContext app;
extern Display    *dpy;
extern int *appcolors;
extern Cursor     appcursors[NUM_CURSORS];
extern Globaldata xobjs;
extern Clientdata areastruct;
extern ApplicationData appdata;
extern short popups, help_up;
extern xcWidget message2, top;
extern char  _STR[150], _STR2[250];
extern short beeper;
extern double saveratio;
extern u_char texttype;
extern short del;
extern aliasptr aliastop;

#ifdef TCL_WRAPPER
extern Tcl_Interp *xcinterp;
#endif

/* double buffer */
#ifdef DOUBLEBUFFER
Pixmap dbuf = (Pixmap)NULL;
#endif

/*----------------------------------------------------------------------------*/
/* Edit Object pushing and popping.                               */
/*----------------------------------------------------------------------------*/

Boolean recursefind(objectptr parent, objectptr suspect)
{
   genericptr *shell;

   if (parent == suspect) return True;

   for (shell = parent->plist; shell < parent->plist + parent->parts; shell++)
      if ((*shell)->type == OBJECT)
         if (recursefind(TOOBJINST(shell)->thisobject, suspect)) return True;

   return False;
}

/*------------------------------------------------------------------*/
/* Transfer objects in the select list to the current object          */
/* (assumes they have been deleted from the previous object)          */
/*------------------------------------------------------------------*/
/* Disallow infinitely recursive loops!                         */
/*------------------------------------------------------------------*/

void transferselects()
{
   if (eventmode == PRESS_MODE || eventmode == COPY2_MODE) {
      short ps = topobject->parts;
      short *newsel;
      areastruct.selects = xc_undelete(NULL, NORMAL, NULL);
      areastruct.selectlist = (short *) malloc(areastruct.selects *
         sizeof(short));
  
      /* replace the select list */
      newsel = areastruct.selectlist;
      for (; ps < topobject->parts; ps++) (*newsel++) = ps;
       
      /* check to make sure this object is not the current object    */
      /* or one of its direct ancestors, else an infinite loop results. */

      for (ps = 0; ps < topobject->parts; ps++)
       if ((*(topobject->plist + ps))->type == OBJECT) {
          if (recursefind(TOOBJINST(topobject->plist + ps)->thisobject,
                topobject)) {
             Wprintf("Attempt to place object inside of itself");
             objectdelete(NORMAL);
             break;
          }
       }
   }
}

/*-------------------------------------------------------------------*/
/* Make a new matrix corresponding to the current position and scale */
/*-------------------------------------------------------------------*/

void newmatrix()
{
   if (DCTM == NULL) {
      DCTM = (Matrixptr)malloc(sizeof(Matrix));
      DCTM->nextmatrix = NULL;
   }
   UResetCTM(DCTM);
   UMakeWCTM(DCTM);
}

/*-------------------------------------------------------*/
/* set the viewscale variable to the proper address    */
/*-------------------------------------------------------*/

void setpage(Boolean killselects)
{
   areastruct.vscale = &(topobject->viewscale);
   areastruct.pcorner = &(topobject->pcorner);
   newmatrix();

   /* New page invalidates the "last select" mechanism */
   if (killselects) destroyselects();
}

/*-------------------------------------------------------*/
/* switch to a new page                          */
/*-------------------------------------------------------*/

int changepage(short pagenumber)
{
   short npage;
   objectptr pageobj;

   /* to add to existing number of top level pages. . . */

   if (pagenumber == 255) {
      if (xobjs.pages == 255) {
       Wprintf("Out of available pages!");
       return -1;
      }
      else pagenumber = xobjs.pages;
   }

   if (pagenumber >= xobjs.pages) {
      
      xobjs.pagelist = (Pagedata **)realloc(xobjs.pagelist, (pagenumber + 1)
            * sizeof(Pagedata *));
      xobjs.pagelist[pagenumber] = (Pagedata *)malloc(sizeof(Pagedata));
      xobjs.pagelist[pagenumber]->filename = NULL;
      xobjs.pagelist[pagenumber]->background.name = NULL;

      for (npage = xobjs.pages; npage <= pagenumber; npage++)
       xobjs.pagelist[npage]->pageinst = NULL;

      xobjs.pages = pagenumber + 1;
      makepagebutton();
   }

   if (eventmode == PRESS_MODE || eventmode == COPY2_MODE)
      objectdelete(NORMAL);
   else
      clearselects();

#ifdef SCHEMA
   if (eventmode != ASSOC_MODE) {
#endif
   areastruct.page = pagenumber;

   free_stack(&areastruct.stack);

#ifdef SCHEMA
   }
#endif
   if (xobjs.pagelist[pagenumber]->pageinst == NULL) {

      /* initialize a new page */

      pageobj = (objectptr) malloc (sizeof(object));
      initmem(pageobj);
      sprintf(pageobj->name, "Page %d", pagenumber + 1);

      xobjs.pagelist[pagenumber]->pageinst = newpageinst(pageobj);
      xobjs.pagelist[pagenumber]->filename = NULL;
      xobjs.pagelist[pagenumber]->background.name = NULL;

      pagereset(pagenumber);
   }
   areastruct.topinstance = xobjs.pagelist[pagenumber]->pageinst;

   setpage(TRUE);
   return 0;
}

/*-------------------------------------------------------*/
/* switch to a new page and redisplay                  */
/*-------------------------------------------------------*/

void newpage(short pagenumber)
{
   if (eventmode == CATALOG_MODE) {
      eventmode = NORMAL_MODE;
      catreturn();
   }
   else if (eventmode != NORMAL_MODE && eventmode != COPY2_MODE
            && eventmode != PRESS_MODE) {
      Wprintf("Cannot switch pages from this mode");
      return;
   }

   if (changepage(pagenumber) >= 0) {
      transferselects();
      renderbackground();
      refresh(NULL, NULL, NULL);

      togglegrid((u_short)xobjs.pagelist[areastruct.page]->coordstyle);

#ifdef SCHEMA
      setsymschem();
#endif

   }
}

/*---------------------------------------*/
/* Stack structure push and pop routines */
/*---------------------------------------*/

void push_stack(pushlistptr *stackroot, objinstptr thisinst)
{
   pushlistptr newpush;

   newpush = (pushlistptr)malloc(sizeof(pushlist));
   newpush->next = *stackroot;
   newpush->thisinst = thisinst;
   *stackroot = newpush;
}

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

void pop_stack(pushlistptr *stackroot)
{
   pushlistptr lastpush;

   if (!(*stackroot)) {
      Fprintf(stderr, "pop_genstack() Error: NULL instance stack!\n");
      return;
   }

   lastpush = (*stackroot)->next;
   free(*stackroot);
   *stackroot = lastpush;
}

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

void free_stack(pushlistptr *stackroot)
{
   while ((*stackroot) != NULL)
      pop_stack(stackroot);
}

/*-----------------------------*/
/* Menu push hierarchy command */
/*-----------------------------*/

void startpush(xcWidget w, caddr_t clientdata, caddr_t nulldata)
{
   XEvent devent;
   XButtonEvent *b_event = (XButtonEvent *)(&devent);

   if (eventmode == NORMAL_MODE) {
      if (areastruct.selects > 1) objectdeselect();

      if (areastruct.selects == 1) {
       b_event->x = b_event->y = 0;
       pushobject(b_event);
      }
      else {
         eventmode = PUSH_MODE;
         Wprintf("Click on object to push");
      }
   }
}

/*------------------------------------------*/
/* Push object onto hierarchy stack to edit */
/*------------------------------------------*/

void pushobject(XButtonEvent *event)
{
   short i, *selectobj;
   objinstptr pushinst;

   if (eventmode == PRESS_MODE || eventmode == COPY2_MODE)
      objectdelete(NORMAL);

   window_to_user(event->x, event->y, &areastruct.save);
   selectobj = areastruct.selectlist;
   if (areastruct.selects == 0) selectobj = objectselect(OBJECT);
   if (areastruct.selects == 0) {
      Wprintf("No objects selected.");
      if (eventmode == PRESS_MODE || eventmode == COPY2_MODE)
       transferselects();
      return;
   }
   else if (areastruct.selects > 1) {
      Wprintf("Choose only one object.");
      return;
   }
   else if (SELECTTYPE(selectobj) != OBJECT) {
      Wprintf("Element to push must be an object.");
      return;
   }
   else if (event->button == Button3) {
      objectdeselect();
      return;
   }
   else pushinst = SELTOOBJINST(selectobj);

   /* save the address of the current object to the push stack */

   push_stack(&areastruct.stack, areastruct.topinstance);
   areastruct.topinstance = pushinst;

   /* move selected items to the new object */

   setpage(TRUE);
   transferselects();
   refresh(NULL, NULL, NULL);

#ifdef SCHEMA
   setsymschem();
#endif

}

/*--------------------------*/
/* Pop edit hierarchy stack */
/*--------------------------*/

void popobject(xcWidget w, caddr_t clientdata, caddr_t calldata)
{
   if (areastruct.stack == NULL || (eventmode != NORMAL_MODE && eventmode != PRESS_MODE
      && eventmode != COPY2_MODE && eventmode != FONTCAT_MODE &&
#ifdef SCHEMA
      eventmode != ASSOC_MODE &&
#endif
      eventmode != FONTCAT2_MODE)) return;

   if ((eventmode == PRESS_MODE || eventmode == COPY2_MODE) &&
      ((areastruct.stack->thisinst == xobjs.libtop[LIBRARY]) ||
       (areastruct.stack->thisinst == xobjs.libtop[USERLIB]))) return;

   /* remove any selected items from the current object */

   if (eventmode == PRESS_MODE || eventmode == COPY2_MODE)
      objectdelete(NORMAL);
   else
      objectdeselect();

   areastruct.topinstance = areastruct.stack->thisinst;
   pop_stack(&areastruct.stack);

   /* if new object is a library or PAGELIB, put back into CATALOG_MODE */

   if (is_library(topobject) >= 0) eventmode = CATALOG_MODE;

   /* move selected items to the new object */

   setpage(TRUE);
#ifdef SCHEMA
   setsymschem();
   if (eventmode != ASSOC_MODE)
#endif
   transferselects();
   refresh(NULL, NULL, NULL);
}

/*-------------------------------------------------------------------------*/
/* Destructive reset of entire object                                */
/*-------------------------------------------------------------------------*/

void resetbutton(xcWidget button, pointertype pageno, caddr_t calldata)
{
   short page;
   objectptr pageobj;
   objinstptr pageinst;

   if (eventmode != NORMAL_MODE) return;
 
   page = (short)pageno;
   if (page == 0) page = areastruct.page;

   pageinst = xobjs.pagelist[page]->pageinst;

   if (pageinst == NULL) return; /* page already cleared */

   pageobj = pageinst->thisobject;

   /* Make sure this is a real top-level page */

   if ((pageno == 0) && (is_page(topobject) < 0)) {
      Wprintf("Can only clear top-level pages!");
      return;
   }

#ifdef SCHEMA
   /* Watch for pages which are linked by schematic/symbol. */
   
   if (pageobj->symschem != NULL) {
      sprintf(_STR, "Schematic association to object %s", pageobj->symschem->name);
      Wprintf(_STR);
      return;
   }

#endif

   sprintf(pageobj->name, "Page %d", page + 1);
   xobjs.pagelist[page]->filename = (char *)realloc(xobjs.pagelist[page]->filename,
            (strlen(pageobj->name) + 1) * sizeof(char));
   strcpy(xobjs.pagelist[page]->filename, pageobj->name);
   reset(pageobj, NORMAL);

   if (page == areastruct.page) {
      drawarea(areastruct.area, NULL, NULL);
      printname(pageobj);
      renamepage(page);
      Wprintf("Page cleared.");
   }
}

/*------------------------------------------------------*/
/* Redraw the horizontal scrollbar              */
/*------------------------------------------------------*/

void drawhbar(xcWidget bar, caddr_t clientdata, caddr_t calldata)
{
   Window bwin;
   float frac;
   long rleft, rright, rmid;

   if (!xcIsRealized(bar)) return;

   bwin = xcWindow(bar);

   if (topobject->bbox.width > 0) {
      frac = (float) areastruct.width / (float) topobject->bbox.width;
      rleft = (long)(frac * (float)(areastruct.pcorner->x
            - topobject->bbox.lowerleft.x));
      rright = rleft + (long)(frac * (float)areastruct.width / (*areastruct.vscale));
   }
   else {
      rleft = 0L;
      rright = (long)areastruct.width;
   }
   rmid = (rright + rleft) >> 1;

   if (rleft < 0) rleft = 0;
   if (rright > areastruct.width) rright = areastruct.width;

   XSetFunction(dpy, areastruct.gc, GXcopy);
   XSetForeground(dpy, areastruct.gc, BARCOLOR);
   if (rmid > 0 && rleft > 0)
      XClearArea(dpy, bwin, 0, 0, (int)rleft, SBARSIZE, FALSE);
   XFillRectangle(dpy, bwin, areastruct.gc, (int)rleft + 1, 1,
        (int)(rright - rleft), SBARSIZE - 1);
   if (rright > rmid)
      XClearArea(dpy, bwin, (int)rright + 1, 0, areastruct.width
        - (int)rright, SBARSIZE, FALSE);
   XClearArea(dpy, bwin, (int)rmid - 1, 1, 3, SBARSIZE, FALSE);

   XSetFunction(dpy, areastruct.gc, areastruct.gctype);
   XSetForeground(dpy, areastruct.gc, areastruct.gccolor);
}

/*------------------------------------------------------*/
/* Redraw the vertical scrollbar                */
/*------------------------------------------------------*/

void drawvbar(xcWidget bar, caddr_t clientdata, caddr_t calldata)
{
   Window bwin = xcWindow(bar);
   float frac;
   long rtop, rbot, rmid;

   if (!xcIsRealized(bar)) return;

   if (topobject->bbox.height > 0) {
      frac = (float)areastruct.height / (float)topobject->bbox.height;
      rbot = (long)(frac * (float)(topobject->bbox.lowerleft.y
            - areastruct.pcorner->y + topobject->bbox.height));
      rtop = rbot - (long)(frac * (float)areastruct.height / (*areastruct.vscale));
   }
   else {
      rbot = areastruct.height;
      rtop = 0;
   }
   rmid = (rtop + rbot) >> 1;

   if (rtop < 0) rtop = 0;
   if (rbot > areastruct.height) rbot = areastruct.height;

   XSetFunction(dpy, areastruct.gc, GXcopy);
   XSetForeground(dpy, areastruct.gc, BARCOLOR);
   if (rmid > 0 && rtop > 0)
      XClearArea(dpy, bwin, 0, 0, SBARSIZE, (int)rtop, FALSE);
   XFillRectangle(dpy, bwin, areastruct.gc, 0, (int)rtop + 2, SBARSIZE,
           (int)(rbot - rtop));
   if (rbot > rmid)
      XClearArea(dpy, bwin, 0, (int)rbot + 1, SBARSIZE, areastruct.height
            - (int)rbot, FALSE);
   XClearArea(dpy, bwin, 0, (int)rmid - 1, SBARSIZE, 3, FALSE);

   XSetFunction(dpy, areastruct.gc, areastruct.gctype);
   XSetForeground(dpy, areastruct.gc, areastruct.gccolor);
}

/*------------------------------------------------------*/
/* Simultaneously scroll the screen and horizontal    */
/* bar when dragging the mouse in the scrollbar area  */
/*------------------------------------------------------*/

void panhbar(xcWidget bar, caddr_t clientdata, XButtonEvent *event)
{
   long  newx, newpx;
   short savex = areastruct.pcorner->x;

   if (eventmode == SELAREA_MODE) return;

   newx = (long)(event->x * ((float)topobject->bbox.width /
      areastruct.width) + topobject->bbox.lowerleft.x - 0.5 * 
      ((float)areastruct.width / (*areastruct.vscale)));
   areastruct.pcorner->x = (short)newx;
   drawhbar(bar, NULL, NULL);
   areastruct.pcorner->x = savex;

#ifdef DOUBLEBUFFER
   if ((newpx = (long)(newx - savex) * (*areastruct.vscale)) == 0) return;
   XSetFunction(dpy, areastruct.gc, GXcopy);
   if (newpx > 0) {
      XCopyArea(dpy, dbuf, areastruct.areawin, areastruct.gc, newpx, 0,
           areastruct.width - newpx, areastruct.height, 0, 0);
      XClearArea(dpy, areastruct.areawin, areastruct.width - newpx, 0, newpx,
           areastruct.height, FALSE);
   }
   else {
      XCopyArea(dpy, dbuf, areastruct.areawin, areastruct.gc, 0, 0,
           areastruct.width + newpx, areastruct.height, -newpx, 0);
      XClearArea(dpy, areastruct.areawin, 0, 0, -newpx, areastruct.height, FALSE);
   }
#endif
}

/*------------------------------------------------------*/
/* End the horizontal scroll and refresh entire screen      */
/*------------------------------------------------------*/

void endhbar(xcWidget bar, caddr_t clientdata, XButtonEvent *event)
{
   long  newx;
   short savex = areastruct.pcorner->x;

   newx = (long)(event->x * ((float)topobject->bbox.width /
      areastruct.width) + topobject->bbox.lowerleft.x - 0.5 * 
      ((float)areastruct.width / (*areastruct.vscale)));

   areastruct.pcorner->x = (short)newx;

   if ((newx << 1) != (long)((short)(newx << 1)) || checkbounds() == -1) {
      areastruct.pcorner->x = savex;
      Wprintf("Reached boundary:  cannot pan further");
   }
   else
      Wprintf(" ");

   areastruct.lastbackground = NULL;
   renderbackground();
   drawhbar(bar, NULL, NULL);
   drawarea(bar, NULL, NULL);
}

/*------------------------------------------------------*/
/* Simultaneously scroll the screen and vertical      */
/* bar when dragging the mouse in the scrollbar area  */
/*------------------------------------------------------*/

void panvbar(xcWidget bar, caddr_t clientdata, XButtonEvent *event)
{
   long  newy, newpy;
   short savey = areastruct.pcorner->y;

   if (eventmode == SELAREA_MODE) return;

   newy = (int)((areastruct.height - event->y) *
      ((float)topobject->bbox.height / areastruct.height) +
      topobject->bbox.lowerleft.y - 0.5 * ((float)areastruct.height /
           (*areastruct.vscale)));
   areastruct.pcorner->y = (short)newy;
   drawvbar(bar, NULL, NULL);
   areastruct.pcorner->y = savey;

#ifdef DOUBLEBUFFER
   if ((newpy = (long)(newy - savey) * (*areastruct.vscale)) == 0) return;
   XSetFunction(dpy, areastruct.gc, GXcopy);
   if (newpy > 0) {
      XCopyArea(dpy, dbuf, areastruct.areawin, areastruct.gc, 0, 0,
           areastruct.width, areastruct.height - newpy, 0, newpy);
      XClearArea(dpy, areastruct.areawin, 0, 0, areastruct.width, newpy, FALSE);
   }
   else {
      XCopyArea(dpy, dbuf, areastruct.areawin, areastruct.gc, 0, -newpy,
           areastruct.width, areastruct.height + newpy, 0, 0);
      XClearArea(dpy, areastruct.areawin, 0, areastruct.height + newpy,
           areastruct.width, -newpy, FALSE);
   }
#endif

}

/*------------------------------------------------------*/
/* End the vertical scroll and refresh entire screen  */
/*------------------------------------------------------*/

void endvbar(xcWidget bar, caddr_t clientdata, XButtonEvent *event)
{
   long  newy;
   short savey = areastruct.pcorner->y;

   newy = (int)((areastruct.height - event->y) *
      ((float)topobject->bbox.height / areastruct.height) +
      topobject->bbox.lowerleft.y - 0.5 * ((float)areastruct.height /
           (*areastruct.vscale)));

   areastruct.pcorner->y = (short)newy;

   if ((newy << 1) != (long)((short)(newy << 1)) || checkbounds() == -1) {
      areastruct.pcorner->y = savey;
      Wprintf("Reached boundary:  cannot pan further");
   }
   else
      Wprintf(" ");

   areastruct.lastbackground = NULL;
   renderbackground();
   drawvbar(bar, NULL, NULL);
   drawarea(bar, NULL, NULL);
}

/*--------------------------------------------------------------------*/
/* Zoom functions-- zoom box, zoom in, zoom out, and pan.         */
/*--------------------------------------------------------------------*/

void postzoom()
{
   Wprintf(" ");
   areastruct.lastbackground = NULL;
   renderbackground();
   newmatrix();
}

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

void zoombox(xcWidget button, caddr_t clientdata, caddr_t calldata)
{
   if (eventmode == NORMAL_MODE) {
      eventmode = SELAREA2_MODE;
      Wprintf("Click and make box to zoom.");
   }
}

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

void zoomin(xcWidget button, caddr_t clientdata, XButtonEvent *event)
{
   float savescale;
   XPoint ucenter, ncenter, savell;

   savescale = *areastruct.vscale;
   savell.x = areastruct.pcorner->x;
   savell.y = areastruct.pcorner->y;

   /* zoom-box function: corners are in areastruct.save and areastruct.origin */

   if (eventmode == SELAREA_MODE) {
      float delxscale, delyscale;
      /* select box has lower-left corner in .origin, upper-right in .save */

      /* ignore if zoom box is size zero */

      if (areastruct.save.x == areastruct.origin.x || areastruct.save.y ==
        areastruct.origin.y) {
       Wprintf("Zoom box of size zero: Ignoring.");
       eventmode = NORMAL_MODE;
       return;
      }

      /* determine whether x or y is limiting factor in zoom */
      delxscale = (areastruct.width / (*areastruct.vscale)) /
         abs(areastruct.save.x - areastruct.origin.x);
      delyscale = (areastruct.height / (*areastruct.vscale)) /
         abs(areastruct.save.y - areastruct.origin.y);
      (*areastruct.vscale) *= min(delxscale, delyscale);

      areastruct.pcorner->x = min(areastruct.origin.x, areastruct.save.x) -
       (areastruct.width / (*areastruct.vscale) - 
       abs(areastruct.save.x - areastruct.origin.x)) / 2;
      areastruct.pcorner->y = min(areastruct.origin.y, areastruct.save.y) -
       (areastruct.height / (*areastruct.vscale) - 
       abs(areastruct.save.y - areastruct.origin.y)) / 2;
      eventmode = NORMAL_MODE;
   }
   else {
      window_to_user(areastruct.width / 2, areastruct.height / 2, &ucenter);
      (*areastruct.vscale) *= areastruct.zoomfactor;
      window_to_user(areastruct.width / 2, areastruct.height / 2, &ncenter);
      areastruct.pcorner->x += (ucenter.x - ncenter.x);
      areastruct.pcorner->y += (ucenter.y - ncenter.y);
   }

   /* check for minimum scale */

   if (checkbounds() == -1) {
      areastruct.pcorner->x = savell.x;
      areastruct.pcorner->y = savell.y;
      (*areastruct.vscale) = savescale;
      Wprintf("At minimum scale: cannot scale further");

      /* this is a rare case where an object gets out-of-bounds */

      if (checkbounds() == -1) {
       if (beeper) XBell(dpy, 100);
       Wprintf("Unable to scale: Delete out-of-bounds object!");
      }
      return;
   }
   else if (eventmode == PRESS_MODE || eventmode == COPY2_MODE)
      drag(areastruct.area, NULL, event);

   postzoom();
}

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

void zoominrefresh(xcWidget button, caddr_t clientdata, XButtonEvent *event)
{
   zoomin(button, NULL, event);
   refresh(NULL, NULL, NULL);
}

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

void zoomout(xcWidget button, caddr_t clientdata, XButtonEvent *event)
{
   float savescale;
   XPoint ucenter, ncenter, savell;
   XlPoint newll;

   savescale = (*areastruct.vscale);
   savell.x = areastruct.pcorner->x;
   savell.y = areastruct.pcorner->y;

   /* zoom-box function, analogous to that for zoom-in */

   if (eventmode == SELAREA_MODE) {
      float delxscale, delyscale, scalefac;

      /* ignore if zoom box is size zero */

      if (areastruct.save.x == areastruct.origin.x || areastruct.save.y ==
        areastruct.origin.y) {
       Wprintf("Zoom box of size zero: Ignoring.");
       eventmode = NORMAL_MODE;
       return;
      }

      /* determine whether x or y is limiting factor in zoom */
      delxscale = abs(areastruct.save.x - areastruct.origin.x) /
         (areastruct.width / (*areastruct.vscale));
      delyscale = abs(areastruct.save.y - areastruct.origin.y) /
         (areastruct.height / (*areastruct.vscale));
      scalefac = min(delxscale, delyscale);
      (*areastruct.vscale) *= scalefac;

      /* compute lower-left corner of (reshaped) select box */
      if (delxscale < delyscale) {
         newll.y = min(areastruct.save.y, areastruct.origin.y);
       newll.x = (areastruct.save.x + areastruct.origin.x
            - (abs(areastruct.save.y - areastruct.origin.y) *
            areastruct.width / areastruct.height)) / 2;
      }
      else {
         newll.x = min(areastruct.save.x, areastruct.origin.x);
       newll.y = (areastruct.save.y + areastruct.origin.y
            - (abs(areastruct.save.x - areastruct.origin.x) *
            areastruct.height / areastruct.width)) / 2;
      }

      /* extrapolate to find new lower-left corner of screen */
      newll.x = areastruct.pcorner->x - (int)((float)(newll.x -
            areastruct.pcorner->x) / scalefac);
      newll.y = areastruct.pcorner->y - (int)((float)(newll.y -
            areastruct.pcorner->y) / scalefac);

      eventmode = NORMAL_MODE;
   }
   else {
      window_to_user(areastruct.width / 2, areastruct.height / 2, &ucenter); 
      (*areastruct.vscale) /= areastruct.zoomfactor;
      window_to_user(areastruct.width / 2, areastruct.height / 2, &ncenter); 
      newll.x = (long)areastruct.pcorner->x + (long)(ucenter.x - ncenter.x);
      newll.y = (long)areastruct.pcorner->y + (long)(ucenter.y - ncenter.y);
   }
   areastruct.pcorner->x = (short)newll.x;
   areastruct.pcorner->y = (short)newll.y;

   if ((newll.x << 1) != (long)(areastruct.pcorner->x << 1) || (newll.y << 1)
       != (long)(areastruct.pcorner->y << 1) || checkbounds() == -1) {
      (*areastruct.vscale) = savescale; 
      areastruct.pcorner->x = savell.x;
      areastruct.pcorner->y = savell.y;
      Wprintf("At maximum scale: cannot scale further.");
      return;
   }
   else if (eventmode == PRESS_MODE || eventmode == COPY2_MODE)
      drag(areastruct.area, NULL, event);

   postzoom();
}

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

void zoomoutrefresh(xcWidget button, caddr_t clientdata, XButtonEvent *event)
{
   zoomout(button, NULL, event);
   refresh(NULL, NULL, NULL);
}

/*---------------------------------------*/
/* ButtonPress handler during center pan */
/*---------------------------------------*/

void panbutton(u_int ptype, XButtonEvent *event)
{
   Window pwin;
   int  xpos, ypos, newllx, newlly;
   XPoint savell, newpos;
   Dimension hwidth = areastruct.width >> 1, hheight = areastruct.height >> 1;

   savell.x = areastruct.pcorner->x;
   savell.y = areastruct.pcorner->y;

   switch(ptype) {
      case 1:
         xpos = 0;
         ypos = hheight;
       break;
      case 2:
         xpos = areastruct.width;
         ypos = hheight;
       break;
      case 3:
         xpos = hwidth;
         ypos = 0;
       break;
      case 4:
         xpos = hwidth;
         ypos = areastruct.height;
       break;
      case 5:
         xpos = event->x;
         ypos = event->y;
       break;
      default:
       newpos = UGetCursor();
       xpos = newpos.x;
       ypos = newpos.y;
         XWarpPointer(dpy, None, areastruct.areawin, 0, 0, 0, 0, hwidth, hheight);
       break;
   }

   xpos -= hwidth;
   ypos = hheight - ypos;

   newllx = (int)areastruct.pcorner->x + (int)((float)xpos / (*areastruct.vscale));
   newlly = (int)areastruct.pcorner->y + (int)((float)ypos / (*areastruct.vscale));

   areastruct.pcorner->x = (short) newllx;
   areastruct.pcorner->y = (short) newlly;

   if ((newllx << 1) != (long)(areastruct.pcorner->x << 1) || (newlly << 1)
         != (long)(areastruct.pcorner->y << 1) || checkbounds() == -1) {
      areastruct.pcorner->x = savell.x;
      areastruct.pcorner->x = savell.y;
      Wprintf("Reached bounds:  cannot pan further.");
      return;
   }
   else if (eventmode == PRESS_MODE || eventmode == COPY2_MODE)
      drag(areastruct.area, NULL, event);

   postzoom();
}

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

void panrefresh(u_int ptype, XButtonEvent *event)
{
   panbutton(ptype, event);
   refresh(NULL, NULL, NULL);
}

/*--------------------------------------------------------------*/
/* Pan screen so that cursor position moves to center of screen */
/*--------------------------------------------------------------*/

void centerpan(xcWidget button, caddr_t clientdata, caddr_t calldata)
{
   Wprintf("Click button1 to pan.");
   XDefineCursor (dpy, areastruct.areawin, CIRCLE); 
   if (eventmode == CATALOG_MODE) eventmode = CATPAN_MODE;
   else eventmode = PAN_MODE;
}

/*----------------------------------------------------------------*/
/* Check for out-of-bounds before warping pointer, and pan window */
/* if necessary.                                                  */
/*----------------------------------------------------------------*/

void checkwarp(XPoint *userpt)
{
  XPoint wpoint;
  XButtonEvent event;

  user_to_window(*userpt, &wpoint);

  if (wpoint.x < 0 || wpoint.y < 0 || wpoint.x > areastruct.width ||
        wpoint.y > areastruct.height) {
     event.x = wpoint.x;
     event.y = wpoint.y;
     panrefresh(5, &event); 
     wpoint.x = areastruct.width >> 1;
     wpoint.y = areastruct.height >> 1;
     snap(wpoint.x, wpoint.y, userpt);
  }
  XWarpPointer(dpy, None, areastruct.areawin, 0, 0, 0, 0, wpoint.x, wpoint.y);
}

/*--------------------------------------------------------------*/
/* Return next edit point on a polygon                      */
/*--------------------------------------------------------------*/

int checkcycle(short points, short dir)
{
   int tmppt = areastruct.editcycle + dir;
   if (tmppt < 0) tmppt += points;
   tmppt %= points;
   return tmppt;
}

/*--------------------------------------------------------------*/
/* Change to next edit point on a polygon             */
/*--------------------------------------------------------------*/

void nextpolycycle(polyptr nextpoly, short dir)
{
   XPoint *polypt;

   areastruct.editcycle = checkcycle(nextpoly->number, dir);
   finddir(nextpoly);
   printeditbindings();

   polypt = nextpoly->points + areastruct.editcycle;
   checkwarp(polypt);
}

/*--------------------------------------------------------------*/
/* Change to next edit cycle on a spline              */
/*--------------------------------------------------------------*/

void nextsplinecycle(splineptr nextspline, short dir)
{
   areastruct.editcycle = checkcycle(4, dir);

   if (areastruct.editcycle == 1 || areastruct.editcycle == 2)
      Wprintf("Adjust control point");
   else
      Wprintf("Adjust endpoint position");

   checkwarp(&nextspline->ctrl[areastruct.editcycle]);
}

/*--------------------------------------------------------------*/
/* Change to next edit cycle on an arc                      */
/*--------------------------------------------------------------*/

void nextarccycle(arcptr nextarc, short dir)
{
   XPoint curang;
   double rad;

   areastruct.editcycle = checkcycle(4, dir);

   switch(areastruct.editcycle) {
      case 0:
       curang.x = nextarc->position.x + abs(nextarc->radius);
       curang.y = nextarc->position.y;
       if (abs(nextarc->radius) != nextarc->yaxis)
          Wprintf("Adjust ellipse size");
       else
          Wprintf("Adjust arc radius");
       break;
      case 3:
       curang.x = nextarc->position.x;
       curang.y = nextarc->position.y + nextarc->yaxis;
       Wprintf("Adjust ellipse minor axis");
       break;
      case 1:
         rad = (double)(nextarc->angle1 * RADFAC);
         curang.x = nextarc->position.x + abs(nextarc->radius) * cos(rad);
         curang.y = nextarc->position.y + nextarc->yaxis * sin(rad);
       Wprintf("Adjust arc endpoint");
       break;
      case 2:
         rad = (double)(nextarc->angle2 * RADFAC);
         curang.x = nextarc->position.x + abs(nextarc->radius) * cos(rad);
         curang.y = nextarc->position.y + nextarc->yaxis * sin(rad);
       Wprintf("Adjust arc endpoint");
       break;
      }
   checkwarp(&curang);
}

/*------------------------------------------------------*/
/* Get a numerical response from the keyboard (0-9)   */
/*------------------------------------------------------*/

#ifndef TCL_WRAPPER

short getkeynum()
{
   XEvent event;
   XKeyEvent *keyevent = (XKeyEvent *)(&event);
   KeySym keypressed;

   for (;;) {
      XNextEvent(dpy, &event);
      if (event.type == KeyPress) break;
      else xcDispatchEvent(&event);
   }
   XLookupString(keyevent, _STR, 150, &keypressed, NULL);
   if (keypressed > XK_0 && keypressed <= XK_9)
      return (short)(keypressed - XK_1);
   else
      return -1;
}

#endif

/*--------------------------*/
/* Register a "press" event */
/*--------------------------*/

#ifdef TCL_WRAPPER
xcTimeOutProc makepress(caddr_t clientdata)
#else
xcTimeOutProc makepress(caddr_t clientdata, xcIntervalId *id) 
#endif
{
   /* Button/Key was pressed long enough to make a "press", not a "tap" */

   if (eventmode != PENDING_MODE) return;
   eventmode = PRESS_MODE;
   if (areastruct.selects == 0) objectselect(SEL_ANY);
   u2u_snap(&areastruct.save);
   XDefineCursor(dpy, areastruct.areawin, ARROW);
#ifdef TCL_WRAPPER
   Tk_CreateEventHandler(areastruct.area, ButtonMotionMask,
                (Tk_EventProc *)xctk_drag, NULL);
#endif

}

/*------------------------------------------------------*/
/* Handle button events as if they were keyboard events */
/*------------------------------------------------------*/

void buttonhandler(xcWidget w, caddr_t clientdata, XButtonEvent *event)
{
   XKeyEvent *kevent = (XKeyEvent *)event;

   if (event->type == ButtonPress)
      kevent->type = KeyPress;
   else
      kevent->type = KeyRelease;

   switch (event->button) {
      case Button1:
       kevent->state |= Button1Mask;
         break;
      case Button2:
       kevent->state |= Button2Mask;
         break;
      case Button3:
       kevent->state |= Button3Mask;
         break;
   }
   keyhandler(w, clientdata, kevent);
}

/*------------------------*/
/* Handle keyboard inputs */
/*------------------------*/

void keyhandler(xcWidget w, caddr_t clientdata, XKeyEvent *event)
{
   KeySym keypressed;
   XButtonEvent *b_event;
   int keywstate; /* KeySym with prepended state information      */
   short value;         /* For return values from isnbound()            */

   if (popups > 0 && help_up == 0) return;

   /* printeventmode(); */

   XLookupString(event, _STR, 150, &keypressed, NULL);

   /* Ignore Shift, Control, Caps Lock, and Meta (Alt) keys       */
   /* when pressed alone.                             */

   if (keypressed == XK_Control_L || keypressed == XK_Control_R ||
        keypressed == XK_Alt_L || keypressed == XK_Alt_R ||
        keypressed == XK_Caps_Lock || keypressed == XK_Shift_L ||
      keypressed == XK_Shift_R)
      return;

   /* Only keep key state information pertaining to Shift, Caps Lock,   */
   /* Control, and Alt (Meta)                               */

   b_event = (XButtonEvent *)((XEvent *)event);
   keywstate = (keypressed & 0xffff);

   /* ASCII values already come upper/lowercase; we only want to register  */
   /* a Shift key if it's a non-ASCII key or another modifier is in effect */

   keywstate |= (((LockMask | ControlMask | Mod1Mask) & event->state) << 16);
   if (keywstate > 255) keywstate |= ((ShiftMask & event->state) << 16);

   /* Treat button events and key events in the same way by setting a key */
   /* state for buttons and a button number for keys.               */

   if (keypressed == 0)
      keywstate |= (((Button1Mask | Button2Mask | Button3Mask | ShiftMask)
            & event->state) << 16);
   else
      b_event->button = Button1;

   /* Button events are handled first */

   if (event->type == KeyRelease) {
      /* Key release only has an effect for the default button modes where  */
      /* there is a difference between a "tap" and a "press".  The only way */
      /* to tell if the button has been "tapped" is to execute the command  */
      /* on the release event.                                        */

      Boolean pending = (eventmode == PENDING_MODE) ? False : True;
      if (isbound(keywstate, XCF_Start)) {
       b_event->button = Button1;
       releasebutton(b_event);
      }
      else if (isbound(keywstate, XCF_Finish)) {
       b_event->button = Button2;
       releasebutton(b_event);
      }
      else if (isbound(keywstate, XCF_Cancel)) {
       b_event->button = Button3;
       releasebutton(b_event);
      }
      else return;  /* Ignore all other release events */
      if (pending) return;
   }
   else {
      if (isbound(keywstate, XCF_Start)) {
       b_event->button = Button1;
       selectbutton(b_event);
       return;
      }
      else if (isbound(keywstate, XCF_Finish)) {
       b_event->button = Button2;
       selectbutton(b_event);
       return;
      }
      else if (isbound(keywstate, XCF_Cancel)) {
       b_event->button = Button3;
       selectbutton(b_event);
       return;
      }
   }

   /* A few event-specific things */

   if ((eventmode == COPY2_MODE || eventmode == PRESS_MODE ||
        eventmode == EBOX_MODE) && isbound(keywstate, XCF_Attach)) {

      /* Conditions: One element is selected, key "A" is pressed. */
      /* Then there must exist a spline, polygon, or arc to attach to.  */

      if (areastruct.selects <= 1) {
       short *refsel;

         if (attachto == 1) {
          attachto = 0; /* toggle value */
          Wprintf("Unconstrained moving");
       }
       else {
          attachto = 1;
          if ((refsel = objectselect(SPLINE|ARC|POLYGON)) != NULL) {
      
             /* transfer refsel over to (global) refselect */

             refselect = *refsel;
             areastruct.selects--;
             XSetFunction(dpy, areastruct.gc, GXcopy);
             XTopSetForeground(SELTOCOLOR(refsel));
             easydraw(refselect, DEFAULTCOLOR);

             /* restore graphics state */
             XSetFunction(dpy, areastruct.gc, areastruct.gctype);
             XSetForeground(dpy, areastruct.gc, areastruct.gccolor);

             Wprintf("Constrained attach");
          }
          else {
             attachto = 0;
             Wprintf("Nothing found to attach to");
          }
         }
      }
   }
   else if (eventmode == TEXT2_MODE || eventmode == TEXT3_MODE) {

      /* Add text or controls to currently edited text label */

      if (isbound(keywstate, XCF_Return_Char))
       labeltext(RETURN, (char *)1);
      else if (isbound(keywstate, XCF_Halfspace))
       labeltext(HALFSPACE, (char *)1);
      else if (isbound(keywstate, XCF_Quarterspace))
       labeltext(QTRSPACE, (char *)1);
      else if (isbound(keywstate, XCF_TabStop))
       labeltext(TABSTOP, (char *)1);
      else if (isbound(keywstate, XCF_TabForward))
       labeltext(TABFORWARD, (char *)1);
      else if (isbound(keywstate, XCF_TabBackward))
       labeltext(TABBACKWARD, (char *)1);
      else if (isbound(keywstate, XCF_Superscript))
         labeltext(SUPERSCRIPT, (char *)1);
      else if (isbound(keywstate, XCF_Subscript))
         labeltext(SUBSCRIPT, (char *)1);
      else if (isbound(keywstate, XCF_Normalscript))
         labeltext(NORMALSCRIPT, (char *)1);
      else if (isbound(keywstate, XCF_Underline))
         labeltext(UNDERLINE, (char *)1);
      else if (isbound(keywstate, XCF_Overline))
         labeltext(OVERLINE, (char *)1);
      else if (isbound(keywstate, XCF_Nextfont))
       setfont(NULL, 1000, NULL);
      else if (isbound(keywstate, XCF_Boldfont))
       fontstyle(NULL, 1, NULL);
      else if (isbound(keywstate, XCF_Italicfont))
       fontstyle(NULL, 2, NULL);
      else if (isbound(keywstate, XCF_Normalfont))
       fontstyle(NULL, 0, NULL);
      else if (isbound(keywstate, XCF_ISO_Encoding))
       fontencoding(NULL, 2, NULL);
      else if (isnbound(keywstate, XCF_Justify, &value))
         rejustify(value);
      else if (isbound(keywstate, XCF_Parameter))
       insertparam();
      else if (isbound(keywstate, XCF_Special))
       dospecial();
      else
         labeltext(keywstate, NULL);
      return;
   }

   /* Library object names can only contain normal text characters */
   else if (eventmode == CATTEXT_MODE) {
      labeltext(keywstate, NULL);
      return;
   }

   /* Ways of changing functionality while in edit mode */
   /* (this ought to be substantially improved)       */

   else if (eventmode == EBOX_MODE || eventmode == EARC_MODE ||
      eventmode == ESPLINE_MODE || eventmode == EPATH_MODE) {
      genericptr *keygen = EDITPART;
      if ((*keygen)->type == PATH)
       keygen = (*((pathptr *)EDITPART))->plist + areastruct.editsubpart;

      switch((*keygen)->type) {
       case POLYGON: {
          polyptr lwire = TOPOLY(keygen);
            XPoint *lpoint;

            /* Break the polygon at the point, if the point isn't an endpoint */
          if (isbound(keywstate, XCF_Edit_Break)) {
             polyptr *newpoly;
             XPoint *npoint;

             if (areastruct.editcycle == lwire->number - 1 ||
                  areastruct.editcycle == 0)
                return;
             UDrawPolygon(lwire);
             lwire->style |= UNCLOSED;
             NEW_POLY(newpoly, topobject);
             topobject->parts++;
             (*newpoly)->style = lwire->style;
             (*newpoly)->color = lwire->color;
             (*newpoly)->width = lwire->width;
             (*newpoly)->number = lwire->number - areastruct.editcycle;
             (*newpoly)->points = (XPoint *)malloc((*newpoly)->number *
                  sizeof(XPoint));
             (*newpoly)->num_params = 0;
             (*newpoly)->passed = NULL;
             lpoint = lwire->points + areastruct.editcycle;
             for (npoint = (*newpoly)->points; npoint < (*newpoly)->points +
                   (*newpoly)->number; npoint++) {
                npoint->x = lpoint->x;
                npoint->y = (lpoint++)->y;
             }
             lwire->number = areastruct.editcycle + 1;
               areastruct.editcycle = 0;
             reset(areastruct.editstack, NORMAL);

             UDrawPolygon(lwire);
             lwire = (*newpoly);
             UDrawPolygon(lwire);
             incr_changes(topobject);
#ifdef SCHEMA
               if (!nonnetwork(lwire)) topobject->valid = False;
#endif

            }

            /* Remove a point from the polygon */
          if (isbound(keywstate, XCF_Edit_Delete)) {
             if (lwire->number < 3) return;
             UDrawPolygon(lwire);
             if (lwire->number == 3 && !(lwire->style & UNCLOSED)) 
                lwire->style |= UNCLOSED;
             lwire->number--;
             for (lpoint = lwire->points + areastruct.editcycle; lpoint <
                  lwire->points + lwire->number; lpoint++)
                *lpoint = *(lpoint + 1);
             UDrawPolygon(lwire);
             nextpolycycle(lwire, -1);
            }
            /* Add a point to the polygon */
          if (isbound(keywstate, XCF_Edit_Insert)) {
             UDrawPolygon(lwire);
             lwire->number++;
             lwire->points = (XPoint *)realloc(lwire->points, lwire->number
                  * sizeof(XPoint));
             for (lpoint = lwire->points + lwire->number - 1; lpoint > lwire->
                     points + areastruct.editcycle; lpoint--)
                *lpoint = *(lpoint - 1);
             UDrawPolygon(lwire);
            }
          /* Parameterize the position of a polygon point */
          if (isbound(keywstate, XCF_Edit_Param)) {
             makenumericalp(keygen, P_POSITION_X);
             makenumericalp(keygen, P_POSITION_Y);
          }
          if (isbound(keywstate, XCF_Edit_Next))
               nextpolycycle(lwire, 1);
            polyeditpush(lwire);
         } break;
       case SPLINE:
          if (isbound(keywstate, XCF_Edit_Next)) {
             splineptr keyspline = TOSPLINE(keygen);
               nextsplinecycle(keyspline, -1);
               splineeditpush(keyspline);
            }
          break;
       case ARC:
          if (isbound(keywstate, XCF_Edit_Next)) {
             arcptr keyarc = TOARC(keygen);
               nextarccycle(keyarc, 1);
               arceditpush(keyarc);
          }
      }
   }
   else if (eventmode == CATALOG_MODE) {

      /* First set of modes is defined only for the object libraries */
      if (is_library(topobject) >= 0) {
       if (isbound(keywstate, XCF_Next_Library))
          changecat();
       else if (isbound(keywstate, XCF_Library_Directory))
          startcatalog(w, LIBLIB, NULL);
       else if (isbound(keywstate, XCF_Library_Copy)) {
          b_event->button = Button1;
          catbutton(1, b_event);
       }
       else if (isbound(keywstate, XCF_Library_Edit)) {
            window_to_user(event->x, event->y, &areastruct.save);
          objectdeselect();
          objectselect(LABEL);
          if (areastruct.selects == 1) 
             edit(b_event);  
       }
       else if (isbound(keywstate, XCF_Library_Delete)) {
          b_event->button = Button2;
          catbutton(0, b_event);
          catdelete();
       }
       else if (isbound(keywstate, XCF_Library_Duplicate)) {
          b_event->button = Button2;
          catbutton(0, b_event);
          copycat();
       }
       else if (isbound(keywstate, XCF_Library_Hide)) {
          b_event->button = Button2;
          catbutton(0, b_event);
          cathide();
       }
       else if (isbound(keywstate, XCF_Library_Virtual)) {
          b_event->button = Button2;
          catbutton(0, b_event);
          catvirtualcopy();
       }
       else if (isbound(keywstate, XCF_Pop)) {
          eventmode = NORMAL_MODE;
          catreturn();
       }
       else if (isbound(keywstate, XCF_Push)) {
          eventmode = NORMAL_MODE;
          pushobject(b_event);
       }
      }

      /* This macro is defined for all but the LIBLIB page (to which */
      /* it will be extended at a later date).                   */

      if (areastruct.topinstance != xobjs.libtop[LIBLIB])
       if (isbound(keywstate, XCF_Library_Move))
          catmove(event);

      /* This macro is defined for all catalog modes */

      if (isbound(keywstate, XCF_Help)) starthelp(w, NULL, NULL);

      /* Avoid doing callbacks more that once! */
      if (eventmode != CATALOG_MODE) return;
   }

   /* The following events are allowed in any mode */

   if (isbound(keywstate, XCF_View))
      zoomview(w, NULL, NULL);
   else if (isbound(keywstate, XCF_Redraw))
      drawarea(w, NULL, NULL);
   else if (isbound(keywstate, XCF_Zoom_In))
      zoominrefresh(w, NULL, b_event);
   else if (isbound(keywstate, XCF_Zoom_Out))
      zoomoutrefresh(w, NULL, b_event);
   else if (isbound(keywstate, XCF_Pan))
      panrefresh(0, b_event);
   else if (isbound(keywstate, XCF_Double_Snap))
      setsnap(1);
   else if (isbound(keywstate, XCF_Halve_Snap))
      setsnap(-1);
   else if (isbound(keywstate, XCF_Pan_Left))
      panrefresh(1, b_event);
   else if (isbound(keywstate, XCF_Pan_Right))
      panrefresh(2, b_event);
   else if (isbound(keywstate, XCF_Pan_Up))
      panrefresh(3, b_event);
   else if (isbound(keywstate, XCF_Pan_Down))
      panrefresh(4, b_event);
   else if (isbound(keywstate, XCF_Write))
#ifdef TCL_WRAPPER
      Tcl_Eval(xcinterp, "xcircuit::promptsavepage");
#else
      outputpopup(NULL, NULL, NULL);
#endif
   else if (isbound(keywstate, XCF_SnapTo))
      if (areastruct.snapto) {
         areastruct.snapto = False;
       Wprintf("Snap-to off");
      }
      else {
         areastruct.snapto = True;
       Wprintf("Snap-to on");
      }
   else if (isnbound(keywstate, XCF_Page, &value)) {
      if (value < 0 || value > xobjs.pages)
       Wprintf("Page out of range.");
      else
       newpage(value - 1);
      return;
   }
#ifdef SCHEMA
   else if (isbound(keywstate, XCF_Netlist))
      callgennet(w, 0, NULL);
#endif

   /* Some events can occur in both PRESS and COPY2 modes */

   if (eventmode == PRESS_MODE || eventmode == COPY2_MODE) {
      snap(event->x, event->y, &areastruct.save);
      if (isnbound(keywstate, XCF_Rotate, &value))
       objectrotate(value);
      else if (isbound(keywstate, XCF_Flip_X))
       objectflip();
      else if (isbound(keywstate, XCF_Flip_Y))
       objectvflip();
      else if (isbound(keywstate, XCF_Snap))
       snapobject();
      else if (isbound(keywstate, XCF_Pop))
       popobject(w, NULL, NULL);
      else if (isbound(keywstate, XCF_Push))
       pushobject(b_event);
      else if (isnbound(keywstate, XCF_Justify, &value))
         rejustify(value);
#ifdef SCHEMA
      else if (isbound(keywstate, XCF_Swap))
       swapschem(w, 0, NULL);
#endif
   }

   /* The following events are restricted to normal mode */

   else if (eventmode == NORMAL_MODE) {
      window_to_user(event->x, event->y, &areastruct.save);
      if (isbound(keywstate, XCF_Select))
       objectselect(SEL_ANY);
      else if (isbound(keywstate, XCF_Wire)) {
       u2u_snap(&areastruct.save);
       startwire(areastruct.save);
       eventmode = WIRE_MODE;
      }
      else if (isbound(keywstate, XCF_Delete))
       deletebutton(b_event);
      else if (isbound(keywstate, XCF_Pop))
       popobject(w, NULL, NULL);
      else if (isbound(keywstate, XCF_Push))
       pushobject(b_event);
#ifdef SCHEMA
      else if (isbound(keywstate, XCF_Swap))
       swapschem(w, 0, NULL);
      else if (isbound(keywstate, XCF_Pin_Label)) {
       eventmode = TEXT2_MODE;
       textbutton(LOCAL, b_event);
      }
      else if (isbound(keywstate, XCF_Pin_Global)) {
       eventmode = TEXT2_MODE;
       textbutton(GLOBAL, b_event);
      }
      else if (isbound(keywstate, XCF_Info_Label)) {
       eventmode = TEXT2_MODE;
       textbutton(INFO, b_event);
      }
      else if (isbound(keywstate, XCF_Connectivity))
       connectivity(w, NULL, NULL);
#endif
      else if (isnbound(keywstate, XCF_Rotate, &value))
       objectrotate(value);
      else if (isbound(keywstate, XCF_Flip_X))
       objectflip();
      else if (isbound(keywstate, XCF_Flip_Y))
       objectvflip();
      else if (isbound(keywstate, XCF_Box))
       boxbutton(b_event);
      else if (isbound(keywstate, XCF_Arc))
       arcbutton(b_event);
      else if (isbound(keywstate, XCF_Text)) {
       eventmode = TEXT2_MODE;
       textbutton(NORMAL, b_event);
      }
      else if (isbound(keywstate, XCF_Snap))
       snapobject();
      else if (isbound(keywstate, XCF_Exchange))
       exchange();
      else if (isbound(keywstate, XCF_Delete))
       deletebutton(b_event);
      else if (isbound(keywstate, XCF_Copy))
       copybutton(b_event);
      else if (isbound(keywstate, XCF_Next_Library))
       changecat();
      else if (isbound(keywstate, XCF_Library_Directory))
       startcatalog(w, LIBLIB,  NULL);
      else if (isbound(keywstate, XCF_Page_Directory))
       startcatalog(w, PAGELIB, NULL);
      else if (isbound(keywstate, XCF_Join))
       join();
      else if (isbound(keywstate, XCF_Unjoin))
       unjoin();
      else if (isbound(keywstate, XCF_Spline))
       splinebutton(b_event);
      else if (isbound(keywstate, XCF_Edit))
       edit(b_event);
      else if (isbound(keywstate, XCF_Undelete))
       xc_undelete(w, DRAW, b_event);
      else if (isbound(keywstate, XCF_Select_Save))
#ifdef TCL_WRAPPER
         Tcl_Eval(xcinterp, "xcircuit::promptmakeobject");
#else
       selectsave(w, NULL, NULL);
#endif
      else if (isbound(keywstate, XCF_Unselect))
       objectselect(-SEL_ANY);
      else if (isbound(keywstate, XCF_Help))
       starthelp(w, NULL, NULL);
      else if (isbound(keywstate, XCF_Dashed))
       setline(NULL, DASHED, NULL);
      else if (isbound(keywstate, XCF_Dotted))
       setline(NULL, DOTTED, NULL);
      else if (isbound(keywstate, XCF_Solid))
       setline(NULL, NORMAL, NULL);
#ifndef TCL_WRAPPER
      else if (isbound(keywstate, XCF_Prompt))
       docommand();
#endif
      else if (isbound(keywstate, XCF_Nothing))
       DoNothing(w, NULL, NULL);
      else if (isbound(keywstate, XCF_Exit))
       quitcheck(w, NULL, NULL);
      else if (isnbound(keywstate, XCF_Justify, &value))
         rejustify(value);
      else if (isbound(keywstate, XCF_Dot)) {
       snap(event->x, event->y, &areastruct.save);
       drawdot(areastruct.save.x, areastruct.save.y);
       drawarea(w, NULL, NULL);
      }
       
#ifdef SCHEMA
      /* These are mostly for diagnostics;  I'm trying to avoid making  */
      /* them easy to reach, particularly by accident.                  */
      else if (isbound(keywstate, XCF_Sim))
       gennet("sim", "sim");
      else if (isbound(keywstate, XCF_SPICE))
       gennet("spice", "spc");
      else if (isbound(keywstate, XCF_SPICEflat))
       gennet("flatspice", "fspc");
      else if (isbound(keywstate, XCF_PCB))
       gennet("pcb", "pcbnet");
#endif
      else if (!ismacro(keywstate)) {
       char *keystring = key_to_string(keywstate);
#ifdef HAVE_PYTHON
       if (python_key_command(keywstate) < 0) {
#endif
       sprintf(_STR, "Key \'%s\' is not bound to a macro", keystring);
       Wprintf(_STR);
#ifdef HAVE_PYTHON
       }
#endif
       free(keystring);
      }
   }
}

/*--------------------------------*/
/* Set snap spacing from keyboard */
/*--------------------------------*/

void setsnap(short direction)
{
   float oldsnap = xobjs.pagelist[areastruct.page]->snapspace;
   char buffer[50];

   if (direction > 0) xobjs.pagelist[areastruct.page]->snapspace *= 2;
   else {
      if (oldsnap >= 2.0)
         xobjs.pagelist[areastruct.page]->snapspace /= 2;
      else {
       measurestr(xobjs.pagelist[areastruct.page]->snapspace, buffer);
       sprintf(_STR, "Snap space at minimum value of %s", buffer);
       Wprintf(_STR);
      }
   }
   if (xobjs.pagelist[areastruct.page]->snapspace != oldsnap) {
      measurestr(xobjs.pagelist[areastruct.page]->snapspace, buffer);
      sprintf(_STR, "Snap spacing set to %s", buffer);
      Wprintf(_STR);
      drawarea(NULL, NULL, NULL);
   }
}

/*-----------------------------------------*/
/* Reposition an object onto the snap grid */
/*-----------------------------------------*/

void snapobject()
{
   short *selectobj;

   if (!checkselect(SEL_ANY)) return;
   XSetFunction(dpy, areastruct.gc, GXcopy);
   XSetForeground(dpy, areastruct.gc, BACKGROUND);
   for (selectobj = areastruct.selectlist; selectobj < areastruct.selectlist
      + areastruct.selects; selectobj++) {
      easydraw(*selectobj, DOFORALL);
      switch(SELECTTYPE(selectobj)) {
         case OBJECT: {
          objinstptr snapobj = SELTOOBJINST(selectobj);

            u2u_snap(&snapobj->position);
          } break;
         case LABEL: {
          labelptr snaplabel = SELTOLABEL(selectobj);

          u2u_snap(&snaplabel->position);
          } break;
         case POLYGON: {
          polyptr snappoly = SELTOPOLY(selectobj);
          pointlist snappoint;

          for (snappoint = snappoly->points; snappoint < snappoly->points +
               snappoly->number; snappoint++)
             u2u_snap(snappoint);
          } break;
         case ARC: {
          arcptr snaparc = SELTOARC(selectobj);

          u2u_snap(&snaparc->position);
          if (areastruct.snapto) {
             snaparc->radius = (snaparc->radius /
                xobjs.pagelist[areastruct.page]->snapspace) *
                xobjs.pagelist[areastruct.page]->snapspace;
             snaparc->yaxis = (snaparc->yaxis /
                xobjs.pagelist[areastruct.page]->snapspace) *
                  xobjs.pagelist[areastruct.page]->snapspace;
          }
          calcarc(snaparc);
          } break;
       case SPLINE: {
          splineptr snapspline = SELTOSPLINE(selectobj);
          short i;

          for (i = 0; i < 4; i++)
             u2u_snap(&snapspline->ctrl[i]);
          calcspline(snapspline);
          } break;
      }
      if (eventmode != NORMAL_MODE) {
         XSetForeground(dpy, areastruct.gc, SELECTCOLOR);
       easydraw(*selectobj, DOFORALL);
      }
   }
   if (eventmode == NORMAL_MODE) objectdeselect();
}

/*----------------------------------------------*/
/* Routines to print the cursor position  */
/*----------------------------------------------*/

/*----------------------------------------------*/
/* fast integer power-of-10 routine             */
/*----------------------------------------------*/

int ipow10(int a)
{
   int i;
   char istr[12];
   
   switch (a) {
      case 0: return 1;     break;
      case 1: return 10;    break;
      case 2: return 100;   break;
      case 3: return 1000;  break;
      default:
         istr[0] = '1';
       for (i = 1; i < a + 1; i++) istr[i] = '0';
       istr[i] = '\0';
       return atoi(istr);
       break;
   }
}

/*--------------------------------------------------*/
/* find greatest common factor between two integers */
/*--------------------------------------------------*/

int calcgcf(int a, int b)
{
   register int mod;

   if ((mod = a % b) == 0) return (b);
   else return (calcgcf(b, mod));
}

/*--------------------------------------------------------------*/
/* generate a fraction from a float, if possible            */
/* fraction returned as a string (must be allocated beforehand)   */
/*--------------------------------------------------------------*/

void fraccalc(float xyval, char *fstr)
{
   short i, t, rept;
   int ip, mant, divisor, denom, numer, rpart;
   double fp;
   char num[10], *nptr = &num[2], *sptr;
   
   ip = (int)xyval;
   fp = fabs(xyval - ip);

   /* write fractional part and grab mantissa as integer */

   sprintf(num, "%1.7f", fp);
   num[8] = '\0';       /* no rounding up! */
   sscanf(nptr, "%d", &mant);

   if (mant != 0) {    /* search for repeating substrings */
      for (i = 1; i <= 3; i++) {
         rept = 1;
       nptr = &num[8] - i;
       while ((sptr = nptr - rept * i) >= &num[2]) {
            for (t = 0; t < i; t++)
             if (*(sptr + t) != *(nptr + t)) break;
          if (t != i) break;
          else rept++;
       }
       if (rept > 1) break;
      }
      nptr = &num[8] - i;
      sscanf(nptr, "%d", &rpart);  /* rpart is repeating part of mantissa */
      if (i > 3 || rpart == 0) { /* no repeat */ 
       divisor = calcgcf(1000000, mant);
       denom = 1000000 / divisor;
      }
      else { /* repeat */
       int z, p, fd;

       *nptr = '\0';
       sscanf(&num[2], "%d", &z);
       p = ipow10(i) - 1;
       mant = z * p + rpart;
       fd = ipow10(nptr - &num[2]) * p;

       divisor = calcgcf(fd, mant);
       denom = fd / divisor;
      }
      numer = mant / divisor;
      if (denom > 1024)
       sprintf(fstr, "%5.3f", xyval); 
      else if (ip == 0)
         sprintf(fstr, "%hd/%hd", (xyval > 0) ? numer : -numer, denom);
      else
         sprintf(fstr, "%hd %hd/%hd", ip, numer, denom);
   }
   else sprintf(fstr, "%hd", ip);
}

/*------------------------------------------------------------------------------*/
/* Print the position of the cursor in the upper right-hand message window    */
/*------------------------------------------------------------------------------*/

void printpos(short xval, short yval)
{
   float f1, f2;
   float oscale, iscale = (float)xobjs.pagelist[areastruct.page]->drawingscale.y /
      (float)xobjs.pagelist[areastruct.page]->drawingscale.x;
   int llen, lwid;
   u_char wlflag = 0;
   XPoint *tpoint, *npoint;
   char *sptr;

   /* For polygons, print the length (last line of a wire or polygon) or  */
   /* length and width (box only)                             */

   if (eventmode == BOX_MODE || eventmode == EBOX_MODE || eventmode == WIRE_MODE) {
      polyptr lwire = (eventmode == BOX_MODE) ?  TOPOLY(ENDPART) : TOPOLY(EDITPART);
      if ((eventmode != WIRE_MODE) && (lwire->number > 2)) {
       tpoint = lwire->points + areastruct.editcycle;
       npoint = lwire->points + checkcycle(lwire->number, 1);
         llen = wirelength(tpoint, npoint);
       npoint = lwire->points + checkcycle(lwire->number, -1);
         lwid = wirelength(tpoint, npoint);
         wlflag = 3;
       if (lwire->style & UNCLOSED) {   /* unclosed polys */
          if (areastruct.editcycle == 0)
             wlflag = 1;
          else if (areastruct.editcycle == lwire->number - 1) {
             wlflag = 1;
             llen = lwid;
          }
       }
       if ((npoint->y - tpoint->y) == 0) {      /* swap width and length */
          int tmp = lwid;
          lwid = llen;
          llen = tmp;
       }
      }
      else {
         tpoint = lwire->points + lwire->number - 1;
         llen = wirelength(tpoint - 1, tpoint);
         wlflag = 1;
      }
   }
   else if (eventmode == ARC_MODE || eventmode == EARC_MODE) {
      arcptr larc = (eventmode == ARC_MODE) ?  TOARC(ENDPART) : TOARC(EDITPART);
      llen = larc->radius;
      if (abs(larc->radius) != larc->yaxis) {
       lwid = larc->yaxis;
       wlflag = 3;
      }
      else
         wlflag = 1;
   }

   switch (xobjs.pagelist[areastruct.page]->coordstyle) {
      case DEC_INCH:
         oscale = xobjs.pagelist[areastruct.page]->outscale * INCHSCALE;
         f1 = ((float)(xval) * iscale * oscale) / 72.0;
         f2 = ((float)(yval) * iscale * oscale) / 72.0;
       sprintf(_STR, "%5.3f, %5.3f in", f1, f2);
       sptr = _STR + strlen(_STR);
       if (wlflag) {
            f1 = ((float)(llen) * iscale * oscale) / 72.0;
          if (wlflag & 2) {
               f2 = ((float)(lwid) * iscale * oscale) / 72.0;
             sprintf(sptr, " (%5.3f x %5.3f in)", f1, f2);
          }
          else
             sprintf(sptr, " (length %5.3f in)", f1);
       }
       break;
      case FRAC_INCH: {
       char fstr1[30], fstr2[30];
       
         oscale = xobjs.pagelist[areastruct.page]->outscale * INCHSCALE;
       fraccalc((((float)(xval) * iscale * oscale) / 72.0), fstr1); 
         fraccalc((((float)(yval) * iscale * oscale) / 72.0), fstr2);
       sprintf(_STR, "%s, %s in", fstr1, fstr2);
       sptr = _STR + strlen(_STR);
       if (wlflag) {
          fraccalc((((float)(llen) * iscale * oscale) / 72.0), fstr1);
          if (wlflag & 2) {
             fraccalc((((float)(lwid) * iscale * oscale) / 72.0), fstr2);
             sprintf(sptr, " (%s x %s in)", fstr1, fstr2);
          }
          else
             sprintf(sptr, " (length %s in)", fstr1);
       }
       } break;
      case CM:
         oscale = xobjs.pagelist[areastruct.page]->outscale * CMSCALE;
         f1 = ((float)(xval) * iscale * oscale) / IN_CM_CONVERT;
         f2 = ((float)(yval) * iscale * oscale) / IN_CM_CONVERT;
       sprintf(_STR, "%5.3f, %5.3f cm", f1, f2);
       sptr = _STR + strlen(_STR);
       if (wlflag) {
            f1 = ((float)(llen) * iscale * oscale) / IN_CM_CONVERT;
          if (wlflag & 2) {
               f2 = ((float)(lwid) * iscale * oscale) / IN_CM_CONVERT;
             sprintf(sptr, " (%5.3f x %5.3f cm)", f1, f2);
          }
          else
             sprintf(sptr, " (length %5.3f cm)", f1);
       }
       break;
   }
   W1printf(_STR);
}

/*---------------------------------------------------*/
/* Find nearest point of intersection of the cursor  */
/* position to a wire and move there.                */
/*---------------------------------------------------*/

void findwirex(XPoint *endpt1, XPoint *endpt2, XPoint *userpt,
            XPoint *newpos, int *rot)
{
   long xsq, ysq, zsq;
   float frac;

   xsq = sqwirelen(endpt1, endpt2);
   ysq = sqwirelen(endpt1, userpt);
   zsq = sqwirelen(endpt2, userpt);
   frac = 0.5 + (float)(ysq - zsq) / (float)(xsq << 1);
   if (frac > 1) frac = 1;
   else if (frac < 0) frac = 0;
   newpos->x = endpt1->x + (int)((endpt2->x - endpt1->x) * frac);
   newpos->y = endpt1->y + (int)((endpt2->y - endpt1->y) * frac);

   *rot = 180 + (int)(INVRFAC * atan2((double)(endpt1->x -
        endpt2->x), (double)(endpt1->y - endpt2->y)));

   /* make adjustment for nearest-integer calculation        */
   /* ((*rot)++, (*rot)-- works if (360 / RSTEPS >= 2))  */  /* ? */

   if (*rot > 0)
      (*rot)++;
   else if (*rot < 0)
      (*rot)--;
}

/*----------------------------------------------------------------*/
/* Find the closest point of attachment from the pointer position */
/* to the "refselect" element.                                */
/*----------------------------------------------------------------*/

void findattach(XPoint *newpos, int *rot, XPoint *userpt)
{
   XPoint *endpt1, *endpt2;
   float frac;
   double tmpang;

   /* find point of intersection and slope */

   if (SELECTTYPE(&refselect) == ARC) {
      arcptr aarc = SELTOARC(&refselect);
      float tmpdeg;
      tmpang = atan2((double)(userpt->y - aarc->position.y) * (double)
            (abs(aarc->radius)), (double)(userpt->x - aarc->position.x) *
            (double)aarc->yaxis);

      /* don't follow the arc beyond its endpoints */

      tmpdeg = (float)(tmpang * INVRFAC);
      if (tmpdeg < 0) tmpdeg += 360;
      if (((aarc->angle2 > 360) && (tmpdeg > aarc->angle2 - 360) &&
            (tmpdeg < aarc->angle1)) ||
            ((aarc->angle1 < 0) && (tmpdeg > aarc->angle2) &&
            (tmpdeg < aarc->angle1 + 360)) ||
            ((aarc->angle1 >= 0) && (aarc->angle2 <= 360) && ((tmpdeg
            > aarc->angle2) || (tmpdeg < aarc->angle1)))) {
       float testd1 = aarc->angle1 - tmpdeg;
       float testd2 = tmpdeg - aarc->angle2;
       if (testd1 < 0) testd1 += 360;
       if (testd2 < 0) testd2 += 360;

       /* if arc is closed, attach to the line between the endpoints */

       if (!(aarc->style & UNCLOSED)) {
          XPoint end1, end2;
          tmpang = (double) aarc->angle1 / INVRFAC;
          end1.x = aarc->position.x + abs(aarc->radius) * cos(tmpang);
          end1.y = aarc->position.y + aarc->yaxis  * sin(tmpang);
          tmpang = (double) aarc->angle2 / INVRFAC;
          end2.x = aarc->position.x + abs(aarc->radius) * cos(tmpang);
          end2.y = aarc->position.y + aarc->yaxis  * sin(tmpang);
            findwirex(&end1, &end2, userpt, newpos, rot);
          return;
       }
       else
          tmpang = (double)((testd1 < testd2) ? aarc->angle1 : aarc->angle2)
                  / INVRFAC;
      }

      /* get position in user coordinates nearest to the intersect pt */

      newpos->x = aarc->position.x + abs(aarc->radius) * cos(tmpang);
      newpos->y = aarc->position.y + aarc->yaxis  * sin(tmpang);

      /* rotation of object is normal to the curve of the ellipse */

      *rot = 90 - (int)(INVRFAC * tmpang);
      if (*rot < 0) *rot += 360;
   }
   else if (SELECTTYPE(&refselect) == SPLINE) {
       splineptr aspline = SELTOSPLINE(&refselect);
       frac = findsplinemin(aspline, userpt);
       findsplinepos(aspline, frac, newpos, rot);
   }
   else if (SELECTTYPE(&refselect) == POLYGON) {
       polyptr apoly = SELTOPOLY(&refselect);
       XPoint *testpt, *minpt, *nxtpt;
       long mindist = 1000000, testdist;
       for (testpt = apoly->points; testpt < apoly->points +
            apoly->number - 1; testpt++) {
       testdist =  finddist(testpt, testpt + 1, userpt);
       if (testdist < mindist) {
          mindist = testdist;
          minpt = testpt;
          nxtpt = testpt + 1;
       }
      }
      if (!(apoly->style & UNCLOSED)) {
       testdist = finddist(testpt, apoly->points, userpt);
       if (testdist < mindist) {
          mindist = testdist;
          minpt = testpt;
          nxtpt = apoly->points;
       }
      }
      endpt1 = minpt;
      endpt2 = nxtpt;
      findwirex(endpt1, endpt2, userpt, newpos, rot);
   }
}

/*--------------------------------------------*/
/* Find closest point in a path to the cursor */
/*--------------------------------------------*/

XPoint *pathclosepoint(pathptr dragpath, XPoint *newpos)
{
   XPoint *rpoint;
   genericptr *cpoint;
   short mpoint;
   int mdist = 1000000, tdist;

   for (cpoint = dragpath->plist; cpoint < dragpath->plist + dragpath->parts;
         cpoint++) {
      switch((*cpoint)->type) {
       case ARC:
         tdist = wirelength(&(TOARC(cpoint)->position), newpos);
          if (tdist < mdist) {
             mdist = tdist;
             rpoint = &(TOARC(cpoint)->position);
          }
          break;
       case POLYGON:
          mpoint = closepoint(TOPOLY(cpoint), newpos);
          tdist = wirelength(TOPOLY(cpoint)->points + mpoint, newpos);
          if (tdist < mdist) {
             mdist = tdist;
             rpoint = TOPOLY(cpoint)->points + mpoint;
          }
          break;
       case SPLINE:
          tdist = wirelength(&(TOSPLINE(cpoint)->ctrl[0]), newpos);
          if (tdist < mdist) {
             mdist = tdist;
             rpoint = &(TOSPLINE(cpoint)->ctrl[0]);
          }
          tdist = wirelength(&(TOSPLINE(cpoint)->ctrl[3]), newpos);
          if (tdist < mdist) {
             mdist = tdist;
             rpoint = &(TOSPLINE(cpoint)->ctrl[3]);
          }
          break;
      }
   }
   return rpoint;
}

/*-------------------------------------------*/
/* Drag a selected element around the screen */
/*-------------------------------------------*/

void drag(xcWidget w, caddr_t clientdata, XButtonEvent *event)
{
   XEvent again;
   Boolean eventcheck = False;
   XPoint userpt;
   short deltax, deltay;

   /* flush out multiple pointermotion events from the event queue */
   /* use only the last motion event */
   while (XCheckWindowEvent(dpy, areastruct.areawin, PointerMotionMask |
      Button1MotionMask, &again) == True) eventcheck = True;
   if (eventcheck) event = (XButtonEvent *)&again;

   /* Determine if this event is supposed to be handled by  */
   /* trackselarea(), or whether we should not be here at all     */
   /* (button press and mouse movement in an unsupported mode)    */

   if (eventmode == SELAREA_MODE || eventmode == SELAREA2_MODE) {
      trackselarea();
      return;
   }
   else if (eventmode != PRESS_MODE && eventmode != COPY2_MODE)
      return;

   snap(event->x, event->y, &userpt);
   deltax = userpt.x - areastruct.save.x;
   deltay = userpt.y - areastruct.save.y;
   if (deltax == 0 && deltay == 0) return;

   areastruct.save.x = userpt.x;
   areastruct.save.y = userpt.y;

   /* set up the graphics state for moving a selected object */

   XSetXORFg(SELECTCOLOR, BACKGROUND);
   XSetFunction(dpy, areastruct.gc, GXxor);

   placeselects(deltax, deltay, &userpt);

   /* restore graphics state */

   XSetForeground(dpy, areastruct.gc, areastruct.gccolor);
   XSetFunction(dpy, areastruct.gc, areastruct.gctype);

   /* print the position and other useful measurements */

   printpos(userpt.x, userpt.y);
}

/*-------------------------------------------------*/
/* Wrapper for object rotate routine               */
/*-------------------------------------------------*/
   
void rotatebutton(XButtonEvent *event)
{     
   window_to_user(event->x, event->y, &areastruct.save);
         
   if (event->button == Button1) {
      if (!checkselect(SEL_ANY)) return;
      if (saverot == 512) objectflip();
      else if (saverot == 1024) objectvflip();
      else objectrotate(saverot);
      objectdeselect();
   }     
   else if (event->button == Button2)
      objectselect(SEL_ANY);
   if (event->button == Button3) {
      objectdeselect();
      eventmode = NORMAL_MODE;
      XDefineCursor (dpy, areastruct.areawin, CROSS);
   }
}

/*----------------------------------------------*/
/* Rotate an element of a path                  */
/*----------------------------------------------*/

void elemrotate(genericptr *genobj, short direction)
{
   XPoint negpt, *newpts = (XPoint *)NULL;

   negpt.x = -areastruct.save.x;
   negpt.y = -areastruct.save.y;

   switch((*genobj)->type) {
      case(ARC):{
       arcptr rotatearc = TOARC(genobj);
       rotatearc->angle1 -= (float)(direction);
       rotatearc->angle2 -= (float)(direction);
         if (rotatearc->angle1 >= 360) {
            rotatearc->angle1 -= 360;
            rotatearc->angle2 -= 360;
         }
         else if (rotatearc->angle2 <= 0) {
            rotatearc->angle1 += 360;
            rotatearc->angle2 += 360;
         } 
       newpts = (XPoint *)malloc(sizeof(XPoint));
       UTransformPoints(&rotatearc->position, newpts, 1, negpt, 1.0, 0);
       UTransformPoints(newpts, &rotatearc->position, 1, areastruct.save,
                  1.0, direction);
       calcarc(rotatearc);
       }break;

      case(SPLINE):{
       splineptr rotatespline = TOSPLINE(genobj);
       newpts = (XPoint *)malloc(4 * sizeof(XPoint));
       UTransformPoints(rotatespline->ctrl, newpts, 4, negpt, 1.0, 0);
       UTransformPoints(newpts, rotatespline->ctrl, 4, areastruct.save,
                  1.0, direction);
       calcspline(rotatespline);
       }break;

      case(POLYGON):{
       polyptr rotatepoly = TOPOLY(genobj);
       newpts = (XPoint *)malloc(rotatepoly->number * sizeof(XPoint));
       UTransformPoints(rotatepoly->points, newpts, rotatepoly->number,
               negpt, 1.0, 0);
       UTransformPoints(newpts, rotatepoly->points, rotatepoly->number,
               areastruct.save, 1.0, direction);
       }break;
   }
   if (newpts) free(newpts);
}

/*------------------------------------------------------*/
/* Rotate an element or group of elements       */
/* Objects and labels, if selected singly, rotate     */
/* about their position point.  All other elements,   */
/* and groups, rotate about the cursor point.         */
/*------------------------------------------------------*/

void objectrotate(short direction)
{
   short    *selectobj, ld;
   Boolean  single = False;
   XPoint   newpt, negpt;

   negpt.x = -areastruct.save.x;
   negpt.y = -areastruct.save.y;

   if (!checkselect(SEL_ANY)) return;
   if (areastruct.selects == 1) single = True;
   u2u_snap(&areastruct.save);

   for (selectobj = areastruct.selectlist; selectobj < areastruct.selectlist
      + areastruct.selects; selectobj++) { 

      /* erase the element */
      XSetFunction(dpy, areastruct.gc, GXcopy);
      XSetForeground(dpy, areastruct.gc, BACKGROUND);
      easydraw(*selectobj, DOFORALL);

      switch(SELECTTYPE(selectobj)) {

       case(OBJECT):{
            objinstptr rotateobj = SELTOOBJINST(selectobj);

            rotateobj->rotation += direction;
          while (rotateobj->rotation >= 360) rotateobj->rotation -= 360;
          while (rotateobj->rotation <= 0) rotateobj->rotation += 360;
          if (!single) {
             UTransformPoints(&rotateobj->position, &newpt, 1, negpt, 1.0, 0);
             UTransformPoints(&newpt, &rotateobj->position, 1, areastruct.save,
                  1.0, direction);
          }
          }break;

       case(LABEL):{
            labelptr rotatetext = SELTOLABEL(selectobj);

            rotatetext->rotation += direction;
          while (rotatetext->rotation >= 360) rotatetext->rotation -= 360;
          while (rotatetext->rotation <= 0) rotatetext->rotation += 360;
          if (!single) {
             UTransformPoints(&rotatetext->position, &newpt, 1, negpt, 1.0, 0);
             UTransformPoints(&newpt, &rotatetext->position, 1, areastruct.save,
                  1.0, direction);
          }
          }break;

       case POLYGON: case ARC: case SPLINE:
          elemrotate(topobject->plist + *selectobj, direction);
          break;

       case PATH:{
          genericptr *genpart;
          pathptr flippath = SELTOPATH(selectobj);

          for (genpart = flippath->plist; genpart < flippath->plist
              + flippath->parts; genpart++)
             elemrotate(genpart, direction);
          }break;
      }

      /* redisplay the element */
      if (eventmode != NORMAL_MODE) {
       XSetForeground(dpy, areastruct.gc, SELECTCOLOR);
       easydraw(*selectobj, DOFORALL);
      }
   }
   if (eventmode == NORMAL_MODE) objectdeselect();
   pwriteback(areastruct.topinstance);
   calcbbox(areastruct.topinstance);
}

/*-------------------------------------------------*/
/* Edit an element in an element-dependent fashion */
/*-------------------------------------------------*/

void edit(XButtonEvent *event)
{
   short *selectobj;

   if (areastruct.selects != 1) selectobj = objectselect(LABEL |
      POLYGON | SPLINE | ARC | PATH);
   else selectobj = areastruct.selectlist;
   if (areastruct.selects != 1) {
      if (areastruct.selects > 1) Wprintf("Select one only to edit");
      objectdeselect();
      return;
   }
   areastruct.editpart = *selectobj;

   XDefineCursor (dpy, areastruct.areawin, EDCURSOR);
   switch(SELECTTYPE(selectobj)) {
       case LABEL: {
       labelptr *lastlabel = (labelptr *)EDITPART;
       short curfont;
       XPoint tmppt;
       TextExtents tmpext;
      
       objectdeselect();

       /* save the old string, including parameters */
       labelbuf = stringcopyall((*lastlabel)->string, areastruct.topinstance);

       /* fill any NULL instance parameters with the default value */
       copyparams(areastruct.topinstance, areastruct.topinstance);

       /* place text cursor line at point nearest the cursor */
       /* unless textend is set. . .                         */
 
       if (textend == 0) {
          window_to_user(event->x, event->y, &areastruct.save);
          InvTransformPoints(&areastruct.save, &tmppt, 1, (*lastlabel)->position,
            (*lastlabel)->scale, (*lastlabel)->rotation);
            tmpext = ULength((*lastlabel)->string, areastruct.topinstance, 0.0, 0, NULL);
          tmppt.x += ((*lastlabel)->justify & NOTLEFT ?
            ((*lastlabel)->justify & RIGHT ? tmpext.width : tmpext.width >> 1) : 0);
          tmppt.y += ((*lastlabel)->justify & NOTBOTTOM ?
            ((*lastlabel)->justify & TOP ? tmpext.ascent :
            (tmpext.ascent + tmpext.base) >> 1) : tmpext.base);
#ifdef SCHEMA
          if ((*lastlabel)->pin)
             pinadjust((*lastlabel)->justify, &tmppt.x, NULL, -1);
#endif
            tmpext = ULength((*lastlabel)->string, areastruct.topinstance, 0.0, 0, &tmppt);
          textpos = tmpext.width;
       }

       /* find current font */

       curfont = findcurfont(textpos, (*lastlabel)->string, areastruct.topinstance);

       /* change menu buttons accordingly */

       setfontmarks(curfont, (*lastlabel)->justify);

         tmpext = ULength((*lastlabel)->string, areastruct.topinstance, (*lastlabel)->scale, 0, NULL);

         areastruct.origin.x = (*lastlabel)->position.x + ((*lastlabel)->
          justify & NOTLEFT ? ((*lastlabel)->justify & RIGHT ? 0 : tmpext.width
          / 2) : tmpext.width);
         areastruct.origin.y = (*lastlabel)->position.y + ((*lastlabel)->
          justify & NOTBOTTOM ? ((*lastlabel)->justify & TOP ? -tmpext.ascent :
          -(tmpext.ascent + tmpext.base) / 2) : -tmpext.base);
#ifdef SCHEMA
       if ((*lastlabel)->pin)
          pinadjust((*lastlabel)->justify, &(areastruct.origin.x),
            &(areastruct.origin.y), 1);
#endif
      
         UDrawTLine(*lastlabel);

       if (eventmode == CATALOG_MODE) eventmode = CATTEXT_MODE;
       else eventmode = TEXT3_MODE;
         XDefineCursor(dpy, areastruct.areawin, TEXTPTR);

       /* write the text at the bottom */

       charreport(*lastlabel);
      } break;
      case PATH: {
       /* choose editcycle type etc. from the type of the first */
       /* part encountered (for now?).                  */         

       pathedit((*((pathptr *)EDITPART))->plist + areastruct.editsubpart,
            PATH);

      } break;
      case POLYGON: case ARC: case SPLINE:
       pathedit(EDITPART, 0);
       break;
   }
}

/*------------------------------------------------------------------*/
/* edit() routine for path parts                          */
/*------------------------------------------------------------------*/

void pathedit(genericptr *editpart, short mode)
{
   switch((*editpart)->type) {
      case POLYGON: {
       polyptr *lastpoly = (polyptr *)editpart;
       XPoint *savept;

       objectdeselect();

       /* determine which point of polygon is closest to cursor */
       areastruct.editcycle = closepoint(*lastpoly, &areastruct.save);
         savept = (*lastpoly)->points + areastruct.editcycle;

       /* Push onto the editstack */
       polyeditpush(*lastpoly);

       checkwarp(savept);

       XcSetXORFg((*lastpoly)->color, BACKGROUND);
       XcSetFunction(GXxor);

       finddir(*lastpoly);

       xcAddEventHandler(areastruct.area, PointerMotionMask, False,
          (xcEventHandler)trackpoly, NULL);
       eventmode = (mode == PATH) ? EPATH_MODE : EBOX_MODE;
       printeditbindings();
         printpos(savept->x, savept->y);
      } break;
      case SPLINE: {
       splineptr *lastspline = (splineptr *)editpart;
       XPoint *curpt;

       objectdeselect();

       /* find which point is closest to the cursor */

         areastruct.editcycle =  (wirelength(&(*lastspline)->ctrl[0],
            &areastruct.save) < wirelength(&(*lastspline)->ctrl[3],
            &areastruct.save)) ? 0 : 3;
       curpt = &(*lastspline)->ctrl[areastruct.editcycle];

       /* Push onto the editstack */
       splineeditpush(*lastspline);

         checkwarp(curpt);

       UDrawXLine((*lastspline)->ctrl[0], (*lastspline)->ctrl[1]);
       UDrawXLine((*lastspline)->ctrl[3], (*lastspline)->ctrl[2]);
         XcSetXORFg((*lastspline)->color, BACKGROUND);
         XcSetFunction(GXxor);
       xcAddEventHandler(areastruct.area, PointerMotionMask, False,
               (xcEventHandler)trackspline, NULL);
       eventmode = (mode == PATH) ? EPATH_MODE : ESPLINE_MODE;
      } break;
      case ARC: {
       arcptr *lastarc = (arcptr *)editpart;
       XPoint curpt;
       float tmpratio, tlen;

       objectdeselect();

       /* find a part of the arc close to the pointer */

       tlen = (float)wirelength(&areastruct.save, &((*lastarc)->position));
       tmpratio = (float)(abs((*lastarc)->radius)) / tlen;
       curpt.x = (*lastarc)->position.x + tmpratio * (areastruct.save.x
           - (*lastarc)->position.x);
       tmpratio = (float)(*lastarc)->yaxis / tlen;
       curpt.y = (*lastarc)->position.y + tmpratio * (areastruct.save.y
           - (*lastarc)->position.y);
       areastruct.editcycle = 0;
       saveratio = (double)((*lastarc)->yaxis) / (double)(abs((*lastarc)->radius));
       
       /* Push onto the editstack */
       arceditpush(*lastarc);

       checkwarp(&curpt);

       UDrawXLine(curpt, (*lastarc)->position);
       areastruct.save.x = curpt.x; /* for redrawing dotted edit line */
       areastruct.save.y = curpt.y;
       XcSetXORFg((*lastarc)->color, BACKGROUND);
       XcSetFunction(GXxor);
       xcAddEventHandler(areastruct.area, PointerMotionMask, False,
          (xcEventHandler)trackarc, NULL);
       eventmode = (mode == PATH) ? EPATH_MODE : EARC_MODE;
         printpos(curpt.x, curpt.y);
      } break;
   }
}

/*-------------------------------------------------*/
/* Raise/Lower an object                     */
/*-------------------------------------------------*/

void xc_raise(short *selectno)
{
   genericptr *raiseobj, *genobj, temp;

   raiseobj = topobject->plist + *selectno;
   temp = *raiseobj;
   for (genobj = topobject->plist + *selectno; genobj <
            topobject->plist + topobject->parts - 1; genobj++)
      *genobj = *(genobj + 1);
   *(topobject->plist + topobject->parts - 1) = temp;
   *selectno = topobject->parts - 1;
}

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

void xc_lower(short *selectno)
{
   genericptr *lowerobj, *genobj, temp;

   lowerobj = topobject->plist + *selectno;
   temp = *lowerobj;
   for (genobj = topobject->plist + topobject->parts - 2;
            genobj >= topobject->plist; genobj--)
      *(genobj + 1) = *genobj;
   *(topobject->plist) = temp;
   *selectno = 0;
}

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

void exchange()
{
   short *selectno = areastruct.selectlist;
   genericptr *exchobj, *exchobj2, temp;

   if (areastruct.selects > 2 || areastruct.selects == 0) {
      Wprintf("Select 1 or 2 objects");
      return;
   }

   if (areastruct.selects == 1) {  /* lower if on top; raise otherwise */
      if (*selectno == topobject->parts - 1)
       xc_lower(selectno);
      else
       xc_raise(selectno);
   }
   else {  /* exchange the two objects */
      exchobj = topobject->plist + *selectno;
      exchobj2 = topobject->plist + *(selectno + 1);

      temp = *exchobj;
      *exchobj = *exchobj2;
      *exchobj2 = temp;
   }

   incr_changes(topobject);
   clearselects();
   drawarea(NULL, NULL, NULL);
}

/*--------------------------------------------------------*/
/* Flip an element horizontally (POLYGON, ARC, or SPLINE) */
/*--------------------------------------------------------*/

void elhflip(genericptr *genobj)
{
   switch((*genobj)->type) {
      case POLYGON:{
       polyptr flippoly = TOPOLY(genobj);
       pointlist ppoint;
       for (ppoint = flippoly->points; ppoint < flippoly->points +
             flippoly->number; ppoint++)
          ppoint->x = (areastruct.save.x << 1) - ppoint->x; 
       }break;

      case ARC:{
       arcptr fliparc = TOARC(genobj);
       float tmpang = 180 - fliparc->angle1;
       fliparc->angle1 = 180 - fliparc->angle2;
       fliparc->angle2 = tmpang;
       if (fliparc->angle2 < 0) {
          fliparc->angle1 += 360;
          fliparc->angle2 += 360;
       }
       fliparc->radius = -fliparc->radius;
       fliparc->position.x = (areastruct.save.x << 1) - fliparc->position.x;
       calcarc(fliparc);
       }break;

      case SPLINE:{
       splineptr flipspline = TOSPLINE(genobj);
       int i;
       for (i = 0; i < 4; i++)
          flipspline->ctrl[i].x = (areastruct.save.x << 1) -
              flipspline->ctrl[i].x;
       calcspline(flipspline);
       }break;
   }
}

/*--------------------------------------------------------*/
/* Flip an element vertically (POLYGON, ARC, or SPLINE)   */
/*--------------------------------------------------------*/

void elvflip(genericptr *genobj)
{
   switch((*genobj)->type) {

      case POLYGON:{
       polyptr flippoly = TOPOLY(genobj);
       pointlist ppoint;

       for (ppoint = flippoly->points; ppoint < flippoly->points +
               flippoly->number; ppoint++)
          ppoint->y = (areastruct.save.y << 1) - ppoint->y; 
       }break;

      case ARC:{
       arcptr fliparc = TOARC(genobj);
       float tmpang = 360 - fliparc->angle1;
       fliparc->angle1 = 360 - fliparc->angle2;
       fliparc->angle2 = tmpang;
       if (fliparc->angle1 >= 360) {
          fliparc->angle1 -= 360;
          fliparc->angle2 -= 360;
       }
       fliparc->radius = -fliparc->radius;
       fliparc->position.y = (areastruct.save.y << 1) - fliparc->position.y;
       calcarc(fliparc);
       }break;

      case SPLINE:{
       splineptr flipspline = TOSPLINE(genobj);
       int i;
       for (i = 0; i < 4; i++)
          flipspline->ctrl[i].y = (areastruct.save.y << 1) -
              flipspline->ctrl[i].y;
       calcspline(flipspline);
       }break;
   }
}

/*--------------------------------------------------------*/
/* Horizontally flip an object (any except LABEL)       */
/*--------------------------------------------------------*/

void objectflip()
{
   short *selectobj;
   Boolean single = False;

   if (!checkselect(SEL_ANY)) return;
   if (areastruct.selects == 1) single = True;
   u2u_snap(&areastruct.save);
   for (selectobj = areastruct.selectlist; selectobj < areastruct.selectlist
      + areastruct.selects; selectobj++) {

      /* erase the object */
      XSetFunction(dpy, areastruct.gc, GXcopy);
      XSetForeground(dpy, areastruct.gc, BACKGROUND);
      easydraw(*selectobj, DOFORALL);

      switch(SELECTTYPE(selectobj)) {
       case LABEL:{
          labelptr fliplab = SELTOLABEL(selectobj);
          if ((fliplab->justify & (RIGHT | NOTLEFT)) != NOTLEFT)
             fliplab->justify ^= (RIGHT | NOTLEFT);
          if (!single)
             fliplab->position.x = (areastruct.save.x << 1) - fliplab->position.x;
          }break;
       case OBJECT:{
            objinstptr flipobj = SELTOOBJINST(selectobj);
          flipobj->scale = -flipobj->scale;
          if (!single)
             flipobj->position.x = (areastruct.save.x << 1) - flipobj->position.x;
          }break;
       case POLYGON: case ARC: case SPLINE:
          elhflip(topobject->plist + *selectobj);
          break;
       case PATH:{
          genericptr *genpart;
          pathptr flippath = SELTOPATH(selectobj);

          for (genpart = flippath->plist; genpart < flippath->plist
              + flippath->parts; genpart++)
             elhflip(genpart);
          }break;
      }

      if (eventmode != NORMAL_MODE) {
         XSetForeground(dpy, areastruct.gc, SELECTCOLOR);
       easydraw(*selectobj, DOFORALL);
      }
   }
   if (eventmode == NORMAL_MODE) {
      objectdeselect();
      incr_changes(topobject);
#ifdef SCHEMA
      topobject->valid = False;
#endif

   }
   pwriteback(areastruct.topinstance);
   calcbbox(areastruct.topinstance);
}

/*------------------------------------------------*/
/* Vertically flip an object (any except LABEL)   */
/*------------------------------------------------*/

void objectvflip()   /* flip and rotate by 180 degrees */
{
   short *selectobj;
   short fsign;
   Boolean single = False;

   if (!checkselect(SEL_ANY)) return;
   if (areastruct.selects == 1) single = True;
   u2u_snap(&areastruct.save);
   for (selectobj = areastruct.selectlist; selectobj < areastruct.selectlist
      + areastruct.selects; selectobj++) {

      /* erase the object */
      XSetFunction(dpy, areastruct.gc, GXcopy);
      XSetForeground(dpy, areastruct.gc, BACKGROUND);
      easydraw(*selectobj, DOFORALL);

      switch(SELECTTYPE(selectobj)) {
       case(LABEL):{
          labelptr fliplab = SELTOLABEL(selectobj);
          if ((fliplab->justify & (TOP | NOTBOTTOM)) != NOTBOTTOM)
             fliplab->justify ^= (TOP | NOTBOTTOM);
          if (!single)
             fliplab->position.y = (areastruct.save.y << 1) - fliplab->position.y;
          } break;
       case(OBJECT):{
            objinstptr flipobj = SELTOOBJINST(selectobj);

          flipobj->scale = -(flipobj->scale);
          flipobj->rotation += 180;
          while (flipobj->rotation >= 360) flipobj->rotation -= 360;
          if (!single)
             flipobj->position.y = (areastruct.save.y << 1) - flipobj->position.y;
          }break;
       case POLYGON: case ARC: case SPLINE:
          elvflip(topobject->plist + *selectobj);
          break;
       case PATH:{
          genericptr *genpart;
          pathptr flippath = SELTOPATH(selectobj);

          for (genpart = flippath->plist; genpart < flippath->plist
              + flippath->parts; genpart++)
             elvflip(genpart);
          }break;
      }
      if (eventmode != NORMAL_MODE) {
         XSetForeground(dpy, areastruct.gc, SELECTCOLOR);
       easydraw(*selectobj, DOFORALL);
      }
   }
   if (eventmode == NORMAL_MODE) {
      objectdeselect();
      incr_changes(topobject);
#ifdef SCHEMA
      topobject->valid = False;
#endif

   }
   pwriteback(areastruct.topinstance);
   calcbbox(areastruct.topinstance);
}

/*---------------------------*/
/* Begin the rotate function */
/*---------------------------*/
   
void startrotate(xcWidget w, pointertype number, caddr_t calldata)
{
   if (eventmode == NORMAL_MODE) {
      saverot = (short)number;
      eventmode = ROTATE_MODE;
      XDefineCursor (dpy, areastruct.areawin, ROTATECURSOR);

      if (areastruct.selects > 0) {
       Wprintf("Click on point of rotation.");
      }
      else {
         if (number == 512 || number == 1024)
            Wprintf("Click on element to flip.");
         else
            Wprintf("Click on element to rotate.");
      }
   }
}     

/*-------------------------*/
/* Begin the copy function */
/*-------------------------*/

void startcopy(xcWidget w, caddr_t clientdata, caddr_t calldata)
{
   if (eventmode == NORMAL_MODE) {
      eventmode = COPY_MODE;
      XDefineCursor (dpy, areastruct.areawin, COPYCURSOR);
      if (areastruct.selects > 0)
       Wprintf("Click and drag objects.");
      else
         Wprintf("Click on element and drag.");
   }
}

/*-------------------------*/
/* Begin the edit function */
/*-------------------------*/

void startedit(xcWidget w, caddr_t clientdata, caddr_t calldata)
{
   XEvent devent;
   XButtonEvent *b_event = (XButtonEvent *)(&devent);

   if (eventmode == NORMAL_MODE) {
      if (areastruct.selects > 1) objectdeselect();

      if (areastruct.selects == 1) {
       b_event->x = b_event->y = 0;
       edit(b_event);
      }
      else {
         eventmode = EDIT_MODE;
         XDefineCursor (dpy, areastruct.areawin, EDCURSOR);
         Wprintf("Click on element to edit.");
      }
   }
}

/*---------------------------*/
/* Begin the delete function */
/*---------------------------*/

void startdelete(xcWidget w, caddr_t clientdata, caddr_t calldata)
{
   XEvent devent;
   XButtonEvent *b_event = (XButtonEvent *)(&devent);

   if (eventmode == NORMAL_MODE) {
      if (areastruct.selects > 0) {
       b_event->x = b_event->y = 0;
       b_event->button = Button1;
       deletebutton(b_event);
      }
      else { 
         eventmode = DELETE_MODE;
         XDefineCursor (dpy, areastruct.areawin, SCISSORS);
         Wprintf("Click on element to delete.");
      }
   }
}

/*----------------------------------------*/
/* Process of object instance deletion      */
/*----------------------------------------*/

void objectdelete(short drawmode)
{
   short *selectobj;
   objectptr *delbuf, delobj;
   genericptr *genobj;

#ifdef SCHEMA
   Boolean pinchange = False;
#endif

   if (!checkselect(SEL_ANY)) return;

   /* flush the delete buffer and realloc */

   if (areastruct.selects > 0) {
      if (xobjs.delbuffer.number == DELBUFSIZE) {
         reset(*(xobjs.delbuffer.library), DESTROY);
       for (delbuf = xobjs.delbuffer.library + 1; delbuf < xobjs.delbuffer.library
            + xobjs.delbuffer.number; delbuf++)
          *(delbuf - 1) = *delbuf;
      }
      else xobjs.delbuffer.number++;
      delbuf = xobjs.delbuffer.library + xobjs.delbuffer.number - 1;
      *delbuf = (objectptr) malloc(sizeof(object));
      delobj = *delbuf; 
      initmem(delobj);
   }

   if (drawmode) {
      XSetFunction(dpy, areastruct.gc, GXcopy);
      XSetForeground(dpy, areastruct.gc, BACKGROUND);
   }

   /* remove any parameters from the object, if they exist */
   unparameterize(-1);  /* -1 assumes that a selectlist exists */

   for (selectobj = areastruct.selectlist; selectobj < areastruct.selectlist
       + areastruct.selects; selectobj++) {
      genobj = topobject->plist + *selectobj;
      if (drawmode) easydraw(*selectobj, DOFORALL);
      PLIST_INCR(delobj);     
      *(delobj->plist + delobj->parts) = *genobj;
      delobj->parts++;

#ifdef SCHEMA
      /* mark pin label in corresponding schematic/symbol as "orphaned" */
      /* by changing designation from type "pin" to type "label". */

      if (areastruct.schemon && (*genobj)->type == LABEL) {
       if (TOLABEL(genobj)->pin) {
          if (findlabelcopy(TOLABEL(genobj), TOLABEL(genobj)->string) == NULL) {
             changeotherpins(NULL, TOLABEL(genobj)->string);
             if (TOLABEL(genobj)->pin == INFO) pinchange = True;
          }
       }
      }
#endif

      for (++genobj; genobj < topobject->plist + topobject->parts; genobj++)
       *(genobj - 1) = *genobj;
      topobject->parts--;
      reviseselect(selectobj);
   }

#ifdef SCHEMA
   if (pinchange) setobjecttype(topobject);
#endif

   if (areastruct.selects > 0) {
      clearselects();
      incr_changes(topobject);  /* Treat as one change */
#ifdef SCHEMA
      topobject->valid = False;
#endif
   }
   if (drawmode) {
      XSetForeground(dpy, areastruct.gc, FOREGROUND);
      drawarea(NULL, NULL, NULL);
   }
}
  
/*----------------------------------------*/
/* ButtonPress handler during delete mode */
/*----------------------------------------*/

void deletebutton(XButtonEvent *event)
{
   if (eventmode == DELETE_MODE)
      window_to_user(event->x, event->y, &areastruct.save);

   if (event->button == Button1) {
      objectdelete(ERASE);
      calcbbox(areastruct.topinstance);
   }
   else if (event->button == Button2)
      objectselect(SEL_ANY);
   if (event->button == Button3) {
      objectdeselect();
      eventmode = NORMAL_MODE;
      XDefineCursor (dpy, areastruct.areawin, CROSS); 
   }
}

/*-------------------------------*/
/* Undelete last deleted objects */
/*-------------------------------*/

short xc_undelete(xcWidget w, pointertype mode, XButtonEvent *event)
{
   objectptr delobj = *(xobjs.delbuffer.library + xobjs.delbuffer.number - 1);
   genericptr *regen;
   short      count = 0;

   if (xobjs.delbuffer.number == 0) return 0;

   if (mode) {
      XSetFunction(dpy, areastruct.gc, GXcopy);
   }

   for (regen = delobj->plist; regen < delobj->plist + delobj->parts; regen++) {
      PLIST_INCR(topobject);
      topobject->parts++; count++;
      *(ENDPART - 1) = *regen;
      if (mode) {
         XTopSetForeground((*regen)->color);
       easydraw(topobject->parts - 1, DEFAULTCOLOR);
      }
   }
   incr_changes(topobject);   /* treat as one change */
   calcbbox(areastruct.topinstance);
#ifdef SCHEMA
   topobject->valid = False;
#endif


   /* flush the delete buffer and realloc but don't delete the elements */
   reset(delobj, SAVE);
   free(delobj);
   xobjs.delbuffer.number--;
   return count;
}

/*----------------------------*/
/* select save object handler */
/*----------------------------*/

void printname(objectptr curobject)
{
   Dimension swidth, swidth2, sarea;
   char tmpname[256];
   char editstr[10], pagestr[10];
   char *sptr = tmpname;
   Arg      wargs[1];
   short ispage;
   
   /* print full string to make message widget proper size */

   strcpy(editstr, ((ispage = is_page(curobject)) >= 0) ? "Editing: " : "");
   if (strstr(curobject->name, "Page") == NULL && (ispage >= 0))
      sprintf(pagestr, " (p. %d)", areastruct.page + 1);
   else
      pagestr[0] = '\0';
   sprintf(_STR, "%s%s%s", editstr, curobject->name, pagestr); 
   W2printf(_STR);

   /* Tcl doesn't update width changes immediately. . . what to do? */
   /* (i.e., Tk_Width(message2) gives the original width) */
#ifndef TCL_WRAPPER

   XtSetArg(wargs[0], XtNwidth, &sarea);
   XtGetValues(message2, wargs, 1); 
   
   /* in the remote case that the string is longer than message widget,    */
   /* truncate the string and denote the truncation with an ellipsis (...) */

   strcpy(tmpname, curobject->name);
   swidth2 = XTextWidth(appdata.xcfont, editstr, strlen(editstr));
   swidth = XTextWidth(appdata.xcfont, tmpname, strlen(tmpname));

   if ((swidth + swidth2) > sarea) {
      char *ip;
      while ((swidth + swidth2) > sarea) {
         sptr++;
         swidth = XTextWidth(appdata.xcfont, sptr, strlen(sptr));
      }
      for(ip = sptr; ip < sptr + 3 && *ip != '\0'; ip++) *ip = '.';

      sprintf(_STR, "Editing: %s", sptr); 
      W2printf(_STR);
   }
#endif
}

/* List of strings which appear in the profile and cannot be used
   as object names---these are the "most likely" offenders */

#define NUMSTRS 40
static char *psstrings[] = {"add", "and", "arc", "arcn", "array", "bitshift", "bop",
      "copy", "curveto", "dict", "elb", "ele", "ellipse", "eq", "exch", "exec",
      "fill", "if", "index", "label", "mul", "ne", "neg", "nellip", "not", "or",
      "pellip", "polyc", "polygon", "pop", "repeat", "roll", "save", "scale",
      "setgray", "spline", "sub", "transform", "scb", "sce", "xor"};

/*--------------------------------------------------------------*/
/* Make sure that name for new object does not conflict with      */
/* existing object definitions or PostScript commands/defs  */
/* found in xcircps.pro                               */
/* Return:  True if name required change, False otherwise   */
/*--------------------------------------------------------------*/

Boolean checkname(objectptr newobj)
{
   int i, j;
   short dupl;  /* flag a duplicate string */
   objectptr *libobj;
   int errtype = 0;
   char *sptr, *pptr;
   float dfloat;  /* dummy floating-point for syntax check */
   aliasptr aref;
   slistptr sref;

   /* The first character cannot be "/", which is an "immediately    */
   /* evaluated name"                                      */

   sptr = newobj->name;
   while (*sptr == '/') sptr++;

   /* copy the string from the first valid character */

   pptr = (char *)malloc(strlen(sptr) + 2);
   strcpy(pptr, sptr);

   /* Check for illegal characters which have syntactical meaning in */
   /* PostScript, and the presence of nonprintable characters or     */
   /* whitespace.                                    */

   for (sptr = pptr; *sptr != '\0'; sptr++) {
      if (*sptr == '/' || *sptr == '}' || *sptr == '{' ||
           *sptr == ']' || *sptr == '[' || *sptr == ')' ||
           *sptr == '(' || *sptr == '<' || *sptr == '>') {
       *sptr = '_';
       errtype = 1;
      }
      else if ((!isprint(*sptr)) || isspace(*sptr)) {
       *sptr = '_';
       errtype = 1;
      }
   }

   /* The whole string cannot be interpreted as a number (integer    */
   /* or floating-point).  Appending an underscore suffices to make  */
   /* the name legal.                                      */

   if (sscanf(pptr, "%f", &dfloat) == 1) {
      int flen;
      sscanf(pptr, "%f%n", &dfloat, &flen);  /* any leftover characters? */
      if (flen == strlen(pptr)) {
         *sptr++ = '_';
         *sptr = '\0';
         errtype = 2;
      }
   }

   do {
      dupl = 0;
      for (i = 0; i < NUMSTRS; i++)
         if (!strcmp(pptr, psstrings[i])) {
            pptr = (char *)realloc(pptr, strlen(psstrings[i]) + 2);
          sprintf(pptr, "_%s", psstrings[i]);
          dupl = 1;
          errtype = 3;
          break;
         }
      for (i = 0; i < xobjs.numlibs; i++) {
       for (j = 0; j < xobjs.userlibs[i].number; j++) {
          libobj = xobjs.userlibs[i].library + j;

          if (*libobj == newobj) continue;
            if (!strcmp(pptr, (*libobj)->name)) { 
                pptr = (char *)realloc(pptr, strlen((*libobj)->name) + 2);
              sprintf(pptr, "_%s", (*libobj)->name);
              dupl = 1;
            errtype = 4;
          }
       }
      }

      /* If we're in the middle of a file load, the name cannot be the  */
      /* same as an alias, either.                          */
      
      if (aliastop != NULL) {
       for (aref = aliastop; aref != NULL; aref = aref->next) {
          for (sref = aref->aliases; sref != NULL; sref = sref->next) {
             if (!strcmp(pptr, sref->alias)) {
                   pptr = (char *)realloc(pptr, strlen(sref->alias) + 2);
                 sprintf(pptr, "_%s", sref->alias);
                 dupl = 1;
               errtype = 4;
             }
          }
         }
      }

   } while (dupl == 1);

   /* Change name if necessary and report what conflict required the change */
   if (strcmp(pptr, newobj->name)) {
      switch (errtype) {
       case 0:
          Wprintf("Created new object");
          break;
       case 1:
          Wprintf("Replaced illegal character in name with underscore");
          break;
       case 2:
          Wprintf("Name cannot be an integer number:  appended an underscore");
          break;
       case 3:
          Wprintf("Name conflicts with PostScript reserved word:"
                  "  prepended an underscore");
          break;
       case 4:
          Wprintf("Altered name to avoid conflict with existing object");
          break;
      }
      strncpy(newobj->name, pptr, 79);
   }
   free(pptr);
   return (errtype == 0) ? False : True;
}

/*------------------------------------------------------------*/
/* Find the object "dot" in the builtin library, if it exists */
/*------------------------------------------------------------*/

objectptr finddot()
{
   objectptr dotobj;
   short i, j;

   for (i = 0; i < xobjs.numlibs; i++) {
      for (j = 0; j < xobjs.userlibs[i].number; j++) {
       dotobj = *(xobjs.userlibs[i].library + j);
         if (!strcmp(dotobj->name, "dot")) {
            return dotobj;
         }
      }
   }
   return (objectptr)NULL;
}

/*--------------------------------------*/
/* Add value origin to all points   */
/*--------------------------------------*/

void movepoints(genericptr *ssgen, short deltax, short deltay)
{
   switch((*ssgen)->type) {
       case(ARC):{
            fpointlist sspoints;
            TOARC(ssgen)->position.x += deltax;
            TOARC(ssgen)->position.y += deltay;
            for (sspoints = TOARC(ssgen)->points; sspoints < TOARC(ssgen)->points +
                TOARC(ssgen)->number; sspoints++) {
             sspoints->x += deltax;
             sspoints->y += deltay;
            }
          }break;

       case(POLYGON):{
            pointlist sspoints;
            for (sspoints = TOPOLY(ssgen)->points; sspoints < TOPOLY(ssgen)->points +
                TOPOLY(ssgen)->number; sspoints++) {
             sspoints->x += deltax;
             sspoints->y += deltay;
          }
          }break;

       case(SPLINE):{
            fpointlist sspoints;
            short j;
            for (sspoints = TOSPLINE(ssgen)->points; sspoints <
              TOSPLINE(ssgen)->points + INTSEGS; sspoints++) {
             sspoints->x += deltax;
             sspoints->y += deltay;
            }
            for (j = 0; j < 4; j++) {
               TOSPLINE(ssgen)->ctrl[j].x += deltax;
               TOSPLINE(ssgen)->ctrl[j].y += deltay;
            }
            }break;
   }
}

/*----------------------------------------------------------------*/
/* Set the name for a new user-defined object and make the object */
/*----------------------------------------------------------------*/

void domakeobject(xcWidget w, caddr_t nulldata)
{
   objectptr *newobj;
   objinstptr *newinst;
   genericptr *ssgen;
   XPoint origin;
   short libnum = USERLIB - LIBRARY;

   /* make room for new entry in library list */

   xobjs.userlibs[libnum].library = (objectptr *) realloc(xobjs.userlibs[libnum].library,
       (xobjs.userlibs[libnum].number + 1) * sizeof(objectptr));

   newobj = xobjs.userlibs[libnum].library + xobjs.userlibs[libnum].number;

   /* shove selections onto the delete stack, then grab the stack object */

   objectdelete(NORMAL);
   *newobj = *(xobjs.delbuffer.library + xobjs.delbuffer.number - 1);
   xobjs.delbuffer.number--;
   xobjs.userlibs[libnum].number++;

   /* Create the instance of this object so we can compute a bounding box */

   NEW_OBJINST(newinst, topobject);
   topobject->parts++;
   objectdefaults(*newinst, *newobj, 0, 0);
   calcbbox(*newinst);

   /* find closest snap point to bbox center and make this the obj. center */

   if (areastruct.center) {
      origin.x = (*newobj)->bbox.lowerleft.x + (*newobj)->bbox.width / 2;
      origin.y = (*newobj)->bbox.lowerleft.y + (*newobj)->bbox.height / 2;
   }
   else {
      origin.x = origin.y = 0;
   }
   u2u_snap(&origin);

   for (ssgen = (*newobj)->plist; ssgen < (*newobj)->plist + (*newobj)->parts;
        ssgen++) {
      switch((*ssgen)->type) {

       case(OBJECT):
            TOOBJINST(ssgen)->position.x -= origin.x;
            TOOBJINST(ssgen)->position.y -= origin.y;
          break;

       case(LABEL):
            TOLABEL(ssgen)->position.x -= origin.x;
            TOLABEL(ssgen)->position.y -= origin.y;
          break;

       case(PATH):{
          genericptr *pathlist;
          for (pathlist = TOPATH(ssgen)->plist;  pathlist < TOPATH(ssgen)->plist
              + TOPATH(ssgen)->parts; pathlist++) {
             movepoints(pathlist, -origin.x, -origin.y);
          }
          }break;

       default:
          movepoints(ssgen, -origin.x, -origin.y);
          break;
      }
   }

   /* put new object back into place */

   (*newobj)->hidden = False;
#ifdef SCHEMA
   (*newobj)->schemtype = SYMBOL;
   (*newobj)->devname = NULL;
#endif

   objectdefaults(*newinst, *newobj, origin.x, origin.y);
   calcbbox(*newinst);

   incr_changes(*newobj);
   incr_changes(topobject);
#ifdef SCHEMA
   topobject->valid = False;
   (*newobj)->valid = False;
#endif

   XSetFunction(dpy, areastruct.gc, GXcopy);
   XTopSetForeground((*newinst)->color);
   UDrawObject(*newinst, SINGLE, (*newinst)->color, NULL);

   /* copy name from popup prompt buffer and check */

   strcpy((*newobj)->name, _STR2);
   checkname(*newobj);

   /* generate library instance for this object (bounding box     */
   /* should be default, so don't do calcbbox() on it)            */

   addtoinstlist(USERLIB - LIBRARY, *newobj, FALSE);

   /* recompile the user catalog */

   composelib(USERLIB);
}

/*-------------------------------------------*/
/* Make a user object from selected elements */
/*-------------------------------------------*/

#ifndef TCL_WRAPPER

void selectsave(xcWidget w, caddr_t clientdata, caddr_t calldata)
{
   buttonsave *popdata = (buttonsave *)malloc(sizeof(buttonsave));

   if (areastruct.selects == 0) return;  /* nothing was selected */

   /* Get a name for the new object */

   eventmode = NORMAL_MODE;
   popdata->dataptr = NULL;
   popdata->button = NULL; /* indicates that no button is assc'd w/ the popup */
   popupprompt(w, "Enter name for new object:", "\0", domakeobject, popdata, NULL);
}

#endif

/*-----------------------------*/
/* Edit-stack support routines */
/*-----------------------------*/

void arceditpush(arcptr lastarc)
{
   arcptr *newarc;

   NEW_ARC(newarc, areastruct.editstack);
   areastruct.editstack->parts++;
   arccopy(*newarc, lastarc);
}

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

void splineeditpush(splineptr lastspline)
{
   splineptr *newspline;

   NEW_SPLINE(newspline, areastruct.editstack);
   areastruct.editstack->parts++;
   splinecopy(*newspline, lastspline);
}

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

void polyeditpush(polyptr lastpoly)
{
   polyptr *newpoly;

   NEW_POLY(newpoly, areastruct.editstack);
   areastruct.editstack->parts++;
   polycopy(*newpoly, lastpoly);
}

/*-----------------------------*/
/* Copying support routines    */
/*-----------------------------*/

void arccopy(arcptr newarc, arcptr copyarc)
{
   newarc->style = copyarc->style;
   newarc->color = copyarc->color;
   newarc->position.x = copyarc->position.x;
   newarc->position.y = copyarc->position.y;
   newarc->radius = copyarc->radius;
   newarc->yaxis = copyarc->yaxis;
   newarc->angle1 = copyarc->angle1;
   newarc->angle2 = copyarc->angle2;
   newarc->width = copyarc->width;
   newarc->num_params = 0;          /* for now, no parameter copying */
   newarc->passed = NULL;
   calcarc(newarc);
}

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

void polycopy(polyptr newpoly, polyptr copypoly)
{
   pointlist copypoints, newpoints;

   newpoly->style = copypoly->style;
   newpoly->color = copypoly->color;
   newpoly->width = copypoly->width;
   newpoly->number = copypoly->number;
   newpoly->points = (pointlist) malloc(newpoly->number *
        sizeof(XPoint));
   copypoints = copypoly->points;
   for (newpoints = newpoly->points; newpoints < newpoly->points +
            newpoly->number; newpoints++, copypoints++) {
      newpoints->x = copypoints->x;
      newpoints->y = copypoints->y;
   }
   newpoly->num_params = 0;         /* for now, no parameter copying */
   newpoly->passed = NULL;
}

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

void splinecopy(splineptr newspline, splineptr copyspline)
{
   short i;

   newspline->style = copyspline->style;
   newspline->color = copyspline->color;
   newspline->width = copyspline->width;
   for (i = 0; i < 4; i++) {
     newspline->ctrl[i].x = copyspline->ctrl[i].x;
     newspline->ctrl[i].y = copyspline->ctrl[i].y;
   }
   for (i = 0; i < INTSEGS; i++) {
      newspline->points[i].x = copyspline->points[i].x;
      newspline->points[i].y = copyspline->points[i].y;
   }
   newspline->num_params = 0;       /* for now, no parameter copying */
   newspline->passed = NULL;
}

/*--------------------------------------------------------------*/
/* Copy an object instance                            */
/*--------------------------------------------------------------*/

void instcopy(objinstptr newobj, objinstptr copyobj)
{
   newobj->position.x = copyobj->position.x;
   newobj->position.y = copyobj->position.y;
   newobj->rotation = copyobj->rotation;
   newobj->scale = copyobj->scale;
   newobj->thisobject = copyobj->thisobject;
   newobj->color = copyobj->color;
   newobj->num_params = 0;
   newobj->passed = NULL;
   newobj->params = NULL;
   newobj->bbox.lowerleft.x = copyobj->bbox.lowerleft.x;
   newobj->bbox.lowerleft.y = copyobj->bbox.lowerleft.y;
   newobj->bbox.width = copyobj->bbox.width;
   newobj->bbox.height = copyobj->bbox.height;

   copyparams(newobj, copyobj);

#ifdef SCHEMA
   /* If the parameters are the same, the bounding box should be, too. */
   if (copyobj->schembbox != NULL) {
      newobj->schembbox = (BBox *)malloc(sizeof(BBox));
      newobj->schembbox->lowerleft.x = copyobj->schembbox->lowerleft.x;
      newobj->schembbox->lowerleft.y = copyobj->schembbox->lowerleft.y;
      newobj->schembbox->width = copyobj->schembbox->width;
      newobj->schembbox->height = copyobj->schembbox->height;
   }
   else
      newobj->schembbox = NULL;
#endif

}

/*-----------------------------------------------------------------*/
/* For copying:  Check if an object is about to be placed directly */
/* on top of the same object.  If so, delete the one underneath.   */
/*-----------------------------------------------------------------*/

void checkoverlap()
{
   short *sobj, *cobj;
   genericptr *sgen, *pgen;
   Boolean tagged = False;

   /* Work through the select list */

   for (sobj = areastruct.selectlist; sobj < areastruct.selectlist +
      areastruct.selects; sobj++) {
      sgen = topobject->plist + (*sobj);

      /* For each object being copied, compare it against every object  */
      /* on the current page (except self).  Flag if it's the same.     */

      for (pgen = topobject->plist; pgen < topobject->plist + topobject->parts;
            pgen++) {
       if (pgen == sgen) continue;
       if (compare_single(sgen, pgen)) {
          /* Make sure that this object is not part of the selection, */
          /* else chaos will reign.                         */
          for (cobj = areastruct.selectlist; cobj < areastruct.selectlist +
                  areastruct.selects; cobj++) {
             if (pgen == topobject->plist + (*cobj)) break;
          }
          /* Tag it for future deletion and prevent further compares */
          if (cobj == areastruct.selectlist + areastruct.selects) {
             tagged = True;
             (*pgen)->type = 0;
         }
       }     
      }
   }
   if (tagged) Wprintf("Duplicate object deleted");

   /* Delete the tagged elements */

   while (tagged) {
      int i, j;
      tagged = False;
      for (i = 0; i < topobject->parts; i++) {
       pgen = topobject->plist + i;
         if ((*pgen)->type == 0) {
          tagged = True;
          free_single(pgen);
          for (j = i + 1; j < topobject->parts; j++)
             *(topobject->plist + j - 1) = *(topobject->plist + j);
          topobject->parts--;
          for (sobj = areastruct.selectlist; sobj < areastruct.selectlist +
                  areastruct.selects; sobj++)
             if (*sobj > i) (*sobj)--;
       }
      }
   }
}

/*--------------------------------------------------------------*/
/* Direct placement of elements.  Assumes that the selectlist     */
/* contains all the elements to be positioned.  "deltax" and      */
/* "deltay" are relative x and y positions to move the            */
/* elements.                                          */
/*--------------------------------------------------------------*/

void placeselects(short deltax, short deltay, XPoint *userpt)
{
   short *dragselect;
   XPoint newpos;
   int rot;
   short closest;
   short doattach = (userpt == NULL) ? 0 : attachto;

   /* under attachto condition, keep element attached to */
   /* the refselect element.                     */

   if (doattach) findattach(&newpos, &rot, userpt);

   for (dragselect = areastruct.selectlist; dragselect < areastruct.selectlist
      + areastruct.selects; dragselect++) {

      switch(SELECTTYPE(dragselect)) {
         case OBJECT: {
          objinstptr dragobject = SELTOOBJINST(dragselect);
          UDrawObject(dragobject, SINGLE, DOFORALL, NULL);
          if (doattach) {
             dragobject->position.x = newpos.x;
             dragobject->position.y = newpos.y;
             while (rot >= 360) rot -= 360;
             while (rot < 0) rot += 360;
             dragobject->rotation = rot;
          }
          else {
             dragobject->position.x += deltax;
             dragobject->position.y += deltay;
          }
          UDrawObject(dragobject, SINGLE, DOFORALL, NULL);
       } break;
       case LABEL: {
          labelptr draglabel = SELTOLABEL(dragselect);
          UDrawString(draglabel, DOFORALL, areastruct.topinstance);
#ifdef SCHEMA
          if (draglabel->pin == False)
#endif
          UDrawX(draglabel);
          if (doattach) {
             draglabel->position.x = newpos.x;
             draglabel->position.y = newpos.y;
             draglabel->rotation = rot;
          }
          else {
             draglabel->position.x += deltax;
             draglabel->position.y += deltay;
          }
          UDrawString(draglabel, DOFORALL, areastruct.topinstance);
#ifdef SCHEMA
          if (draglabel->pin == False)
#endif
          UDrawX(draglabel);
       } break;
       case PATH: {
          pathptr dragpath = SELTOPATH(dragselect);
          genericptr *pathlist;
          
          UDrawPath(dragpath);
          if (doattach) {
             XPoint *pdelta = pathclosepoint(dragpath, &newpos);
             deltax = newpos.x - pdelta->x;
             deltay = newpos.y - pdelta->y;
          }
          for (pathlist = dragpath->plist;  pathlist < dragpath->plist
              + dragpath->parts; pathlist++) {
             movepoints(pathlist, deltax, deltay);
          }
          UDrawPath(dragpath);
       } break;
       case POLYGON: {
          polyptr dragpoly = SELTOPOLY(dragselect);
          pointlist dragpoints;

          UDrawPolygon(dragpoly);
          if (doattach) {
             closest = closepoint(dragpoly, &newpos);
             deltax = newpos.x - dragpoly->points[closest].x;
             deltay = newpos.y - dragpoly->points[closest].y;
          }
          for (dragpoints = dragpoly->points; dragpoints < dragpoly->points
                 + dragpoly->number; dragpoints++) {
             dragpoints->x += deltax;
             dragpoints->y += deltay;
          }
          UDrawPolygon(dragpoly);
       } break;   
       case SPLINE: {
          splineptr dragspline = SELTOSPLINE(dragselect);
          short j;
          fpointlist dragpoints;

          UDrawSpline(dragspline);
          if (doattach) {
             closest = (wirelength(&dragspline->ctrl[0], &newpos)
              > wirelength(&dragspline->ctrl[3], &newpos)) ? 3 : 0;
             deltax = newpos.x - dragspline->ctrl[closest].x;
             deltay = newpos.y - dragspline->ctrl[closest].y;
          }
          for (dragpoints = dragspline->points; dragpoints < dragspline->
               points + INTSEGS; dragpoints++) {
             dragpoints->x += deltax;
             dragpoints->y += deltay;
          }
          for (j = 0; j < 4; j++) {
             dragspline->ctrl[j].x += deltax;
             dragspline->ctrl[j].y += deltay;
          }
          UDrawSpline(dragspline);
       } break;
       case ARC: {
          arcptr dragarc = SELTOARC(dragselect);
          fpointlist dragpoints;

          UDrawArc(dragarc);
          if (doattach) {
             deltax = newpos.x - dragarc->position.x;
             deltay = newpos.y - dragarc->position.y;
          }
          dragarc->position.x += deltax;
          dragarc->position.y += deltay;
          for (dragpoints = dragarc->points; dragpoints < dragarc->
             points + dragarc->number; dragpoints++) {
             dragpoints->x += deltax;
             dragpoints->y += deltay;
          }
          UDrawArc(dragarc);
         } break;
      }
   }
}



/*----------------------------------------------------------------------*/
/* Copy handler.  Assumes that the selectlist contains the elements     */
/* to be copied, and that the initial position of the copy is held      */
/* in areastruct.save.                                      */
/*----------------------------------------------------------------------*/

void createcopies()
{
   short *selectobj;

   if (!checkselect(SEL_ANY)) return;
   u2u_snap(&areastruct.save);
   for (selectobj = areastruct.selectlist; selectobj < areastruct.selectlist
            + areastruct.selects; selectobj++) {
      switch(SELECTTYPE(selectobj)) {
         case LABEL: { /* copy label */
          labelptr copytext = SELTOLABEL(selectobj);
          labelptr *newtext;
      
          NEW_LABEL(newtext, topobject);
          (*newtext)->string = stringcopy(copytext->string);
          (*newtext)->position.x = copytext->position.x;
          (*newtext)->position.y = copytext->position.y;
          (*newtext)->rotation = copytext->rotation;
          (*newtext)->scale = copytext->scale;
          (*newtext)->justify = copytext->justify;
          (*newtext)->color = copytext->color;
          (*newtext)->num_params = 0;
          (*newtext)->passed = NULL;
#ifdef SCHEMA
          (*newtext)->pin = copytext->pin;

          /* copy label both here and on corresponding schematic/symbol */
          copypinlabel(*newtext);
#endif
         } break;
         case OBJECT: { /* copy object instance */
          objinstptr copyobj = SELTOOBJINST(selectobj);
          objinstptr *newobj;
          NEW_OBJINST(newobj, topobject);
          instcopy(*newobj, copyobj);
         } break;
       case PATH: { /* copy path */
          pathptr copypath = SELTOPATH(selectobj);
          pathptr *newpath;
          genericptr *genpart;

          NEW_PATH(newpath, topobject);
          (*newpath)->style = copypath->style;
          (*newpath)->color = copypath->color;
          (*newpath)->width = copypath->width;
          (*newpath)->num_params = 0;
          (*newpath)->passed = NULL;
          (*newpath)->parts = 0;
          (*newpath)->plist = (genericptr *)malloc(copypath->parts *
                 sizeof(genericptr));
          for (genpart = copypath->plist; genpart < copypath->plist
              + copypath->parts; genpart++) {
             switch((*genpart)->type){
              case ARC: {
                       arcptr copyarc = TOARC(genpart);
                       arcptr *newarc;
                       NEW_ARC(newarc, (*newpath));
                       arccopy(*newarc, copyarc);
                       (*newpath)->parts++;
                 } break;
              case POLYGON: {
                       polyptr copypoly = TOPOLY(genpart);
                       polyptr *newpoly;
                       NEW_POLY(newpoly, (*newpath));
                       polycopy(*newpoly, copypoly);
                       (*newpath)->parts++;
                 } break;
              case SPLINE: {
                       splineptr copyspline = TOSPLINE(genpart);
                       splineptr *newspline;
                       NEW_SPLINE(newspline, (*newpath));
                       splinecopy(*newspline, copyspline);
                       (*newpath)->parts++;
                 } break;
             }
          }
       } break;   
       case ARC: { /* copy arc */
          arcptr copyarc = SELTOARC(selectobj);
          arcptr *newarc;
          NEW_ARC(newarc, topobject);
          arccopy(*newarc, copyarc);
         } break;
         case POLYGON: { /* copy polygons */
            polyptr copypoly = SELTOPOLY(selectobj);
            polyptr *newpoly;
          NEW_POLY(newpoly, topobject);
          polycopy(*newpoly, copypoly);
         } break;
       case SPLINE: { /* copy spline */
          splineptr copyspline = SELTOSPLINE(selectobj);
          splineptr *newspline;
          NEW_SPLINE(newspline, topobject);
          splinecopy(*newspline, copyspline);
       } break;
      }

      /* change selection from the old to the new object */

      *selectobj = topobject->parts;
      topobject->parts++;
   }
}

/*--------------------------------------------------------------*/
/* Function which initiates interactive placement of copied */
/* elements.                                          */
/*--------------------------------------------------------------*/

void copydrag()
{
   short *selectobj;

   if (areastruct.selects > 0) {

      /* Put all selected objects into the "select" color */ 

      XSetFunction(dpy, areastruct.gc, GXxor);
      for (selectobj = areastruct.selectlist; selectobj < areastruct.selectlist
            + areastruct.selects; selectobj++) {
         XSetXORFg(SELTOCOLOR(selectobj), BACKGROUND);
         easydraw(*selectobj, DOFORALL);
      }

      if (eventmode == NORMAL_MODE || eventmode == COPY_MODE) {
       XDefineCursor(dpy, areastruct.areawin, COPYCURSOR);
       eventmode = COPY2_MODE;
#ifdef TCL_WRAPPER
         Tk_CreateEventHandler(areastruct.area, PointerMotionMask,
            (Tk_EventProc *)xctk_drag, NULL);
#else
         XtAddEventHandler(areastruct.area, PointerMotionMask, False, 
            (XtEventHandler)drag, NULL);
#endif
      }
      incr_changes(topobject);            /* treat as a single change */
#ifdef SCHEMA
      topobject->valid = False;
#endif

   }
}

/*-----------------------------------------------------------*/
/* Copy handler for copying from a button push or key event. */
/*-----------------------------------------------------------*/

void copybutton(XButtonEvent *event)
{
   window_to_user(event->x, event->y, &areastruct.save);

   if (event->button == Button1) {
      createcopies();   /* This function does all the hard work */
      copydrag(); /* Start interactive placement */
   }
   else if (event->button == Button2)
      objectselect(SEL_ANY);
   if (event->button == Button3) {
      objectdeselect();
      eventmode = NORMAL_MODE;
      XDefineCursor (dpy, areastruct.areawin, CROSS);
   }
}

/*---------------------*/
/* ButtonPress handler */
/*---------------------*/

void selectbutton(XButtonEvent *event)
{
   XPoint ppos;

   /* ignore multiple buttons (using local button information) */

   if (curbutton != (u_char)0) return;

   curbutton = (u_char)event->button;

   if (eventmode != EARC_MODE && eventmode != ARC_MODE)
      window_to_user(event->x, event->y, &areastruct.save);
   snap(event->x, event->y, &ppos);
   printpos(ppos.x, ppos.y);

   /* Fprintf(stderr, "Press:  mode is %d\n", eventmode); */
   switch(eventmode) {
      case(NORMAL_MODE):
       eventmode = PENDING_MODE;
         if (event->button == Button1) /* setup timeout event */
            areastruct.time_id = xcAddTimeOut(app, PRESSTIME, 
            (xcTimeOutProc)makepress, topobject);
         else if (event->button == Button2)
          areastruct.time_id = xcAddTimeOut(app, PRESSTIME,
            (xcTimeOutProc)startselect, topobject);
         else if (event->button == Button3)
          if (areastruct.selects > 0)
               objectdeselect(); 
#ifdef SCHEMA
          /* Return any highlighed networks to normal */
          else if (!highlightnet(topobject, areastruct.topinstance, -1, 0)
                  && areastruct.lastselects > 0) {
#else
          else if (areastruct.lastselects > 0) {
#endif
             areastruct.selectlist = areastruct.selectlast;
             areastruct.selects = areastruct.lastselects;
             areastruct.lastselects = 0;
             drawselects(topobject, areastruct.topinstance);
          }
         break;
      case(TEXT3_MODE): case(CATTEXT_MODE): {
       labelptr curlabel = TOLABEL(EDITPART);
       UDrawTLine(curlabel);
       if (event->button == Button3) {
          undrawtext(curlabel);
          freelabel(curlabel->string);
          /* restore the original text */
          curlabel->string = stringcopyback(labelbuf, areastruct.topinstance);
          freelabel(labelbuf);
          labelbuf = NULL;
          /* determine which parameter instances revert to the default */
          resolveparams(areastruct.topinstance);
          redrawtext(curlabel);
          if (eventmode == CATTEXT_MODE) eventmode = CATALOG_MODE;
          else eventmode = NORMAL_MODE;
          Wprintf("");
          setdefaultfontmarks();
       }
       else textreturn();  /* Generate "return" key character */
       textend = 0;
      } break;
      case(PAN_MODE): case(CATPAN_MODE):
       panrefresh(0, event);
         break; 
      case(BOX0_MODE):
       if (event->button != Button3) boxbutton(event);
       else {
          eventmode = NORMAL_MODE;
          Wprintf("Cancelled box.");
       }
       break;
      case(SPLINE0_MODE):
         if (event->button != Button3) splinebutton(event);
         else {
            eventmode = NORMAL_MODE;
            Wprintf("Cancelled spline.");
         }
         break;
      case(POLY_MODE):
       startwire(areastruct.save);
       eventmode = WIRE_MODE;
       break;
      case(ARC0_MODE):
         if (event->button != Button3) arcbutton(event);
         else {
            eventmode = NORMAL_MODE;
            Wprintf("Cancelled arc.");
         }
         break;
      case(TEXT1_MODE):
       if (event->button != Button3)
          textbutton(texttype, event);
       else {
          eventmode = NORMAL_MODE;
          Wprintf("Cancelled text.");
          XDefineCursor(dpy, areastruct.areawin, CROSS);
       }
       break;
      case(SELAREA2_MODE):
       eventmode = PENDING_MODE;
#ifdef TCL_WRAPPER
       startselect(NULL);
#else
       startselect(NULL, NULL);
#endif
       eventmode = SELAREA2_MODE;
       break;
      case(DELETE_MODE):
       deletebutton(event);
       break;
      case(PUSH_MODE):
       pushobject((XButtonEvent *)event);
       break;
      case(DESEL_MODE):
         objectselect(-SEL_ANY);
       setoptionmenu(); /* return Option menu to default state */
       break;
      case(COPY_MODE):
       copybutton(event);
       break;
      case(ROTATE_MODE):
       rotatebutton(event);
       break;
      case(LPARAM_MODE):
       parameterize(P_SUBSTRING);
       break;
      case(IPARAM_MODE):
       parameterize(-1);      /* select with SEL_ANY */
       break;
      case(ULPARAM_MODE):
       unparameterize(P_SUBSTRING);
       break;
      case(UIPARAM_MODE):
       unparameterize(-1);    /* select with SEL_ANY */
       break;
      case(EDIT_MODE):
       edit(event);
       break;
      case(COPY2_MODE):
       u2u_snap(&areastruct.save);
       break;
#ifdef SCHEMA
      case(CONNECT_MODE):
       connectivity(NULL, NULL, NULL);
       break;
      case(ASSOC_MODE):
#endif
      case(CATALOG_MODE):
       catbutton(0, event);
       break;
      case(FONTCAT_MODE): case(FONTCAT2_MODE):
       fontcatbutton(event);
       break;
      case(WIRE_MODE):
       wirebutton(event);
       break;
   }
}

/*-----------------------*/
/* ButtonRelease handler */
/*-----------------------*/

void releasebutton(XButtonEvent *event)
{
   /* ignore multiple buttons (using local button information) */

   if ((u_int)curbutton != event->button) return;
   curbutton = (u_char)0;

   /* remove timeout, if pending */

   if (areastruct.time_id != 0) {
      xcRemoveTimeOut(areastruct.time_id);
      areastruct.time_id = 0;
   }

/* Fprintf(stdout, "Release:  mode is %d\n", eventmode); */
   switch(eventmode) {
      case(PUSH_MODE): case(ROTATE_MODE): case(EDIT_MODE):
      case(DESEL_MODE): case(PAN_MODE): case(LPARAM_MODE):
      case(IPARAM_MODE): case(ULPARAM_MODE): case(UIPARAM_MODE):
       eventmode = NORMAL_MODE;
       break;
      case (CONNECT_MODE):
       eventmode = NORMAL_MODE;
       return;
       break;
      case (FONTCAT_MODE):
       eventmode = TEXT2_MODE;
       XDefineCursor (dpy, areastruct.areawin, TEXTPTR);
         drawarea(NULL, NULL, NULL);
       break;
      case (FONTCAT2_MODE):
       eventmode = TEXT3_MODE;
       XDefineCursor (dpy, areastruct.areawin, TEXTPTR);
         drawarea(NULL, NULL, NULL);
       break;
      case (CATPAN_MODE):
       eventmode = CATALOG_MODE;
         XDefineCursor(dpy, areastruct.areawin, CROSS);
       break;
      case(WIRE_MODE):
       if(event->button == Button2) {
          eventmode = NORMAL_MODE;
          singlebbox(EDITPART);
         }
       break;
      case(COPY_MODE):
       if(event->button == Button1)
            objectdeselect();
       break;
      case(COPY2_MODE): {
       eventmode = NORMAL_MODE;
       attachto = 0;
       Wprintf("");
#ifdef TCL_WRAPPER
         xcRemoveEventHandler(areastruct.area, PointerMotionMask, False, 
          (xcEventHandler)xctk_drag, NULL);
#else
         xcRemoveEventHandler(areastruct.area, PointerMotionMask, False, 
          (xcEventHandler)drag, NULL);
#endif
       XDefineCursor(dpy, areastruct.areawin, CROSS);
       if(event->button == Button3) {
          objectdelete(NORMAL);

          /* Redraw the screen so that an object directly under */
          /* the delete object won't get painted black */

            drawarea(NULL, NULL, NULL);
       }
       else if (event->button == Button2) {
          /* If selected objects are the only ones on the page, */
          /* then do a full bbox calculation.                 */
          if (topobject->parts == areastruct.selects)
             calcbbox(areastruct.topinstance);
          else    
             calcbboxselect();
          checkoverlap();
          clearselects();
       }
       else {
          short *csel;
            XSetFunction(dpy, areastruct.gc, GXcopy);
            XSetForeground(dpy, areastruct.gc, SELECTCOLOR);
          for (csel = areastruct.selectlist; csel < areastruct.selectlist +
                  areastruct.selects; csel++)
             easydraw(*csel, DOFORALL);
          if (topobject->parts == areastruct.selects)
             calcbbox(areastruct.topinstance);
          else
             calcbboxselect();
          checkoverlap();
          copybutton(event);
       }
      } break;
      case(TEXT1_MODE): {
       eventmode = TEXT2_MODE;
      } break;
      case(TEXT2_MODE): {
       labelptr curlabel = TOLABEL(EDITPART);
       UDrawTLine(curlabel);
       if (event->button == Button3) {
           redrawtext(curlabel);
           freelabel(curlabel->string); 
           free(curlabel);
           curlabel = NULL;
       }
       else {
          topobject->parts++;
          singlebbox(EDITPART);
          incr_changes(topobject);
#ifdef SCHEMA
            topobject->valid = False;
#endif
       }
       eventmode = NORMAL_MODE;
       setdefaultfontmarks();
      } break;
      case(PRESS_MODE): {
#ifdef TCL_WRAPPER
       Tk_DeleteEventHandler(areastruct.area, ButtonMotionMask,
                (Tk_EventProc *)xctk_drag, NULL);
#endif
         eventmode = NORMAL_MODE;
       if (areastruct.selects > 0) {
          pwriteback(areastruct.topinstance);
          incr_changes(topobject);
#ifdef SCHEMA
          topobject->valid = False;
#endif
       }
       attachto = 0;
       Wprintf("");
       /* full calc needed: move may shrink bbox */
       calcbbox(areastruct.topinstance);
       checkoverlap();
       clearselects();
      } break;
      case(SELAREA_MODE): {
       eventmode = NORMAL_MODE;
       UDrawBox(areastruct.origin, areastruct.save);

#ifdef TCL_WRAPPER
       Tk_DeleteEventHandler(areastruct.area, ButtonMotionMask,
                (Tk_EventProc *)xctk_drag, NULL);
#endif

       /* Zero-width boxes act like a normal selection.  Otherwise,     */
       /* proceed with the area select.                     */

       if ((areastruct.origin.x == areastruct.save.x) &&
           (areastruct.origin.y == areastruct.save.y))
            objectselect(SEL_ANY);
       else
          selectarea();
      } break;
      case(SELAREA2_MODE): {
       eventmode = SELAREA_MODE;
       zoominrefresh(NULL, NULL, event);

#ifdef TCL_WRAPPER
       Tk_DeleteEventHandler(areastruct.area, ButtonMotionMask,
                (Tk_EventProc *)xctk_drag, NULL);
#endif

      } break;
      case(EPATH_MODE):
       pathbutton((*((pathptr *)EDITPART))->plist + areastruct.editsubpart,
            event);
       break;
      case(BOX_MODE): case(EBOX_MODE): case (ARC_MODE):
           case(EARC_MODE): case(SPLINE_MODE): case(ESPLINE_MODE):
       pathbutton(EDITPART, event);
       break;
      case(PENDING_MODE):
       eventmode = NORMAL_MODE;
       break;
   }
   if (eventmode == NORMAL_MODE) {
      XDefineCursor(dpy, areastruct.areawin, CROSS);
#ifdef DOUBLEBUFFER
      drawarea(NULL, NULL, NULL);
#endif
   }
}

/*--------------------------------------------------------------*/
/* Button release handler for path components               */
/*--------------------------------------------------------------*/

void pathbutton(genericptr *editpart, XButtonEvent *event)
{
   short etype = (*editpart)->type;

   switch(etype) {
      case(POLYGON): {
       if (eventmode == BOX_MODE) {
            polyptr   newbox;

            newbox = TOPOLY(editpart);
          UDrawPolygon(newbox);

            /* prevent length and/or width zero boxes */
            if (newbox->points->x != (newbox->points + 2)->x && (newbox->points 
               + 1)->y != (newbox->points + 3)->y) {
             if (event->button != Button3) {
                  XSetFunction(dpy, areastruct.gc, GXcopy);
                  XTopSetForeground(newbox->color);
                topobject->parts++;
                  UDrawPolygon(newbox);
                incr_changes(topobject);
             }
             else free(newbox);
            }
          else free(newbox);

            xcRemoveEventHandler(areastruct.area, PointerMotionMask, False,
               (xcEventHandler)trackbox, NULL);
            eventmode = NORMAL_MODE;
         }
       else {   /* EBOX_MODE or EPATH_MODE */
            genericptr   *replace;
            polyptr      newpoly;
            short        dotrack = True;
  
            replace = editpart;
            newpoly = TOPOLY(replace);
            attachto = 0;
   
            if (event->button != Button1) {
             dotrack = False;
               UDrawPolygon(newpoly);
            }

            if (event->button == Button1) {
             nextpolycycle(newpoly, 1);
             polyeditpush(newpoly);
          }
            else if (event->button == Button2) {
               XSetFunction(dpy, areastruct.gc, GXcopy); 
               XTopSetForeground(newpoly->color);
               UDrawPolygon(newpoly);
          }
            else {
             free(newpoly);
             if (areastruct.editstack->parts > 0) {
                newpoly = TOPOLY(areastruct.editstack->plist +
                  areastruct.editstack->parts - 1);
                areastruct.editstack->parts--;
                *replace = (genericptr)newpoly;
                if (areastruct.editstack->parts > 0) {
                 dotrack = True;
                 nextpolycycle(newpoly, -1);
                }
                else {
                 XcSetFunction(GXcopy);
                 XcSetForeground(newpoly->color);
                }
                  UDrawPolygon(newpoly);
             }
             else
                topobject->parts--;
          }
          pwriteback(areastruct.topinstance);

            if (!dotrack) {
             /* Free the editstack */
             reset(areastruct.editstack, NORMAL);

               xcRemoveEventHandler(areastruct.area, PointerMotionMask, False,
                   (xcEventHandler)trackpoly, NULL);
               eventmode = NORMAL_MODE;
            }
       }
      } break;
      case(ARC): {
       genericptr *replace;
         arcptr   newarc;
         short    dotrack = True;

       replace = editpart;
       newarc = TOARC(replace);

       if (event->button != Button1) {
          dotrack = False;
          UDrawArc(newarc);
          UDrawXLine(areastruct.save, newarc->position);
         }

       if (event->button == Button1) {
          nextarccycle(newarc, 1);
          arceditpush(newarc);
       }

         /* prevent radius zero arcs */
         else if (newarc->radius != 0 && newarc->yaxis != 0 &&
               (newarc->angle1 != newarc->angle2)) {
            if (event->button == Button2) {
               XSetFunction(dpy, areastruct.gc, GXcopy);
               XTopSetForeground(newarc->color);
               if (eventmode == ARC_MODE) {
              topobject->parts++;
                incr_changes(topobject);
             }
               UDrawArc(newarc);
          }
          else {   /* restore previous arc from edit stack */
             free(newarc);
             if (areastruct.editstack->parts > 0) {
                newarc = TOARC(areastruct.editstack->plist +
                  areastruct.editstack->parts - 1);
                areastruct.editstack->parts--;
              *replace = (genericptr)newarc;
              if (areastruct.editstack->parts > 0) {
                 dotrack = True;
                   nextarccycle(newarc, -1);
                 UDrawXLine(areastruct.save, newarc->position);
              }
              else {
                     XSetFunction(dpy, areastruct.gc, GXcopy);
                     XTopSetForeground(newarc->color);
              }
                  UDrawArc(newarc);
             }
          }
         }
       else {
          if (eventmode != ARC_MODE) topobject->parts--;
          free(newarc);
       } 
       pwriteback(areastruct.topinstance);

       if (!dotrack) {
          /* Free the editstack */
          reset(areastruct.editstack, NORMAL);

            xcRemoveEventHandler(areastruct.area, PointerMotionMask, False,
               (xcEventHandler)trackarc, NULL);
            eventmode = NORMAL_MODE;
       }
      } break;
      case(SPLINE): {
       genericptr *replace;
       splineptr  newspline;
       short            dotrack = True;

       replace = editpart;
       newspline = TOSPLINE(editpart);

       if (event->button != Button1) {
          UDrawEditSpline(newspline);
          dotrack = False;
       }

       if (event->button == Button1) {
          nextsplinecycle(newspline, -1);
          splineeditpush(newspline);
       }

       /* unlikely but possible to create zero-length splines */
       else if (newspline->ctrl[0].x != newspline->ctrl[3].x ||
            newspline->ctrl[0].x != newspline->ctrl[1].x ||
            newspline->ctrl[0].x != newspline->ctrl[2].x ||
            newspline->ctrl[0].y != newspline->ctrl[3].y ||
            newspline->ctrl[0].y != newspline->ctrl[1].y ||
            newspline->ctrl[0].y != newspline->ctrl[2].y) {

          if (event->button == Button2) {
             XSetFunction(dpy, areastruct.gc, GXcopy);
             XTopSetForeground(newspline->color);
             if (eventmode == SPLINE_MODE) {
              topobject->parts++;
                incr_changes(topobject);
             }
             UDrawSpline(newspline);
          }
          else {  /* restore previous spline from edit stack */
             free(newspline);
             if (areastruct.editstack->parts > 0) {
                newspline = TOSPLINE(areastruct.editstack->plist +
                  areastruct.editstack->parts - 1);
                areastruct.editstack->parts--;
              *replace = (genericptr)newspline;
              if (areastruct.editstack->parts > 0) {
                 dotrack = True;
                   nextsplinecycle(newspline, 1);
                   UDrawEditSpline(newspline);
              }
              else {
                   XSetFunction(dpy, areastruct.gc, GXcopy);
                   XTopSetForeground(newspline->color);
                   UDrawSpline(newspline);
              }
             }
          }
       }
       else {
          if (eventmode != SPLINE_MODE) topobject->parts--;
          free(newspline);
       }
       pwriteback(areastruct.topinstance);

       if (!dotrack) {
          /* Free the editstack */
          reset(areastruct.editstack, NORMAL);

          xcRemoveEventHandler(areastruct.area, PointerMotionMask, False,
               (xcEventHandler)trackspline, NULL);
          eventmode = NORMAL_MODE;
       }
      } break;
   }
   /* singlebbox(editpart); */
   calcbbox(areastruct.topinstance);
}

/*-------------------------------------------------------*/
/* Recalculate values for a drawing-area widget resizing */
/*-------------------------------------------------------*/

void resizearea(xcWidget w, caddr_t clientdata, caddr_t calldata)
{
   Arg      wargs[2];
   XEvent discard;
   int savewidth = areastruct.width, saveheight = areastruct.height;

   if (xcIsRealized(areastruct.area)) {

#ifdef TCL_WRAPPER
      areastruct.width = Tk_Width(w);
      areastruct.height = Tk_Height(w);
#else
      XtSetArg(wargs[0], XtNwidth, &areastruct.width);
      XtSetArg(wargs[1], XtNheight, &areastruct.height);
      XtGetValues(areastruct.area, wargs, 2);
#endif

      if (areastruct.width != savewidth || areastruct.height != saveheight) {
#ifdef DOUBLEBUFFER
         if (dbuf != (Pixmap)NULL) XFreePixmap(dpy, dbuf);
         dbuf = XCreatePixmap(dpy, areastruct.areawin, areastruct.width,
            areastruct.height, DefaultDepthOfScreen(xcScreen(w)));
#endif
         reset_gs();

         /* Re-center image in resized window */

         zoomview(NULL, NULL, NULL);
      }

      /* Flush all expose events from the buffer */

      while (XCheckWindowEvent(dpy, areastruct.areawin, ExposureMask, &discard) == True);
   }
}

/*------------------------------------------*/
/* Draw the primary graphics window, "area" */
/*------------------------------------------*/

void drawarea(xcWidget w, caddr_t clientdata, caddr_t calldata)
{
   Window win, tmpwin;
   float x, y, spc, spc2, i, j, fpart;
   short *selectobj;
   XPoint originpt;
   XEvent discard;

   if (!xcIsRealized(areastruct.area)) return;

   newmatrix();
   XSetFunction(dpy, areastruct.gc, GXcopy);

#ifdef DOUBLEBUFFER
   tmpwin = areastruct.areawin;
   win = (Window)dbuf;
   areastruct.areawin = win;
   if (xobjs.pagelist[areastruct.page]->background.name == (char *)NULL
      || (copybackground() < 0)) {
      XSetForeground(dpy, areastruct.gc, BACKGROUND);
      XFillRectangle(dpy, win, areastruct.gc, 0, 0, areastruct.width,
         areastruct.height);
   }
#else

   win = areastruct.areawin;

   if (xobjs.pagelist[areastruct.page]->background.name != (char *)NULL)
      copybackground();
   else
      XClearWindow(dpy, win);
#endif

   XSetLineAttributes(dpy, areastruct.gc, 0, LineSolid, CapRound, JoinBevel);

   /* draw GRIDCOLOR lines for grid; mark axes in AXESCOLOR */

   if (eventmode != CATALOG_MODE && eventmode != CATPAN_MODE
#ifdef SCHEMA
      && eventmode != ASSOC_MODE
#endif
      && eventmode != FONTCAT_MODE && eventmode != FONTCAT2_MODE) {

      spc = xobjs.pagelist[areastruct.page]->gridspace * (*areastruct.vscale);
      if (areastruct.gridon && spc > 8) {
       fpart = (float)(-areastruct.pcorner->x)
                  / xobjs.pagelist[areastruct.page]->gridspace;
         x = xobjs.pagelist[areastruct.page]->gridspace *
            (fpart - (float)((int)fpart)) * (*areastruct.vscale);
       fpart = (float)(-areastruct.pcorner->y)
                  / xobjs.pagelist[areastruct.page]->gridspace;
         y = xobjs.pagelist[areastruct.page]->gridspace *
            (fpart - (float)((int)fpart)) * (*areastruct.vscale);

         XSetForeground(dpy, areastruct.gc, GRIDCOLOR);
         for (i = x; i < (float)areastruct.width; i += spc) 
          XDrawLine (dpy, win, areastruct.gc, (int)(i + 0.5),
            0, (int)(i + 0.5), areastruct.height);
         for (j = (float)areastruct.height - y; j > 0; j -= spc)
            XDrawLine (dpy, win, areastruct.gc, 0, (int)(j - 0.5),
            areastruct.width, (int)(j - 0.5));
      };
      if (areastruct.axeson) {
         XPoint zeropt;
         zeropt.x = zeropt.y = 0;
         XSetForeground(dpy, areastruct.gc, AXESCOLOR);
         user_to_window(zeropt, &originpt);
         XDrawLine (dpy, win, areastruct.gc, originpt.x, 0,
                  originpt.x, areastruct.height);
         XDrawLine (dpy, win, areastruct.gc, 0, originpt.y,
                  areastruct.width, originpt.y);
      }

      /* bounding box goes beneath everything except grid/axis lines */
      UDrawBBox();

      /* draw a little red dot at each snap-to point */

      spc2 = xobjs.pagelist[areastruct.page]->snapspace * (*areastruct.vscale);
      if (areastruct.snapto && spc2 > 8) {
         float x2, y2;

       fpart = (float)(-areastruct.pcorner->x)
                  / xobjs.pagelist[areastruct.page]->snapspace;
         x2 = xobjs.pagelist[areastruct.page]->snapspace *
            (fpart - (float)((int)fpart)) * (*areastruct.vscale);
       fpart = (float)(-areastruct.pcorner->y)
                  / xobjs.pagelist[areastruct.page]->snapspace;
         y2 = xobjs.pagelist[areastruct.page]->snapspace *
            (fpart - (float)((int)fpart)) * (*areastruct.vscale);

         XSetForeground(dpy, areastruct.gc, SNAPCOLOR);
         for (i = x2; i < areastruct.width; i += spc2)
            for (j = areastruct.height - y2; j > 0; j -= spc2)
               XDrawPoint (dpy, win, areastruct.gc, (int)(i + 0.5),
                  (int)(j - 0.5));
      };
      XSetBackground(dpy, areastruct.gc, BACKGROUND);

      /* Determine the transformation matrix for the topmost object */
      /* and draw the hierarchy above the current edit object (if   */
      /* "edit-in-place" is selected).                          */

      if (areastruct.editinplace == True) {
         if (areastruct.stack != NULL) {
          pushlistptr lastlist, thislist;
          Matrix mtmp;

          UPushCTM();   /* save our current state */

          /* It's easiest if we first push the current page onto the stack, */
          /* then we don't need to treat the top-level page separately.  We */
          /* pop it at the end.                                   */
          push_stack(&areastruct.stack, areastruct.topinstance);

          thislist = areastruct.stack;

          while ((thislist != NULL) &&
                  (is_library(thislist->thisinst->thisobject) < 0)) {

             /* Invert the transformation matrix of the instance on the stack */
             /* to get the proper transformation matrix of the drawing one    */
             /* up in the hierarchy.                                    */

             UResetCTM(&mtmp);
             UPreMultCTM(&mtmp, thislist->thisinst->position,
                  thislist->thisinst->scale, thislist->thisinst->rotation);
             InvertCTM(&mtmp);
             UPreMultCTMbyMat(DCTM, &mtmp);

             lastlist = thislist;
             thislist = thislist->next;

#ifdef SCHEMA
             /* The following will be true for moves between schematics and symbols */
             if ((thislist != NULL) && (thislist->thisinst->thisobject->symschem
                  == lastlist->thisinst->thisobject))
                break;
#endif
          }

          if (lastlist != NULL) {
             pushlistptr stack = NULL;
             XSetForeground(dpy, areastruct.gc, OFFBUTTONCOLOR);
               UDrawObject(lastlist->thisinst, SINGLE, DOFORALL, &stack);
             if (stack) free_stack(&stack);     /* shouldn't happen. . . */
          }

          pop_stack(&areastruct.stack); /* restore the original stack state */
          UPopCTM();                  /* restore the original matrix state */
       }
      }
   }

   /* draw all of the elements on the screen */

   XSetForeground(dpy, areastruct.gc, FOREGROUND);
   UDrawObject(areastruct.topinstance, TOPLEVEL, FOREGROUND, NULL);

#ifdef SCHEMA
   /* draw the highlighted netlist, if any */
   if (checkvalid(topobject) != -1)
      highlightnet(topobject, areastruct.topinstance, -1, 1);
#endif

   /* draw selected elements in the SELECTION color */
   /* special case---single label partial text selected */

   if ((areastruct.selects == 1) && SELECTTYPE(areastruct.selectlist) == LABEL
                && textend > 0 && textpos > textend) {
      labelptr drawlabel = SELTOLABEL(areastruct.selectlist);
      UDrawString(drawlabel, DOSUBSTRING, areastruct.topinstance);
   }
   else {
      for (selectobj = areastruct.selectlist; selectobj < areastruct.selectlist +
            areastruct.selects; selectobj++)
         drawselected(selectobj);
   }

   /* fast copy of buffer */

#ifdef DOUBLEBUFFER
   areastruct.areawin = tmpwin;
   XSetFunction(dpy, areastruct.gc, GXcopy);
   XCopyArea(dpy, dbuf, areastruct.areawin, areastruct.gc, 0, 0, areastruct.width,
           areastruct.height, 0, 0);
#endif

   /* draw pending elements, if any */

   if (eventmode != NORMAL_MODE) {
      XSetFunction(dpy, areastruct.gc, GXcopy);
      if (eventmode == TEXT2_MODE) {
         labelptr newlabel = TOLABEL(EDITPART);
         UDrawString(newlabel, newlabel->color, areastruct.topinstance);
       UDrawTLine(newlabel);
      }
      else if (eventmode == TEXT3_MODE || eventmode == CATTEXT_MODE) {
       labelptr newlabel = TOLABEL(EDITPART);
       UDrawTLine(newlabel);
      }
      else if (eventmode == SELAREA_MODE) {
       UDrawBox(areastruct.origin, areastruct.save);
      }
      else {
         XSetFunction(dpy, areastruct.gc, GXxor);
         if (eventmode == BOX_MODE || eventmode == WIRE_MODE) {
            polyptr newpoly = TOPOLY(EDITPART);
            XSetXORFg(newpoly->color, BACKGROUND);
            UDrawPolygon(newpoly);
         }
         else if (eventmode == ARC_MODE) {
            arcptr newarc = TOARC(EDITPART);
            XSetXORFg(newarc->color, BACKGROUND);
            UDrawArc(newarc);
          UDrawXLine(areastruct.save, newarc->position);
         }
         else if (eventmode == SPLINE_MODE) {
            splineptr newspline = TOSPLINE(EDITPART);
            XSetXORFg(newspline->color, BACKGROUND);
            UDrawEditSpline(newspline);
         }
      }
   }

   /* flush out multiple expose/resize events from the event queue */

   while (XCheckWindowEvent(dpy, areastruct.areawin, ExposureMask, &discard) == True);

   /* end by restoring graphics state */

   XSetForeground(dpy, areastruct.gc, areastruct.gccolor);
   XSetFunction(dpy, areastruct.gc, areastruct.gctype);
}

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

Generated by  Doxygen 1.6.0   Back to index