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

/*
** PaintRegion.c -- Hopefully all of the routines to get,set and  
**   manipulate the selection region.
**
**  Not part of the "selection" operation, since this really
**   need to know lots of hidden information (why?)
*/

#include <math.h>
#include <X11/IntrinsicP.h>
#include <X11/StringDefs.h>
#include <X11/cursorfont.h>
#include <X11/Xatom.h>
#include <X11/Xaw/Grip.h>
#include "PaintP.h"

#define SHAPE

#ifdef SHAPE
#include <X11/extensions/shape.h>
#endif

static void regionExpose(Widget, PaintWidget, XEvent *, Boolean *);

#define regionRedraw(pw)	regionExpose(pw->paint.region.child, pw, NULL, NULL)

#define BoolStr(flg)	((flg) ? "True" : "False")

#undef INTERACTIVE

/*
**  Border Width of child widget
*/
#define BW	0

/*
**  2x2 matrix stuff
**
*/

#define XFORM(x,y,mat,nx,ny)	nx = mat[0][0] * x + mat[0][1] * y; \
				ny = mat[1][0] * x + mat[1][1] * y
#define COPY_MAT(s,d)	d[0][0] = s[0][0]; d[0][1] = s[0][1]; d[1][0] = s[1][0]; d[1][1] = s[1][1]

#define INVERT_MAT(mat, inv) do {							\
		float	_d = 1.0 / (mat[0][0] * mat[1][1] - mat[0][1] * mat[1][0]);	\
		(inv)[0][0] =  (mat)[1][1] * _d;					\
		(inv)[1][1] =  (mat)[0][0] * _d;					\
		(inv)[0][1] = -(mat)[0][1] * _d;					\
		(inv)[1][0] = -(mat)[1][0] * _d;					\
	} while (0)
#define ZERO(v)		(((v) > -1e-5) && ((v) < 1e-5))

#ifndef MIN
#define MIN(a,b)        (((a) < (b)) ? (a) : (b))
#endif
#ifndef MAX
#define MAX(a,b)        (((a) > (b)) ? (a) : (b))
#endif
#ifndef SIGN
#define SIGN(a)		(((a) < 0) ? -1 : 1)
#endif
#ifndef ABS
#define ABS(a)          ((a > 0) ? (a) : 0 - (a))
#endif

#define PRINT_MAT(msg,mat) printf("matrix %s =\n\t%f %f\n\t%f %f\n", msg, \
				mat[0][0], mat[0][1], mat[1][0], mat[1][1])
#define MKMAT(pw) do {							\
		pwMatrix	m;					\
		m[0][0] = pw->paint.region.scaleX;			\
		m[1][1] = pw->paint.region.scaleY;			\
		m[0][1] = m[1][0] = 0.0;				\
		mm(pw->paint.region.rotMat, m, pw->paint.region.mat);	\
	} while (0)
		
static pwMatrix	matIdentity = { { 1, 0 }, { 0, 1 } };

static void doCallbacks(PaintWidget pw, Boolean flag)
{
	PaintWidget	tpw = (pw->paint.paint == None) ? pw : (PaintWidget)pw->paint.paint;
	int		i;

	XtCallCallbackList((Widget)tpw, tpw->paint.regionCalls, (XtPointer)flag);
	for (i = 0; i < tpw->paint.paintChildrenSize; i++) {
		PaintWidget	p = (PaintWidget)tpw->paint.paintChildren[i];
		XtCallCallbackList((Widget)p, p->paint.regionCalls, (XtPointer)flag);
	}
}

static void mm(pwMatrix a, pwMatrix b, pwMatrix r)
{
	float	t00, t10, t01, t11;

	t00 = a[0][0] * b[0][0] + a[0][1] * b[1][0];
	t01 = a[0][0] * b[0][1] + a[0][1] * b[1][1];
	t10 = a[1][0] * b[0][0] + a[1][1] * b[1][0];
	t11 = a[1][0] * b[0][1] + a[1][1] * b[1][1];

	r[0][0] = t00;
	r[1][0] = t10;
	r[0][1] = t01;
	r[1][1] = t11;
}

/*
**   PwRegionSet -- set the active image region
**     add handles and other useful things
**     if the pix == None, use the current paint info in rect
**     else use the pixmap.
**
*/
static void	buildSources(PaintWidget pw)
{
	if (pw->paint.region.sourceImg == NULL) {
		pw->paint.region.sourceImg = XGetImage(XtDisplay(pw), 
							pw->paint.region.source, 0, 0, 
							pw->paint.region.orig.width, 
							pw->paint.region.orig.height, AllPlanes, ZPixmap);
	}

	if (pw->paint.region.mask == None)
		return;

	if (pw->paint.region.maskImg == NULL) {
		pw->paint.region.maskImg = XGetImage(XtDisplay(pw), 
							pw->paint.region.mask, 0, 0, 
							pw->paint.region.orig.width, 
							pw->paint.region.orig.height, AllPlanes, ZPixmap);
	}
}

static void resizeImg(PaintWidget pw, pwMatrix inv, 
			Pixmap *pix, XImage *pixSrc, Pixmap *msk, XImage *mskSrc, Pixmap *shape)
{
	int		width, height, depth;
	int		x, y;
	XImage		*pixDst, *mskDst, *shapeDst = NULL;
	int		sourceW, sourceH;
	int		ix, iy, fx, fy, cx, cy;
	float		dx, dy, sx, sy;

	if (pixSrc == NULL || pix == NULL)
		return;
	if (pw->paint.region.rect.width == 0 || pw->paint.region.rect.height == 0)
		return;

	/*
	**  Construct the dest pixmap.
	*/

	if (*pix != None)
		XFreePixmap(XtDisplay(pw), *pix);
	if (*msk != None && mskSrc != NULL)
		XFreePixmap(XtDisplay(pw), *msk);

	depth = pixSrc->depth;

	width  = pw->paint.region.rect.width  / GET_ZOOM(pw);
	height = pw->paint.region.rect.height / GET_ZOOM(pw);

	*pix   = XCreatePixmap(XtDisplay(pw), XtWindow(pw), width, height, depth);
	pixDst = XCreateImage(XtDisplay(pw), pw->paint.visual, 
				depth, ZPixmap, 0, NULL, width, height, 32, 0);
	pixDst->data = (char*)XtMalloc(height * pixDst->bytes_per_line);

	if (mskSrc != NULL) {
		*msk = XCreatePixmap(XtDisplay(pw), XtWindow(pw), width, height, 1);
		mskDst = XCreateImage(XtDisplay(pw), pw->paint.visual, 
					1, ZPixmap, 0, NULL, width, height, 32, 0);
		mskDst->data = (char*)XtMalloc(height * mskDst->bytes_per_line);
#ifdef SHAPE
		if (shape != NULL) {
			*shape = XCreatePixmap(XtDisplay(pw), XtWindow(pw), width, height, 1);
			shapeDst = XCreateImage(XtDisplay(pw), pw->paint.visual, 
						1, ZPixmap, 0, NULL, width, height, 32, 0);
			shapeDst->data = (char*)XtMalloc(height * shapeDst->bytes_per_line);
		}
#endif
	}


	cx = pw->paint.region.orig.width  / 2;
	cy = pw->paint.region.orig.height / 2;
	fx = (int)(-width  / 2);
	fy = (int)(-height / 2);
	sourceW = pixSrc->width;
	sourceH = pixSrc->height;
	for (y = 0, dy = fy; y < height; y++, dy++) {
		for (x = 0, dx = fx; x < width; x++, dx++) {
			XFORM(dx,dy,inv,sx,sy);
			ix = (sx + cx);
			iy = (sy + cy);
			if (ix >= 0 && ix < sourceW && iy >= 0 && iy < sourceH) {
				xxPutPixel(pixDst, x, y, xxGetPixel(pixSrc, ix, iy));
				if (mskSrc != NULL)
					XPutPixel(mskDst, x, y, XGetPixel(mskSrc, ix, iy));
#ifdef SHAPE
				if (shapeDst != NULL) 
					XPutPixel(shapeDst, x, y, True);
#endif
			} else if (mskSrc != NULL) {
				XPutPixel(mskDst, x, y, False);
#ifdef SHAPE
				if (shapeDst != NULL) 
					XPutPixel(shapeDst, x, y, False);
#endif
			}
		}
	}

	XPutImage(XtDisplay(pw), *pix,  pw->paint.tgc, pixDst, 0, 0, 0, 0, width, height);
	XDestroyImage(pixDst);

	if (mskSrc != NULL) {
		XPutImage(XtDisplay(pw), *msk, pw->paint.mgc, mskDst, 0, 0, 0, 0, width, height);
		XDestroyImage(mskDst);
	}
#ifdef SHAPE
	if (shapeDst != NULL) {
		XPutImage(XtDisplay(pw), *shape, pw->paint.mgc, shapeDst, 0, 0, 0, 0, width, height);
		XDestroyImage(shapeDst);
	}
#endif
}

static void regionCreateNotMask(PaintWidget pw)
{
	if (pw->paint.region.rect.width == 0 || pw->paint.region.rect.height == 0)
		return;

	if (pw->paint.region.mask == None)
		return;

	if (pw->paint.region.notMask != None)
		XFreePixmap(XtDisplay(pw), pw->paint.region.notMask);

	pw->paint.region.notMask = XCreatePixmap(XtDisplay(pw), XtWindow(pw), 
					pw->paint.region.rect.width, pw->paint.region.rect.height, 1);
	XSetFunction(XtDisplay(pw), pw->paint.mgc, GXcopyInverted);
	XCopyArea(XtDisplay(pw), pw->paint.region.mask, pw->paint.region.notMask, pw->paint.mgc,
			0, 0, pw->paint.region.rect.width, pw->paint.region.rect.height, 0, 0);
	XSetFunction(XtDisplay(pw), pw->paint.mgc, GXcopy);

	XSetClipMask(XtDisplay(pw), pw->paint.region.bg_gc, pw->paint.region.notMask);
	XSetClipMask(XtDisplay(pw), pw->paint.region.fg_gc, pw->paint.region.mask);
}

static void createVtxPts(PaintWidget pw, float vtx[9][2], Boolean flag, Boolean useZoom)
{
	int	zoom;
	int	i;
	int	x0, x1, y0, y1;
	int	width  = pw->paint.region.orig.width;
	int	height = pw->paint.region.orig.height;

	if (useZoom)
		zoom = GET_ZOOM(pw);
	else
		zoom = 1;

	x0 = (-width / 2);
	x1 = ( width + x0);
	y0 = (-height / 2);
	y1 = ( height + y0);
	x0 *= zoom;
	x1 *= zoom;
	y0 *= zoom;
	y1 *= zoom;

	/*
	**  Watch out, these are points 0,1, and _3__
	*/
	XFORM(x0,y0,pw->paint.region.mat,vtx[0][0],vtx[0][1]);
	XFORM(x1,y0,pw->paint.region.mat,vtx[1][0],vtx[1][1]);
	XFORM(x0,y1,pw->paint.region.mat,vtx[3][0],vtx[3][1]);

	if (flag) {
		XFORM(x1,y1,pw->paint.region.mat,vtx[2][0],vtx[2][1]);

		for (i = 0; i < 4; i++) {
			vtx[i][0] += pw->paint.region.centerX * zoom;
			vtx[i][1] += pw->paint.region.centerY * zoom;
		}
	} else {
		/*
		**  sort the points, so that point 0,0 is top left corner
		*/
		if (vtx[0][0] > vtx[1][0]) {
			float	t = x0;
			x0 = x1;
			x1 = t;
		}
		if (vtx[0][1] > vtx[3][1]) {
			float	t = y0;
			y0 = y1;
			y1 = t;
		}

		XFORM(x0,y0,pw->paint.region.mat,vtx[0][0],vtx[0][1]);
		XFORM( 0,y0,pw->paint.region.mat,vtx[1][0],vtx[1][1]);
		XFORM(x1,y0,pw->paint.region.mat,vtx[2][0],vtx[2][1]);

		XFORM(x0, 0,pw->paint.region.mat,vtx[3][0],vtx[3][1]);
		XFORM( 0, 0,pw->paint.region.mat,vtx[4][0],vtx[4][1]);
		XFORM(x1, 0,pw->paint.region.mat,vtx[5][0],vtx[5][1]);

		XFORM(x0,y1,pw->paint.region.mat,vtx[6][0],vtx[6][1]);
		XFORM( 0,y1,pw->paint.region.mat,vtx[7][0],vtx[7][1]);
		XFORM(x1,y1,pw->paint.region.mat,vtx[8][0],vtx[8][1]);
	}
}

static void doResize(PaintWidget pw)
{
	pwMatrix	inv;
	Pixmap		shape, *shp = &shape;

	buildSources(pw);

	/*
	**  First find out the bounding extent of the transformed
	**   area, then scale it to fit inside of the "region box"
	*/

	if (pw->paint.region.maskImg == NULL) {
		Boolean		needMask = False;
		float		vtx[9][2];
		float		minX, minY, maxX, maxY;
		int		x, cmin, cmax;

		createVtxPts(pw, vtx, True, False);

		minX = MIN(vtx[0][0],MIN(vtx[1][0],MIN(vtx[2][0],vtx[3][0])));
		minY = MIN(vtx[0][1],MIN(vtx[1][1],MIN(vtx[2][1],vtx[3][1])));
		maxX = MAX(vtx[0][0],MAX(vtx[1][0],MAX(vtx[2][0],vtx[3][0])));
		maxY = MAX(vtx[0][1],MAX(vtx[1][1],MAX(vtx[2][1],vtx[3][1])));

		/*
		**  After computing min, max see if there are points
		**   on all vertices, if so then set the correct return code
		*/
		for (cmin = cmax = x = 0; x < 4; x++) {
			if ((int)vtx[x][0] == (int)minX)
				cmin++;
			else if ((int)vtx[x][0] == (int)maxX)
				cmax++;
		}
		needMask |= (cmin != 2 || cmax != 2);
		for (cmin = cmax = x = 0; x < 4; x++) {
			if ((int)vtx[x][1] == (int)minY)
				cmin++;
			else if ((int)vtx[x][1] == (int)maxY)
				cmax++;
		}
		needMask |= (cmin != 2 || cmax != 2);

		if (needMask) {
			Pixmap	mask;
			GC	mgc;

			/*
			**  If the image we just transformed needs a mask
			**  and one doesn't exist, construct one.
			*/
			mask = pw->paint.region.mask = XCreatePixmap(XtDisplay(pw), XtWindow(pw), 
					pw->paint.region.orig.width, pw->paint.region.orig.height, 1);
			mgc = GET_MGC(pw, mask);
			XSetFunction(XtDisplay(pw), mgc, GXset);
			XFillRectangle(XtDisplay(pw), mask, mgc, 0, 0,
						pw->paint.region.orig.width, 
						pw->paint.region.orig.height);
			XSetFunction(XtDisplay(pw), mgc, GXcopy);
			
			buildSources(pw);

			if (pw->paint.region.fg_gc == None) {
				pw->paint.region.fg_gc = XCreateGC(XtDisplay(pw), XtWindow(pw), 0, 0);
				pw->paint.region.bg_gc = XCreateGC(XtDisplay(pw), XtWindow(pw), 0, 0);
			}
		}
	}

	INVERT_MAT(pw->paint.region.mat, inv);
#ifdef SHAPE
	if (pw->paint.region.maskImg == NULL || GET_ZOOM(pw) != 1)
		shp = NULL;
#else
	shp = NULL;
#endif
	resizeImg(pw, inv, &pw->paint.region.source, pw->paint.region.sourceImg,
			&pw->paint.region.mask, pw->paint.region.maskImg, shp);
	regionCreateNotMask(pw);

#ifdef SHAPE
	if (shp != NULL) {
		XShapeCombineMask(XtDisplay(pw), XtWindow(pw->paint.region.child), 
					ShapeBounding, 0, 0, shape, ShapeSet);
		XFreePixmap(XtDisplay(pw), shape);
	}
#endif

	pw->paint.region.needResize = False;
}

static void drawRegionBox(PaintWidget pw, Boolean flag)
{
	static XPoint	xvtxLast[5];
	XPoint		xvtx[5];
	float		vtx[9][2];
	Window		window = XtWindow(pw);
	int		i;

	createVtxPts(pw, vtx, True, True);

	xvtx[0].x = vtx[0][0];	xvtx[0].y = vtx[0][1];
	xvtx[1].x = vtx[1][0];	xvtx[1].y = vtx[1][1];
	xvtx[2].x = vtx[2][0];	xvtx[2].y = vtx[2][1];
	xvtx[3].x = vtx[3][0];	xvtx[3].y = vtx[3][1];
	xvtx[4].x = vtx[0][0];	xvtx[4].y = vtx[0][1];

	for (i = 0; i < 5; i++) {
		xvtx[i].x += pw->paint.region.child->core.x;
		xvtx[i].y += pw->paint.region.child->core.y;
	}

	if (pw->paint.region.isDrawn) {
		XDrawLines(XtDisplay(pw), window, pw->paint.xgc, xvtxLast, 5, CoordModeOrigin);
		pw->paint.region.isDrawn = False;
	}

	if (flag) {
		XDrawLines(XtDisplay(pw), window, 
				pw->paint.xgc, xvtx, 5, CoordModeOrigin);
		memcpy(xvtxLast, xvtx, sizeof(xvtxLast));
		pw->paint.region.isDrawn = True;
	}
}

/*
**
*/

static void regionResizeWindow(PaintWidget pw, Boolean sameCenter)
{
	int	zoom = GET_ZOOM(pw);
	int	minX, minY, maxX, maxY;
	int	width, height, dx, dy, nx, ny;
	int	newX, newY;
	int	i;
	float	vtx[9][2];

#ifndef INTERACTIVE
	if (pw->paint.region.isTracking)
		return;
#endif

	createVtxPts(pw, vtx, False, False);

	minX = MIN(vtx[0][0], vtx[6][0]);
	maxX = MAX(vtx[2][0], vtx[8][0]);
	minY = MIN(vtx[0][1], vtx[2][1]);
	maxY = MAX(vtx[6][1], vtx[8][1]);

	width  = maxX - minX + 0.5;
	height = maxY - minY + 0.5;

	newX = pw->paint.region.centerX - (width  / 2);
	newY = pw->paint.region.centerY - (height / 2);

	newX += pw->paint.region.rect.x;
	newY += pw->paint.region.rect.y;
	if (!sameCenter) {
		pw->paint.region.centerX = width  / 2;
		pw->paint.region.centerY = height / 2;
	} else {
		pw->paint.region.centerX = width / 2;
		pw->paint.region.centerY = height / 2;
	}

	if ((width  *= zoom) < 10)
		width = 10;
	if ((height *= zoom) < 10)
		height = 10;
	XtResizeWidget(pw->paint.region.child, width, height, BW);
	nx = (newX - pw->paint.zoomX) * zoom;
	ny = (newY - pw->paint.zoomY) * zoom;
	XtMoveWidget(pw->paint.region.child, nx - BW, ny - BW);

	pw->paint.region.rect.x      = newX;
	pw->paint.region.rect.y      = newY;
	pw->paint.region.rect.width  = width;
	pw->paint.region.rect.height = height;

	/*
	**  Now place all the grips.
	*/
	createVtxPts(pw, vtx, False, True);
	width  = pw->paint.region.grip[0]->core.width;
	height = pw->paint.region.grip[0]->core.height;
	dx     = pw->paint.region.centerX * zoom - width / 2;
	dy     = pw->paint.region.centerY * zoom - height / 2;
	for (i = 0; i < 9; i++) {
		int	x, y;

		if (i == 4)
			continue;

		x = vtx[i][0] + dx;
		y = vtx[i][1] + dy;

		if (x < 0)
			x = 0;
		if (y < 0)
			y = 0;
		if (x + width > pw->paint.region.rect.width)
			x = pw->paint.region.rect.width - width;
		if (y + height > pw->paint.region.rect.height)
			y = pw->paint.region.rect.height - height;

		XtMoveWidget(pw->paint.region.grip[i], x, y);
	}
}

/*
**
*/

static void moveGrips(PaintWidget pw)
{
	int		width, height;
	int		i, gx, gy;
	Dimension	w, h;
	Widget		widget = pw->paint.region.grip[0];

	w      = widget->core.width;
	h      = widget->core.height;
	width  = widget->core.parent->core.width;
	height = widget->core.parent->core.height;

	for (i = 0; i < 9; i++) {
		if (i == 4)
			continue;

		switch (i % 3) {
		case 0: gx = 0;                 break;
		case 1: gx = width / 2 - w / 2; break;
		case 2: gx = width - w;         break;
		}
		switch (i / 3) {
		case 0: gy = 0;                  break;
		case 1: gy = height / 2 - h / 2; break;
		case 2: gy = height - h;         break;
		}

		XtMoveWidget(pw->paint.region.grip[i], gx, gy);
	}
}

static void gripPress(Widget w, PaintWidget pw, XButtonEvent *event, Boolean *junk)
{
	static int	fixedPoint[] = { 8, 8, 6, 8, -1, 6, 2, 2, 0 };
	float	vtx[9][2], fvtx[9][2];
	float	x0, x1, x2, y0, y1, y2, t1, t2, l;
	int	index, i;

	pw->paint.region.offX = event->x;
	pw->paint.region.offY = event->y;
	pw->paint.region.baseX = event->x_root - w->core.x;
	pw->paint.region.baseY = event->y_root - w->core.y;

	pw->paint.region.isTracking = True;

	/*
	**  Compute which grip was grabbed, to determine constrain line.
	*/
	for (index = 0; index < 9 && pw->paint.region.grip[index] != w; index++)
		;

	createVtxPts(pw, vtx, False, False);
	createVtxPts(pw, fvtx, True, False);

	x0 = vtx[0][0];		y0 = vtx[0][1];
	x1 = vtx[2][0];		y1 = vtx[2][1];
	x2 = vtx[6][0];		y2 = vtx[6][1];

	pw->paint.region.lineBase[0]  = 0;
	pw->paint.region.lineBase[1]  = 0;

	t1 = x1 - x0;	t2 = y1 - y0;
	l = sqrt(t1 * t1 + t2 * t2);
	pw->paint.region.lineDelta[0] = t1 / l;
	pw->paint.region.lineDelta[1] = t2 / l;

	t1 = x2 - x0;	t2 = y2 - y0;
	l = sqrt(t1 * t1 + t2 * t2);
	pw->paint.region.lineDelta[2] = t1 / l;
	pw->paint.region.lineDelta[3] = t2 / l;

	pw->paint.region.startScaleX = pw->paint.region.scaleX;
	pw->paint.region.startScaleY = pw->paint.region.scaleY;

	/*
	**  Now compute which corner of the 4 cornerd box doesn't move
	**    as the object is resized.
	*/
	for (i = 0; i < 4; i++) {
		float	fx = vtx[fixedPoint[index]][0];
		float	fy = vtx[fixedPoint[index]][1];
		float	px = fvtx[i][0] - pw->paint.region.centerX;
		float	py = fvtx[i][1] - pw->paint.region.centerY;

		if (ZERO(fx - px) && ZERO(fy - py))
			break;
	}
	pw->paint.region.fixedPoint = i;
}
static void regionButtonPress(Widget w, PaintWidget pw, XButtonEvent *event, Boolean *junk)
{
	pw->paint.region.isRotate = event->button == Button2;

	pw->paint.region.offX = event->x;
	pw->paint.region.offY = event->y;
	pw->paint.region.baseX = event->x_root - w->core.x;
	pw->paint.region.baseY = event->y_root - w->core.y;

	if (pw->paint.region.isRotate) {
		int	dx, dy;

		XDefineCursor(XtDisplay(w), XtWindow(pw->paint.region.child), XCreateFontCursor(XtDisplay(w), XC_exchange));

		dx = event->x - pw->paint.region.rect.width / 2;
		dy = event->y - pw->paint.region.rect.height / 2;
		pw->paint.region.lastAngle = atan2((double)dy, (double)dx);
	}

	/*
	**  Only draw the interactive box when we are rotating.
	*/
	pw->paint.region.isTracking = pw->paint.region.isRotate;
}
static void gripRelease(Widget w, PaintWidget pw, XButtonEvent *event, Boolean *junk)
{
	pw->paint.region.isTracking = False;
	drawRegionBox(pw, False);
#ifndef INTERACTIVE
	if (pw->paint.region.needResize) {
		regionResizeWindow(pw, False);
		regionRedraw(pw);
	}
#endif
}
static void regionButtonRelease(Widget w, PaintWidget pw, XButtonEvent *event, Boolean *junk)
{
	pw->paint.region.isTracking = False;
	drawRegionBox(pw, False);

	if (!pw->paint.region.isRotate)
		return;

#ifndef INTERACTIVE
	if (pw->paint.region.needResize) {
		regionResizeWindow(pw, False);
		regionRedraw(pw);
	}
#endif

	XDefineCursor(XtDisplay(w), XtWindow(pw->paint.region.child), XCreateFontCursor(XtDisplay(w), XC_fleur));
}
static void regionGrab(Widget w, PaintWidget pw, XMotionEvent *event, Boolean *junk)
{
	int	dx, dy, nx, ny;

	while (XCheckTypedWindowEvent(XtDisplay(w), XtWindow(w), MotionNotify, (XEvent*)event));

	PwRegionTear((Widget)pw);

	if (pw->paint.region.isRotate) {
		double		da, na;
		pwMatrix	m;

		dx = event->x - pw->paint.region.rect.width / 2;
		dy = event->y - pw->paint.region.rect.height / 2;
		na = atan2((double)dy, (double)dx);
		da = na - pw->paint.region.lastAngle;
		pw->paint.region.lastAngle = na;

		m[0][0] =  cos(da);
		m[0][1] = -sin(da);
		m[1][0] =  sin(da);
		m[1][1] =  cos(da);

		PwRegionAppendMatrix((Widget)pw, m);
	} else {
		int	zoom = GET_ZOOM(pw);

		nx = event->x_root - pw->paint.region.baseX;
		ny = event->y_root - pw->paint.region.baseY;

		dx = (nx - w->core.x) / zoom;
		dy = (ny - w->core.y) / zoom;

		if (dx == 0 && dy == 0)
			return;

		pw->paint.region.rect.x += dx;
		pw->paint.region.rect.y += dy;

		nx = (pw->paint.region.rect.x - pw->paint.zoomX) * zoom;
		ny = (pw->paint.region.rect.y - pw->paint.zoomY) * zoom;

		XtMoveWidget(pw->paint.region.child, nx - BW, ny - BW);
	}
}
static void regionExpose(Widget w, PaintWidget pw, XEvent *event, Boolean *junk)
{
	XImage		*xim = NULL;
	XRectangle	rect, nrect;
	int		isExpose;
	int		zoom = GET_ZOOM(pw);

	if (!pw->paint.region.isVisible)
		return;

	if (event == NULL) {
		isExpose = True;
		rect.x = 0;
		rect.y = 0;
		rect.width = w->core.width;
		rect.height = w->core.height;
	} else if (event->xany.type == Expose) {
		rect.x = event->xexpose.x;
		rect.y = event->xexpose.y;
		rect.width = event->xexpose.width;
		rect.height = event->xexpose.height;
		isExpose = True;
	} else if (event->xany.type == ConfigureNotify) {
		isExpose = False;
		rect.x = 0;
		rect.y = 0;
		rect.width = w->core.width;
		rect.height = w->core.height;
	} else {
		return;
	}

#ifndef INTERACTIVE
	if (pw->paint.region.isTracking) {
		drawRegionBox(pw, True);
		return;
	}
#endif
	if (pw->paint.region.needResize)
		doResize(pw);

	if (zoom != 1) {
		int	width, height;
		int	pixW, pixH;

		nrect.x      = rect.x / zoom;
		nrect.y      = rect.y / zoom;
		width        = (rect.width + zoom - 1) / zoom;
		height       = (rect.height + zoom - 1) / zoom;
		/*
		**  It is possible for this top happen, when resizes and 
		**    redraws are slightly out of sync.
		*/
		pixW = pw->paint.region.rect.width / zoom;
		pixH = pw->paint.region.rect.height / zoom;
		if (width + nrect.x > pixW)
			width = pixW - nrect.x;
		if (height + nrect.y > pixH)
			height = pixH - nrect.y;
		if (width <= 0 || height <= 0)
			return;
		nrect.width  = width;
		nrect.height = height;
	}

	if (isExpose) {
		if (zoom == 1) {
			XCopyArea(XtDisplay(w), pw->paint.region.source, XtWindow(w), 
					pw->paint.region.fg_gc == None ? pw->paint.tgc : pw->paint.region.fg_gc,
					rect.x, rect.y, rect.width, rect.height, rect.x, rect.y);
		} else  {
			XImage		*src, *msk = NULL;
			XRectangle	tr;

			tr = nrect;
			tr.x = 0;
			tr.y = 0;

			src = XGetImage(XtDisplay(pw), pw->paint.region.source, 
						nrect.x, nrect.y, nrect.width, nrect.height,
						AllPlanes, ZPixmap);
			if (pw->paint.region.mask != None)
				msk = XGetImage(XtDisplay(pw), pw->paint.region.mask, 
						nrect.x, nrect.y, nrect.width, nrect.height,
						AllPlanes, ZPixmap);
			_PwZoomDraw(pw, w, pw->paint.tgc, src, msk,
					False, nrect.x, nrect.y, zoom, &tr);
			XDestroyImage(src);
			if (msk != NULL)
				XDestroyImage(msk);
		}
	}

	/*
	**  XXX -- This should merge, and do fun things.. but.
	*/
	if (isExpose && (event != NULL && event->xexpose.count != 0))
		return;

	if (pw->paint.region.mask != None) {
		int	x = w->core.x + w->core.border_width;
		int	y = w->core.y + w->core.border_width;

		/*
		**  Copy in the background picture
		*/
		if (GET_ZOOM(pw) != 1) {
			XRectangle	tr;
			XImage		*nmsk;
			int		dx = 0, dy = 0;
			PaintWidget	tpw = (pw->paint.paint == None) ? pw : (PaintWidget)pw->paint.paint;
			int		width, height;

			tr.x = (nrect.x + x) / zoom + pw->paint.zoomX;
			tr.y = (nrect.y + y) / zoom + pw->paint.zoomY;
			width  = nrect.width;
			height = nrect.height;

			/*
			**  We could use PwGetImage, but it returns
			**    the original image, not the sub-region
			*/
			if (tr.x < 0) {
				width += tr.x;
				dx = -tr.x;
				tr.x = 0;
			}
			if (tr.y < 0) {
				height += tr.y;
				dy = -tr.y;
				tr.y = 0;
			}
			if (tr.x + width > tpw->paint.drawWidth) 
				width = tpw->paint.drawWidth - tr.x;
			if (tr.y + height > tpw->paint.drawHeight) 
				height = tpw->paint.drawHeight - tr.y;
			if (width > 0 && height > 0) {
				tr.width = width;
				tr.height = height;
				xim = XGetImage(XtDisplay(w), GET_PIXMAP(pw),
						tr.x, tr.y, tr.width, tr.height, 
						AllPlanes, ZPixmap);
				tr.x = tr.y = 0;
				nmsk = XGetImage(XtDisplay(pw), pw->paint.region.notMask, 
						nrect.x + dx, nrect.y + dy, tr.width, tr.height,
						AllPlanes, ZPixmap);

				_PwZoomDraw(pw, w, pw->paint.tgc, xim, nmsk, False, 
						dx, dy, zoom, &tr);
				XDestroyImage(nmsk);
				XDestroyImage(xim);
			}
		} else {
			XCopyArea(XtDisplay(w), GET_PIXMAP(pw), XtWindow(w), 
					pw->paint.region.bg_gc,
					x, y, w->core.width, w->core.height, 0, 0);
		}
	}
}
static void regionMove(Widget w, PaintWidget pw, XEvent *event, Boolean *junk)
{
	if (pw->paint.region.mask == None)
		return;
	/*
	**  For some reason, I had to generate an extra function.. 
	*/
	regionExpose(w, pw, event, junk);
}


static void regionSetGripCursors(PaintWidget pw)
{
	static int	cursors[9][2] = {
				{ XC_top_left_corner, NorthWestGravity },
				{ XC_top_side, NorthGravity },
				{ XC_top_right_corner, NorthEastGravity },
				{ XC_left_side, WestGravity },
				{ 0, 0 },
				{ XC_right_side, EastGravity },
				{ XC_bottom_left_corner, SouthWestGravity },
				{ XC_bottom_side, SouthGravity },
				{ XC_bottom_right_corner, SouthEastGravity },
			};
	static int	list[9] = { None, None, None, None, None, None, None, None, None };
	int		i;

	if (list[0] == None) {
		for (i = 0; i < 9; i++) {
			if (i != 4)
				list[i] = XCreateFontCursor(XtDisplay(pw), cursors[i][0]);
		}
	}

	for (i = 0; i < 9; i++)
		if (i != 4)
			XDefineCursor(XtDisplay(pw), XtWindow(pw->paint.region.grip[i]), list[i]);
}

static void gripGrab(Widget w, PaintWidget pw, XMotionEvent *event, Boolean *junk)
{
	static Boolean	isLeftEdge[] = {True,  False, False,
					True,  False, False,
					True,  False, False };
	static Boolean	isTopEdge[] = {	True,  True,  True,
					False, False, False,
					False, False, False };
	static Boolean	isMiddle[] = {	False, True,  False,
					True,  False, True,
					False, True, False };
	Boolean		sameScale;
	int		index, i, fp;
	int		zoom = GET_ZOOM(pw);
	float		v[2];
	int		width, height;
	float		ovtx[9][2], nvtx[9][2];
	float		dx, dy;

	for (index = 0; index < 9 && pw->paint.region.grip[index] != w; index++)
		;

	/*
	**  Find the intersection point
	*/
	for (i = 0; i < 2; i++) {
		float	x0 = pw->paint.region.lineBase[0];
		float	y0 = pw->paint.region.lineBase[1];
		float	xm = event->x;
		float	ym = event->y;

		dx = pw->paint.region.lineDelta[0 + i * 2];
		dy = pw->paint.region.lineDelta[1 + i * 2];
		v[i] = dx * (xm - x0) - dy * (y0 - ym);
	}

	if (pw->paint.region.startScaleX < 0)
		v[0] = -v[0];
	if (pw->paint.region.startScaleY < 0)
		v[1] = -v[1];
	if (isLeftEdge[index])
		v[0] = -v[0];
	if (isTopEdge[index])
		v[1] = -v[1];

	v[0] /= (float)zoom;
	v[1] /= (float)zoom;

	PwRegionTear((Widget)pw);

	sameScale = False;
	if (isMiddle[index] || (event->state & ShiftMask) != 0) {
		/*
		**  Apply the constraint
		*/
		if (index == 1 || index == 7)
			v[0] = 0;
		else if (index == 3 || index == 5)
			v[1] = 0;
		else
			sameScale = True;
	}

	width   = pw->paint.region.startScaleX * pw->paint.region.orig.width;
	height  = pw->paint.region.startScaleY * pw->paint.region.orig.height;

	createVtxPts(pw, ovtx, True, False);

	pw->paint.region.scaleX = (float)((int)( width + v[0]))/pw->paint.region.orig.width;
	pw->paint.region.scaleY = (float)((int)(height + v[1]))/pw->paint.region.orig.height;

	if (sameScale) {
		float	sx = ABS(pw->paint.region.scaleX);
		float	sy = ABS(pw->paint.region.scaleY);

		sx = MIN(sx, sy);

		pw->paint.region.scaleX = SIGN(pw->paint.region.scaleX) * sx;
		pw->paint.region.scaleY = SIGN(pw->paint.region.scaleY) * sx;
	}

	MKMAT(pw);
	createVtxPts(pw, nvtx, True, False);

	fp = pw->paint.region.fixedPoint;
	dx = ovtx[fp][0] - nvtx[fp][0];
	dy = ovtx[fp][1] - nvtx[fp][1];

	pw->paint.region.centerX += dx;
	pw->paint.region.centerY += dy;

	pw->paint.region.needResize = True;
	regionRedraw(pw);
}

/*
**
**
*/
static void writeRegion(PaintWidget pw)
{
	GC		gc;
	Pixmap		pix, src;
	XRectangle	nr;
	int		zoom = GET_ZOOM(pw);

	if (!pw->paint.region.isVisible)
		return;
	/*
	**  No need to write it, it's still on the drawable.
	*/
	if (pw->paint.region.isAttached)
		return;

	nr.x      = pw->paint.region.rect.x;
	nr.y      = pw->paint.region.rect.y;
	nr.width  = pw->paint.region.rect.width / zoom;
	nr.height = pw->paint.region.rect.height / zoom;

	/*
	**  If we've already modified the background (aka ripped the
	**    image up.  Then don't get a new undo buffer.
	**    The second case of the if is if you've done an undo inbetween.
	*/
	if (pw->paint.region.undoPixmap == None ||
	    pw->paint.region.undoPixmap != GET_PIXMAP(pw))
		pix = PwUndoStart((Widget)pw, &nr);
	else
		pix = pw->paint.region.undoPixmap;

	PwUndoAddRectangle((Widget)pw, &nr);

	if (pw->paint.region.fg_gc != None) {
		gc = pw->paint.region.fg_gc;
		XSetClipOrigin(XtDisplay(pw), gc, nr.x, nr.y);
	} else {
		gc = pw->paint.tgc;
	}

	if (pw->paint.region.proc != NULL) {
		XImage	*sim = pw->paint.region.sourceImg;
		Boolean	made = False;

		if (sim == NULL) {
			sim = XGetImage(XtDisplay(pw), 
					pw->paint.region.source, 0, 0, 
					pw->paint.region.orig.width, 
					pw->paint.region.orig.height, AllPlanes, ZPixmap);
			made = True;
		}

		src = (*pw->paint.region.proc)((Widget)pw, sim, pw->paint.region.mat);
		if (made)
			XDestroyImage(sim);
	} else {
		src = pw->paint.region.source;
	}

	XCopyArea(XtDisplay(pw), src, pix, gc,
			0, 0,
			nr.width, nr.height,
			nr.x, nr.y);

	if (pw->paint.region.fg_gc != None)
		XSetClipOrigin(XtDisplay(pw), gc, 0, 0);

	PwUpdate((Widget)pw, &nr, False);
}

/*
**  Called when the parent widgets zoom factor changes
*/
static void zoomValueChanged(Widget w, XtPointer junk1, XtPointer junk2)
{
	PaintWidget	pw = (PaintWidget)w;
	int		zoom = GET_ZOOM(pw);
	int		nx, ny;
	int		nw, nh;

	if (zoom == pw->paint.region.curZoom)
		return;

	if (!pw->paint.region.isVisible)
		return;

	nx = (pw->paint.region.rect.x - pw->paint.zoomX) * zoom;
	ny = (pw->paint.region.rect.y - pw->paint.zoomY) * zoom;
	nw = (pw->paint.region.rect.width / pw->paint.region.curZoom) * zoom;
	pw->paint.region.rect.width = nw;
	nh = (pw->paint.region.rect.height / pw->paint.region.curZoom) * zoom;
	pw->paint.region.rect.height = nh;
	pw->paint.region.curZoom = zoom;

	XtMoveWidget(pw->paint.region.child, nx - BW, ny - BW);
	XtResizeWidget(pw->paint.region.child, nw, nh, BW);

	pw->paint.region.needResize = True;
	moveGrips(pw);
	regionRedraw(pw);
}

void	pwRegionZoomPosChanged(PaintWidget pw)
{
	int	nx, ny;
	int	zoom;

	if (!pw->paint.region.isVisible)
		return;

	zoom = GET_ZOOM(pw);

	nx = (pw->paint.region.rect.x - pw->paint.zoomX) * zoom;
	ny = (pw->paint.region.rect.y - pw->paint.zoomY) * zoom;

	XtMoveWidget(pw->paint.region.child, nx - BW, ny - BW);
}


static void writeCleanRegion(PaintWidget pw, Boolean flag)
{
	if (!pw->paint.region.isVisible)
		return;

	writeRegion(pw);
	
	/*
	**  Free up temporary images
	*/
	if (pw->paint.region.sourceImg != NULL)
		XDestroyImage(pw->paint.region.sourceImg);
	if (pw->paint.region.maskImg != NULL)
		XDestroyImage(pw->paint.region.maskImg);

	pw->paint.region.sourceImg     = NULL;
	pw->paint.region.maskImg       = NULL;
	if (pw->paint.region.source != None) {
		XFreePixmap(XtDisplay(pw), pw->paint.region.source);
		pw->paint.region.source = None;
	}
	if (pw->paint.region.mask != None) {
		XFreePixmap(XtDisplay(pw), pw->paint.region.mask);
		pw->paint.region.mask = None;
	}
	if (pw->paint.region.notMask != None) {
		XFreePixmap(XtDisplay(pw), pw->paint.region.notMask);
		pw->paint.region.notMask = None;
	}

	pw->paint.region.undoPixmap = None;

	if (flag) {
		if (pw->paint.region.child != None) 
			XtUnmapWidget(pw->paint.region.child);
		pw->paint.region.isVisible = False;
	}
}

void PwRegionFinish(Widget w, Boolean flag)
{
	PaintWidget	pw = (PaintWidget)w;
	PaintWidget	pp = (PaintWidget)pw->paint.paint;
	PaintWidget	tpw;
	int		i;

	if (flag) {
		tpw = (pp == None) ? pw : pp;
		writeCleanRegion(tpw, True);
		for (i = 0; i < tpw->paint.paintChildrenSize; i++)
			writeCleanRegion((PaintWidget)tpw->paint.paintChildren[i], True);
		doCallbacks(pw, False);
	} else {
		writeCleanRegion(pw, True);
	}
}

void PwRegionSet(Widget w, XRectangle *rect, Pixmap pix, Pixmap mask)
{
	PaintWidget	pw = (PaintWidget)w;
	int		i;
	int		nx, ny, x, y, width, height;
	int		zoom = GET_ZOOM(pw);
	Boolean		setIsAttached = False;

	/*
	**  If there is an image, write it
	**     rect == NULL, then this is just a "write" & "unmap" request
	*/
	PwRegionFinish(w, True);
	if (rect == NULL) 
		return;

	pw->paint.region.curZoom = zoom;

	x = (rect->x + pw->paint.zoomX) * zoom;
	y = (rect->y + pw->paint.zoomY) * zoom;
	width  = rect->width * zoom;
	height = rect->height * zoom;

	/*
	**  A little "initializing"
	*/
	pw->paint.region.isDrawn    = False;
	pw->paint.region.isTracking = False;
	pw->paint.region.needResize = False;

	pw->paint.region.rect = *rect;

	if (pix == None) {
		setIsAttached = True;

		if (rect->x < 0) {
			rect->width += rect->x;
			rect->x = 0;
		}
		if (rect->y < 0) {
			rect->height += rect->y;
			rect->y = 0;
		}
		if (rect->width > pw->paint.drawWidth)
			rect->width = pw->paint.drawWidth;
		if (rect->height > pw->paint.drawHeight)
			rect->height = pw->paint.drawHeight;

		pw->paint.region.source = XCreatePixmap(XtDisplay(pw), XtWindow(pw), 
							rect->width, rect->height,
							pw->core.depth);
		XCopyArea(XtDisplay(pw), GET_PIXMAP(pw), pw->paint.region.source,
					pw->paint.gc,
					rect->x, rect->y, 
					rect->width, rect->height, 
					0, 0);
	} else {
		pw->paint.region.source = pix;
	}

	pw->paint.region.mask = mask;
	pw->paint.region.orig = *rect;

	/*
	**  If there is a clipping mask, create a fg and bg GC with clip-masks
	**    to draw through.
	*/
	if (mask != None) {

		if (pw->paint.region.fg_gc == None) {
			pw->paint.region.fg_gc = XCreateGC(XtDisplay(w), XtWindow(w), 0, 0);
			pw->paint.region.bg_gc = XCreateGC(XtDisplay(w), XtWindow(w), 0, 0);
		}

		/*
		**  Make sure the Mask GC is built.
		*/
		GET_MGC(pw, mask);
		regionCreateNotMask(pw);
	} else {
		/*
		**  No clip mask, make sure we aren't using one.
		*/
		if (pw->paint.region.fg_gc != None) {
			XSetClipMask(XtDisplay(w), pw->paint.region.fg_gc, None);
			XSetClipMask(XtDisplay(w), pw->paint.region.bg_gc, None);
		}
	}

	if (pw->paint.region.child == None) {
		pw->paint.region.child = XtVaCreateWidget("region",
					compositeWidgetClass, w,
					XtNborderWidth, BW,
					NULL);
		XtAddEventHandler(pw->paint.region.child, ButtonPressMask, 
				False, 
				(XtEventHandler)regionButtonPress, 
				(XtPointer)pw);
		XtAddEventHandler(pw->paint.region.child, ButtonReleaseMask, 
				False, 
				(XtEventHandler)regionButtonRelease, 
				(XtPointer)pw);
		XtAddEventHandler(pw->paint.region.child, ButtonMotionMask, 
				False, 
				(XtEventHandler)regionGrab, 
				(XtPointer)pw);
		XtAddEventHandler(pw->paint.region.child, ExposureMask, 
				False, 
				(XtEventHandler)regionExpose, 
				(XtPointer)pw);
		XtAddEventHandler(pw->paint.region.child, StructureNotifyMask, 
				False, 
				(XtEventHandler)regionMove, 
				(XtPointer)pw);
		XtAddCallback((Widget)pw, XtNsizeChanged, (XtCallbackProc)zoomValueChanged, (XtPointer)NULL);
		XtVaSetValues(pw->paint.region.child, XtNx, x, XtNy, y, XtNwidth, width, XtNheight, height, NULL);
		XtManageChild(pw->paint.region.child);
		XDefineCursor(XtDisplay(w), XtWindow(pw->paint.region.child), XCreateFontCursor(XtDisplay(w), XC_fleur));

		for (i = 0; i < 9; i++) {
			if (i == 4)
				continue;

			pw->paint.region.grip[i] = XtVaCreateManagedWidget("grip",
						gripWidgetClass, pw->paint.region.child,
						XtNwidth, 6,
						XtNheight, 6,
						NULL);

			XtAddEventHandler(pw->paint.region.grip[i], ButtonPressMask, 
					False, 
					(XtEventHandler)gripPress, 
					(XtPointer)pw);
			XtAddEventHandler(pw->paint.region.grip[i], ButtonMotionMask, 
					False, 
					(XtEventHandler)gripGrab, 
					(XtPointer)pw);
			XtAddEventHandler(pw->paint.region.grip[i], ButtonReleaseMask, 
					False, 
					(XtEventHandler)gripRelease, 
					(XtPointer)pw);
		}
		regionSetGripCursors(pw);
		pw->paint.region.isVisible = True;
	} else {
		XClearArea(XtDisplay(pw), XtWindow(pw->paint.region.child),
				0, 0, 0, 0, True);
	}

	if (setIsAttached) {
		nx = rect->x;
		ny = rect->y;
	} else {
		nx = pw->paint.downX;
		ny = pw->paint.downY;
	}
	pw->paint.region.rect.x = nx;
	pw->paint.region.rect.y = ny;
	nx -= pw->paint.zoomX;
	ny -= pw->paint.zoomY;
	XtVaSetValues(pw->paint.region.child, XtNx, nx * zoom - BW, 
					      XtNy, ny * zoom - BW, 
					      XtNwidth, width, 
					      XtNheight, height, 
					      NULL);

	pw->paint.region.scaleX  = 1.0;
	pw->paint.region.scaleY  = 1.0;
	pw->paint.region.centerX = pw->paint.region.orig.width / 2;
	pw->paint.region.centerY = pw->paint.region.orig.height / 2;
	COPY_MAT(matIdentity, pw->paint.region.rotMat);
	MKMAT(pw);

	if (zoom != 1) {
		pw->paint.region.rect.width  *= zoom;
		pw->paint.region.rect.height *= zoom;
	}

	moveGrips(pw);

	if (!pw->paint.region.isVisible) {
		XtMapWidget(pw->paint.region.child);
		pw->paint.region.isVisible = True;
	}

#ifdef SHAPE
	XShapeCombineMask(XtDisplay(pw), XtWindow(pw->paint.region.child), 
					ShapeBounding, 0, 0, None, ShapeSet);
#endif

	pw->paint.region.isAttached = setIsAttached;
	doCallbacks(pw, True);
}

static PaintWidget getActiveRegion(PaintWidget pw)
{
	PaintWidget	pp = (PaintWidget)pw->paint.paint;
	PaintWidget	tpw = (pp == None) ? pw : pp;
	int		i;

	if (pw->paint.region.isVisible && pw->paint.region.source != None)
		return pw;

	if (tpw->paint.region.isVisible && tpw->paint.region.source != None)
		return tpw;

	for (i = 0; i < tpw->paint.paintChildrenSize; i++) {
		PaintWidget	p = (PaintWidget)tpw->paint.paintChildren[i];
		if (p->paint.region.source != None) 
			return p;
	}

	return None;
}

void PwRegionSetRawPixmap(Widget w, Pixmap pix)
{
	PaintWidget	pw = getActiveRegion((PaintWidget)w);

	if (pw == None)
		return;

	XFreePixmap(XtDisplay(pw), pw->paint.region.source);

	if (pw->paint.region.sourceImg != NULL) {
		XDestroyImage(pw->paint.region.sourceImg);
		pw->paint.region.sourceImg = NULL;
	}

	pw->paint.region.source    = pix;

	doResize(pw);
	regionRedraw(pw);
}

Boolean PwRegionGet(Widget w, Pixmap *pix, Pixmap *mask)
{
	Display		*dpy = XtDisplay(w);
	Window		win  = XtWindow(w);
	PaintWidget	pw = getActiveRegion((PaintWidget)w);
	Pixmap		myMask = None, notMask = None;
	int		zoom;
	int		width, height;

	if (pw == None)
		return False;
	zoom = GET_ZOOM(pw);
	width  = pw->paint.region.orig.width;
	height = pw->paint.region.orig.height;

	if (pix)  *pix  = None;
	if (mask) *mask = None;

	if (pw->paint.region.source != None && pix != NULL) {
		*pix = XCreatePixmap(dpy, win, width, height, pw->core.depth);
		if (pw->paint.region.sourceImg != NULL) {
			XPutImage(dpy, *pix, pw->paint.tgc,
				pw->paint.region.sourceImg, 
				0, 0, 0, 0, width, height);
		} else {
			XCopyArea(dpy, pw->paint.region.source, 
				*pix, pw->paint.tgc,
				0, 0, width, height, 0, 0);
		}
	}
	if (pw->paint.region.mask != None) {
		myMask  = XCreatePixmap(dpy, win, width, height, 1);
		notMask = XCreatePixmap(dpy, win, width, height, 1);

		if (pw->paint.region.maskImg != NULL) {
			XPutImage(dpy, myMask, pw->paint.mgc,
				pw->paint.region.maskImg, 
				0, 0, 0, 0, width, height);
		} else {
			XCopyArea(dpy, pw->paint.region.mask, 
				myMask, pw->paint.mgc,
				0, 0, width, height, 0, 0);
		}

		XSetFunction(dpy, pw->paint.mgc, GXcopyInverted);
		XCopyArea(dpy, myMask, notMask, pw->paint.mgc, 0, 0, 
				width, height, 0, 0);
		XSetFunction(dpy, pw->paint.mgc, GXcopy);

		if (mask == NULL)
			XFreePixmap(dpy, myMask);
		else
			*mask = myMask;
	}
	if (notMask != None && pix != NULL) {
		XSetClipOrigin(dpy, pw->paint.igc, 0, 0);
		XSetClipMask(XtDisplay(pw), pw->paint.igc, notMask);
		XFillRectangle(XtDisplay(pw), *pix, pw->paint.igc, 0, 0,
				width, height);
		XSetClipMask(XtDisplay(pw), pw->paint.igc, None);

	}

	if (notMask != None)
		XFreePixmap(dpy, notMask);

	return True;
}

void PwRegionClear(Widget w)
{
	PaintWidget	pw = getActiveRegion((PaintWidget)w);

	if (pw == None)
		return;

	PwRegionTear(w);

	pw->paint.region.isVisible = False;
	if (pw->paint.region.child != None)
		XtUnmapWidget(pw->paint.region.child);
	doCallbacks(pw, False);
}

/*
**  Insure that the current region is not connected to the
**    source pixmap.
*/
void PwRegionTear(Widget w)
{
	PaintWidget	pw = getActiveRegion((PaintWidget)w);
	XRectangle	nr;

	if (pw == None)
		return;
	if (!pw->paint.region.isAttached || !pw->paint.region.isVisible)
		return;

	nr = pw->paint.region.rect;
	nr.width  /= GET_ZOOM(pw);
	nr.height /= GET_ZOOM(pw);

	pw->paint.region.undoPixmap = PwUndoStart((Widget)pw, &nr);

	if (pw->paint.region.mask != None) {
		XSetClipOrigin(XtDisplay(pw), pw->paint.igc, nr.x, nr.y);
		XSetClipMask(XtDisplay(pw), pw->paint.igc, pw->paint.region.mask);
	}
#if 0
	if (pw->paint.sourcePixmap == None) {
#endif
		XFillRectangles(XtDisplay(pw), pw->paint.region.undoPixmap,
			pw->paint.igc, &nr, 1);
#if 0
	} else {
		XCopyArea(XtDisplay(pw), pw->paint.sourcePixmap, pw->paint.region.undoPixmap, 
				pw->paint.igc, 
				nr.x, nr.y,
				nr.width, nr.height,
				nr.x, nr.y);
	}
#endif
	PwUpdate((Widget)pw, &nr, False);
	if (pw->paint.region.mask != None) {
		XSetClipOrigin(XtDisplay(pw), pw->paint.igc, 0, 0);
		XSetClipMask(XtDisplay(pw), pw->paint.igc, None);
	}
	pw->paint.region.isAttached = False;
}

void PwRegionAppendMatrix(Widget w, pwMatrix mat)
{
	PaintWidget	pw = getActiveRegion((PaintWidget)w);

	if (pw == None)
		return;
	
	PwRegionTear((Widget)pw);

	mm(pw->paint.region.rotMat, mat, pw->paint.region.rotMat);
	MKMAT(pw);

	pw->paint.region.needResize = True;
	regionResizeWindow(pw, False);
	regionRedraw(pw);
}

void PwRegionSetMatrix(Widget w, pwMatrix mat)
{
	PaintWidget	pw = getActiveRegion((PaintWidget)w);

	if (pw == None)
		return;

	PwRegionTear((Widget)pw);

	COPY_MAT(mat, pw->paint.region.rotMat);
	MKMAT(pw);

	pw->paint.region.needResize = True;
	regionResizeWindow(pw, False);
	regionRedraw(pw);
}

void PwRegionAddScale(Widget w, float *xs, float *ys)
{
	PaintWidget	pw = getActiveRegion((PaintWidget)w);

	if (pw == None)
		return;

	if (xs != NULL)
		pw->paint.region.scaleX *= *xs;
	if (ys != NULL)
		pw->paint.region.scaleY *= *ys;

	MKMAT(pw);

	pw->paint.region.needResize = True;
	regionResizeWindow(pw, False);
	regionRedraw(pw);
}

void PwRegionSetScale(Widget w, float *xs, float *ys)
{
	PaintWidget	pw = getActiveRegion((PaintWidget)w);

	if (pw == None)
		return;

	if (xs != NULL)
		pw->paint.region.scaleX = *xs;
	if (ys != NULL)
		pw->paint.region.scaleY = *ys;

	MKMAT(pw);

	pw->paint.region.needResize = True;
	regionResizeWindow(pw, False);
	regionRedraw(pw);
}

void PwRegionReset(Widget w, Boolean flag)
{
	PaintWidget	pw = getActiveRegion((PaintWidget)w);
	pwMatrix	mat;

	if (pw == None)
		return;

	PwRegionTear((Widget)pw);

	mat[0][0] = mat[1][1] = 1;
	mat[1][0] = mat[0][1] = 0;

	COPY_MAT(mat, pw->paint.region.rotMat);
	pw->paint.region.scaleY = 1.0;
	pw->paint.region.scaleX = 1.0;
	MKMAT(pw);

	pw->paint.region.needResize = True;
	regionResizeWindow(pw, False);
	regionRedraw(pw);

	/* XXX flag should reset X & Y position as well */
}
