
/* +-----------------------------------------------------------------------+ */
/* | Copyright 2000, J.-P. Demailly (jean-pierre.demailly@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.15 2005/03/20 20:15:32 demailly 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 SUBDIV 50

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

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

typedef struct {
    int rx, ry, mode, npoints, nprev;
    XPoint *points;
    XPoint *interm1;
    XPoint *interm2;
    Boolean *breakpt;
    /*
    **  Borrowed from my info structure.
     */
    GC gcx, gc1, gc2;
    Pixmap pixmap;
    Boolean isFat;
} LocalInfo;

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

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

    dpy = XtDisplay(w);
    iswindow = (d==XtWindow(w));
    XtVaGetValues(w, XtNzoom, &zoom, NULL);

    if (flag == FINISH && d == l->pixmap) {
        z = zoom;
        rx = l->rx;
	ry = l->ry;
    } else {
        z = 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->gc1;
    else
        gc = l->gcx;

    if (n == 1) {
         xp = (XPoint *) xmalloc(2 * sizeof(XPoint));
         if (z>0) {
	     xp[0].x = rx + l->points[0].x / z;
	     xp[0].y = ry + l->points[0].y / z;
	     xp[1].x = rx + l->points[1].x / z;
	     xp[1].y = ry + l->points[1].y / z;
	 } else {
	     xp[0].x = rx + l->points[0].x;
	     xp[0].y = ry + l->points[0].y;
	     xp[1].x = rx + l->points[1].x;
	     xp[1].y = ry + l->points[1].y;
	 }
         if (iswindow && zoom<0) {
	     XDrawLine(dpy, d, gc, xp[0].x/(-zoom), xp[0].y/(-zoom), 
                                   xp[0].x/(-zoom), xp[0].y/(-zoom));
             XDrawLine(dpy, d, gc, xp[0].x/(-zoom), xp[0].y/(-zoom),
                                   xp[1].x/(-zoom), xp[1].y/(-zoom));
	 } else {
             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));
        if (z>0) {
	    xp[0].x = rx + l->points[0].x / z;
	    xp[0].y = ry + l->points[0].y / z;
	} else {
	    xp[0].x = rx + l->points[0].x;
	    xp[0].y = ry + l->points[0].y;
	}
	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 = ((double) j)/((double) 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));
                if (z>0) {
		    xp[++n].x = rx + u / z;
		    xp[n].y = ry + v / z;
		} else {
		    xp[++n].x = rx + u;
		    xp[n].y = ry + v;
		}
		if (d == l->pixmap)
		    UndoGrow(w, xp[n].x, xp[n].y);
	    }
	}
	if (Filled==1)
	    XFillPolygon(dpy, d, l->gc2, xp, n+1, Complex, CoordModeOrigin);
	if (Filled && l->mode == OPEN) 
	        xp[++n] = xp[0];
        if (Filled<=1)
	    XDrawLines(dpy, d, l->gc1, xp, n+1, CoordModeOrigin);
	if (Filled==2 && d!=XtWindow(w)) {
	    CreatePolygonalRegion(w, xp, n);
	    free(xp);
            return;
	}
    } 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 = ((double) j)/((double) 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));
                if (iswindow && zoom<0) {
		    XDrawLine(dpy, d, gc, up/(-zoom), vp/(-zoom), 
                                          up/(-zoom), vp/(-zoom));
		    XDrawLine(dpy, d, gc, up/(-zoom), vp/(-zoom),
                                          u/(-zoom), v/(-zoom));
		} else {
		    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);

    if (l->npoints > 0) {
        SetCapAndJoin(w, l->gc1,
           ((Global.cap)?Global.cap-1:CapButt),
           ((Global.join)?Global.join-1: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
redundant(LocalInfo * l)
{
    int i = l->npoints;

    if (i == 0) return;
    if (l->points[i].x == l->points[i-1].x &&
        l->points[i].y == l->points[i-1].y ) 
        --l->npoints;
}

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

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

    if ((l->npoints < 0) && (event->button == Button1)) {
      if (Filled==2 && pw->paint.region.isVisible) {
	  l->npoints = -2;
          return;
      }
      XtVaGetValues(w, XtNzoom, &zoom, NULL);
      if (info->surface == opPixmap) {
	l->isFat = info->isFat;
	l->gc1 = info->first_gc;
	l->gc2 = info->second_gc;
        if (zoom>0) {
            l->rx = info->x - l->points[0].x / zoom;
            l->ry = info->y - l->points[0].y / zoom;
	} else {
	    l->rx = info->x - l->points[0].x;
	    l->ry = info->y - l->points[0].y;
	}
	l->npoints = 0;
        l->mode = ModeCtrl;
        UndoStart(w, info);
        l->pixmap = info->drawable;
      } else {
        if (zoom>0) {
	    l->points[0].x = event->x;
	    l->points[0].y = event->y;
	} else {
	    l->points[0].x = event->x * (-zoom);
	    l->points[0].y = event->y * (-zoom);
	}
        l->breakpt[0] = (ModeShift)? True : False;
      }
      return;        
    }
 
    if ((l->npoints >= 0) && (event->button == Button2) &&
	(info->surface == opWindow)) {
        redundant(l);
  	finish(w, l);
	return;
    }
}

static void
shift(Widget w, LocalInfo *l, int dx, int dy)
{
    int i, mode;
    Drawable d = XtWindow(w);

    redundant(l);
    mode =  l->mode;
    l->nprev = l->npoints;

    XDrawContour(w, d, l, ALLSTEPS);
    for (i=0; i<=l->npoints+1; i++) {
        l->points[i].x += dx;
        l->points[i].y += dy;
    }
    for (i=0; i<=l->npoints; i++) {
        l->interm1[i].x += dx;
        l->interm1[i].y += dy;
        l->interm2[i].x += dx;
        l->interm2[i].y += dy;
    }
    l->mode = mode;
    l->nprev = l->npoints;
    XDrawContour(w, d, l, ALLSTEPS);
}

void 
releaseSplineBand(Widget w, LocalInfo * l, XButtonEvent * event, OpInfo * info)
{
    PaintWidget pw = (PaintWidget) w;
    KeySym keysym;
    int i, zoom;
    char buf[21];

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

        if (keysym == XK_Escape) {
            if (l->npoints > 0) XDrawContour(w, XtWindow(w), l, ALLSTEPS);
            l->npoints = -1;
            l->nprev = -1;
            return;
	}

        if (keysym == XK_Return) {
	    redundant(l);
	    finish(w, l);
            return;
	}

        if (keysym == XK_Right) shift(w, l, 1, 0);
        else
        if (keysym == XK_Left) shift(w, l, -1, 0);
        else
	if (keysym == XK_Down) shift(w, l, 0, 1);
        else
	if (keysym == XK_Up) shift(w, l, 0, -1);
        else
        if (keysym == XK_Delete || keysym == XK_BackSpace) {
	    if (l->npoints > 0) {
	        redundant(l);
                i = --l->npoints;
	        XDrawContour(w, XtWindow(w), l, ERASE);
                XtVaGetValues(w, XtNzoom, &zoom, NULL);
                if (zoom>0) {
	            l->points[i].x = event->x;
	            l->points[i].y = event->y;
	        } else {
	            l->points[i].x = event->x * (-zoom);
	            l->points[i].y = event->y * (-zoom);
		}
	        XDrawContour(w, XtWindow(w), l, DRAW);
	    } else {
	        l->npoints = -1;
	        l->nprev = -1;
	    }
        }
        return;
    }

    if (l->npoints==-2 && Filled==2 && pw->paint.region.isVisible) {
        PwRegionFinish(w, True);
        pw->paint.region.isVisible = False;
	l->npoints = -1;
	l->nprev = -1;
        return;
    }

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

    if (l->npoints < 0) 
        return;

    if (l->npoints >= MAXPOINTS - 3) {
        MAXPOINTS += 10;
        l->points = 
           (XPoint *) realloc(l->points, MAXPOINTS*sizeof(XPoint));
        l->interm1 = 
           (XPoint *) realloc(l->interm1, MAXPOINTS*sizeof(XPoint));
        l->interm2 = 
           (XPoint *) realloc(l->interm2, MAXPOINTS*sizeof(XPoint));
        l->breakpt = 
           (Boolean *) realloc(l->breakpt, MAXPOINTS*sizeof(Boolean));
    }

    if ((event->button == Button1) && (info->surface == opWindow)) {
        check_modifiers(event);
        XtVaGetValues(w, XtNzoom, &zoom, NULL);
        if (zoom>0) {
	    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;
	} else {
	    if (l->npoints>0 &&
                event->x==l->points[l->npoints-1].x*(-zoom) &&
	        event->y==l->points[l->npoints-1].y*(-zoom)) return;
            ++l->npoints;
	    l->points[l->npoints].x = event->x * (-zoom);
	    l->points[l->npoints].y = event->y * (-zoom);
	}
    }
}

void 
motionSplineBand(Widget w, LocalInfo * l, XMotionEvent * event, OpInfo * info)
{
    int zoom;

    /*
    **  Haven't done the first button press
     */
    if (l->npoints < 0)
	return;

    XtVaGetValues(w, XtNzoom, &zoom, NULL);

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

static
LocalInfo * createLocalInfo()
{
    LocalInfo * l;
    l = (LocalInfo *) xmalloc(sizeof(LocalInfo));
    l->points =  (XPoint *) xmalloc(MAXPOINTS*sizeof(XPoint));
    l->interm1 = (XPoint *) xmalloc(MAXPOINTS*sizeof(XPoint));
    l->interm2 = (XPoint *) xmalloc(MAXPOINTS*sizeof(XPoint));
    l->breakpt = (Boolean *) xmalloc(MAXPOINTS*sizeof(Boolean));
    return l;
}

static 
void freeLocalInfo(LocalInfo *l)
{
    free((void *)l->points);
    free((void *) l->interm1);
    free((void *) l->interm2);
    free((void *) l->breakpt);
    free((void *) l);
}

void *
SplineAdd(Widget w)
{
    LocalInfo *l;
    
    l = createLocalInfo();
    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) pressSplineBand, l);
    OpAddEventHandler(w, opWindow, PointerMotionMask, FALSE,
		      (OpEventProc) motionSplineBand, l);
    OpAddEventHandler(w, opWindow | opPixmap, 
                      KeyReleaseMask|ButtonReleaseMask, FALSE,
		      (OpEventProc) releaseSplineBand, l);
    SetCrossHairCursor(w);

    return l;
}

void 
SplineRemove(Widget w, void *l)
{
    OpRemoveEventHandler(w, opWindow | opPixmap, ButtonPressMask, FALSE,
			 (OpEventProc) pressSplineBand, l);
    OpRemoveEventHandler(w, opWindow, PointerMotionMask, FALSE,
			 (OpEventProc) motionSplineBand, l);
    OpRemoveEventHandler(w, opWindow | opPixmap, 
                         KeyReleaseMask|ButtonReleaseMask, FALSE,
			 (OpEventProc) releaseSplineBand, l);

    finish(w, (LocalInfo *) l);
    freeLocalInfo((LocalInfo *) l);
}

void *
FilledSplineAdd(Widget w)
{
    LocalInfo *l;

    l = createLocalInfo();
    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) pressSplineBand, l);
    OpAddEventHandler(w, opWindow, PointerMotionMask, FALSE,
		      (OpEventProc) motionSplineBand, l);
    OpAddEventHandler(w, opWindow | opPixmap,
                      KeyReleaseMask|ButtonReleaseMask, FALSE,
		      (OpEventProc) releaseSplineBand, l);
    SetCrossHairCursor(w);

    return l;
}

void 
FilledSplineRemove(Widget w, void *l)
{
    OpRemoveEventHandler(w, opWindow | opPixmap, ButtonPressMask, FALSE,
			 (OpEventProc) pressSplineBand, l);
    OpRemoveEventHandler(w, opWindow, PointerMotionMask, FALSE,
			 (OpEventProc) motionSplineBand, l);
    OpRemoveEventHandler(w, opWindow | opPixmap,
                         KeyReleaseMask|ButtonReleaseMask, FALSE,
			 (OpEventProc) releaseSplineBand, l);

    finish(w, (LocalInfo *) l);
    freeLocalInfo((LocalInfo *) l);
}

void *
SelectSplineAdd(Widget w)
{
    LocalInfo *l;

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

    XtVaSetValues(w, XtNcompress, True, NULL);

    OpAddEventHandler(w, opWindow | opPixmap, ButtonPressMask, FALSE,
		      (OpEventProc) pressSplineBand, l);
    OpAddEventHandler(w, opWindow, PointerMotionMask, FALSE,
		      (OpEventProc) motionSplineBand, l);
    OpAddEventHandler(w, opWindow | opPixmap,
                      KeyReleaseMask|ButtonReleaseMask, FALSE,
		      (OpEventProc) releaseSplineBand, l);

    SetCrossHairCursor(w);

    return l;
}

void 
SelectSplineRemove(Widget w, void *l)
{
    OpRemoveEventHandler(w, opWindow | opPixmap, ButtonPressMask, FALSE,
			 (OpEventProc) pressSplineBand, l);
    OpRemoveEventHandler(w, opWindow, PointerMotionMask, FALSE,
			 (OpEventProc) motionSplineBand, l);
    OpRemoveEventHandler(w, opWindow | opPixmap,
                         KeyReleaseMask|ButtonReleaseMask, FALSE,
			 (OpEventProc) releaseSplineBand, l);

    finish(w, (LocalInfo *) l);
    freeLocalInfo((LocalInfo *) l);
}
