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

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

#include <stdlib.h>
#include <math.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	REGION		0x4
#define	FILL		0x2
#define	POLY		0x1
#define IsRegion(x)	(x & REGION)
#define IsPoly(x)	(x & POLY)
#define	IsFill(x)	(x & FILL)

typedef struct {
    int flag;
    int npoints, start;
    XPoint * points;
    XPoint * realpts;
    /*
    **  Borrowed from my info structure.
     */
    GC lgc, fgc, gcx;
    Widget widget;
    Pixmap pixmap;
    Boolean isFat;
} LocalInfo;

static int MAXPOINTS = 30;
static int polygonType = 1;
static int polygonSides = 5;
static double polygonRatio = 0.381966; /* (3-sqrt(5))/2 for a perfect star */

void Vertices(XPoint *xp) 
{
    int i, num, ex, ey;
    double c, s;

    /* Draw vertices of regular/starlike polygon 
       with center xp[0] and initial vertex point xp[1] */

    ex = xp[1].x - xp[0].x;
    ey = xp[1].y - xp[0].y;

    num = (polygonType-1)*polygonSides;

    for (i=0; i<=num; i++) {
        s = 2*i*M_PI/num;
        c = cos(s);
        s = sin(s);
        if (polygonType == 3 && (i&1)) {
	    c *= polygonRatio;
            s *= polygonRatio;
	}
        xp[i+2].x = xp[0].x + (int)(c*ex - s*ey + 0.5);
        xp[i+2].y = xp[0].y + (int)(s*ex + c*ey + 0.5);
    }
}

void
CreatePolygonalRegion(Widget w, XPoint *xp, int n)
{
    Display *dpy;
    XRectangle rect;
    int i, xmin, ymin, xmax, ymax, width, height;
    Pixmap mask;
    GC gc;
    
    dpy = XtDisplay(w);

    /* Create region */
    XtVaGetValues(w, XtNdrawWidth, &width, 
                     XtNdrawHeight, &height, NULL);
    xmin = width;
    ymin = height;
    xmax = 0;
    ymax = 0;

    /* n = 0 is special polygonal type */
    if (n == 0) {
        Vertices(xp);
        xp += 2;
        n = (polygonType-1) * polygonSides + 1;
    }

    for (i=0; i<n; i++) {
        if (xp[i].x<xmin) xmin = xp[i].x;
        if (xp[i].y<ymin) ymin = xp[i].y;
        if (xp[i].x>xmax) xmax = xp[i].x;
        if (xp[i].y>ymax) ymax = xp[i].y;
    }
    if (xmin<0) xmin = 0;
    if (ymin<0) ymin = 0;
    if (xmax>=width) xmax = width-1;
    if (ymax>=height) ymax = height-1;

    rect.x = xmin;
    rect.y = ymin;
    rect.width = xmax-xmin+1;
    rect.height = ymax-ymin+1;
    mask = XCreatePixmap(XtDisplay(w), XtWindow(w), rect.width, rect.height, 1);
    gc = XCreateGC(XtDisplay(w), mask, 0, 0);
    XSetFunction(XtDisplay(w), gc, GXclear);
    XFillRectangle(XtDisplay(w), mask, gc, 0, 0,
                   rect.width, rect.height);
    XSetFunction(XtDisplay(w), gc, GXset);
    for (i=0; i<n; i++) {
         xp[i].x -= xmin;
         xp[i].y -= ymin;
    }
    XFillPolygon(XtDisplay(w), mask, gc, xp, n, Complex, CoordModeOrigin);

    XFreeGC(XtDisplay(w), gc);
    if (xmax==xmin || ymax==ymin) {
        PwRegionFinish(w, True);
        return;
    }
    if (SelectGetCutMode() != 0 && !chromaCut(w, &rect, &mask)) {
        PwRegionFinish(w, True);
        return;
    }
    PwRegionSet(w, &rect, None, mask);
}

static Boolean
SpecialPolygon(LocalInfo * l)
{     
    if (l->flag && polygonType >= 2) return True;
    return False;
}

static void
contour(LocalInfo * l, Drawable d, GC gc)
{
    XPoint * xp;
    int i, n;

    if (!d || l->npoints <= 1)
      return;
    
    if (d == l->pixmap)
        xp = l->realpts;
    else
        xp = l->points;

    if (SpecialPolygon(l)) {
        Vertices(xp);
        xp += 2;
        n = (polygonType-1) * polygonSides + 1;
    } else {
        i = (l->start>=0)? l->start : 0;
        xp += i;
        n = l->npoints - i;
    }

    XDrawLines(Global.display, d, gc, xp, n, CoordModeOrigin);

    if (d == l->pixmap)
        for (i=0; i<n; i++) UndoGrow(l->widget, xp[i].x, xp[i].y);
}

static void 
finish(Widget w, LocalInfo * l, Boolean flag)
{
    XPoint *xp;
    int n;

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

    l->start = 0;
    contour(l, XtWindow(w), l->gcx);

    if (IsRegion(l->flag)) {
        if (SpecialPolygon(l))
	    n = 0;
        else
	    n = l->npoints;
        CreatePolygonalRegion(w, l->realpts, n);
        l->npoints = -1;
        return;
    }

    if (IsFill(l->flag)) {
        if (SpecialPolygon(l)) {
            Vertices(l->realpts);
            n = (polygonType-1) * polygonSides + 1;
            xp = l->realpts + 2;
	} else {
	    xp = l->realpts;
            n = l->npoints;
	}
	if (!l->isFat)
	    XFillPolygon(XtDisplay(w), XtWindow(w), l->fgc,
			 xp, n, Complex, CoordModeOrigin);
	XFillPolygon(XtDisplay(w), l->pixmap, l->fgc,
		     xp, n, Complex, CoordModeOrigin);
    }

    if (IsPoly(l->flag) && polygonType == 1) {
	l->points[l->npoints] = l->points[0];
	l->realpts[l->npoints] = l->realpts[0];
	l->npoints++;
    }

    SetCapAndJoin(w, l->lgc,
                  ((Global.cap)?Global.cap-1:CapButt),
                  ((Global.join)?Global.join-1:JoinMiter));
    if (!l->isFat)
        contour(l, XtWindow(w), l->lgc);

    contour(l, l->pixmap, l->lgc);
    PwUpdate(w, NULL, False);

    l->npoints = -1;
}

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

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

    if (l->npoints<0 && IsRegion(l->flag)
        && pw->paint.region.isVisible) {
	  l->npoints = -3;
          return;
    }

    if (event->button == Button2)
        finish(w, l, True);
}

static void
shift(Widget w, LocalInfo * l, KeySym keysym)
{
    int i, zoom;
    l->start = 0;
    contour(l, XtWindow(w), l->gcx);
    XtVaGetValues(w, XtNzoom, &zoom, NULL);
    switch (keysym) {
        case XK_Up:
            for (i=0; i<= l->npoints; i++) {
  	        --l->realpts[i].y;
	        if (zoom>0) {
	            l->points[i].y -= zoom;
	        } else {
		    l->points[i].y = l->realpts[i].y / (-zoom);
		}
	    }
	    break;
        case XK_Down:
            for (i=0; i<= l->npoints; i++) {
  	        ++l->realpts[i].y;
	        if (zoom>0) {
	            l->points[i].y += zoom;
	        } else {
		    l->points[i].y = l->realpts[i].y / (-zoom);
		}
	    }
            break;
        case XK_Left:
            for (i=0; i<= l->npoints; i++) {
  	        --l->realpts[i].x;
	        if (zoom>0) {
	            l->points[i].x -= zoom;
	        } else {
		    l->points[i].x = l->realpts[i].x / (-zoom);
		}
	    }
            break;
        case XK_Right:
            for (i=0; i<= l->npoints; i++) {
  	        ++l->realpts[i].x;
	        if (zoom>0) {
	            l->points[i].x += zoom;
	        } else {
		    l->points[i].x = l->realpts[i].x / (-zoom);
		}
	    }
            break;
    }
    contour(l, XtWindow(w), l->gcx);
}

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

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

        if (keysym == XK_Escape) {
            l->start = 0;
            contour(l, XtWindow(w), l->gcx);
            l->npoints = -1;
      	} 
        else
        if (keysym == XK_Return) finish(w, l, True);
        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 >= 2) {
                l->start = l->npoints - 3;
                contour(l, XtWindow(w), l->gcx);
                --l->npoints;
                i = l->npoints-1;
                if (i >= 1) {
		    l->points[i] = l->points[l->npoints];
		    l->realpts[i] = l->realpts[l->npoints];
                    contour(l, XtWindow(w), l->gcx);
		}
	    }
            /* if just one point left, restart from scratch */
            if (l->npoints <= 1)
                l->npoints = -1;
        }
        event->type = None;
        return;
    }

    if (event->type != ButtonRelease || event->button >= Button2) return;
    /* now event = ButtonRelase and button = 1 */
 
    if (l->npoints == -2) {
        l->npoints = -1;
        return;
    }
    if (l->npoints == -3 && IsRegion(l->flag) && 
        pw->paint.region.isVisible) {
        PwRegionFinish(w, True);
        pw->paint.region.isVisible = False;
        l->npoints = -2;
        return;
    }

    if (info->surface == opPixmap && !l->pixmap) {
        UndoStartPoint(w, info, info->x, info->y);
        l->pixmap = info->drawable;
        l->widget = w;
    }
       
    if (l->npoints<0 && info->surface == opWindow) {
	l->npoints = 1;
	if (SpecialPolygon(l)) {
	    MAXPOINTS = (polygonType-1)*polygonSides + 3;
	    l->points = 
                (XPoint *) realloc(l->points, MAXPOINTS*sizeof(XPoint));
	    l->realpts = 
                (XPoint *) realloc(l->realpts, MAXPOINTS*sizeof(XPoint));
	}
	l->points[0].x = event->x;
	l->points[0].y = event->y;
	l->realpts[0].x = info->x;
	l->realpts[0].y = info->y;

	l->isFat = info->isFat;
	l->fgc = info->second_gc;
	l->lgc = info->first_gc;
    }

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

    /*
    **  else on the window.
     */
    if (l->npoints >= MAXPOINTS - 3) {
        MAXPOINTS += 10;
        l->points = 
           (XPoint *) realloc(l->points, MAXPOINTS*sizeof(XPoint));
        l->realpts = 
           (XPoint *) realloc(l->realpts, MAXPOINTS*sizeof(XPoint));
    }

    l->start = l->npoints - 2;
    contour(l, XtWindow(w), l->gcx);

    /* merely center / vertex for polygonType >= 2 */
    if (SpecialPolygon(l))
        l->npoints = 1;

    l->points[l->npoints].x = event->x;
    l->points[l->npoints].y = event->y;
    l->realpts[l->npoints].x = info->x;
    l->realpts[l->npoints].y = info->y;
    l->npoints++;

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

static void 
motion(Widget w, LocalInfo * l, XMotionEvent * event, OpInfo * info)
{
    int i;

    if (l->npoints <= 1) 
        return;
     
    l->start = l->npoints - 2;
    contour(l, XtWindow(w), l->gcx);

    i = l->npoints - 1;
    l->points[i].x = event->x;
    l->points[i].y = event->y;
    l->realpts[i].x = info->x;
    l->realpts[i].y = info->y;

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

static
LocalInfo * createLocalInfo()
{
    LocalInfo * l;
    l = (LocalInfo *) xmalloc(sizeof(LocalInfo));
    l->points = (XPoint *) xmalloc(MAXPOINTS*sizeof(XPoint));
    l->realpts =   (XPoint *) xmalloc(MAXPOINTS*sizeof(XPoint));
    l->pixmap = None;
    l->npoints = -1;
    l->start = 0;
    return l;
}

static 
void freeLocalInfo(LocalInfo *l)
{
    free((void *) l->points);
    free((void *) l->realpts);
    free((void *) l);
}

/*
**  Those public functions
 */
void *
PolygonAdd(Widget w)
{
    LocalInfo *l = 

    l = (LocalInfo *) createLocalInfo();

    l->flag = POLY;
    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,
                      KeyReleaseMask|ButtonReleaseMask, FALSE,
		      (OpEventProc) release, l);
    SetCrossHairCursor(w);

    return l;
}

void 
PolygonRemove(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,
                         KeyReleaseMask|ButtonReleaseMask, FALSE,
			 (OpEventProc) release, l);

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

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

    l = (LocalInfo *) createLocalInfo();
    l->flag = POLY | FILL;
    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,
                      KeyReleaseMask|ButtonReleaseMask, FALSE,
		      (OpEventProc) release, l);
    SetCrossHairCursor(w);

    return l;
}

void 
FilledPolygonRemove(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,
                         KeyReleaseMask|ButtonReleaseMask, FALSE,
			 (OpEventProc) release, l);

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

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

    l = (LocalInfo *) createLocalInfo();
    l->flag = POLY | REGION;
    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,
                      KeyReleaseMask|ButtonReleaseMask, FALSE,
		      (OpEventProc) release, l);

    SetCrossHairCursor(w);

    return l;
}

void 
SelectPolygonRemove(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,
                         KeyReleaseMask|ButtonReleaseMask, FALSE,
			 (OpEventProc) release, l);

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

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

    l = createLocalInfo();
    l->flag = 0;
    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,
                      KeyReleaseMask|ButtonReleaseMask, FALSE,
		      (OpEventProc) release, l);
    SetCrossHairCursor(w);

    return l;
}

void 
BrokenlineRemove(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,
                         KeyReleaseMask|ButtonReleaseMask, FALSE,
			 (OpEventProc) release, l);

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

/*
**  Those public functions
*/
void
PolygonSetParameters(int t, int s, double a)
{
    if (t) 
       polygonType = t;
    else {
       polygonSides = s;
       if (a==1.0)
	   polygonRatio = 1/(2*cos(M_PI/s)+1.0);
       else
           polygonRatio = a;
    }
}

void
PolygonGetParameters(int *t, int *s, double *a)
{
  *t = polygonType;
  *s = polygonSides;
  *a = polygonRatio;
}
