
/* +-------------------------------------------------------------------+ */
/* | 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: freehandOp.c,v 1.17 2005/03/20 20:15:32 demailly Exp $ */

#ifdef __VMS
#define XtDisplay XTDISPLAY
#define XtWindow XTWINDOW
#endif

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

#define CONTOUR		0x1
#define FILL		0x2
#define SELECT		0x4

#define IsContour(x)	(x & CONTOUR)
#define IsFill(x)	(x & FILL)
#define IsSelect(x)	(x & SELECT)

typedef struct {
    Boolean flag, tracking, pressed;
    GC gcx, lgc, fgc;
    int npoints, size;
    XPoint *points, *winpts;
    XRectangle pixBox;
} LocalInfo;

char freehandMode = 0;

static void 
motion(Widget w, LocalInfo * l, XMotionEvent * event, OpInfo * info)
{
    int i, x1,y1, x2, y2, zoom;

    if (!l->pressed) return;
    if (!l->tracking) return;
    if (l->npoints == -1) return;

    if (info->surface != opPixmap)
	return;

    i = (l->npoints>0)? l->npoints - 1 : 0;
    XtVaGetValues(w, XtNzoom, &zoom, NULL);

    x1 = l->winpts[i].x;
    y1 = l->winpts[i].y;

    if (zoom>0) {
        x2 = event->x * zoom;
        y2 = event->y * zoom;
    } else {
        zoom = -zoom; 
        x2 = event->x / zoom;
        y2 = event->y / zoom;
    }

    XDrawLine(XtDisplay(w), XtWindow(w), l->gcx, x1, y1, x2, y2);
    XDrawLine(XtDisplay(w), XtWindow(w), l->gcx, x2, y2, x2, y2);

    l->points[l->npoints].x = event->x;
    l->points[l->npoints].y = event->y;
    l->winpts[l->npoints].x = x2;
    l->winpts[l->npoints].y = y2;
    l->npoints++;

    if (DoVxp(w)) {
        sprintf(Global.vxpinput, "\n%d,%d", event->x, event->y);
	RecordVxp(w);
    }

    if (l->npoints > l->size - 3) {
	l->size += 256;
	l->winpts = (XPoint *) XtRealloc((XtPointer) l->winpts,
					  sizeof(XPoint) * l->size);
	l->points = (XPoint *) XtRealloc((XtPointer) l->points,
					 sizeof(XPoint) * l->size);
    }
}

static void 
press(Widget w, LocalInfo * l, XButtonEvent * event, OpInfo * info)
{
    PaintWidget pw = (PaintWidget) w;
    int i, zoom;

    l->pressed = True;

    if (!l->tracking) {
        if (pw->paint.region.isVisible) {
            PwRegionFinish(w, True);
            pw->paint.region.isVisible = False;
            l->pressed = False;
            return;
	}
        l->lgc = info->first_gc;
        l->fgc = info->second_gc;
        l->npoints = 0;
        UndoStartPoint(w, info, event->x, event->y);
    }

    if (event->button >= Button4) return;

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

    if ((event->state & AllButtonsMask) != 0)
	return;
    if (event->button == Button3) return;

    l->points[l->npoints].x = event->x;
    l->points[l->npoints].y = event->y;
    XtVaGetValues(w, XtNzoom, &zoom, NULL);

    if (zoom>0) {
        l->winpts[l->npoints].x = event->x * zoom;
        l->winpts[l->npoints].y = event->y * zoom;
    } else {
        zoom = - zoom;
        l->winpts[l->npoints].x = event->x / zoom;
        l->winpts[l->npoints].y = event->y / zoom;
    }

    if (l->npoints > 0) {
        i = l->npoints - 1;
	XDrawLine(XtDisplay(w), XtWindow(w), l->gcx,
		  l->winpts[i].x, l->winpts[i].y, 
		  l->winpts[l->npoints].x, l->winpts[l->npoints].y);
	XDrawLine(XtDisplay(w), XtWindow(w), l->gcx,
		  l->winpts[l->npoints].x, l->winpts[l->npoints].y,
		  l->winpts[l->npoints].x, l->winpts[l->npoints].y);
    }

    if (DoVxp(w)) {
        if (l->npoints == 0) {
            char *descr, *filled;
	    if (IsSelect(l->flag))
	        descr = "20 freehandselect";
	    else
	    if (IsFill(l->flag))
	        descr = "19 freehandfilled";
	    else
	        descr = "18 freehand";
	    sprintf(Global.vxpinput, "\n*%s\n%d,%d", descr,
		    l->winpts[0].x, l->winpts[0].y);
        } else
	    sprintf(Global.vxpinput, "\n%d,%d",
		    l->winpts[l->npoints].x, l->winpts[l->npoints].y);
	RecordVxp(w);
    }
    
    l->npoints++;
    l->tracking = True;    
}

static void
contour( LocalInfo * l, Drawable d, GC gc)
{
    int i, x, y;
    XDrawLines(Global.display, d, l->gcx,
	       l->winpts, l->npoints, CoordModeOrigin);
    i = l->npoints - 1;
    x = l->winpts[i].x;
    y = l->winpts[i].y;
    XDrawLine(Global.display, d, l->gcx, x, y, x, y);
}

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

    contour(l, XtWindow(w), l->gcx);
    XtVaGetValues(w, XtNzoom, &zoom, NULL);

    switch (keysym) {
        case XK_Up:
            for (i=0; i<l->npoints; i++) {
  	        --l->winpts[i].y;
	        if (zoom>0) {
	            l->points[i].y = l->winpts[i].y / zoom;
	        } else {
		    l->points[i].y += zoom;
		}
	    }
	    break;
        case XK_Down:
            for (i=0; i<l->npoints; i++) {
  	        ++l->winpts[i].y;
	        if (zoom>0) {
	            l->points[i].y = l->winpts[i].y / zoom;
	        } else {
		    l->points[i].y -= zoom;
		}
	    }
            break;
        case XK_Left:
            for (i=0; i<l->npoints; i++) {
  	        --l->winpts[i].x;
	        if (zoom>0) {
	            l->points[i].x = l->winpts[i].y / zoom;
	        } else {
		    l->points[i].x += zoom;
		}
	    }
            break;
        case XK_Right:
            for (i=0; i<l->npoints; i++) {
  	        ++l->winpts[i].x;
	        if (zoom>0) {
	            l->points[i].x = l->winpts[i].y / zoom;
	        } else {
		    l->points[i].x -= zoom;
		}
	    }
            break;
    }

    contour(l, XtWindow(w), l->gcx);
}

static Boolean
lasso(Widget w, LocalInfo * l, XButtonEvent * event,
      OpInfo * info, Pixmap * mask)
{
    int xmax, xmin, ymax, ymin;
    int width, height, i;
    GC gc;
    XPoint tmp;

    l->tracking = False;

    xmin = xmax = l->points[0].x;
    ymin = ymax = l->points[0].y;
    for (i = l->npoints - 1; i > 0; i--) {
	xmin = MIN(xmin, l->points[i].x);
	ymin = MIN(ymin, l->points[i].y);
	xmax = MAX(xmax, l->points[i].x);
	ymax = MAX(ymax, l->points[i].y);

	l->winpts[i].x = l->winpts[i].x - l->winpts[i - 1].x;
	l->winpts[i].y = l->winpts[i].y - l->winpts[i - 1].y;
	l->points[i].x = l->points[i].x - l->points[i - 1].x;
	l->points[i].y = l->points[i].y - l->points[i - 1].y;
    }

    XYtoRECT(xmin, ymin, xmax, ymax, &l->pixBox);
    XtVaGetValues(w, XtNdrawWidth, &width, XtNdrawHeight, &height, NULL);
    if (l->pixBox.x + l->pixBox.width > width)
	l->pixBox.width = width - l->pixBox.x;
    if (l->pixBox.y + l->pixBox.height > height)
	l->pixBox.height = height - l->pixBox.y;
    if (l->pixBox.x < 0) {
	l->pixBox.width += l->pixBox.x;
	l->pixBox.x = 0;
    }
    if (l->pixBox.y < 0) {
	l->pixBox.height += l->pixBox.y;
	l->pixBox.y = 0;
    }

    *mask = XCreatePixmap(XtDisplay(w), XtWindow(w),
			  l->pixBox.width, l->pixBox.height, 1);
    gc = XCreateGC(XtDisplay(w), *mask, 0, 0);
    XSetFunction(XtDisplay(w), gc, GXclear);
    XFillRectangle(XtDisplay(w), *mask, gc, 0, 0,
		   l->pixBox.width, l->pixBox.height);
    XSetFunction(XtDisplay(w), gc, GXset);
    tmp = l->points[0];
    l->points[0].x = l->points[0].x - xmin;
    l->points[0].y = l->points[0].y - ymin;
    XFillPolygon(XtDisplay(w), *mask, gc, l->points, l->npoints,
		 Complex, CoordModePrevious);
    l->points[0] = tmp;

    XFreeGC(XtDisplay(w), gc);

    return True;
}

static void 
release(Widget w, LocalInfo * l, XButtonEvent * event, OpInfo * info)
{
    int mask;
    Pixmap mpix;
    KeySym keysym;
    char buf[21];
    int i;

    if (!l->tracking) return;

    if (event->type == KeyRelease) {
        i = XLookupString((XKeyEvent *)event, buf, sizeof(buf)-1, &keysym, NULL);

        if (keysym == XK_Escape) {
            contour(l, XtWindow(w), l->gcx);
            l->npoints = -1;
            l->tracking = False;
      	} 
        else
	if (keysym == XK_Return) {
            i = (l->npoints>0)? l->npoints - 1 : 0;
            goto finish;
        } else
        if (keysym == XK_Right || keysym == XK_Left ||
            keysym == XK_Up || keysym == XK_Down) 
            shift(w, l, keysym);
        else
        if (keysym == XK_Delete || keysym == XK_BackSpace) {            
	    if (l->npoints > 0) {
                contour(l, XtWindow(w), l->gcx);
                --l->npoints;
                contour(l, XtWindow(w), l->gcx);
	    } else {
            /* if just one point left, restart from scratch */
                l->npoints = -1;
                l->tracking = False;
	    }
        }
        event->type = None;
        return;
    }

    if (!l->pressed) return;
    l->pressed = False;

    /*
    **	Check to make sure all buttons are up, before doing this
     */
    if (event->button >= Button4) return;      
    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 (event->button == Button1 || event->button == Button3) return;

 finish:
    contour(l, XtWindow(w), l->gcx);
    l->tracking = False;
    l->pressed = False;

    if (freehandMode == 1 || !IsContour(l->flag)) {
        l->points[l->npoints] = l->points[0];
        l->winpts[l->npoints] = l->winpts[0];
        l->npoints++;
    }

    if (IsFill(l->flag)) {
	if (!info->isFat)
	    XFillPolygon(XtDisplay(w), XtWindow(w), l->fgc,
			 l->points, l->npoints, Complex, CoordModeOrigin);
	XFillPolygon(XtDisplay(w), info->drawable, l->fgc,
		     l->points, l->npoints, Complex, CoordModeOrigin);
    }

    if (IsContour(l->flag) || IsFill(l->flag)) {
        SetCapAndJoin(w, l->lgc,
                  ((Global.cap)?Global.cap-1:CapButt),
                  ((Global.join)?Global.join-1:JoinMiter));
        if (!info->isFat)
            XDrawLines(XtDisplay(w), XtWindow(w), l->lgc,
	               l->points, l->npoints, CoordModeOrigin);
        XDrawLines(XtDisplay(w), info->drawable, l->lgc,
	               l->points, l->npoints, CoordModeOrigin);
    }

    if (IsSelect(l->flag)) {
        mpix = None;
        if (lasso(w, l, event, info, &mpix) == False) {
	    PwRegionFinish(w, True);
	}
	if (cutMode && !chromaCut(w, l?&l->pixBox:NULL, &mpix)) {
	    PwRegionFinish(w, True);
	    return;
	}
        PwRegionSet(w, &l->pixBox, None, mpix);
    }

    for (i=0; i<l->npoints; i++) 
        UndoGrow(w, l->points[i].x, l->points[i].y);

    PwUpdate(w, NULL, False);
}

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

    XtVaSetValues(w, XtNcompress, False, NULL);
    l->gcx = GetGCX(w);
    l->flag = CONTOUR;

    l->size = 256;
    l->winpts = (XPoint *) XtCalloc(sizeof(XPoint), l->size);
    l->points = (XPoint *) XtCalloc(sizeof(XPoint), l->size);
    l->npoints = -1;
    l->tracking = False;
    l->pressed = False;

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

    return l;
}
void 
FreehandRemove(Widget w, void *l)
{
    OpRemoveEventHandler(w, opPixmap, ButtonPressMask, FALSE,
			 (OpEventProc) press, l);
    OpRemoveEventHandler(w, opPixmap, PointerMotionMask, FALSE,
			 (OpEventProc) motion, l);
    OpRemoveEventHandler(w, opPixmap,
                         KeyReleaseMask|ButtonReleaseMask, FALSE,
			 (OpEventProc) release, l);

    XtFree((XtPointer) ((LocalInfo *) l)->winpts);
    XtFree((XtPointer) ((LocalInfo *) l)->points);
    XtFree((XtPointer) l);
}

void *
FilledFreehandAdd(Widget w)
{
    LocalInfo *l = (LocalInfo *) XtMalloc(sizeof(LocalInfo));
    l->gcx = GetGCX(w);
    l->flag = FILL;

    l->size = 256;
    l->winpts = (XPoint *) XtCalloc(sizeof(XPoint), l->size);
    l->points = (XPoint *) XtCalloc(sizeof(XPoint), l->size);
    l->npoints = -1;
    l->tracking = False;
    l->pressed = False;

    XtVaSetValues(w, XtNcompress, True, NULL);

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

    SetPencilCursor(w);

    return l;
}

void 
FilledFreehandRemove(Widget w, void *l)
{
    OpRemoveEventHandler(w, opPixmap, ButtonPressMask, FALSE,
			 (OpEventProc) press, l);
    OpRemoveEventHandler(w, opPixmap, PointerMotionMask, FALSE,
			 (OpEventProc) motion, l);
    OpRemoveEventHandler(w, opPixmap,
                         KeyReleaseMask|ButtonReleaseMask, FALSE,
			 (OpEventProc) release, l);
    XtFree((XtPointer) ((LocalInfo *) l)->winpts);
    XtFree((XtPointer) ((LocalInfo *) l)->points);
    XtFree((XtPointer) l);
}

void *
SelectFreehandAdd(Widget w)
{
    LocalInfo *l = (LocalInfo *) XtMalloc(sizeof(LocalInfo));
    l->gcx = GetGCX(w);
    l->flag = SELECT;

    l->size = 256;
    l->winpts = (XPoint *) XtCalloc(sizeof(XPoint), l->size);
    l->points = (XPoint *) XtCalloc(sizeof(XPoint), l->size);
    l->npoints = -1;
    l->tracking = False;
    l->pressed = False;

    XtVaSetValues(w, XtNcompress, True, NULL);

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

    SetPencilCursor(w);

    return l;
}

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

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

    XtFree((XtPointer) ((LocalInfo *) l)->winpts);
    XtFree((XtPointer) ((LocalInfo *) l)->points);
    XtFree((XtPointer) l);
}
