/* +-------------------------------------------------------------------+ */
/* | 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.                                              | */
/* |                                                                   | */
/* +-------------------------------------------------------------------+ */

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

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

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 & (Button1Mask|Button2Mask|Button3Mask|Button4Mask|Button5Mask)) != 0)
		return;

	if (event->button == Button1 && ((!l->tracking && l->isRay) || !l->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)
			XDrawLine(XtDisplay(w), info->drawable, l->gcx,
					l->startX, l->startY, l->endX, l->endY);
		l->tracking = False;
		return;
	}

	if (l->tracking) {
		if (l->drawn)
			XDrawLine(XtDisplay(w), info->drawable, l->gcx,
					l->startX, l->startY, l->endX, l->endY);

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

		XDrawLine(XtDisplay(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) {
		XDrawLine(XtDisplay(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)) {
		XDrawLine(XtDisplay(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)
		return;

	/*
	**  Check to make sure all buttons are up, before doing this
	*/
	mask = Button1Mask|Button2Mask|Button3Mask|Button4Mask|Button5Mask;
	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) {
		XDrawLine(XtDisplay(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;
	}

	XDrawLine(XtDisplay(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);
	}
}

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

	l->isRay = False;
	l->tracking = False;
	l->gcx   = GetGCX(w);
        XtVaSetValues(w, XtNcompress, True, NULL);

	OpAddEventHandler(w, opWindow, ButtonPressMask, FALSE, (OpEventProc)press, l);
	OpAddEventHandler(w, opWindow, ButtonMotionMask, FALSE, (OpEventProc)motion, l);
	OpAddEventHandler(w, opWindow|opPixmap, ButtonReleaseMask, FALSE, (OpEventProc)release, l);
	SetCrossHairCursor(w);
	return l;
}
void LineRemove(Widget w, LocalInfo *l)
{
	OpRemoveEventHandler(w, opWindow, ButtonPressMask, FALSE, (OpEventProc)press, l);
	OpRemoveEventHandler(w, opWindow, ButtonMotionMask, FALSE, (OpEventProc)motion, l);
	OpRemoveEventHandler(w, opWindow|opPixmap, ButtonReleaseMask, FALSE, (OpEventProc)release, l);
	XtFree((XtPointer)l);
}
void *RayAdd(Widget w)
{
	LocalInfo	*l = (LocalInfo *)XtMalloc(sizeof(LocalInfo));

	l->isRay = True;
	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 RayRemove(Widget w, LocalInfo *l)
{
	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 (l->tracking && l->drawn)
		XDrawLine(XtDisplay(w), l->drawable, l->gcx,
				l->startX, l->startY, l->endX, l->endY);

	XtFree((XtPointer)l);
}
