/* +-------------------------------------------------------------------+ */
/* | 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.3 1996/04/19 09:09:01 torsten Exp $ */

#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <math.h>
#include "xpaint.h"
#include "misc.h"
#include "Paint.h"
#include "ops.h"

static Boolean isRay = False;
static Boolean isArrow = False;

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

static void 
DrawArrow (Widget w, Drawable d, GC gc, int stX,int stY, int enX, int enY)
{
  XPoint xpoints[3];
  Display *dpy; 
  int lw;

  int l, m, ux, uy, lx=0, ly=0, mx=0, my=0;
  int DeltaX, DeltaY;
  float norm;

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

  if (isArrow) {
    DeltaX = enX - stX;
    DeltaY = enY - stY;
    norm = sqrt(pow(DeltaX, 2) + pow(DeltaY, 2)) + 0.001;
   
    if ( lw <= 2 ) {
       l = 9;
       m = 5;
    } else {
       l = 4*lw+1;
       m = 2*l/5;
    }
   
    lx = (int) (0.49 + (float) (abs(DeltaX) * l) / norm);
    ly = (int) (0.49 + (float) (abs(DeltaY) * l) / norm);
    mx = (int) (0.49 + (float) (abs(DeltaY) * m) / norm);
    my = (int) (0.49 + (float) (abs(DeltaX) * m) / 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;
    }
  }
   
  XDrawLine(dpy, d, gc, stX, stY, enX-ux, enY-uy);

  if (isArrow) {
    xpoints[0].x = enX;
    xpoints[0].y = enY;
    xpoints[1].x = enX - lx + mx;
    xpoints[1].y = enY - ly + my;
    xpoints[2].x = enX - lx - mx;
    xpoints[2].y = enY - ly - my;
   
    XFillPolygon(dpy, d, gc, xpoints, 3, Complex, CoordModeOrigin);
  }
}
 
static void 
press(Widget w, LocalInfo * l, XButtonEvent * event, OpInfo * info)
{
    /*
    **	Check to make sure all buttons are up, before doing this
     */
    if ((event->state & AllButtonsMask) != 0)
	return;

    if (event->button == Button1 && ((!l->tracking && isRay) || !isRay)) {
	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->didSet = False;
	l->tracking = True;
    } else if (event->button == Button2) {
	if (l->drawn)
	    DrawArrow(w, info->drawable, l->gcx,
		      l->startX, l->startY, l->endX, l->endY);
	l->tracking = False;
	l->drawn = False;
        return;
    }
    if (l->tracking) {
	if (l->drawn)
	    DrawArrow(w, info->drawable, l->gcx,
		      l->startX, l->startY, l->endX, l->endY);

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

	DrawArrow(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)
	return;

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

static void 
release(Widget w, LocalInfo * l, XButtonEvent * event, OpInfo * info)
{
    XRectangle undo;
    int mask;

    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;

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

    if (info->surface == opWindow && info->isFat)
	return;

    if (!l->didUndo && info->surface == opPixmap) {
	UndoStart(w, info);
	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;
	    }
	}

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

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

    if (info->surface == opPixmap) {
	XYtoRECT(l->rx, l->ry, l->endX, l->endY, &undo);

	if (l->didSet) {
	    UndoGrow(w, l->endY, l->endY);
	} else {
	    UndoSetRectangle(w, &undo);
	    l->didSet = True;
	}

	PwUpdate(w, &undo, False);
    }

    if (!isRay)
        l->tracking = False;
}

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

    l->tracking = False;
    l->gcx = GetGCX(w);
    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, 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, ButtonReleaseMask, FALSE,
			 (OpEventProc) release, l);

    if (isRay && l->tracking && l->drawn)
	DrawArrow(w, l->drawable, l->gcx,
		  l->startX, l->startY, l->endX, l->endY);

    XtFree((XtPointer) l);
}

Boolean
RayGetStyle(void)
{
    return isRay;
}

void 
RaySetStyle(Boolean flag)
{
    isRay = flag;
}

Boolean
ArrowGetStyle(void)
{
    return isArrow;
}

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

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

    len = strlen(dashstyle);
    if (len > 48) {
        dashstyle[48] = '\0';
	len = 48;
    }
    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;
}

