/* +-------------------------------------------------------------------+ */
/* | Copyright 1992, 1993, David Koblas (koblas@netcom.com)	       | */
/* |								       | */
/* | Permission to use, copy, modify, and to distribute this software  | */
/* | and its documentation for any purpose is hereby granted without   | */
/* | fee, provided that the above copyright notice appear in all       | */
/* | copies and that both that copyright notice and this permission    | */
/* | notice appear in supporting documentation.	 There is no	       | */
/* | representations about the suitability of this software for	       | */
/* | any purpose.  this software is provided "as is" without express   | */
/* | or implied warranty.					       | */
/* |								       | */
/* +-------------------------------------------------------------------+ */

/* $Id: lineOp.c,v 1.17 2005/03/20 20:15:32 demailly Exp $ */

#ifdef __VMS
#define XtDisplay XTDISPLAY
#endif

#include <X11/Xlib.h>
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include "xaw_incdir/TextSrc.h"

#include <math.h>
#include <stdlib.h>
#include "xpaint.h"
#include "Paint.h"
#include "PaintP.h"
#include "misc.h"
#include "Paint.h"
#include "ops.h"

int objectMode = 0;

int headType = 1;
int headSize = 15;
double headAngle = 25.0;

int tickType = 1;
int tickEnds = 3;
int tickNumber = 5;
double tickSize = 6;
double tickAngle = 0;

typedef struct {
    int type;
    Boolean didUndo, tracking, drawn, canceled, returned;
    int rx, ry;
    int startX, startY, endX, endY;
    Drawable drawable;
    GC gcx;
} LocalInfo;

static void
DrawObject (Widget w, Drawable d, GC gc, int stX,int stY, int enX, int enY)
{
  XPoint xpoints[4];
  Display *dpy; 
  int lw;
  int i, ux, uy, lx=0, ly=0, mx=0, my=0, z;
  int DeltaX, DeltaY;
  double norm, ll, mm;

  dpy = XtDisplay(w);
  XtVaGetValues(w, XtNlineWidth, &lw, XtNzoom, &z, NULL);
  ux = uy = 0;

  if (!(objectMode & ARROWFLAG) && (objectMode & TICKFLAG)) {
    DeltaX = enX - stX;
    DeltaY = enY - stY;
    norm = sqrt(pow(DeltaX, 2) + pow(DeltaY, 2)) + 0.001;
    ll = atan2(DeltaY, DeltaX);
    mm = norm / tickNumber;
    mx = 1 - (tickEnds) & 1;
    my = 1 - ((tickEnds>>1)&1);
    if (tickType == 1) {
        lx = tickSize * sin(ll + tickAngle * M_PI/ 180.0);
        ly = -tickSize * cos(ll + tickAngle * M_PI / 180.0);    
        for (i = mx; i <= tickNumber-my; i++) {
            ux = stX + i * mm * cos(ll);
            uy = stY + i * mm * sin(ll);
            XDrawLine(dpy, d, gc, ux-lx, uy-ly, ux+lx, uy+ly);
	}
    } else
    if (tickType == 2) {
        lx = tickSize * cos(ll + tickAngle * M_PI / 180.0 + M_PI/4);
        ly = tickSize * sin(ll + tickAngle * M_PI / 180.0 + M_PI_4);    
        for (i = mx; i <= tickNumber-my; i++) {
            ux = stX + i * mm * cos(ll);
            uy = stY + i * mm * sin(ll);
            XDrawLine(dpy, d, gc, ux-lx, uy-ly, ux+lx, uy+ly);
            XDrawLine(dpy, d, gc, ux+ly, uy-lx, ux-ly, uy+lx);	    
	}
    } else
    if (tickType == 3) {
        lx = (int)tickSize;
        for (i = mx; i <= tickNumber-my; i++) {
            ux = stX + i * mm * cos(ll);
            uy = stY + i * mm * sin(ll);
            XDrawArc(dpy, d, gc, ux - lx, uy - lx, 2*lx, 2*lx, 0, 360*64);
	}
    } else
    if (tickType == 4) {
        lx = (int)tickSize;
        for (i = mx; i <= tickNumber-my; i++) {
            ux = stX + i * mm * cos(ll);
            uy = stY + i * mm * sin(ll);
            XFillArc(dpy, d, gc, ux - lx, uy - lx, 2*lx, 2*lx, 0, 360*64);
	}
    }      
    return;
  }
  
  if ((objectMode & ARROWFLAG) || (objectMode & HEADFLAG)) {
    DeltaX = enX - stX;
    DeltaY = enY - stY;
    norm = sqrt(pow(DeltaX, 2) + pow(DeltaY, 2)) + 0.001;
   
    if ( lw <= 2 || ((objectMode & ARROWFLAG) && (objectMode & NOTAILFLAG)))
        ll = (double) headSize;
    else
        ll = (double) (4*(lw-1)+headSize);

    if (!(objectMode & NOZOOMFLAG)) {
       if (z>0) ll = ll * (double) z;
       if (z<0) ll = ll / (double)(-z);
    }

    mm = ll * tan(headAngle*M_PI/180.0) +0.5;
   
    lx = (int) (0.49 + (double) (abs(DeltaX) * ll) / norm);
    ly = (int) (0.49 + (double) (abs(DeltaY) * ll) / norm);
    mx = (int) (0.49 + (double) (abs(DeltaY) * mm) / norm);
    my = (int) (0.49 + (double) (abs(DeltaX) * mm) / norm);
    my = -my;
   
    if (DeltaX < 0) {
         lx = -lx; 
         my = -my;
    }
    if (DeltaY < 0) {
         ly = -ly; 
         mx = -mx;
    }
    if (lw>0) {
       ux = 9*lx/10;
       uy = 9*ly/10;
    }
    if (headType >= 2) {
       ux /= headType;
       uy /= headType;
    }
  }
   
  if (!(objectMode & ARROWFLAG) || !(objectMode & NOTAILFLAG))
    XDrawLine(dpy, d, gc, stX, stY, enX-ux, enY-uy);

  if ((objectMode & ARROWFLAG) || (objectMode & HEADFLAG)) {
    if ((objectMode & ARROWFLAG) && (objectMode & NOTAILFLAG)) {
       xpoints[0].x = stX;
       xpoints[0].y = stY;
    } else {
       xpoints[0].x = enX;
       xpoints[0].y = enY;
    }
    if (headType == 1) {
       xpoints[1].x = xpoints[0].x - lx + mx;
       xpoints[1].y = xpoints[0].y - ly + my;
       xpoints[2].x = xpoints[0].x - lx - mx;
       xpoints[2].y = xpoints[0].y - ly - my;
       z = 3;
    } else
       z = 4;
    if (headType == 2) {
       xpoints[1].x = xpoints[0].x - lx + mx;
       xpoints[1].y = xpoints[0].y - ly + my;
       xpoints[2].x = xpoints[0].x - lx/2;
       xpoints[2].y = xpoints[0].y - ly/2;
       xpoints[3].x = xpoints[0].x - lx - mx;
       xpoints[3].y = xpoints[0].y - ly - my;
    }
    if (headType == 3) {
       xpoints[1].x = xpoints[0].x - lx + mx;
       xpoints[1].y = xpoints[0].y - ly + my;
       xpoints[2].x = xpoints[0].x - lx/3;
       xpoints[2].y = xpoints[0].y - ly/3;
       xpoints[3].x = xpoints[0].x - lx - mx;
       xpoints[3].y = xpoints[0].y - ly - my;
       XFillPolygon(dpy, d, gc, xpoints, 4, Complex, CoordModeOrigin);
       if (objectMode & NOZOOMFLAG)
	   for (i=0; i<4; i++) UndoGrow(w, (int)xpoints[i].x,(int)xpoints[i].y);
       lx /= 3;
       ly /= 3;
       for (i=0; i<4; i++) {
           xpoints[i].x -= lx;
           xpoints[i].y -= ly;
       }
    }
    XFillPolygon(dpy, d, gc, xpoints, z, Complex, CoordModeOrigin);
    if (objectMode & NOZOOMFLAG)
       for (i=0; i<z; i++) UndoGrow(w, (int)xpoints[i].x, (int)xpoints[i].y);
  }
}
 
static void 
press(Widget w, LocalInfo * l, XButtonEvent * event, OpInfo * info)
{
    char input[32];
    char *data, *newdata;
    Widget wid;
    XawTextPosition p;
    
    /*
    **	Check to make sure all buttons are up, before doing this
     */

    l->canceled = False;
    l->returned = False;
      
    if ((event->state & AllButtonsMask) != 0)
	return;

    if (event->button >= Button4) return;
   
    if (event->button <= Button2 &&
	((!l->tracking && (objectMode & MULTIFLAG)) || !(objectMode & MULTIFLAG))) {
      if (!(objectMode & ARROWFLAG) && (objectMode & POSITIONFLAG)) {
	    wid = XtNameToWidget(Global.data_xy, "*list");
	    if (wid) {
	        XtVaGetValues(wid, XtNstring, &data, NULL);
		if (!data) return;
		sprintf(input, "%d,%d\n", info->x, info->y);
                newdata = (char *)
		    malloc(strlen(data)+strlen(input)+3);
		strcpy(newdata,data);
	        strcat(newdata, input);
		p = (XawTextPosition)strlen(newdata);
	        XtVaSetValues(wid, XtNstring, newdata, NULL);
		XawTextSetInsertionPoint(wid, p);
		free(newdata);
	    }
	    return;
	}
        l->rx = info->x;
	l->ry = info->y;
	l->startX = event->x;
	l->startY = event->y;

	l->drawable = info->drawable;
	l->drawn = False;
	l->didUndo = False;
	l->tracking = True;
    } 
    if (l->tracking) {
	if (l->drawn)
	    DrawObject(w, info->drawable, l->gcx,
		       l->startX, l->startY, l->endX, l->endY);

	l->endX = event->x;
	l->endY = event->y;

	DrawObject(w, info->drawable, l->gcx,
		  l->startX, l->startY, l->endX, l->endY);
	l->drawn = True;
    }
}

static void 
motion(Widget w, LocalInfo * l, XMotionEvent * event, OpInfo * info)
{

    if (!l->tracking || l->canceled || l->returned)
	return;

    if (l->drawn)
	DrawObject(w, info->drawable, l->gcx,
		  l->startX, l->startY, l->endX, l->endY);

    l->type = (event->state & ShiftMask);

    if (l->type) {
	int ax = ABS(event->x - l->startX);
	int ay = ABS(event->y - l->startY);
	int dx = MIN(ax, ay);
	int v, v1 = dx - ax, v2 = dx - ay;
	int addX, addY;

	v = v1 * v1 + v2 * v2;

	if (ay * ay < v) {
	    addX = event->x - l->startX;
	    addY = 0;
	} else if (ax * ax < v) {
	    addX = 0;
	    addY = event->y - l->startY;
	} else {
	    if (ax < ay) {
		addX = event->x - l->startX;
		addY = SIGN(event->y - l->startY) * ax;
	    } else {
		addX = SIGN(event->x - l->startX) * ay;
		addY = event->y - l->startY;
	    }
	}

	l->endX = l->startX + addX;
	l->endY = l->startY + addY;
    } else {
	l->endX = event->x;
	l->endY = event->y;
    }

    /*
    **	Really set this flag in the if statement
     */
    if ((l->drawn = (l->startX != l->endX || l->startY != l->endY)))
	DrawObject(w, info->drawable, l->gcx,
		  l->startX, l->startY, l->endX, l->endY);
}

static void
shift(Widget w, Drawable d, LocalInfo * l, KeySym keysym)
{
    int zoom;

    XtVaGetValues(w, XtNzoom, &zoom, NULL);
    DrawObject(w, d, l->gcx,
	      l->startX, l->startY, l->endX, l->endY);

    switch (keysym) {
        case XK_Right:
	    if (zoom > 0) {
	       l->startX += 1;
	       l->rx = l->startX / zoom;
	    } else {
	       l->rx += 1;
	       l->startX = l->rx / (-zoom);
	    }
            break;
        case XK_Left:
	    if (zoom > 0) {
	       l->startX -= 1;
	       l->rx = l->startX / zoom;
	    } else {
	       l->rx -= 1;
	       l->startX = l->rx / (-zoom);
	    }
            break;
        case XK_Up:
	    if (zoom > 0) {
	       l->startY -= 1;
	       l->ry = l->startY / zoom;
	    } else {
	       l->ry -= 1;
	       l->startY = l->ry / (-zoom);
	    }
            break;
        case XK_Down:
	    if (zoom > 0) {
	       l->startY += 1;
	       l->ry = l->startY / zoom;
	    } else {
	       l->ry += 1;
	       l->startY = l->ry / (-zoom);
	    }
            break;
    }

    DrawObject(w, d, l->gcx,
	      l->startX, l->startY, l->endX, l->endY);
}

static void 
release(Widget w, LocalInfo * l, XButtonEvent * event, OpInfo * info)
{
    XRectangle *undo = NULL;
    int mask, zoom;
    KeySym keysym;
    char buf[21];
    char input[107];

    if (l->canceled) return;

    if (event->type == KeyRelease && l->drawn) {
        keysym = 0;
        mask = XLookupString((XKeyEvent *)event, buf, sizeof(buf)-1, &keysym, NULL);
	
        if (keysym == XK_Return) {
	    l->returned = True;
            goto finish;
        }
        if (keysym == XK_Escape) {
	    DrawObject(w, info->drawable, l->gcx,
		      l->startX, l->startY, l->endX, l->endY);
            l->tracking = False;
            l->drawn = False;
            l->canceled = True;
	} else
        if (keysym == XK_Left || keysym == XK_Right ||
            keysym == XK_Up || keysym == XK_Down)
	    if (info->surface == opWindow)
                shift(w, info->drawable, l, keysym);
        return;
    }

    if (!l->tracking && ((info->surface == opWindow) || l->didUndo))
	return;

    /*
    **	Check to make sure all buttons are up, before doing this
     */
    mask = AllButtonsMask;
    switch (event->button) {
    case Button1:
	mask ^= Button1Mask;
	break;
    case Button2:
	mask ^= Button2Mask;
	break;
    case Button3:
	mask ^= Button3Mask;
	break;
    case Button4:
	mask ^= Button4Mask;
	break;
    case Button5:
	mask ^= Button5Mask;
	break;
    }

    if ((event->state & mask) != 0)
	return;

 finish :

    if (l->drawn && info->surface == opWindow) {
	DrawObject(w, info->drawable, l->gcx,
		  l->startX, l->startY, l->endX, l->endY);
	l->drawn = False;
    }

    if (!(objectMode & ARROWFLAG) && (objectMode & MEASUREFLAG)) {
        char *data, *newdata;
        XawTextPosition p;
        Widget wid;
        float u, v, r;
        l->drawn = False;
	l->tracking = False;
	if (info->surface != opWindow) return;
	wid = XtNameToWidget(Global.data_xy, "*list");
	if (wid) {
	    XtVaGetValues(wid, XtNstring, &data, NULL);
	    if (!data) return;
	    u = info->x - l->rx;
	    v = info->y - l->ry;
	    r = sqrt(u*u+v*v);
	    u = -atan2(v,u) * 180 / M_PI;
	    sprintf(input, "%d,%d  -->  %d,%d    r = %g   θ = %g\n",
		    l->rx, l->ry, info->x, info->y, r, u);
            newdata = (char *)
		malloc(strlen(data)+strlen(input)+3);
	    strcpy(newdata,data);
	    strcat(newdata, input);
	    p = (XawTextPosition)strlen(newdata);
	    XtVaSetValues(wid, XtNstring, newdata, NULL);
	    XawTextSetInsertionPoint(wid, p);
	    free(newdata);
	}	
        return;
    }
    
    if (info->surface == opWindow && info->isFat)
	return;

    if (!l->didUndo && info->surface == opPixmap) {
        PaintWidget pw = (PaintWidget) GET_PW(w);
        UndoStart(w, info);
	undo = &pw->paint.undo->box;
        undo->x = l->rx;
        undo->y = l->ry;
        undo->width = pw->paint.lineWidth * 2 + 1;
        undo->height = undo->width;
	l->didUndo = True;
    }

    if (l->type) {
	int ax = ABS(event->x - l->rx);
	int ay = ABS(event->y - l->ry);
	int dx = MIN(ax, ay);
	int v, v1 = dx - ax, v2 = dx - ay;
	int addX, addY;

	v = v1 * v1 + v2 * v2;

	if (ay * ay < v) {
	    addX = event->x - l->rx;
	    addY = 0;
	} else if (ax * ax < v) {
	    addX = 0;
	    addY = event->y - l->ry;
	} else {
	    if (ax < ay) {
		addX = event->x - l->rx;
		addY = SIGN(event->y - l->ry) * ax;
	    } else {
		addX = SIGN(event->x - l->rx) * ay;
		addY = event->y - l->ry;
	    }
	}
        if (!l->returned) {
	    l->endX = l->rx + addX;
	    l->endY = l->ry + addY;
	}
    } else {
        if (!l->returned) {
	    l->endX = event->x;
	    l->endY = event->y;
	}
    }

    SetCapAndJoin(w, info->first_gc, 
                  ((Global.cap)?Global.cap-1:CapButt),
                  ((Global.join)?Global.join-1:JoinMiter));

    if (l->returned) {
        XtVaGetValues(w, XtNzoom, &zoom, NULL);
        if (zoom > 0) {
            l->endX /= zoom;
            l->endY /= zoom;
	} else {
            l->endX *= -zoom;
            l->endY *= -zoom;
	}
    }

    objectMode |= NOZOOMFLAG;
    DrawObject(w, info->drawable, info->first_gc,
	             l->rx, l->ry, l->endX, l->endY);
    objectMode &= ~NOZOOMFLAG;    

    if (info->surface == opPixmap) {
	UndoGrow(w, l->rx, l->ry);
	UndoGrow(w, l->endX, l->endY);
	PwUpdate(w, undo, False);
        if (l->returned)
	    l->tracking = False;
	
        if (DoVxp(w)) {
	    sprintf(Global.vxpinput, "\n*%s\n%d,%d\n%d,%d",
		    (((objectMode & HEADFLAG) || (objectMode & ARROWFLAG))?
		     "9 arrow" : ((objectMode & TICKFLAG)?
				  "6 ticks":"6 segment")),
		    l->rx, l->ry, l->endX, l->endY);	  
	    RecordVxp(w);
	}
    }

    if (!(objectMode & MULTIFLAG) || (objectMode & ARROWFLAG) || 
        ((event->button == Button2 || l->returned) &&
        info->surface == opPixmap))
        l->tracking = False;
}

/*
**  Those public functions
 */
void *
LineAdd(Widget w)
{
    LocalInfo *l = (LocalInfo *) XtMalloc(sizeof(LocalInfo));

    l->tracking = False;
    l->gcx = GetGCX(w);
    objectMode &= ~ARROWFLAG;

    XtVaSetValues(w, XtNcompress, True, NULL);

    OpAddEventHandler(w, opWindow, ButtonPressMask, FALSE,
		      (OpEventProc) press, l);
    OpAddEventHandler(w, opWindow, PointerMotionMask, FALSE,
		      (OpEventProc) motion, l);
    OpAddEventHandler(w, opWindow | opPixmap,
                      KeyReleaseMask|ButtonReleaseMask, FALSE,
		      (OpEventProc) release, l);
    SetCrossHairCursor(w);
    return l;
}

void 
LineRemove(Widget w, void *p)
{
    LocalInfo *l = (LocalInfo *) p;

    OpRemoveEventHandler(w, opWindow, ButtonPressMask, FALSE,
			 (OpEventProc) press, l);
    OpRemoveEventHandler(w, opWindow, PointerMotionMask, FALSE,
			 (OpEventProc) motion, l);
    OpRemoveEventHandler(w, opWindow | opPixmap,
                         KeyReleaseMask|ButtonReleaseMask, FALSE,
			 (OpEventProc) release, l);

    if ((objectMode & MULTIFLAG) && l->tracking && l->drawn)
	DrawObject(w, l->drawable, l->gcx,
		  l->startX, l->startY, l->endX, l->endY);

    XtFree((XtPointer) l);
}

void *
ArrowAdd(Widget w)
{
    LocalInfo *l = (LocalInfo *) XtMalloc(sizeof(LocalInfo));

    l->tracking = False;
    l->gcx = GetGCX(w);
    objectMode |= ARROWFLAG;

    XtVaSetValues(w, XtNcompress, True, NULL);

    OpAddEventHandler(w, opWindow, ButtonPressMask, FALSE,
		      (OpEventProc) press, l);
    OpAddEventHandler(w, opWindow, PointerMotionMask, FALSE,
		      (OpEventProc) motion, l);
    OpAddEventHandler(w, opWindow | opPixmap,
                      KeyReleaseMask|ButtonReleaseMask, FALSE,
		      (OpEventProc) release, l);
    SetCrossHairCursor(w);
    return l;
}

void
ArrowRemove(Widget w, void *p)
{
    LocalInfo *l = (LocalInfo *) p;

    OpRemoveEventHandler(w, opWindow, ButtonPressMask, FALSE,
			 (OpEventProc) press, l);
    OpRemoveEventHandler(w, opWindow, PointerMotionMask, FALSE,
			 (OpEventProc) motion, l);
    OpRemoveEventHandler(w, opWindow | opPixmap,
                         KeyReleaseMask|ButtonReleaseMask, FALSE,
			 (OpEventProc) release, l);

    if ((objectMode & MULTIFLAG) && l->tracking && l->drawn)
	DrawObject(w, l->drawable, l->gcx,
		   l->startX, l->startY, l->endX, l->endY);

    XtFree((XtPointer) l);
}

void
DashSetStyle(char *dashstyle)
{
    int i, j, len, shift;
    char c;

    len = strlen(dashstyle);
    if (len > 64) {
        dashstyle[64] = '\0';
	len = 64;
    }
    Global.dashoffset = 0;
    c = dashstyle[0];
    j = 0;
    if (c == '=')
        shift = 0;
    else
        shift = 1;
    Global.dashnumber = 0;

    for (i = 0; i <= len; i++) 
        if (dashstyle[i] == c)
            ++j;
        else {
	    Global.dashlist[Global.dashnumber+shift] = j;
	    ++Global.dashnumber;
	    c = dashstyle[i];
            j = 1;
	}

    if (shift)
        Global.dashlist[0] = Global.dashlist[Global.dashnumber];
    Global.dashlist[Global.dashnumber] = '\0';
        
    if (Global.dashnumber >= 2) {
        if (shift)
            Global.dashoffset = (int) Global.dashlist[0];
        else
            Global.dashoffset = 0;
    }
    else
        Global.dashnumber = 0;
}

/*
**  Those public functions
*/
void
ArrowHeadSetParameters(int t, int s, double a)
{
    headType = t;
    headSize = s;
    headAngle = a;
}

