/*
 * kali.c - Nina Amenta, Aug. 1989
 * rewritten by Ed Chi, summer 1994
 * 
 * $Id: kali.c,v 1.20 1996/10/07 16:55:24 slevy Exp $
 * $Log: kali.c,v $
 * Revision 1.20  1996/10/07  16:55:24  slevy
 * Back-port to XForms 0.75 -- the only version available for Irix 4.
 *
 * Revision 1.19  1996/10/07  15:37:33  slevy
 * Remove SGI & VOGL dregs -- we're now pure X, using only XForms.
 * Consume outstanding events in idle callback -- it seems to get called
 * even when we're not really idle yet.
 *
 * Revision 1.18  1996/10/04  21:36:23  slevy
 * Finish porting to vogl-less mode.
 * Rotate correctly now that X has mirrored our coordinates.
 * Redraw only as an idle callback.  (This doesn't seem to help responsiveness,
 * though.)
 * Toss KALIVOGL code.
 *
 * Revision 1.16  1996/09/28  15:04:46  slevy
 * Allow interaction with current line at all times; always show
 * which line is current by coloring.
 * Allow loading a file just by mentioning it on command line
 * (but still accept -k).
 * Reimplement move-closest-endpoint feature.
 * Move getsize() etc. here -- flqueue.c no longer needed.
 *
 * Revision 1.15  1996/06/11 06:18:05  slevy
 * Major changes.  Make compatible with released XForms library.
 * Implement an object-style I/O structure, with "GL" (actually VOGL),
 * Postscript, and Pick devices.  Implement picking in software,
 * not requiring help from GL.
 * Add keyboard shortcuts for mode selection: (z)oom, (d)raw, (m)ove,
 * (x)delete, (r)otate, (a)ngle, (R)atio.  Also, backspace and delete keys
 * delete any selected line.
 *
 * Revision 1.14  1994/12/22  18:46:29  munzner
 * make sure to set the proper window at startup.
 *
 * Revision 1.13  1994/12/15  06:18:46  munzner
 * add options to load file, specify window placement on command line.
 * on SGI, use RGBmode not colormap mode to avoid flashing.
 *
 * Revision 1.12  1994/12/14  23:57:30  munzner
 * Ed Chi's changes: port to X11.
 *
 * Revision 1.2  1994/08/15  20:05:38  chi
 * significant code cleanup.
 * header files created.
 * main.h created
 * changed #defines in main.h from DRAW to KALIDRAW, CUT to .... etc
 */

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


#define SET_COLOR  	cpack
#define BACKCOLOR	0xFFFFFF	/* 255,255,255 -- white */
#define	POINTCOLOR	0x0		/* 0,0,0 -- black */
#define LINECOLOR	0xFF0000	/* 0,0,255 -- blue */
#define MOVECOLOR	0x8000FF	/* 255,0,255 -- magenta */
#define	DELETECOLOR	0x00FF00	/* 0,255,0 -- ugly green */
#define GRIDCOLOR	0x3F6DFF	/* 255,109,63 -- yellow-green */

#include "forms.h"

#include "main.h"
#include "io.h"
#include "symmetry.h"
#include "kali.h"

#ifndef TRUE
#define TRUE  1
#endif

#define NAMESTACKSIZE 50

#define STREQ(s1,s2) (strcmp(s1,s2)==0)

/*------------------------------------------------ variable decl */
float zoom;  /* used in callback.c */
RECTANGLE win_rect,sym_rect;  /* used in callback.c */
RECTANGLE kwin, pwin; /* size & position for kali, panel windows */
int count;
MATRIX xforms[5];
int sym_index;
int pick_for;
void (*xformfnc) ();
int lit;             /* a line is colored for delete or move */
WINDOW win;
int mode;

int debug=0;

extern DRAWER GXDraw;
	


/* the following three are used in handlers() and main() */
int oldxposition;
POINT tmppoint;
POINT refpoint;


POINT *sym_pts=NULL;
LINE *Lines = NULL;
SYMMETRY sym_storage;
SYMMETRY *sym = &sym_storage;

int frieze = 0; /* wallpaper or frieze groups? */
int moron = 0; /* don't disable various buttons */
int GridDisplay = TRUE;
void MapCursor(int x, int y, POINT *p, RECTANGLE *r, float zoom);


/*--------------------------------------------------- event handlers */

void handleRedraw()
{
  reshapeviewport();
  AdjustWindowRectangle(&win_rect);
  DefineSymWindow(&sym_rect,sym,&win_rect,zoom);
  count = SetUpSymmetry(sym,&sym_pts,xforms,&sym_rect,&win_rect);
  DrawCurrent(&sym_rect,Lines,sym,sym_pts,xforms,count,&GXDraw,GridDisplay);
  if(lit)
    HighLiteLine(&sym_rect, Lines, sym, sym_pts, xforms, count, MOVECOLOR);
  swapbuffers();
}

static int scheduled = 0;

int doRedraw(XEvent *ev, void *junk)
{
  XEvent newev;

  /* XForms documentation suggests that this idle callback is only called
   * when no events are pending for any window.  This doesn't seem to be true,
   * so we force any outstanding clicks & drags to be processed now
   * before wasting time redrawing.
   */
  while(XCheckWindowEvent(fl_display, win,
	ButtonPressMask|ButtonMotionMask|ButtonReleaseMask|KeyPressMask,
	&newev)) {
    user_event_cb(&newev, NULL);
  }

  DrawCurrent(&sym_rect,Lines,sym,sym_pts,xforms,count,&GXDraw,GridDisplay);
  if(lit)
    HighLiteLine(&sym_rect, Lines, sym, sym_pts, xforms, count, MOVECOLOR);
  swapbuffers();
  fl_set_idle_callback(NULL, NULL);
  scheduled = 0;
  return 0;
}

void scheduleRedraw()
{
  if(!scheduled) {
    fl_set_idle_callback(doRedraw, NULL);
    scheduled = 1;
  }
}
	

static char modechars[] = "?dcptmza/rD";  /* draw, cut, pick, transform,
						move, zoom, angle, /ratio,
						rotate, Delete */


void handleLeftmouse(int how, int x, int y)
{
  MapCursor(x, y, &tmppoint, &win_rect, zoom);

  if(how != 0) {		/* Button pressed or dragged */
    switch(mode) {
    case KALIDRAW:
    case KALIMOVE:
    case KALIPICK:
      if(RectIncludesPoint(sym_rect, tmppoint)) {
	tmppoint.x -= refpoint.x;
	tmppoint.y -= refpoint.y;
	VectorMatrixMult(&tmppoint,xforms[4],&tmppoint);
	if(how > 0 && (mode == KALIPICK || mode == KALIMOVE)) {
	/* On initial click, swap endpoints to find closest */
	  if(distance(tmppoint.x, tmppoint.y, Lines->m[EX], Lines->m[EY]) >
	   distance(tmppoint.x, tmppoint.y, Lines->m[SX], Lines->m[SY])) {
	    float t;
	    t = Lines->m[EX];  Lines->m[EX] = Lines->m[SX];  Lines->m[SX] = t;
	    t = Lines->m[EY];  Lines->m[EY] = Lines->m[SY];  Lines->m[SY] = t;
	  }
	}
	Lines->m[EX]=tmppoint.x;
	Lines->m[EY]=tmppoint.y;
	scheduleRedraw();
      }
      return;

    case KALITRANSFORM:
	if(how > 0)		/* Record initial X */
	    oldxposition = x;
	xformfnc( (x - oldxposition) / win_rect.width, sym );
	oldxposition = x;
	DefineSymWindow(&sym_rect,sym,&win_rect,zoom);
	count = SetUpSymmetry(sym,&sym_pts,xforms,&sym_rect,&win_rect);
	scheduleRedraw();
	return;
    }
  } else {	/* Button release */
    if ((mode == KALIDRAW) || (mode == KALIMOVE)) { /* fix endpoint */
	POINT p;
	/* Reference to closest center of symmetry */
	p.x = tmppoint.x-refpoint.x;
	p.y = tmppoint.y-refpoint.y;
	/* Convert to standard basis */
	VectorMatrixMult(&p,xforms[4],&p);
	Lines->m[EX]=p.x;
	Lines->m[EY]=p.y;
    }
    if (mode == KALIMOVE) mode = KALIPICK;
    if (mode == 0) mode = KALIDRAW;

    /* start drawing new line */
    if (mode == KALIDRAW) {
	refpoint = sym_pts[closest(&tmppoint,sym_pts,count)];
	Lines = NewLine(Lines);
	NewId(Lines);
	/* Reference to closest center of symmetry */
	tmppoint.x -= refpoint.x;
	tmppoint.y -= refpoint.y;
	/* Convert to standard basis */
	VectorMatrixMult(&tmppoint,xforms[4],&tmppoint);
	Lines->m[EX]=Lines->m[SX]=tmppoint.x;
	Lines->m[EY]=Lines->m[SY]=tmppoint.y;
	scheduleRedraw();
    }
  }
}

/*
 * Discard a line if the time is ripe -- if we're in delete mode and
 * one is visibly selected, or if we're in draw mode (delete last-drawn line).
 */
void mayTossLine()
{
  if(Lines != NULL && (lit || mode == KALIDRAW)) {
    Lines = DropLine(Lines);
    scheduleRedraw();
  }
  if(mode == KALIDRAW)
    mode = 0;
  lit = (Lines != NULL);
}

void handleMiddlemouse(int how, int x, int y) 
{
  if(how > 0)
    mayTossLine();
}

void
handleKeyboard(int ch)
{
  switch(ch) {
  case '\b':
  case 0177:
	mayTossLine();
	break;

  case '<': StartLoadProc(NULL, 0); break;

  case '>': StartSaveProc(NULL, 0); break;

  case 'z': set_mode(KALIZOOM); break;
  case 'd': set_mode(KALIDRAW); break;
  case 'm': set_mode(KALIMOVE); break;
  case 'x': set_mode(KALIDELETE); break;
  case 'r': set_mode(KALIROTATE); break;
  case 'a': set_mode(KALIANGLE); break;
  case 'R': set_mode(KALIRATIO); break;
  }
}
	    

struct Best {
   float r2;		/* squared distance to nearest point */
   int found;
   LINE line;		/* The line picked */
   POINT pt;		/* Translation reference-point of best line */
} best;

extern void PickStart(float cursx, float cursy, float maxdist);

void handleRightmouse(int how, int cursx, int cursy)
{
  int truex = cursx * zoom;
  int truey = cursy * zoom;

  PickStart(truex, truey, 2*zoom);

  /* don't draw the grid when picking to avoid picking the grid! */
  DrawCurrent(&sym_rect, Lines, sym, sym_pts, xforms, count, &PickDraw, 0);

  /* Redraw, so we can overlay to highlight the selected one */
  DrawCurrent(&sym_rect,Lines,sym,
    sym_pts,xforms,count,&GXDraw,GridDisplay);

  if(best.found) {
    Lines = GrabLine(best.line.id,Lines);	/* Move chosen one first */
    refpoint = best.pt;
    /* get correct rot & ref */
    UseRightCopy(Lines,sym,xforms,best.line.obj_pos);
    /* get cursor relative to ref */
    tmppoint.x = truex - refpoint.x;
    tmppoint.y = truey - refpoint.y;
    /* Convert to standard basis */
    VectorMatrixMult(&tmppoint,xforms[4],&tmppoint);
    ClosestEndpoint(Lines,&tmppoint);
    HighLiteLine(&sym_rect,Lines,sym,
       sym_pts,xforms,count,
       (pick_for == KALIDELETE) ? DELETECOLOR : MOVECOLOR);
  }
  else lit = FALSE;
  swapbuffers();

}


int user_event_cb(XEvent *xev, void *userdata) {
  int x, y, how = 0, button;

  switch(xev->type) {
  case MotionNotify:			/* Drag: how = -1 */
    how = -1;
    button = xev->xbutton.state & Button1Mask ? Button1
	   : xev->xbutton.state & Button2Mask ? Button2
	   : xev->xbutton.state & Button3Mask ? Button3 : 0;
    goto mote;

  case ButtonPress:			/* Button press: how = 1 */
    how = 1;
  case ButtonRelease: 			/* Button release: how = 0 */
    button = xev->xbutton.button;
  mote:
    x = xev->xbutton.x;
    y = xev->xbutton.y;
    switch(button) {
    case Button1: handleLeftmouse(how, xev->xbutton.x, xev->xbutton.y);  break;
    case Button2: handleMiddlemouse(how, xev->xbutton.x, xev->xbutton.y); break;
    case Button3: handleRightmouse(how, xev->xbutton.x, xev->xbutton.y); break;
    default:
      fprintf(stderr, "Kali small error:  hmmm.... X doesn't\
 understand button %d\n", button);
      break;
    }
    break;

  case ConfigureNotify:
  case Expose:
    /* redraw event */
    handleRedraw();
    break;

  case KeyPress:    /* keyboard device */
    {
	char chars[1];
	if(XLookupString(&xev->xkey, chars, sizeof(chars), NULL, NULL) > 0) {
	    handleKeyboard( chars[0] );
	}
	break;
    }

  case EnterNotify:
  case LeaveNotify:    /* inputchange */
    break;

  case ClientMessage:
  case CirculateNotify:
  case CreateNotify:
  case DestroyNotify:
  case GravityNotify:
  case ReparentNotify:
  case MapNotify:
  case UnmapNotify:    /* Ignore these Events... */
    break;
  default:
#ifdef DEBUG
    fprintf(stderr, "Kali small error: user window does not respond to\
 this type of event");
#endif
    break;
  }
}

    
  

/*--------------------------------------------------------- main */

int main(int argc, char** argv)  {
  FILE *f = NULL;
  char *fname = NULL;
  /* window defaults */
  kwin.x = 243;
  kwin.y = 8;
  kwin.width = 626;
  kwin.height = 670;
  pwin.x = 8;
  pwin.y = 8;
  pwin.width = 220;
  pwin.height = 670;

  for (++argv; --argc > 0; argv++) {
    if (STREQ(*argv, "-f"))
      frieze=1;
    else if (STREQ(*argv, "-w"))
      frieze=0;
    else if (STREQ(*argv, "-m"))
      moron=1;
    else if (STREQ(*argv, "-k") && ++argv) {
      --argc;
      fname = *argv;
    } else if (STREQ(*argv, "-g") && ++argv) {
      sscanf(*argv, "%f,%f,%f,%f", &kwin.x, &kwin.y, &kwin.width, &kwin.height);
      argc--;
    } else if (STREQ(*argv, "-p") && ++argv) {
      sscanf(*argv, "%f,%f,%f,%f", &pwin.x, &pwin.y, &pwin.width, &pwin.height);
      argc--;
    } else if((*argv)[0] != '-') {
      fname = *argv;
    } else {
      fprintf(stderr,"\
kali [-f] [-w] [-m] [kalifile]\n\
Draw Escher-like tilings interactively.\n\
-f		Begin with the 7 1D frieze symmetry groups.\n\
-w 		Begin with the 17 2D planar symmetry groups.\n\
-m		Disable switching between frieze and planar groups.\n\
-g x,y,w,h 	Position and size of main window.\n\
-p x,y,w,h	Position and size of control panel.\n\
		(0,0) is lower left on SGIs, upper left in generic X.\n"
);
      exit(-1);
    }
  }

  if (fname != NULL && (f = fopen(fname, "r")) == NULL)
    fprintf(stderr, "Can't find file %s.",fname);

  zoom = 1.0;
  sym_index = P1;

  {
    static int fakeargc = 1;
    static char *fakeargv[2] = { "kali", NULL };
#if FL_INCLUDE_VERSION <= 75
    fl_initialize("kali", "Kali", NULL, 0, &fakeargc, fakeargv);
#else /* modern versions */
    fl_initialize(&fakeargc, fakeargv,  "Kali", 0, 0);
#endif
  }

  foreground();
  
  win = MakeWindow(&win_rect, &kwin);
  sym_storage = SYMTAB[sym_index];
  DefineSymWindow(&sym_rect,sym,&win_rect,zoom);
  count = SetUpSymmetry(sym,&sym_pts,xforms,&sym_rect,&win_rect);
  DrawCurrent(&sym_rect,Lines,sym,sym_pts,xforms,count,&GXDraw,GridDisplay);
  swapbuffers();

  forms_init(&pwin); 
  winset(win);
  if (f) load_a_file(f);

  mode = 0;

  fl_set_event_callback(user_event_cb, NULL);

  for(;;) {
    mode = 0;
    fl_do_forms();
#ifdef DEBUG    
    fprintf(stderr, "hmmm.... what am I doing here\n");
#endif
  }
}




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

void ChangeScale(double amount, SYMMETRY *sym) {
  zoom *= 1 - amount/2;
}

void ChangeRotation(double amount, SYMMETRY *sym) {
  double angle;
  MATRIX rotation;

  angle = -amount * M_PI / 2;
  SetUpRot(angle,rotation);
  VectorMatrixMult(&(sym->v1),rotation,&(sym->v1));
  VectorMatrixMult(&(sym->v2),rotation,&(sym->v2));
}

void ChangeRatio(double amount, SYMMETRY* sym) {
  VectorScalarMult( &sym->v1, 1 + amount/2 );
}


void ChangeAngle(double amount, SYMMETRY *sym) {
  double angle;
  MATRIX rotation;

  angle = amount * M_PI / 4;
  SetUpRot(angle,rotation);
  VectorMatrixMult(&(sym->v1),rotation,&(sym->v1));
  /* modify rotation matrix for opposite angle */
  rotation[1] = -rotation[1];
  rotation[2] = -rotation[2];
  VectorMatrixMult(&(sym->v2),rotation,&(sym->v2));
}


/* Move the selected line to first in the list */
LINE *GrabLine(short i, LINE* first) {

  LINE *cur,*last;

  last = NULL;  /* previous line we looked at, NOT last in the list */
  for (cur=first; cur != NULL; cur=cur->next)    {
    if (cur->id == i)	{
      if (last != NULL) 	    {
	last->next = cur->next;
	cur->next = first;
      }
      first = cur;
      break;
    }
    last = cur;	  
  }
  return(first);
}


LINE *DropLine(LINE *L) {
  LINE *cur;
  cur = L->next;
  free(L);
  return(cur);
}


void MapCursor(int x, int y, POINT *p, RECTANGLE *r, float zoom) {
  p->x = (x - r->x)*zoom;
  p->y = (y - r->y)*zoom;
}

void DrawCurrent(RECTANGLE *rect, LINE *Lines, SYMMETRY *sym,
		 POINT *pts, MATRIX *xforms, int count,
		 DRAWER *drawer, int GridDisplay)  {
  LINE *obj;
  RECTANGLE bounds;

  fl_winset(win); 

  SET_COLOR(BACKCOLOR);
  clear();

  SET_COLOR(POINTCOLOR);
  DrawPoints(drawer, pts,count);
  if (GridDisplay) {
    obj = MakeCurrentObject(sym->grid,sym,xforms,&bounds);
    SET_COLOR(GRIDCOLOR);
    ReplicateObject(rect,obj,pts,count,&bounds,sym,drawer);
    FreeObject(obj);
  }
  obj = MakeCurrentObject(Lines,sym,xforms,&bounds);
  SET_COLOR(LINECOLOR);
  ReplicateObject(rect,obj,pts,count,&bounds,sym,drawer);
  FreeObject(obj);
}

void HighLiteLine(RECTANGLE *rect, LINE *cur, SYMMETRY *sym,
		  POINT *pts, MATRIX *xforms, int count,
		  int colour) {
    LINE *obj,*temp;
    RECTANGLE bounds;

    if(cur == NULL) {
	return;
    }
    lit = TRUE;
    winset(win);
    temp = cur->next;
    cur->next = NULL;
    obj = MakeCurrentObject(cur,sym,xforms,&bounds);
    cur->next = temp;
    SET_COLOR(colour);
    ReplicateObject(rect,obj,pts,count,&bounds,sym,&GXDraw);
    FreeObject(obj);
}



/* UseRightCopy - changes the endpoints of the line cur according 
to the pos'th transformation that goes into making an object.
Since all transformations of a given line are eqivalent, this makes
no difference in the picture */

void UseRightCopy(LINE *cur, SYMMETRY *sym, MATRIX *xforms, short pos) {
  RECTANGLE bounds;
  LINE *temp,*l,*obj;

  temp = cur->next;
  cur->next = NULL;
  obj = MakeCurrentObject(cur,sym,xforms,&bounds);
  for (l = obj; l->next != NULL; l = l->next) {
    if (l->obj_pos == pos) break;
  }
  *cur = *l;        /* copy line description */
  FreeObject(obj);
  MatrixMultiply(cur->m,xforms[4],cur->m);
  cur->next = temp;
}

long MakeWindow(RECTANGLE *r, RECTANGLE *kwin) {
  long rwin;

  GXinit(&GXDraw, fl_display, 0);
  prefposition( kwin->x, kwin->x+kwin->width, kwin->y, kwin->y+kwin->height);
  rwin = winopen("kali");
  RGBmode();

  winconstraints();
  AdjustWindowRectangle(r);
  doublebuffer();
  gconfig();
  return(rwin);
}

void AdjustWindowRectangle(RECTANGLE *r) {
  long x,y;

  r->x = r->y = 0;
  getsize(&x,&y);
  r->width = x; 
  r->height = y;
}


  
/* Find closest center of symmetry to new motif point */
int closest(POINT *new_pt, POINT *pts, int maxpts) {
  int i,min_i;
  float dist,min=HUGE;
 
  for (i=0; i<maxpts; i++)
    {
      dist = distance(new_pt->x, new_pt->y, pts[i].x, pts[i].y);
      if (dist < min) {	min = dist; min_i = i; }
    }
  return(min_i);
}

void PrintLine(LINE* l) {
  int i;
  printf("%x ",l);
  for (i=0;i<4;i++)   printf("%6.3f ",l->m[i]);
  printf("%d %x \n",l->id,l->next);
}

void DumpLines(LINE* Lines) {
  LINE* cur;    
  int i;
  i = 0;
  for (cur=Lines; cur != NULL; cur=cur->next)    {
    printf("Line %d  ",i++);
    PrintLine(cur);
  }
}

LINE* ThrowAwayLines(LINE *Lines)
{
  LINE *cur,*old_cur;
  cur = Lines;
  while (cur) {
    old_cur = cur;
    cur = cur->next;
    free(old_cur);
  }
  return(NULL);
}


void SaveProc(FILE *pat)
{
  LINE *cur;

  fprintf(pat,"%d\n",sym_index);
  fprintf(pat,"%6.3f %6.3f\n",
	  sym_rect.width,sym_rect.height);
  fprintf(pat,"%6.3f %6.3f\n",
	  sym->v1.x,sym->v1.y);
  fprintf(pat,"%6.3f %6.3f\n",
	  sym->v2.x,sym->v2.y);
  fprintf(pat,"%6.3f \n",
	  zoom);
  for (cur=Lines; cur != NULL; cur=cur->next)
    fprintf(pat,"%6.3f %6.3f %6.3f %6.3f\n",
	    cur->m[0],cur->m[1],cur->m[2],cur->m[3]);
  fclose(pat);
}

void LoadProc(FILE *pat)
{
  float dummy;
  fscanf(pat,"%d\n",&sym_index);
  fscanf(pat,"%f %f\n",&dummy,&dummy);
  sym_storage = SYMTAB[sym_index];
  fscanf(pat,"%f %f\n",
	 &(sym->v1.x),&(sym->v1.y));
  fscanf(pat,"%f %f\n",
	 &(sym->v2.x),&(sym->v2.y));
  fscanf(pat,"%f \n",
	 &zoom);
  DefineSymWindow(&sym_rect,sym,&win_rect,zoom);
  /*			    ortho2(0.0,zoom*win_rect.width,0.0,
			    zoom*win_rect.height); */
  count = SetUpSymmetry(sym,&sym_pts,
			xforms,&sym_rect,&win_rect);
  Lines = ThrowAwayLines(Lines);
  Lines = ReadPattern(Lines,pat);

  scheduleRedraw();
  mode = 0;
}



void DefineSymWindow(RECTANGLE *s_rect, SYMMETRY *sym,
		     RECTANGLE *w_rect, float zoom) {
  float x,y;
  extern WINDOW win;

  s_rect->width = zoom * w_rect->width;
  s_rect->height = zoom * w_rect->height;

  winset(win);
  ortho2(0.0,zoom*w_rect->width,0.0,zoom*w_rect->height);
}

/* Picking */

#define MAXDEPTH 25

#define VADD(dest, a, b)  (dest).x = (a).x + (b).x, (dest).y = (a).y + (b).y
#define VSUB(dest, a, b)  (dest).x = (a).x - (b).x, (dest).y = (a).y - (b).y

static POINT pickpt, pick0, pickT;
static POINT pstack[MAXDEPTH];
static int pdepth;

void
PickStart(float cursorx, float cursory, float maxdist)
{
   pickpt.x = cursorx;
   pickpt.y = cursory;
   best.r2 = maxdist*maxdist;
   best.found = 0;
   pdepth = 0;
}

static void PickPush(DRAWER *drawer) {
   pstack[pdepth] = pickT;
   if(++pdepth >= MAXDEPTH) {
	fprintf(stderr, "kali error: PickPush(%d)\n", pdepth);
	pdepth = MAXDEPTH-1;
   }
}

static void PickPop(DRAWER *drawer) {
   if(--pdepth < 0) {
	fprintf(stderr, "kali error: PickPop(%d)\n", pdepth);
	pdepth = 0;
   }
   pickT = pstack[pdepth];
   VSUB(pick0,  pickpt, pickT);
}

static void PickTranslate(DRAWER *drawer, double x, double y) {
   pickT.x += x;  pickT.y += y;
   
   VSUB(pick0,  pickpt, pickT);
}

static void PickLine(DRAWER *drawer, LINE *lp)
{
   float midx, midy, dx, dy, linex, liney;
   float l2, dot, r2;
   midx = .5*(lp->m[SX] + lp->m[EX]) - pick0.x;
   midy = .5*(lp->m[SY] + lp->m[EY]) - pick0.y;
   dx = .5*(lp->m[SX] - lp->m[EX]);
   dy = .5*(lp->m[SY] - lp->m[EY]);
   l2 = dx*dx + dy*dy;

   if(l2 < best.r2 + midx*midx + midy*midy)
	return;		/* Too far away */

   /* Project origin to our line, clamping it to the ends of the line. */
   dot = midx*dx + midy*dy;		/* p.l */
   if(dot > l2) dot = l2;
   else if(dot < -l2) dot = -l2;
   if(dot != 0) {		/* Slide "midx,midy" along line to nearest pt */
	linex = midx - dx*dot/l2;
	liney = midy - dy*dot/l2;
   } else {
	linex = midx;
	liney = midy;
   }
   r2 = linex*linex + liney*liney;
   if(r2 < best.r2) {
	/* Got one! */
	best.found = 1;
	best.line = *lp;
	best.r2 = r2;
	best.pt = pickT;
   }
}

void
PickLines(DRAWER *drawer, LINE *obj)
{
   while(obj) {
	PickLine(drawer, obj);
	obj = obj->next;
   }
}

DRAWER PickDraw = {
    "Pick",
    PickLines,
    PickPush,
    PickTranslate,
    PickPop,
    PickLine,
    NULL,
    NULL
};
