/*
 * Copyright (c) 1992, The Geometry Center
 *                     University of Minnesota 
 *                     1300 South Second Street
 *                     Minneapolis, MN  55454
 *
 * email address: software@geom.umn.edu
 *
 * This software is copyrighted as noted above.  It may be freely copied,
 * modified, and redistributed, provided that the copyright notice is
 * preserved on all copies.
 *
 * There is no warranty or other guarantee of fitness for this software,
 * it is provided solely "as is".  Bug reports or fixes may be sent
 * to the authors, who may or may not act on them as they desire.
 *
 * You may not include this software in a program or other software product
 * without supplying the source, or without informing the end-user that the
 * source is available for no extra charge.
 *
 * If you modify this software, you should include a notice giving the
 * name of the person performing the modification, the date of modification,
 * and the reason for such modification.
 *
 *     The National Science and Technology Research Center for
 *      Computation and Visualization of Geometric Structures
 */


/* Generated by Interface Builder */
#import <soundkit/soundkit.h>
#import <objc/objc.h>
#include <stdio.h>
#include "link_edit_types.h"
#include "link_types.h"
#include "link_edit_global.h"
#include "link.h"
#import "EditView.h"
#import <dpsclient/wraps.h>
#import <dpsclient/psops.h>
#import <appkit/Application.h>
#import <appkit/NXCursor.h>
#include <math.h>
#import "Link.h"
#import "PrefObj.h"
#import <appkit/Cell.h>
#include <string.h>

@implementation EditView
  
/* Declarations of pswrap functions */
void drawIL(float x1, float y1, float x2, float y2);
void drawL (float x1, float y1, float x2, float y2, float c);
void drawR (float, float, float, float, float);
void drawIR (float, float, float, float);
void fillP(float,float,float,float,float,float,float);
void fillR(float,float,float,float,float);
void clrscrn();
float getwidth(char *);

void halveVolume()
{
  float l,r;
  [Sound getVolume:&l :&r];
  [Sound setVolume:l/2 :r/2];
}

void doubleVolume()
{
  float l,r;
  int i;
  [Sound getVolume:&l :&r];
  for (i=0; i<999; i++);
  [Sound setVolume:2*l :2*r];
}

void makeSmack()
{
  id smack = [Sound findSoundFor:"smack"];
  [smack play];
}

void makeBonk()
{
  id bonk = [Sound findSoundFor:"Bonk"];
  [bonk play];
}

void makeFit()
{
  id fit = [Sound findSoundFor:"fit"];
  [fit play];
}


/* Object Methods */

- (BOOL) acceptsFirstResponder
{
  return YES;
}

- initFrame:(const NXRect *)frameRect
{
  [super initFrame:frameRect];
  [self setFlipped:YES];
  
  /* Init var values */
  lastx = lasty = -1;
  mode = ENTER;
  isDragging = isOut = NO;
  currentPnt = currentEdge = NULL;
  selectedLnk = NULL;
  prefObj = NXGetNamedObject("PrefObjInstance", NXApp);
  return self;
}

- resetCursorRects
{
  NXPoint hotspot;
  switch(mode)
    {
    case ENTER:
    case FLIP:
      if (enterCursor == NULL)
	{
	  id image = [[NXImage alloc] initFromSection:"crosshairs"];
	  enterCursor = [[NXCursor alloc] initFromImage: image];
	  hotspot.x = 6; hotspot.y = 6;
	  [enterCursor setHotSpot:&hotspot];
	}
      [self addCursorRect:&bounds cursor:enterCursor];
      break;

    case ANCHORM:
      if (anchorCursor == NULL)
	{
	  id image = [[NXImage alloc] initFromSection:"anchor"];
	  anchorCursor = [[NXCursor alloc] initFromImage: image];
	  hotspot.x = 8; hotspot.y = 11;
	  [anchorCursor setHotSpot:&hotspot];
	}
      [self addCursorRect:&bounds cursor:anchorCursor];
      break;

    case TRANSLATE:
      if (translateCursor == NULL)
	{
	  id image = [[NXImage alloc] initFromSection:"hand"];
	  translateCursor = [[NXCursor alloc] initFromImage: image];
	  hotspot.x = 8; hotspot.y = 11;
	  [translateCursor setHotSpot:&hotspot];
	}
      [self addCursorRect:&bounds cursor:translateCursor];
      break;

    case ZOOM:
      if (zoomCursor == NULL)
	{
	  id image = [[NXImage alloc] initFromSection:"zoom"];
	  zoomCursor = [[NXCursor alloc] initFromImage: image];
	  hotspot.x = 8; hotspot.y = 11;
	  [zoomCursor setHotSpot:&hotspot];
	}
      [self addCursorRect:&bounds cursor:zoomCursor];
      break;
    }
  return self;
}

- cut:sender
{
  LinkStatus *gnrc = [linkObj get_status];
  if (currentPnt)     /* If a point is selected, delete it */
    {
      [self lockFocus];
      LinkDeletePoint(gnrc,gnrc->current_link,currentPnt);
      currentPnt = currentEdge = NULL;
      [self clearScreen];
      [self ReDrawLinkTopWindow:gnrc];
      [self unlockFocus];
    }
  else if (currentEdge)  /* If an edge is selected, delete it */
    {
      [self lockFocus];
      [self clearScreen];
      LinkCutCurrentLink((VOID *) gnrc, gnrc->current_link, currentEdge);
      gnrc->current_link = NULL;
      selectedLnk = NULL;
      currentPnt = currentEdge = NULL;
      [self ReDrawLinkTopWindow:gnrc];
      [self unlockFocus];
    }
  else if (selectedLnk)  /* If entire strand is selected, delete it */
    {
      [self lockFocus];
      gnrc->current_link = selectedLnk;
      LinkDeleteCurrentSelection((VOID *) gnrc);
      gnrc->current_link = NULL;
      selectedLnk = NULL;
      currentPnt = currentEdge = NULL;
      [self clearScreen];
      [self ReDrawLinkTopWindow:gnrc];
      [self unlockFocus];
    }
  return self;
}

/*** A few menu options implemented here ***/

- hideStrand
{
  if (selectedLnk == NULL)
    {
      LinkPrintMessage("Must select a strand first.");
      return self;
    }
  selectedLnk->visible= LINK_HIDE; 
  [self lockFocus];
  [self clearScreen];
  [self ReDrawLinkTopWindow:[linkObj get_status]];
  [self unlockFocus];
  return self;
}

- reverseArrows
{
  LinkStatus *gnrc = [linkObj get_status];
  if (selectedLnk == NULL)
    {
      LinkPrintMessage("Must select a strand first.");
      return self;
    }
  [self lockFocus];
  LinkReverseArrows(gnrc,selectedLnk);
  [self clearScreen];
  [self ReDrawLinkTopWindow:gnrc];
  [self unlockFocus];
  return self;
}

- eraseWindow
{
  [self lockFocus];
  LinkPublicClear([linkObj get_status]);
  [self unlockFocus];
  selectedLnk = NULL;
  currentPnt = currentEdge = NULL;
  return self;
}

#define TAB 9
#define DELETE 0x7f
- keyDown:(NXEvent *) event
{
  unsigned short code = event->data.key.charCode;
  if (isDragging) return self;
  switch(code)
    {
    case TAB:
      [linkObj setEditorMode:((mode+1) % 6)];
      break;
    case DELETE:
      if (mode == SELECT)
	[self cut:self];
      break;
    }
  return self;
}

- makeNoCurrentPoint
{
  LinkStatus *gnrc = [linkObj get_status];
  lastx = lasty = -1;
  currentPnt = currentEdge = NULL;
  selectedLnk = NULL;
  gnrc->current_link = NULL;
  [self lockFocus];
  [self clearScreen];
  [self ReDrawLinkTopWindow:gnrc];
  [self unlockFocus];
  return self;
}

- setModeSelect
{
  if (mode == ENTER) [self unsetModeEnter];
  mode = SELECT;
  [window invalidateCursorRectsForView:self];
  [linkObj printMessage:"Now in selection mode"];
  return self;
}

- (BOOL) isEnterMode
{
  return (mode == ENTER) ? YES : NO;
}

- setModeEnter
{
  mode = ENTER;
  isOut = NO;
  [window invalidateCursorRectsForView:self];
  [linkObj printMessage:"Now in entering mode"];
  [self makeNoCurrentPoint];
  return self;
}

- unsetModeEnter
{
  if (mode != ENTER) return self;
  if (lastx == -1) return self;
  if (!isOut)
    {
      PSnewinstance();
      PSsetinstance(NO);
      [self unlockFocus];
      [window removeFromEventMask:NX_MOUSEMOVEDMASK];
    }
  lastx = lasty = -1;
  [window discardTrackingRect:0];
  return self;
}

- setModeFlip
{
  if (mode == ENTER) [self unsetModeEnter];
  mode = FLIP;
  [window invalidateCursorRectsForView:self];
  [linkObj printMessage:"Click to flip crossings"];
  return self;
}

- setModeZoom
{
  if (mode == ENTER) [self unsetModeEnter];
  mode = ZOOM;
  [window invalidateCursorRectsForView:self];
  [linkObj printMessage:"Click and drag to zoom view"];
  return self;
}

- setModeTranslate
{
  if (mode == ENTER) [self unsetModeEnter];
  mode = TRANSLATE;
  [window invalidateCursorRectsForView:self];
  [linkObj printMessage:"Click and drag to shift view"];
  return self;
}

- setModeAnchor
{
  if (mode == ENTER) [self unsetModeEnter];
  mode = ANCHORM;
  [window invalidateCursorRectsForView:self];
  [linkObj printMessage:"Click to add anchors"];
  return self;
}

/*** Mouse entered and exited EditView ***/

- mouseEntered: (NXEvent *) event
{
  if (mode != ENTER) return self;
  isOut = NO;
  [self lockFocus];
  PSsetinstance(YES);
  [window addToEventMask:NX_MOUSEMOVEDMASK];
  return self;
}

- mouseExited: (NXEvent *) event
{
  if (mode != ENTER) return self;
  isOut = YES;
  [window removeFromEventMask:NX_MOUSEMOVEDMASK];
  PSnewinstance();
  PSsetinstance(NO);
  [self unlockFocus];
  return self;
}

/*** Mouse down ***/

- mouseDown: (NXEvent *) event
{
  switch(mode)
    {
    case SELECT:
      [self selectMD:event];
      break;
    case ENTER:
      [self enterMD:event];
      break;
    case FLIP:
      [self flipMD:event];
      break;
    case ANCHORM:
      [self anchorMD:event];
      break;
    case TRANSLATE:
      [self translateMD:event];
      break;
    case ZOOM:
      [self zoomMD:event];
      break;
    }
  return self;
}

- enterMD:(NXEvent *) event
{
  float x,y;
  NXRect boundsCopy = bounds;
  LinkStatus *gnrc = [linkObj get_status];
  LinkList *lnk;
  LinkPointList *pnt;
  [self convertPoint: &event->location fromView:nil];
  x = event->location.x; y = event->location.y;
  if (event->data.mouse.click == 1)            /* Do the following on first click */
    {
      if (lastx == -1)         /* This clicks out the first point */
	{
	  if ((lnk = LinkFindLink(gnrc,x,y)) != NULL)
	    {
	      if (!lnk->closed)
		{
		  if ((pnt = LinkFindPoint(gnrc,lnk,x,y)) != NULL)
		    {
		      /* A point on the link was selected */
		      if (pnt->previous->previous == NULL)
			{
			  [self lockFocus];
			  LinkReverseArrows(gnrc,lnk);
			  [self clearScreen];
			  [self ReDrawLinkTopWindow:gnrc];
			  [self unlockFocus];
			}
		      if (pnt->next == NULL)
			{
			  [self lockFocus];
			  [self setCurrentPoint:pnt fromLink:lnk];
			}
		      else return self;/* An internal point was selected */
		    }
		  else return self;    /* Here, a strand was selected, but no point */
		}
	      else return self;        /* Here, a closed strand was selected */
	    }
	  else                         /* Here, no strand was selected */
	    {
	      [self lockFocus];
	      LinkAddPoint(gnrc, x, y);
	    }
	  lastx = x; lasty = y;
	  PSsetinstance(YES);
	  [window addToEventMask:NX_MOUSEMOVEDMASK];
	  [self convertRect:&boundsCopy toView:nil];
	  [window setTrackingRect:&boundsCopy inside:YES owner:self
	                      tag:0 left:NO right:NO];
	  
	}
      else                     /* Intermediate point */
	{
	  int didJoin;
	  PSnewinstance();
	  PSsetinstance(NO);
	  didJoin = LinkAddPoint(gnrc, x, y);
	  if (didJoin == 1)         /* Strand closed or joined */
	    {
	      if ([prefObj get_segmentSounds]) makeSmack();
	      [self makeNoCurrentPoint];
	      [window removeFromEventMask:NX_MOUSEMOVEDMASK];
	      [window discardTrackingRect:0];
	      [self unlockFocus];
	      lastx = lasty = -1;
	    }
	  else if (didJoin == -1)   /* i.e., unsuccessful add-point */
	    {
	      makeBonk();
	      PSsetinstance(YES);
	    }
	  else                     /* Regular point added */
	    {
	      [self clearScreen];
	      [self ReDrawLinkTopWindow:[linkObj get_status]];
	      PSsetinstance(YES);
	      lastx = x; lasty = y;
	    }
	}
    }
  else if (lastx != -1 && event->data.mouse.click == 2)
    /* Do the following on the second click */
    {
      PSnewinstance();
      PSsetinstance(NO);
      [self unlockFocus];
      if ([prefObj get_segmentSounds]) makeFit();
      [self makeNoCurrentPoint];
      [window removeFromEventMask:NX_MOUSEMOVEDMASK];
      [window discardTrackingRect:0];
      lastx = lasty = -1;
    }
  return self;
}

- selectMD:(NXEvent *) event
{
  float x,y;
  LinkList *lnk;
  LinkPointList *pnt;
  LinkStatus *gnrc = [linkObj get_status];
  [self lockFocus];
  [window removeFromEventMask:NX_MOUSEDRAGGEDMASK];
  [self convertPoint: &event->location fromView:nil];
  x = event->location.x; y = event->location.y;
  if ((lnk = LinkFindLink(gnrc,x,y)) == NULL)
    /* Nothing is selected */
    {
      [self setCurrentPoint:NULL fromLink:NULL];
      selectedLnk = NULL;
      [self ReDrawLinkTopWindow:gnrc];
      [self unlockFocus];
      return self;
    }
  if (event->data.mouse.click == 1 && selectedLnk != lnk)
    /* Do the following on first click */
    {
      if ((pnt = LinkFindPoint(gnrc,lnk,x,y)) == NULL)
	/* No point is selected, so select the edge */
	{
	  if ((pnt = LinkFindEdge(gnrc,lnk,x,y)) == NULL)
	    /* Nothing is selected.  You know, this should never happen */
	    {
	      [self setCurrentPoint:NULL fromLink:NULL];
	      selectedLnk = NULL;
	      [self ReDrawLinkTopWindow:gnrc];
	      [self unlockFocus];
	      return self;
	    }
	  else
	    /* Here, an edge was selected */
	    {
	      [self setCurrentPoint:NULL fromLink:NULL];
	      selectedLnk = NULL;
	      currentEdge = pnt;
	      gnrc->current_link = lnk;
	      [self ReDrawLinkTopWindow:gnrc];
	      [self unlockFocus];
	      return self;
	    }
	}
      else
	/* Ok, go and select the point */
	{
	  selectedLnk = NULL;
	  currentEdge = NULL;
	  isDragging = YES;
	  [self setCurrentPoint:pnt fromLink:lnk];
	  [window addToEventMask:NX_MOUSEDRAGGEDMASK];
	  [self clearScreen];
	  [self ReDrawLinkTopWindow:gnrc];
	  PSsetinstance(YES);
	  return self;
	}
    }
  if (event->data.mouse.click < 3)  /* Do this if 2nd click or if link
				       already selected */
    /* Select the link */
    {
      if (currentPnt)            /* If a point is selected... */
	{                        /* Undo dragging stuff */
	  PSnewinstance();
	  PSsetinstance(NO);
	}
      selectedLnk = lnk;
      currentPnt = currentEdge = NULL;
      isDragging = YES;
      origx = x; origy = y;
      [window addToEventMask:NX_MOUSEDRAGGEDMASK];
      [self clearScreen];
      [self ReDrawLinkTopWindow:gnrc];
      PSsetinstance(YES);
      return self;
    }
}

- flipMD:(NXEvent *) event
{
  float x,y;
  LinkCrossingList *crssng;
  LinkStatus *gnrc = [linkObj get_status];
  [self convertPoint:&event->location fromView:nil];
  x = event->location.x; y = event->location.y;
  if((crssng = LinkFindCrossing(gnrc,x,y)) == NULL) {
    LinkPrintMessage("No crossing chosen. Try again.");
    return self;
  }

  if(crssng->z > 0.0) {
    crssng->z = -1.0; crssng->crossing->z = 1.0;
  }
  else {
    crssng->z = 1.0; crssng->crossing->z = -1.0;
  }
  [self lockFocus];
  [self clearScreen];
  [self ReDrawLinkTopWindow:gnrc];
  [self unlockFocus];
  LinkPrintMessage("You may flip another crossing.");
  return self;
}

- anchorMD:(NXEvent *) event
{
  LinkList *lnk;
  LinkPointList *pnt;
  float x,y;
  LinkStatus *gnrc = [linkObj get_status];
  [self convertPoint:&event->location fromView:nil];
  x = event->location.x; y = event->location.y;
  if((lnk = LinkFindLink(gnrc,x,y)) == NULL) {
    LinkPrintMessage("No point or edge chosen. Try again.");
    return self;
  }
  pnt = LinkFindPoint(gnrc,lnk,x,y);
  [self lockFocus];
  LinkAddAnchor(gnrc, lnk, pnt, x, y);
  [self unlockFocus];
  return self;
}

- translateMD:(NXEvent *) event
{
  LinkStatus *gnrc = [linkObj get_status];
  [self lockFocus];
  [window removeFromEventMask:NX_MOUSEDRAGGEDMASK];
  [self convertPoint: &event->location fromView:nil];
  origx = event->location.x; origy = event->location.y;
  isDragging = YES;
  [window addToEventMask:NX_MOUSEDRAGGEDMASK];
  [self clearScreen];
  [self ReDrawLinkTopWindow:gnrc];
  PSsetinstance(YES);
  return self;
}

- zoomMD:(NXEvent *) event
{
  float x0, y0, width, height;
  LinkStatus *gnrc = [linkObj get_status];
  [self convertPoint:&event->location fromView:nil];
  origx = event->location.x; origy = event->location.y;
  width = ((double) (gnrc->width) * LINK_FEEDBACK_XFRACTION);
  height = ((double) (gnrc->height) * LINK_FEEDBACK_YFRACTION);
  x0 = gnrc->width/2 - width/2;
  y0 = gnrc->height/2 - height/2;
  [self lockFocus];
  isDragging = YES;
  [window addToEventMask:NX_MOUSEDRAGGEDMASK];
  [self clearScreen];
  [self ReDrawLinkTopWindow:gnrc];
  [self drawRectangle :x0 :y0 :width :height :0];     /* Draw black rectangle */
  [window flushWindow];
  PSsetinstance(YES);
  return self;
}


/*** Mouse moved & dragged ***/

- mouseMoved: (NXEvent *) event
{
  NXPoint loc = event->location;
  if (lastx == -1 || isOut) return self;
  [self convertPoint:&loc fromView:nil];
  PSnewinstance();
  [self drawLine:lastx :lasty :loc.x :loc.y :1];
  LinkPause(50);
  return self;
}

- mouseDragged: (NXEvent *) event
{
  switch(mode)
    {
    case SELECT:
      [self selectMDr:event];
      break;
    case TRANSLATE:
      [self translateMDr:event];
      break;
    case ZOOM:
      [self zoomMDr:event];
      break;
    }
  return self;
}

- selectMDr:(NXEvent *) event
{
  float x1,y1;
  LinkPointList *pnt = currentPnt, *prev_pnt;
  LinkList *lnk;
  LinkStatus *gnrc = [linkObj get_status];
  if (mode != SELECT) return self;
  [self convertPoint:&event->location fromView:nil];
  x1 = event->location.x; y1 = event->location.y;
  if (pnt)
    /* Here, we're moving a point */
    {
      lnk = gnrc->current_link;
      /* Draw feedback line */
      PSnewinstance();
      if(pnt->previous != &(lnk->point)) {
	[self drawLine:pnt->previous->dcx :pnt->previous->dcy :x1 :y1 :0];
      }
      else if(lnk->closed) {
	prev_pnt = &(lnk->point);
	while(prev_pnt->next != NULL) prev_pnt = prev_pnt->next;
	[self drawLine:prev_pnt->dcx :prev_pnt->dcy :x1 :y1 :0];
      }
      
      if(pnt->next != NULL) {
	[self drawLine:pnt->next->dcx :pnt->next->dcy :x1 :y1 :0];
      }
      else if(lnk->closed) {
	[self drawLine :lnk->point.next->dcx
	 :lnk->point.next->dcy :x1 :y1 :0];
      }
      if(pnt->previous != &(lnk->point)) {
	[self drawLine :pnt->previous->dcx :pnt->previous->dcy :x1 :y1 :0];
      }
      else if(lnk->closed) {
	prev_pnt = &(lnk->point);
	while(prev_pnt->next != NULL) prev_pnt = prev_pnt->next;
	[self drawLine :prev_pnt->dcx :prev_pnt->dcy :x1 :y1 :0];
      }
      
      if(pnt->next != NULL) {
	[self drawLine :pnt->next->dcx :pnt->next->dcy :x1 :y1 :0];
      }
      else if(lnk->closed) {
	[self drawLine :lnk->point.next->dcx :lnk->point.next->dcy :x1 :y1 :0];
      }
      LinkPause(5);
    }
  /* Here we're moving a strand */
  else
    {
      lnk = selectedLnk;
      PSnewinstance();
      if((pnt = lnk->point.next) == NULL) return self;
      while(pnt->next != NULL) {
	[self drawLine :pnt->dcx + x1-origx :pnt->dcy + y1-origy
	 :pnt->next->dcx + x1-origx :pnt->next->dcy + y1-origy :0];
	pnt = pnt->next;
      }
      if(lnk->closed) {
	[self drawLine :pnt->dcx + x1-origx :pnt->dcy + y1-origy
	 :lnk->point.next->dcx + x1-origx :lnk->point.next->dcy + y1-origy :0];
      }
      while(pnt->next != NULL) {
	[self drawLine :pnt->dcx + x1-origx :pnt->dcy + y1-origy
	 :pnt->next->dcx + x1-origx :pnt->next->dcy + y1-origy :0];
	pnt = pnt->next;
      }
      if(lnk->closed) {
	[self drawLine :pnt->dcx + x1-origx :pnt->dcy + y1-origy
	 :lnk->point.next->dcx + x1-origx :lnk->point.next->dcy + y1-origy :0];
      }
    }
  return self;
}

- translateMDr:(NXEvent *) event
{
  float x1, y1;
  [self convertPoint: &event->location fromView:nil];
  x1 = event->location.x; y1 = event->location.y;
  /* Draw feedback line */
  PSnewinstance();
  [self drawLine :origx :origy :x1 :y1 :0];
  LinkPause(50);
  return self;
}

double getRect(LinkStatus *gnrc, float x1, float y1, float origx, float origy,
	       float *xsize, float *ysize, float *xcoord, float *ycoord)
{
  float width = ((double) (gnrc->width) * LINK_FEEDBACK_XFRACTION);
  float height = ((double) (gnrc->height) * LINK_FEEDBACK_YFRACTION);
  float x0 = gnrc->width/2 - width/2;
  double scale;
  *xcoord = x0 - x1 + origx;
  *xsize = width + 2*(x1-origx);
  if(*xsize < 1) *xsize = 1;
  scale =  (double) *xsize/(double) width;
  *ysize =  (0.5 + scale * (double) height);
  *ycoord = gnrc->height/2 - *ysize/2;
  if(*ysize < 1) *ysize = 1;
  return scale;
}

- zoomMDr:(NXEvent *) event
{
  float xsize, xcoord, ysize, ycoord, x1, y1;
  [self convertPoint: &event->location fromView:nil];
  x1 = event->location.x; y1 = event->location.y;
  getRect([linkObj get_status], x1, y1, origx, origy,
	  &xsize, &ysize, &xcoord, &ycoord);
  PSnewinstance();
  [self drawRectangle :xcoord :ycoord :xsize :ysize :0];
  LinkPause(50);
  return self;
}

/*** Mouse up ***/

- mouseUp: (NXEvent *) event
{
  switch(mode)
    {
    case SELECT:
      [self selectMU:event];
      break;
    case TRANSLATE:
      [self translateMU:event];
      break;
    case ZOOM:
      [self zoomMU:event];
      break;
    }
  return self;
}

- selectMU:(NXEvent *) event
{
  float x1,y1, delta_x, delta_y;
  LinkPointList *pnt = currentPnt, *prev_pnt;
  LinkList *lnk;
  LinkStatus *gnrc = [linkObj get_status];
  if (!isDragging) return self;
  isDragging = NO;
  [window removeFromEventMask:NX_MOUSEDRAGGEDMASK];
  PSnewinstance();
  PSsetinstance(NO);     /* Turn off instance drawing */
  [self convertPoint:&event->location fromView:nil];
  x1 = event->location.x; y1 = event->location.y;
  if (pnt)
    /* We've just finished dragging a point */
    {
      lnk = gnrc->current_link;
      pnt->dcx = x1; pnt->dcy = y1;
      LinkComputePointWorldCoords(gnrc,pnt);
      LinkReComputeEdgeCrossings(gnrc,lnk,pnt);
      if(pnt->previous != &(lnk->point))
	LinkReComputeEdgeCrossings(gnrc,lnk,pnt->previous);
      else
	if(lnk->closed) {  /* Recompute edge from last point */
	  prev_pnt = &(lnk->point);
	  while(prev_pnt->next != NULL) prev_pnt = prev_pnt->next;
	  LinkReComputeEdgeCrossings(gnrc,lnk,prev_pnt);
	}
      [self clearScreen];
      [self ReDrawLinkTopWindow:gnrc];
      [self unlockFocus];
    }
  else
    /* We've just finished dragging a strand */
    {
      delta_x = x1 - origx; delta_y = y1 - origy;
      lnk = selectedLnk;
      LinkDCTranslateLink(gnrc,lnk,delta_x,delta_y);
      [self clearScreen];
      [self ReDrawLinkTopWindow:gnrc];
      [self unlockFocus];
    }
  return self;
}

- translateMU:(NXEvent *) event
{
  float delta_x, delta_y;
  LinkStatus *gnrc = [linkObj get_status];
  if (!isDragging) return self;
  isDragging = NO;
  [window removeFromEventMask:NX_MOUSEDRAGGEDMASK];
  PSnewinstance();
  PSsetinstance(NO);     /* Turn off instance drawing */
  [self convertPoint: &event->location fromView:nil];
  delta_x = event->location.x - origx; delta_y = event->location.y - origy;
  gnrc->origin.dcx += delta_x; 
  gnrc->origin.dcy += delta_y;
  LinkComputeDeviceCoords(gnrc);
  [self clearScreen];
  [self ReDrawLinkTopWindow:gnrc];
  [self unlockFocus];
  LinkPrintMessage("Click and drag to shift view");
  return self;
}

- zoomMU:(NXEvent *) event
{
  LinkStatus *gnrc = [linkObj get_status];
  float xsize, ysize, xcoord, ycoord;
  double scale;
  if (!isDragging) return self;
  isDragging = NO;
  [window removeFromEventMask:NX_MOUSEDRAGGEDMASK];
  PSnewinstance();
  PSsetinstance(NO);      /* Turn off instance drawing */
  [self convertPoint:&event->location fromView:nil];
  scale = getRect(gnrc, event->location.x, event->location.y,
		  origx, origy, &xsize, &ysize, &xcoord, &ycoord);
  gnrc->xscale /= scale; 
  gnrc->yscale /= scale; 
  LinkComputeDeviceCoords(gnrc);
  [self clearScreen];
  [self ReDrawLinkTopWindow:gnrc];
  [self unlockFocus];
  LinkPrintMessage("You may zoom view again.");
  return self;
}


- selectMDold:(NXEvent *) event
{
  LinkStatus *gnrc;  /* To make it compatible with the old
			LinkButtonPress */
  /* What follows was taken from the old LinkButtonPress */

  float x,y;


  gnrc = [linkObj get_status];
  [self convertPoint: &event->location fromView:nil];
  x = event->location.x; y = event->location.y;
  
  return self;
}

- ReDrawLinkTopWindow :(LinkStatus *)gnrc
{
  LinkList *lnk;
  LinkPointList sample;
  float width,dcx,dcy;
  char strng[20];
  double w, h;

  if(gnrc->axes == LINK_SHOW)
    {
      dcx = gnrc->origin.dcx;
      dcy = gnrc->origin.dcy;
      [self drawLine :dcx :(float) 0 :dcx :gnrc->height :(float) .33];
      [self drawLine :0 :dcy :gnrc->width :dcy :.33];
    }

  if(gnrc->xruler == LINK_SHOW) {
     [self getBounds :&w :&h];
     dcx = sample.dcx = gnrc->hash_space/2; 
     sample.dcy = 0;
     /*****WHAT IS GOING ON HERE?!?!*******/
/*     if(gnrc->MessageWindow == (Window) 0) dcy = gnrc->height;*/
     /*else*/ dcy = gnrc->height - gnrc->fth - 2* LINK_PAD;

     while(dcx < gnrc->width) {
        LinkComputePointWorldCoords(gnrc,&sample); 
        sprintf(strng,"%+.3g",sample.x);
	/*SOME TEXT DRAWING**********/
        width = getwidth(strng);
	/*	  XTextWidth(gnrc->data_fontstruct,strng,strlen(strng));*/
	[self printText:dcx-width/2 :h-(dcy-gnrc->hash_height-LINK_PAD-6) :strng];
	[self drawLine :dcx :h-dcy :dcx :h-(dcy-gnrc->hash_height) :0];
        dcx += gnrc->hash_space/2;

	[self drawLine :dcx :h-dcy :dcx :h-(dcy-gnrc->hash_height/2) :0];
        dcx += gnrc->hash_space/2;
        sample.dcx = dcx;
      }
   }

  /******HE'S DOING IT AGAIN*********/
  if(gnrc->yruler == LINK_SHOW)
    {
      /*     if(gnrc->MessageWindow == (Window) 0) 
	     dcy = sample.dcy = gnrc->height-gnrc->hash_height;*/
      /*else*/
      dcy = sample.dcy = 
	gnrc->height-gnrc->hash_space/2-gnrc->fth-2*LINK_PAD;
      sample.dcx = 0;
      dcx = 0; 
      
      while(dcy > 0) {
        LinkComputePointWorldCoords(gnrc,&sample);
        sprintf(strng,"%+.3g",sample.y);
	/*******TEXT DRAWING*******/
	[self printText :dcx+13 :(dcy-LINK_PAD) :strng];
	
	[self drawLine :dcx :dcy :dcx+gnrc->hash_height :dcy :0];
        dcy -= gnrc->hash_space/2;

	[self drawLine :dcx :dcy :dcx+gnrc->hash_height/2 :dcy :0];
                          
        dcy -= gnrc->hash_space/2;
        sample.dcy = dcy;
      }
    }
  lnk = gnrc->link.next;
  while(lnk != NULL) {
    [self linkDrawLink :gnrc :lnk];
    lnk = lnk->next;
  }
  [window flushWindow];
  return self;
}


- linkDrawLink :(LinkStatus *)gnrc :(LinkList *)lnk

{
  LinkPointList *pnt;
  float dcx,dcy;
  if(lnk->visible == LINK_HIDE) return self;
  if(gnrc->vertices == LINK_SHOW)
    {  /* Display vertices */
      pnt = lnk->point.next;
      while(pnt != NULL) {
	if (pnt->isVertex)
	  {
	    float col = (pnt == currentPnt || lnk == selectedLnk) ? 1 : 0;
	    dcx = pnt->dcx - LINK_POINT_X_SIZE/2;
	    dcy = pnt->dcy - LINK_POINT_Y_SIZE/2;
	    [self fillRectangle :dcx :dcy :LINK_POINT_X_SIZE :LINK_POINT_Y_SIZE :col];
	  }
	pnt = pnt->next;
      }
    }
  
  if(gnrc->anchors == LINK_SHOW) {  /* Display anchors */
    pnt = lnk->point.next;
    while(pnt != NULL) {
      if (pnt->isAnchor)
	{
	  float col = (pnt == currentPnt || lnk == selectedLnk) ? 1 : 0;
	  dcx = pnt->dcx - LINK_POINT_ANCHOR_SIZE/2;
	  dcy = pnt->dcy - LINK_POINT_ANCHOR_SIZE/2;
	  [self drawAnchor :dcx :dcy :LINK_POINT_ANCHOR_SIZE :col];
	}
      pnt = pnt->next;
    }
  }
  
  pnt = lnk->point.next;
  while(pnt != NULL) {
    [self linkDrawEdge :gnrc :lnk :pnt];
    pnt = pnt->next;
  }
  return self;
}


- linkDrawEdge :(LinkStatus *)gnrc :(LinkList *)lnk :(LinkPointList *)pnt
{
  LinkPointList *nxt;
  LinkCrossingList *crossing;
  float x0,y0,x1,y1;
  float color;
  
  if(pnt == NULL) return(0);
  if(pnt->next == NULL && lnk->closed == LINK_NO) return(0);
  
  if((nxt = pnt->next) == NULL) nxt =lnk->point.next;
  
  if(lnk == selectedLnk || pnt == currentEdge)
    {
      color = 1;
    }
  else
    {
      color = 0;
    }
  
  if(gnrc->arrows && pnt->isVertex == 1) { /* Draw arrow */
    x0 = pnt->dcx; y0 = pnt->dcy;
    x1 = nxt->dcx; y1 = nxt->dcy;
    [self linkDrawArrow :gnrc :color :(9*x0+x1)/10 :(9*y0+y1)/10 :x1-x0 :y1-y0];
  }
  
  if((crossing = pnt->crossing.next) == NULL)
    {
      [self drawLine :pnt->dcx :pnt->dcy :nxt->dcx :nxt->dcy :color];
    }
  else
    {
      float dcx,dcy,delta_x,delta_y;
      double param,gamma,tmp_x,tmp_y;
      
      /* Compute crossing gap vector */
      tmp_x = (double) (nxt->dcx - pnt->dcx)/xppmm;
      tmp_y = (double) (nxt->dcy - pnt->dcy)/yppmm;
      /* gamma = half fraction of total length devoted to each gap */
      gamma = 0.5*LINK_GAP/sqrt(tmp_x*tmp_x + tmp_y*tmp_y);
      delta_x = (int) (gamma * (double) (nxt->dcx - pnt->dcx));
      delta_y = (int) (gamma * (double) (nxt->dcy - pnt->dcy));
      
      dcx = pnt->dcx; dcy = pnt->dcy;
      param = 0;
      while(crossing != NULL) {
        if(crossing->z < 0) {
	  if(param < crossing->param - gamma)
	    {
	      [self drawLine :dcx :dcy :crossing->dcx - delta_x
	       :crossing->dcy - delta_y :color];
	    }
	  if((param = crossing->param + gamma) > 1.0) {
	    param = 1.0;
	    dcx = nxt->dcx; dcy = nxt->dcy;
	  }
	  else {
	    dcx = crossing->dcx + delta_x;dcy = crossing->dcy + delta_y;
	  }
	}
        crossing = crossing->next;
      }
      
      /* Draw final part */
      if(param < 1.0) {
	[self drawLine :dcx :dcy :nxt->dcx :nxt->dcy :color];
      }
    }
  return self;
}

- linkDrawPoint :(LinkStatus *)gnrc :(LinkList *)lnk :(LinkPointList *)pnt
{
  float dcx,dcy;
  float color;

  if(gnrc->vertices != LINK_SHOW) return self;
 
  if(lnk == selectedLnk || pnt == currentPnt)
    color = 1;
  else color = 0;
  dcx = pnt->dcx - LINK_POINT_X_SIZE/2;
  dcy = pnt->dcy - LINK_POINT_Y_SIZE/2;
  [self fillRectangle :dcx :dcy :LINK_POINT_X_SIZE :LINK_POINT_Y_SIZE :color];
  return self;
}

- linkDrawArrow :(LinkStatus *)gnrc :(float) color :(int) x :(int)y :(int)dx :(int)dy
  /* Draw arrow at x,y in direction dx,dy */
{
  float x1,y1,x2,y2,x3,y3;    /* The endpoints */
  double norm,cs,sn;

  /* Compute the endpoints */

  norm = sqrt((double) (dx*dx + dy*dy));

  if(norm < 0.5) {
     return(0);
    }

  cs = ((double) dx)/norm;
  sn = ((double) dy)/norm;

  x1 = (short) x + (short) (LINK_ARROW_SIZE * cs *xppmm);
  y1 = (short) y + (short) (LINK_ARROW_SIZE * sn *yppmm);;

  x2 = (short) x + (short) (0.5*LINK_ARROW_SIZE*(-sn)*xppmm);
  y2 = (short) y + (short) (0.5*LINK_ARROW_SIZE*(cs)*yppmm);

  x3 = (short) x + (short) (0.5*LINK_ARROW_SIZE*(sn)*xppmm);
  y3 = (short) y + (short) (0.5*LINK_ARROW_SIZE*(-cs)*yppmm); 

  [self fillPolygon :x1 :y1 :x2 :y2 :x3 :y3 :color];
  return self;
}

- clearScreen
{
  clrscrn();
  return self;
}

- drawInstanceLine :(float) x1 :(float) y1 :(float) x2 :(float) y2
{
  drawIL(x1,y1,x2,y2);
  return self;
}

- drawLine :(float) x1 :(float) y1 :(float) x2 :(float) y2 :(float) c
{
  drawL(x1,y1,x2,y2,c);
  return self;
}

- drawInstanceRec :(float) x :(float) y :(float) w :(float) h
{
  drawIR(x,y,w,h);
  return self;
}

- drawRectangle :(float) x :(float) y :(float) w :(float) h :(float) c
{
  drawR(x,y,w,h,c);
  return self;
}

- fillPolygon :(float) x1 :(float) y1 :(float) x2 :(float) y2
  :(float) x3 :(float) y3 :(float) color
{
  fillP(x1,y1,x2,y2,x3,y3,color);
  return self;
}

- fillRectangle :(float) x :(float) y :(float) w :(float) h :(float) c
{
  fillR(x,y,w,h,c);
  return self;
}

- drawAnchor :(float) x :(float) y :(float) size :(float) c
{
  drawL(x, y, x+size, y+size, c);
  drawL(x+size, y, x, y+size, c);
  return self;
}

- setCurrentPoint : (LinkPointList *) pnt fromLink:(LinkList *) lnk
{
  LinkStatus *gnrc = [linkObj get_status];
  currentPnt = pnt;
  gnrc->current_link = lnk;
  return self;
}

- getBounds :(double *) w :(double *) h
{
  *w = (double) bounds.size.width;
  *h = (double) bounds.size.height;
  return self;
}

- drawSelf: (NXRect *)dRects:(int)dCount
{
  float *width, *height;
  LinkStatus *link_status;
  LinkData *link_data;
  link_data = [linkObj get_data];
  width = (float *) &link_data->width;
  height = (float *) &link_data->height;
  link_status = [linkObj get_status];
  
  globalEditView = self;     /* Make global for all to see */
  [self getBounds:(double *) width :(double *) height];
  link_status->width = link_data->width;
  link_status->height = link_data->height;
  [self clearScreen];
  [self ReDrawLinkTopWindow:link_status];
  return self;
}
void mysetfont();
- printText: (float) x :(float) y :(char *) s
{
  PSsetgray(0);
  PSfindfont("Times-Roman");
  PSscalefont(10);
  mysetfont();
  PSmoveto(x,y);
  PSrmoveto(0,5);
  PSgsave();
  PSscale(1,-1);
/*  [self setFlipped:NO];*/
  PSshow(s);
  PSgrestore();
/*  [self setFlipped:YES];*/
  return self;
}

- flushItsWindow
{
  [window flushWindow];
  return self;
}

/* Sets the instance variable that points to the link object */

- setLink:(id) linkO
{
  linkObj = linkO;
  return self;
}

/**** Braid routines ****/

LinkList *newLink(LinkStatus *gnrc)
{
  LinkList *lnk = (LinkList *) NXZoneMalloc([globalEditView zone], sizeof(LinkList));
  lnk->next = NULL;
  lnk->point.next = NULL;
  lnk->point.previous = NULL;
  lnk->visible = LINK_YES;
  lnk->closed = LINK_NO;
  lnk->num = 0;
  lnk->visited = 0;
  gnrc->num++;
  return lnk;
}

LinkPointList *newPoint(LinkPointList *prev)
{
  LinkPointList *pnt = (LinkPointList *) NXZoneMalloc([globalEditView zone],
						      sizeof(LinkPointList));
  pnt->z = -1.0;
  pnt->previous = prev;
  prev->next = pnt;
  pnt->next = NULL;
  pnt->isVertex = 1;
  pnt->isAnchor = 0;
  pnt->crossing.next = NULL;
  return pnt;
}

/* Struct used solely to temporarily store braid info */
typedef struct my_braid
{
  int n;
  int isInverse;
  struct my_braid *next;
} Braid;

/* Accepts name like 1 4 2 5i 3 */
- processBraid: (const char *) name :(int) isClosed
{
  int nstrands = 0;
  char *beginning = (char *) malloc (strlen(name) + 3);
  char *nameptr1 = beginning;
  Braid *braid, *bprev = NULL, *first = NULL;
  LinkList **strands, *lnk;
  LinkPointList *pnt, **pnts;
  LinkStatus *gnrc = [linkObj get_status];
  int done = 0, i, y;
  char *nameptr2 = nameptr1;
  int errval = [linkObj checkBraid:name];

  if (errval) return (id) (errval-1);
  strcpy (nameptr1, name);
  
  [self eraseWindow];
  globalEditView = self;

  /* Parse name, put into Braid structs; remember # strands needed */
  while (!done)
    {
      while (*nameptr2 >= '0' && *nameptr2 <= '9') nameptr2++; /* Read number */
      braid = (Braid *) malloc (sizeof(Braid));     /* Allocate braid */
      if (bprev) bprev->next = braid;
      braid->next = NULL;
      if (first == NULL) first = braid;
      if (*nameptr2 == '\0') done = 1;               /* Last entry? */
      braid->isInverse = (*nameptr2 == 'i');        /* Set fields  --inverse */
      *nameptr2 = '\0';
      braid->n = atoi(nameptr1)-1;                  /*             --number */
      if (braid->n + 1 >= nstrands) nstrands = braid->n + 2;   /* Update nstrands */
      nameptr1 = ++nameptr2;                        /* Go to next number */
      while ((*nameptr1 < '0' || *nameptr1 > '9')
	     && (*nameptr1 != '\0'))
	nameptr1++;

      nameptr2 = nameptr1;
      if (nameptr1 == '\0') done = 1;
      bprev = braid;
    }
  
  strands = (LinkList **) malloc (sizeof(LinkList *) * nstrands);
  pnts = (LinkPointList **) malloc (sizeof(LinkPointList *) * nstrands);
  /* Initialize strands */
  for (i=0; i<nstrands; i++)
    {
      strands[i] = newLink(gnrc);
      pnt = newPoint(&strands[i]->point);
      pnt->x = i; pnt->y = 1;
      pnt = newPoint(pnt);
      pnt->x = i; pnt->y = 0;
      strands[i]->num += 2;
      pnts[i] = pnt;
    }

  /* Link strands */
  gnrc->link.next = strands[0];
  for (i=0; i<nstrands-1; i++)
    strands[i]->next = strands[i+1];
  /* Create link_edit structure for the braid */
  for (y=-1, braid = first; braid != NULL; braid = braid->next, y--)
    {
      /* Draw each strand down a level */
      for (i=0; i<nstrands; i++)
	{
	  if (i == braid->n)        /* If this one crosses over... */
	    {
	      /* Take care of i and i+1 here */
	      /* First, draw segments */
	      pnt = newPoint(pnts[i]);
	      pnt->x = i+1; pnt->y = y;
	      strands[i]->num++;
	      pnt = newPoint(pnts[i+1]);
	      pnt->x = i; pnt->y = y;
	      strands[i+1]->num++;

	      /* Update crossings */
	      LinkComputeEdgeCrossings(gnrc, strands[i], pnts[i]);
	      pnts[i]->crossing.next->z = (braid->isInverse) ? 1 : -1;
	      pnts[i+1]->crossing.next->z = (braid->isInverse) ? -1 : 1;

	      /* Update pnts[] and exchange strands[] */
	      pnts[i] = pnts[i]->next;
	      pnts[i+1] = pnts[i+1]->next;
	      lnk = strands[i];
	      strands[i] = strands[i+1];
	      strands[i+1] = lnk;
	      pnt = pnts[i];
	      pnts[i] = pnts[i+1];
	      pnts[i+1] = pnt;
	      i++;
	    }
	  else                      /* Otherwise, just draw it down */
	    {
	      pnt = newPoint(pnts[i]);
	      pnt->x = i; pnt->y = y;
	      strands[i]->num++;
	      pnts[i] = pnt;
	    }
	}
    }

  /* Close up the braid if requested */
  if (isClosed)
    {
      int x;
      [self lockFocus];
      LinkComputeDeviceCoords(gnrc);
      LinkCenterView(gnrc);
      LinkCenterView(gnrc);
      LinkCenterView(gnrc);
      LinkCenterView(gnrc);
      LinkCenterView(gnrc);
      for (x=pnts[nstrands-1]->x + 1, y=1, i=nstrands-1; i>=0; i--, x++, y++)
	{
	  pnt = newPoint(pnts[i]);
	  pnt->x = i; pnt->y = pnts[i]->y - y;
	  pnt = newPoint(pnt);
	  pnt->x = x; pnt->y = pnts[i]->y - y;
	  pnt = newPoint(pnt);
	  pnt->x = x; pnt->y = 1 + y;
	  pnt = newPoint(pnt);
	  pnt->x = i; pnt->y = 1 + y;
	  LinkComputePointDeviceCoords(gnrc, pnt);
	  gnrc->current_link = LinkFindLink(gnrc, pnt->dcx, pnt->dcy);
	  gnrc->current_link->num += 4;
	  LinkAddPointWorldCoords(gnrc, (float) i, (float) 1, NO, YES);
	}
      gnrc->current_link = NULL;
      [self unlockFocus];
      cleanLink(gnrc);
    }

  
  /* Draw braid to view */
  LinkComputeDeviceCoords(gnrc);
  for (i=0; i<5; i++)
    LinkCenterView(gnrc);
  [self lockFocus];
  [self clearScreen];
  [self ReDrawLinkTopWindow:gnrc];
  [self unlockFocus];
  
  /* Get rid of all the temporary structures we've made */
  braid = first;
  while (braid != NULL)
    {
      first = braid->next;
      free(braid);
      braid = first;
    }
  free (pnts);
  free (strands);
  free (beginning);
  return self;
}

- (int) get_editMode
{
  return mode;
}

/*- lockFocus
{
  printf("LOCK focus \n");
  return [super lockFocus];
}
- unlockFocus
{
  printf ("UNLOCK focus \n");
  return [super unlockFocus];
} */


@end
