
/* +-------------------------------------------------------------------+ */
/* | Copyright 2000, J.-P. Demailly (demailly@fourier.ujf-grenoble.fr) | */
/* |                                                                   | */
/* | 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: splineOp.c,v 1.2 2000/10/02 21:06:44 torsten Exp $ */

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

#define MAXPOINTS 198
#define SUBDIV 50
#define FLOAT float

enum {OPEN=0, CLOSED, CLOSEDUP};
enum {FINISH, ALLSTEPS, ERASE, DRAW};

static int Mode = 0;
static int ModeCtrl;
static int ModeShift;
static int Filled = 0;

typedef struct {
    int zoom, rx, ry, mode, npoints, nprev;
    XPoint points[MAXPOINTS + 2];
    XPoint interm1[MAXPOINTS + 2];
    XPoint interm2[MAXPOINTS + 2];
    Boolean breakpt[MAXPOINTS + 2];
    /*
    **  Borrowed from my info structure.
     */
    GC gcx, fgc, sgc;
    Pixmap pixmap;
    Boolean isFat;
} LocalInfo;

void
SplineSetMode(int mode)
{
    Mode = mode;
}

static void 
XDrawContour(Widget w, Drawable d, LocalInfo *l, int flag)
{
    Display *dpy;
    int n, i, j, imax, i0, i1=0, zoom, rx, ry;
    int u, v, up, vp;
    FLOAT ax=0.0, ay=0.0, bx, by, cx, cy, r=0.0, s, t;
    XPoint *xp;
    GC gc;

    dpy = XtDisplay(w);

    if (flag == FINISH && d == l->pixmap) {
        zoom = l->zoom;
	rx = l->rx;
	ry = l->ry;
    } else {
        zoom = 1;
	rx = 0;
	ry = 0;
    }

    if (flag == ERASE || flag == ALLSTEPS) {
        n = l->nprev;
    }
    else {
        n = l->npoints;
	l->mode = ModeCtrl;
    }

    if (n <= 0)
        return;

    if (flag == FINISH)
        gc = l->fgc;
    else
        gc = l->gcx;

    if (n == 1) {
         xp = (XPoint *) xmalloc(2 * sizeof(XPoint));
	 xp[0].x = rx + l->points[0].x / zoom;
	 xp[0].y = ry + l->points[0].y / zoom;
	 xp[1].x = rx + l->points[1].x / zoom;
	 xp[1].y = ry + l->points[1].y / zoom;
         XDrawLine(dpy, d, gc, xp[0].x, xp[0].y, xp[0].x, xp[0].y);
         XDrawLine(dpy, d, gc, xp[0].x, xp[0].y, xp[1].x, xp[1].y);
         if (flag == FINISH && d == l->pixmap)
             for (i=0; i<=1; i++) UndoGrow(w, xp[i].x, xp[i].y);
         l->mode = ModeCtrl;
         l->nprev = l->npoints;
	 free(xp);
         return;
    }

    if (l->mode != OPEN) {
        j = n+2;
        l->points[n+1].x = l->points[0].x;
        l->points[n+1].y = l->points[0].y;
	l->breakpt[n+1] = l->breakpt[0];
        l->points[n+2].x = l->points[1].x;
        l->points[n+2].y = l->points[1].y;
	l->breakpt[n+2] = l->breakpt[1];
    }
    else 
        j = n;

    i0 = n-2;
    if (i0 < 0)
        i0 = 0;
    for (i = i0; i < j; i++) {
        s = r;
	bx = ax;
	by = ay;
	ax = l->points[i+1].x - l->points[i].x;
	ay = l->points[i+1].y - l->points[i].y;
	r = ax*ax + ay*ay + 1E-9;
	ax = ax/r; ay = ay/r;
	r = sqrt(r)/3.0;
	if (i > i0) { 
	    if (l->breakpt[i]) {
	        t = 3.0*r*r;
	        l->interm1[i].x = l->points[i].x + (short)(t*ax);
	        l->interm1[i].y = l->points[i].y + (short)(t*ay);
	        t = 3.0*s*s;
	        l->interm2[i-1].x = l->points[i].x - (short)(t*bx);
	        l->interm2[i-1].y = l->points[i].y - (short)(t*by);
	    } else {
	        cx = (ax+bx)/2; 
                cy = (ay+by)/2;
	        t = sqrt(cx*cx + cy*cy + 1E-9);
	        cx = cx/t; 
	        cy = cy/t;
	        l->interm1[i].x = l->points[i].x + (short)(r*cx);
	        l->interm1[i].y = l->points[i].y + (short)(r*cy);
	        l->interm2[i-1].x = l->points[i].x - (short)(s*cx);
	        l->interm2[i-1].y = l->points[i].y - (short)(s*cy);
	    }
	    if ((i == 1) && (l->mode == OPEN || l->breakpt[i])) {
	        t = 3.0*s*s;
		l->interm1[0].x = l->interm2[0].x - (short)(t*bx);
		l->interm1[0].y = l->interm2[0].y - (short)(t*by);
	    }
	    if ((i == n-1) && (l->mode == OPEN || l->breakpt[i])) {
	        t = 3.0*r*r;
		l->interm2[i].x = l->interm1[i].x + (short)(t*ax);
		l->interm2[i].y = l->interm1[i].y + (short)(t*ay);
	    }
	    if (i == n+1 && !l->breakpt[0]) {
	        l->interm1[0].x = l->interm1[i].x;
		l->interm1[0].y = l->interm1[i].y;
	    }
	}
    }
 
    imax = n;
    if (((l->mode == CLOSED) && (flag == FINISH)) || (l->mode == CLOSEDUP))
        ++imax;

    if (flag <= ALLSTEPS) {
        i0 = -1;
	i1 = 0;
    }
    if (flag == ERASE) {
        if (ModeCtrl == OPEN)
	    i0 = -1;
	else
            i0 = 0;
	i1 = l->npoints-2;
    }
    if (flag == DRAW) {
        if (ModeCtrl == OPEN)
            i0 = -1;
	else
            i0 = 0;
	i1 = n-2;
    } 
    
    if (flag == FINISH) {
        n = 0;
        xp = (XPoint *) xmalloc((imax * SUBDIV + 2) * sizeof(XPoint));
	xp[0].x = rx + l->points[0].x / zoom;
	xp[0].y = ry + l->points[0].y / zoom;
	if (d == l->pixmap)
            UndoGrow(w, xp[0].x, xp[0].y);
        for (i = 0; i < imax; i++) {
	    for (j = 1; j <= SUBDIV; j++) {
		t = ((FLOAT) j)/((FLOAT) SUBDIV);
		s = 1.0-t;
		u = (int) (s*s*(s*l->points[i].x+3.0*t*l->interm1[i].x)+
			     t*t*(t*l->points[i+1].x+3.0*s*l->interm2[i].x));
		v = (int) (s*s*(s*l->points[i].y+3.0*t*l->interm1[i].y)+
		       t*t*(t*l->points[i+1].y+3.0*s*l->interm2[i].y));
		xp[++n].x = rx + u / zoom;
		xp[n].y = ry + v / zoom;
		if (d == l->pixmap)
		    UndoGrow(w, xp[n].x, xp[n].y);
	    }
	}
	if (Filled) {
	    XFillPolygon(dpy, d, l->sgc, xp, n+1, Complex, CoordModeOrigin);
	    if (l->mode == OPEN) 
	        xp[++n] = xp[0];
	}
	XDrawLines(dpy, d, l->fgc, xp, n+1, CoordModeOrigin);
	free(xp);
    } else
    for (i = 0; i < imax; i++) {
        if ((i <= i0) || (i >= i1)) {
	    u = l->points[i].x;
	    v = l->points[i].y;
	    for (j = 1; j <= SUBDIV; j++) {
	        up = u;
		vp = v;
		t = ((FLOAT) j)/((FLOAT) SUBDIV);
		s = 1.0-t;
		u = (int) (s*s*(s*l->points[i].x+3.0*t*l->interm1[i].x)+
			     t*t*(t*l->points[i+1].x+3.0*s*l->interm2[i].x));
		v = (int) (s*s*(s*l->points[i].y+3.0*t*l->interm1[i].y)+
		       t*t*(t*l->points[i+1].y+3.0*s*l->interm2[i].y));
		XDrawLine(dpy, d, gc, up, vp, up, vp);
		XDrawLine(dpy, d, gc, up, vp, u, v);
	    }
	}
    }

    l->mode = ModeCtrl;
    l->nprev = l->npoints;
}

static void 
finish(Widget w, LocalInfo * l)
{
    XRectangle undo;
    int width, height;

    if (l->npoints < 0)
        return;

    XDrawContour(w, XtWindow(w), l, ALLSTEPS);
    --l->npoints;

    if (l->npoints > 0) {
        SetCapAndJoin(w, l->fgc, JoinRound);
        if (!l->isFat)
           XDrawContour(w, XtWindow(w), l, FINISH);
        XDrawContour(w, l->pixmap, l, FINISH);
    }

    XtVaGetValues(w, XtNdrawWidth, &width, 
                     XtNdrawHeight, &height, NULL);
    undo.x = 0;
    undo.y = 0;
    undo.width = width;
    undo.height = height;

    if (l->isFat)
	PwUpdate(w, &undo, True);
    else
	PwUpdate(w, &undo, False);

    l->npoints = -1;
    l->nprev = -1;
}

static void 
check_modifiers(XButtonEvent * event)
{
    ModeCtrl = Mode;
    if (event->state & ControlMask) {
        if (Mode == CLOSED) ModeCtrl = CLOSEDUP;
        if (Mode == CLOSEDUP) ModeCtrl = CLOSED;
    }
    ModeShift = (event->state & ShiftMask)? 1 : 0;
}

static void 
press(Widget w, LocalInfo * l, XButtonEvent * event, OpInfo * info)
{
    if (event->button == Button3) return;
    check_modifiers(event);

    if ((l->npoints < 0) && (event->button == Button1)) {
      if (info->surface == opPixmap) {

	l->isFat = info->isFat;
	l->fgc = info->first_gc;
	l->sgc = info->second_gc;
	XtVaGetValues(w, XtNzoom, &l->zoom, NULL);
        l->rx = info->x - l->points[0].x / l->zoom;
        l->ry = info->y - l->points[0].y / l->zoom;
	l->npoints = 0;
        l->mode = ModeCtrl;
        UndoStart(w, info);
        l->pixmap = info->drawable;
      } else {
	l->points[0].x = event->x;
	l->points[0].y = event->y;
        l->breakpt[0] = (ModeShift)? True : False;
      }
      return;        
    }
 
    if ((l->npoints >= 0) && (event->button == Button2) &&
	(info->surface == opWindow)) {
  	finish(w, l);
	return;
    }
}

static void 
release(Widget w, LocalInfo * l, XButtonEvent * event, OpInfo * info)
{

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

    if (l->npoints < 0) 
        return;

    if (l->npoints == MAXPOINTS - 1)
	return;

    if ((event->button == Button1) && (info->surface == opWindow)) {
        check_modifiers(event);
	if (l->npoints>0 &&
            event->x==l->points[l->npoints-1].x &&
	    event->y==l->points[l->npoints-1].y) return;
        ++l->npoints;
	l->points[l->npoints].x = event->x;
	l->points[l->npoints].y = event->y;
    }
}

static void 
motion(Widget w, LocalInfo * l, XMotionEvent * event, OpInfo * info)
{
    /*
    **  Haven't done the first button press
     */
    if (l->npoints < 0)
	return;

    if ((event->x != l->points[l->npoints].x) ||
	(event->y != l->points[l->npoints].y)) {
        check_modifiers((XButtonEvent *)event);
        XDrawContour(w, info->drawable, l, ERASE);
        l->points[l->npoints].x = event->x;
        l->points[l->npoints].y = event->y;
        l->breakpt[l->npoints-1] = (ModeShift)? True : False;
	XDrawContour(w, info->drawable, l, DRAW);
    }
}

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

    Filled = 0;
    ModeCtrl = Mode;
    l->npoints = -1;
    l->nprev = -1;
    l->gcx = GetGCX(w);

    XtVaSetValues(w, XtNcompress, True, NULL);

    OpAddEventHandler(w, opWindow | opPixmap, 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 
SplineRemove(Widget w, void *l)
{
    OpRemoveEventHandler(w, opWindow | opPixmap, ButtonPressMask, FALSE,
			 (OpEventProc) press, l);
    OpRemoveEventHandler(w, opWindow, PointerMotionMask, FALSE,
			 (OpEventProc) motion, l);
    OpRemoveEventHandler(w, opWindow | opPixmap, ButtonReleaseMask, FALSE,
			 (OpEventProc) release, l);

    finish(w, (LocalInfo *) l);
    XtFree((XtPointer) l);
}

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

    Filled = 1;
    ModeCtrl = Mode;
    l->npoints = -1;
    l->nprev = -1;
    l->gcx = GetGCX(w);

    XtVaSetValues(w, XtNcompress, True, NULL);

    OpAddEventHandler(w, opWindow | opPixmap, 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 
FSplineRemove(Widget w, void *l)
{
    OpRemoveEventHandler(w, opWindow | opPixmap, ButtonPressMask, FALSE,
			 (OpEventProc) press, l);
    OpRemoveEventHandler(w, opWindow, PointerMotionMask, FALSE,
			 (OpEventProc) motion, l);
    OpRemoveEventHandler(w, opWindow | opPixmap, ButtonReleaseMask, FALSE,
			 (OpEventProc) release, l);

    finish(w, (LocalInfo *) l);
    XtFree((XtPointer) l);
}
