/* +-------------------------------------------------------------------+ */
/* | 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 <math.h>
#include <stdlib.h>
#include "xpaint.h"
#include "Paint.h"
#include "PaintP.h"
#include "misc.h"
#include "Paint.h"
#include "ops.h"

#define	GET_PW(w)	((PaintWidget)(((PaintWidget)w)->paint.paint == None ? \
				       w : ((PaintWidget)w)->paint.paint))
static Boolean multiLine = False;
static Boolean withArrow = False;
static Boolean withTail = True;
static int headType = 1;
static int headSize = 15;
static double headAngle = 25.0;

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

static void
DrawArrow (Widget w, Drawable d, GC gc, int stX,int stY, int enX, int enY,
           Boolean arrowType, Boolean dozoom)
{
  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 (withArrow || arrowType) {
    DeltaX = enX - stX;
    DeltaY = enY - stY;
    norm = sqrt(pow(DeltaX, 2) + pow(DeltaY, 2)) + 0.001;
   
    if ( lw <= 2 || (arrowType && !withTail))
        ll = (double) headSize;
    else
        ll = (double) (4*(lw-1)+headSize);

    if (dozoom) {
       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 (!arrowType || withTail)
    XDrawLine(dpy, d, gc, stX, stY, enX-ux, enY-uy);

  if (withArrow || arrowType) {
    if (arrowType && !withTail) {
       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 (!dozoom)
	   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 (!dozoom)
       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)
{
    /*
    **	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 && multiLine) || !multiLine)) {
	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)
	    DrawArrow(w, info->drawable, l->gcx,
		      l->startX, l->startY, l->endX, l->endY, l->arrowType, 1);

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

	DrawArrow(w, info->drawable, l->gcx,
		  l->startX, l->startY, l->endX, l->endY, l->arrowType, 1);
	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)
	DrawArrow(w, info->drawable, l->gcx,
		  l->startX, l->startY, l->endX, l->endY, l->arrowType, 1);

    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)))
	DrawArrow(w, info->drawable, l->gcx,
		  l->startX, l->startY, l->endX, l->endY, l->arrowType, 1);
}

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

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

    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;
    }

    DrawArrow(w, d, l->gcx,
	      l->startX, l->startY, l->endX, l->endY, l->arrowType, 1);
}

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

    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) {
	    DrawArrow(w, info->drawable, l->gcx,
		      l->startX, l->startY, l->endX, l->endY, l->arrowType, 1);
            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) {
	DrawArrow(w, info->drawable, l->gcx,
		  l->startX, l->startY, l->endX, l->endY, l->arrowType, 1);
	l->drawn = False;
    }
   
    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;
	}
    }

    DrawArrow(w, info->drawable, info->first_gc,
	             l->rx, l->ry, l->endX, l->endY, l->arrowType, 0);

    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 (!multiLine || l->arrowType || 
        ((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);
    l->arrowType = False;

    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 (multiLine && l->tracking && l->drawn)
	DrawArrow(w, l->drawable, l->gcx,
		  l->startX, l->startY, l->endX, l->endY, l->arrowType, 1);

    XtFree((XtPointer) l);
}

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

    l->tracking = False;
    l->gcx = GetGCX(w);
    l->arrowType = True;

    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 (multiLine && l->tracking && l->drawn)
	DrawArrow(w, l->drawable, l->gcx,
		  l->startX, l->startY, l->endX, l->endY, l->arrowType, 1);

    XtFree((XtPointer) l);
}

Boolean
MultiGetStyle(void)
{
    return multiLine;
}

void 
MultiSetStyle(Boolean flag)
{
    multiLine = flag;
}

Boolean
TailGetStyle(void)
{
    return withTail;
}

void 
TailSetStyle(Boolean flag)
{
    withTail = flag;
}

Boolean
ArrowGetStyle(void)
{
    return withArrow;
}

void 
ArrowSetStyle(Boolean flag)
{
    withArrow = flag;
}

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;
}
