
/*
 *         PVM version 3.3:  Parallel Virtual Machine System
 *               University of Tennessee, Knoxville TN.
 *           Oak Ridge National Laboratory, Oak Ridge TN.
 *                   Emory University, Atlanta GA.
 *      Authors:  A. L. Beguelin, J. J. Dongarra, G. A. Geist,
 *    W. C. Jiang, R. J. Manchek, B. K. Moore, and V. S. Sunderam
 *                   (C) 1992 All Rights Reserved
 *
 *                              NOTICE
 *
 * Permission to use, copy, modify, and distribute this software and
 * its documentation for any purpose and without fee is hereby granted
 * provided that the above copyright notice appear in all copies and
 * that both the copyright notice and this permission notice appear in
 * supporting documentation.
 *
 * Neither the Institutions (Emory University, Oak Ridge National
 * Laboratory, and University of Tennessee) nor the Authors make any
 * representations about the suitability of this software for any
 * purpose.  This software is provided ``as is'' without express or
 * implied warranty.
 *
 * PVM version 3 was funded in part by the U.S. Department of Energy,
 * the National Science Foundation and the State of Tennessee.
 */

/*
 *	xep.c
 *
 *	Display pixmap calculated by tiled workers in an X window.
 *
 *	Nov 92 Manchek
 */


#include <stdio.h>
#include <math.h>
#include <X11/Xlib.h>
#include <X11/cursorfont.h>
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Shell.h>
#include <X11/Xaw/Label.h>
#include <X11/Xaw/Command.h>
#include <X11/Xaw/Toggle.h>
#include <X11/Xaw/Form.h>
#include "pvm3.h"
#include "../src/bfunc.h"
#include "bars.xbm"
#include "neww.xbm"
#include "quit.xbm"
#include "redo.xbm"
#include "zout.xbm"


#ifndef	min
#define	min(a,b)	((a)<(b)?(a):(b))
#endif
#ifndef	max
#define	max(a,b)	((a)>(b)?(a):(b))
#endif

#define	TALLOC(n,t)	(t*)malloc((n)*sizeof(t))

#define	NCMV	64
#define	byteswide(w) (((w)+7)/8)
#define	TILEHEIGHT	16

typedef	unsigned int IBIT32;

/* describes an image canvas (currently only one) */

struct canvas {
	Widget cn_wgt;			/* widget */
	Window cn_win;			/* window */
	int cn_wd;				/* window size */
	int cn_ht;
	u_char *cn_dat;			/* image data */
	XImage *cn_xim;			/* ximage struct */
	int cn_zoom;			/* display mag */
	int cn_ox;				/* offset for zoom */
	int cn_oy;
	double cn_re1;			/* corner coords */
	double cn_im1;
	double cn_re2;
	double cn_im2;
	int cn_x1;				/* pick coords */
	int cn_y1;
	int cn_x2;
	int cn_y2;
};


void pick();
void canvas_ev();
void pvm_cb();
void redo_cb();
void zout_cb();

extern char *pvm_errlist[];


/***************
 **  Globals  **
 **           **
 ***************/

Display *xDisp;
XtAppContext context;
Widget topLevel;		/* main widget */
int xScrn;
Window xRootW;
int isMono;				/* monochrome display */
int isCmap;				/* display colormapped */
int nPlanes;			/* display depth */
int revByte;			/* X server byte order is opposite ours */
int bimbo;				/* bitmap bit order */
int xBpp;				/* ximage bits per pixel */
int xBypp;				/* ximage bytes per pixel */
int redMask;
int redShift;
int greenMask;
int greenShift;
int blueMask;
int blueShift;
Colormap xCmap;
int cmapInd[NCMV];
Visual *defVis;
IBIT32 fclutr[256];		/* pseudo-color lut for true-color display */
IBIT32 fclutg[256];
IBIT32 fclutb[256];
GC rubGc;				/* rubberbox gc */
GC canGc;				/* canvas painting gc */
Cursor crossCr;
struct canvas imCan;

int dobars = 0;			/* label tiles with worker number */
int hostmask;			/* host field of tid */
int hostshift;			/* host field offset */
int mytid;				/* pvm tid */
int pvmsocket = -1;
int nserv = 1;			/* number of work servers */
int *servtids = 0;		/* worker server task ids */
int *servflist = 0;		/* free-list of workers */
int servfree = -1;		/* head of free-list of workers */
int *servjobs = 0;		/* jobs assigned to workers */
int njobs;				/* number of jobs (tiles) */
int jobstogo;			/* jobs unfinished */
int jobnext;			/* jobs unstarted */
int redoing = 0;		/* already calculating */
char *workerfile = 0;


/***************
 **  Xt Gorp  **
 **           **
 ***************/

char canvasTl[] =
"*canvas.translations:\
<Btn1Down>:pick(start)\\n\
<Btn1Motion>:pick(adjust)\\n\
<Btn1Up>:pick(end)\\n\
<Btn3Down>:pick(modify)\\n\
<Btn3Motion>:pick(adjust)\\n\
<Btn3Up>:pick(end)\\n\
";


/* Widget default values */

static char *fallbacks[] = {
	"*allowShellResize:true",
	"*quitButton.label:Quit",
	"*recalcButton.label:Redo",
	"*workersButton.label:NewWorkers",
	canvasTl,
	0
};

/* To get custom resources */

typedef struct {
	Bool mono;			/* force monochrome display */
	String worker;		/* worker a.out name */
	Bool bars;			/* display processor id bars */
	int n;				/* number of workers */
} app_res_t, *app_resp_t;

static app_res_t app_res;

static XtResource res_list[] = {
	{ "worker", "Worker", XtRString, sizeof(String),
		XtOffset(app_resp_t, worker), XtRString, "mtile" },
	{ "monochrome", "Monochrome", XtRBool, sizeof(Bool),
		XtOffset(app_resp_t, mono), XtRString, "off" },
	{ "bars", "Bars", XtRBool, sizeof(Bool),
		XtOffset(app_resp_t, bars), XtRString, "on" },
	{ "nWorkers", "NWorkers", XtRInt, sizeof(int),
		XtOffset(app_resp_t, n), XtRString, "-1" },
};

static XrmOptionDescRec knownargs[] = {
	{ "-mono", ".monochrome", XrmoptionNoArg, "on" },
	{ "+mono", ".monochrome", XrmoptionNoArg, "off" },
	{ "-worker", ".worker", XrmoptionSepArg, 0 },
	{ "-bars", ".bars", XrmoptionNoArg, "on" },
	{ "+bars", ".bars", XrmoptionNoArg, "off" },
	{ "-n", ".nWorkers", XrmoptionSepArg, 0 },
};

static XtCallbackRec callback[2] = { { 0, 0 }, { 0, 0 } };
static Arg args[16];

static XtActionsRec actbl[] = {
	{"pick", pick},
};

u_char ditclass[8][8] = {
	2,   130, 34,  162, 10,  138, 42,  170,
	194, 66,  226, 98,  202, 74,  234, 106,
	50,  178, 18,  146, 58,  186, 26,  154,
	242, 114, 210, 82,  250, 122, 218, 90,
	14,  142, 46,  174, 6,   134, 38,  166,
	206, 78,  238, 110, 198, 70,  230, 102,
	62,  190, 30,  158, 54,  182, 22,  150,
	254, 126, 222, 94,  246, 118, 214, 86
};


main(argc, argv)
	int argc;
	char **argv;
{
	int n;
	XGCValues xgcv;

	n = 0;
	topLevel = XtAppInitialize(&context, "xep",
		knownargs, XtNumber(knownargs),
		&argc, argv,
		fallbacks,
		args, n);

	if (argc > 1) {
		for (n = 1; n < argc; n++)
			fprintf(stderr, "unknown option <%s>\n", argv[n]);
		fputs("options:\n", stderr);
		fputs("  -worker filename  set calculation task name\n", stderr);
		fputs("  -/+mono           set/unset forced mono mode\n", stderr);
		fputs("  -/+bars           display/don't display processor bars\n", stderr);
		fputs("  -n count          set number of workers\n", stderr);
		exit(1);
	}

	XtGetApplicationResources(topLevel, (caddr_t)&app_res,
		res_list, XtNumber(res_list), 0, 0);

	workerfile = app_res.worker;
	dobars = app_res.bars;

	if ((mytid = pvm_mytid()) < 0)
		exit(1);

	hostmask = pvm_tidtohost(~0);
	hostshift = ffs(hostmask) - 1;

	start_workers();

	XtAppAddActions(context, actbl, XtNumber(actbl));

	xDisp = XtDisplay(topLevel);
	xScrn = DefaultScreen(xDisp);
	xRootW = RootWindow(xDisp, xScrn);

	crossCr = XCreateFontCursor(xDisp, XC_tcross);

	setup_color(app_res.mono);

	xgcv.function = GXxor;
	xgcv.background = BlackPixel(xDisp, xScrn);
	xgcv.foreground = ~(~0 << nPlanes);
	rubGc = XCreateGC(xDisp, xRootW,
			GCBackground|GCForeground|GCFunction, &xgcv);

	xgcv.function = GXcopy;
	xgcv.background = BlackPixel(xDisp, xScrn);
	xgcv.foreground = WhitePixel(xDisp, xScrn);
	canGc = XCreateGC(xDisp, xRootW,
			GCBackground|GCForeground|GCFunction, &xgcv);

	create_xep_widget();

	XtAppMainLoop(context);
}


start_workers()
{
	int n;		/* actual number started */
	int i;

	pvm_config(&nserv, (int*)0, (struct pvmhostinfo**)0);
	if (app_res.n > 0 && app_res.n <= nserv * 10)
		nserv = app_res.n;
/*
	if (nserv > 1)
		nserv--;
*/

	servtids = TALLOC(nserv, int);
	servflist = TALLOC(nserv, int);
	servjobs = TALLOC(nserv, int);
	servfree = -1;
	if (pvm_spawn(workerfile, (char**)0, 0, "", nserv, servtids) < 0) {
		pvm_exit();
		exit(1);
	}

	pvm_initsend(PvmDataDefault);
	for (i = n = 0; i < nserv; i++) {
		if (servtids[i] < 0) {
			fprintf(stderr, "can't initiate worker %s: %s\n", workerfile,
					pvm_errlist[-servtids[i]]);

		} else {
			servtids[n] = servtids[i];
			pvm_send(servtids[n], 3);
			servflist[n] = servfree;
			servfree = n++;
		}
	}
	if (n != nserv) {
		if (nserv = n)
			fprintf(stderr, "using only %d workers\n", n);
		else
			fprintf(stderr, "ack, no workers\n");
	}
	return 0;
}


stop_workers()
{
	if (servtids) {
		while (nserv-- > 0)
			pvm_kill(servtids[nserv]);
		free(servtids);
		free(servflist);
		free(servjobs);
	}
	servtids = 0;
	servflist = 0;
	servjobs = 0;
}


void
pvm_cb(cli, src, id)
	XtPointer cli;
	int *src;
	XtInputId *id;
{
	while (pvm_nrecv(-1, -1) > 0)
		claim_tile();
	if (!jobstogo)
		redoing = 0;
}


setup_color(mono)
	int mono;
{
	int i, j;
	XColor colr;
	IBIT32 rbowr[NCMV], rbowg[NCMV], rbowb[NCMV];
	IBIT32 mbo = 0x04030201;	/* to test machine byte order */

	nPlanes = DefaultDepth(xDisp, xScrn);
	defVis = DefaultVisual(xDisp, xScrn);

	revByte = (ImageByteOrder(xDisp) == MSBFirst) ? 1 : 0;
	if (*(char*)&mbo == 4)
		revByte = !revByte;

	bimbo = BitmapBitOrder(xDisp);

	if (!mono && nPlanes > 1) {
		isMono = 0;

		if (defVis->class == TrueColor || defVis->class == DirectColor) {
			XPixmapFormatValues *pfv;
			int i;

			isCmap = 0;
			if (!(pfv = XListPixmapFormats(xDisp, &i))) {
				fprintf(stderr, "can't get pixmap format list for screen\n");
				exit(1);
			}
			while (--i >= 0)
				if (pfv[i].depth == nPlanes) break;
			if (i < 0) {
				fprintf(stderr, "no pixmap format matches screen depth?\n");
				exit(1);
			}
			xBpp = pfv[i].bits_per_pixel;
			xBypp = xBpp / 8;
			redMask = defVis->red_mask;
			redShift = ffs(redMask & ~(redMask >> 1)) - 8;
			greenMask = defVis->green_mask;
			greenShift = ffs(greenMask & ~(greenMask >> 1)) - 8;
			blueMask = defVis->blue_mask;
			blueShift = ffs(blueMask & ~(blueMask >> 1)) - 8;
			mkrbow(fclutr, fclutg, fclutb, 256);

		} else {
			isCmap = 1;
			xCmap = DefaultColormap(xDisp, xScrn);
			j = 17 - ffs(NCMV);
/*
			for (i = 0; i < NCMV; i++) {
				colr.red = colr.green = colr.blue = i << j;
				if (!XAllocColor(xDisp, xCmap, &colr))
					goto forcebw;
				cmapInd[i] = colr.pixel;
			}
*/
			mkrbow(rbowr, rbowg, rbowb, NCMV);
			for (i = 0; i < NCMV; i++) {
				colr.red = rbowr[i] << j;
				colr.green = rbowg[i] << j;
				colr.blue = rbowb[i] << j;
				if (!XAllocColor(xDisp, xCmap, &colr))
					goto forcebw;
				cmapInd[i] = colr.pixel;
			}
		}
		return;
	}

forcebw:
	isMono = 1;
	fputs("display is binary, it's not gonna look great...\n", stderr);
}


/*	mkrbow()
*
*	Generate a rainbow lut.  0 is black, n-1 is white, and entries
*	between those two go through the spectrum from red to violet.
*	Output values are from 0 to n-1.
*/

mkrbow(r, g, b, n)
	IBIT32 *r, *g, *b;	/* red, grn, blu lut return */
	int n;				/* number of entries (length of r, g, b) */
{
	int i, j;
	double d, e;

	for (i = 1; i < n - 1; i++) {
		j = n - 1 - i;
		d = (d = cos((double)((j - n * 0.16) * (3.1415926535 / n)))) < 0.0
			? 0.0 : d;
		b[i] = d * n;
		d = (d = cos((double)((j - n * 0.52) * (3.1415926535 / n)))) < 0.0
			? 0.0 : d;
		g[i] = d * n;
		d = (d = cos((double)((j - n * .83) * (3.1415926535 / n)))) < 0.0
			? 0.0 : d;
		e = (e = cos((double)(j * (3.1415926535 / n)))) < 0.0
			? 0.0 : e;
		r[i] = d * n + e * (n / 2);
	}
	r[i] = g[i] = b[i] = i;
	r[0] = g[0] = b[0] = 0;
}


void
quit_cb(wgt, cli, cd)
	Widget wgt;
	caddr_t cli;
	caddr_t cd;
{
	stop_workers();
	pvm_exit();
	exit(0);
}


void
zout_cb(wgt, cli, cd)
	Widget wgt;
	caddr_t cli;
	caddr_t cd;
{
	double d;

	if (redoing)
		return;
	redoing = 1;

	d = (imCan.cn_re2 - imCan.cn_re1) / 2;
	imCan.cn_re1 -= d;
	imCan.cn_re2 += d;
	d = (imCan.cn_im2 - imCan.cn_im1) / 2;
	imCan.cn_im1 -= d;
	imCan.cn_im2 += d;

	splat_out(imCan.cn_dat, imCan.cn_wd, imCan.cn_ht, 1);

	repaint_region(&imCan, 0, 0, imCan.cn_wd - 1, imCan.cn_ht - 1);
	refresh_region(&imCan, 0, 0, imCan.cn_wd - 1, imCan.cn_ht - 1);
	imCan.cn_x1 = 0;
	imCan.cn_y1 = 0;
	imCan.cn_x2 = 0;
	imCan.cn_y2 = 0;

	start_recalc();
}


void
redo_cb(wgt, cli, cd)
	Widget wgt;
	caddr_t cli;
	caddr_t cd;
{
	double re1, im1, re2, im2;
	int wd = imCan.cn_wd;
	int ht = imCan.cn_ht;
	int x1 = imCan.cn_x1;
	int y1 = imCan.cn_y1;
	int x2 = imCan.cn_x2;
	int y2 = imCan.cn_y2;
	int t;

	if (x1 != x2 && y1 != y2) {

		if (redoing)
			return;
		redoing = 1;

		if (x1 > x2) {
			t = x1;
			x1 = x2;
			x2 = t;
		}
		if (y1 > y2) {
			t = y1;
			y1 = y2;
			y2 = t;
		}
		re1 = imCan.cn_re1 + x1 * (imCan.cn_re2 - imCan.cn_re1) / wd;
		im1 = imCan.cn_im1 + y1 * (imCan.cn_im2 - imCan.cn_im1) / ht;
		re2 = re1 + (x2 - x1) * (imCan.cn_re2 - imCan.cn_re1) / wd;
		im2 = im1 + (x2 - x1) * (imCan.cn_im2 - imCan.cn_im1) / wd;
		imCan.cn_re1 = re1;
		imCan.cn_im1 = im1;
		imCan.cn_re2 = re2;
		imCan.cn_im2 = im2;

		splat_out(imCan.cn_dat, imCan.cn_wd, imCan.cn_ht, 1);

		repaint_region(&imCan, 0, 0, imCan.cn_wd - 1, imCan.cn_ht - 1);
		refresh_region(&imCan, 0, 0, imCan.cn_wd - 1, imCan.cn_ht - 1);
		imCan.cn_x1 = 0;
		imCan.cn_y1 = 0;
		imCan.cn_x2 = 0;
		imCan.cn_y2 = 0;

		start_recalc();
	}
}


void
bars_cb(wgt, cli, cd)
	Widget wgt;
	caddr_t cli;
	caddr_t cd;
{
	dobars = !dobars;
}


void
workers_cb(wgt, cli, cd)
	Widget wgt;
	caddr_t cli;
	caddr_t cd;
{
	if (redoing)
		return;

	stop_workers();
	start_workers();
}


create_xep_widget()
{
	Widget box;
	Widget w;
	int n;
	char buf[128];
	Pixmap pm;

	n = 0;
	box = XtCreateManagedWidget("", formWidgetClass, topLevel, args, n);

	n = 0;
	XtSetArg(args[n], XtNleft, XtChainLeft); n++;
	XtSetArg(args[n], XtNright, XtChainLeft); n++;
	XtSetArg(args[n], XtNtop, XtChainTop); n++;
	XtSetArg(args[n], XtNbottom, XtChainTop); n++;
	callback[0].callback = (XtCallbackProc)quit_cb;
	callback[0].closure = (caddr_t)0;
	XtSetArg(args[n], XtNcallback, callback); n++;
	pm = XCreatePixmapFromBitmapData(xDisp, xRootW,
			quit_bits, quit_width, quit_height, 1, 0, 1);
	XtSetArg(args[n], XtNbitmap, (XtArgVal)pm); n++;
	w =  XtCreateManagedWidget("quitButton", commandWidgetClass,
			box, args, n);

	n = 0;
	XtSetArg(args[n], XtNleft, XtChainLeft); n++;
	XtSetArg(args[n], XtNright, XtChainLeft); n++;
	XtSetArg(args[n], XtNtop, XtChainTop); n++;
	XtSetArg(args[n], XtNbottom, XtChainTop); n++;
	callback[0].callback = (XtCallbackProc)workers_cb;
	callback[0].closure = (caddr_t)0;
	XtSetArg(args[n], XtNcallback, callback); n++;
	XtSetArg(args[n], XtNfromHoriz, w); n++;
	pm = XCreatePixmapFromBitmapData(xDisp, xRootW,
			neww_bits, neww_width, neww_height, 1, 0, 1);
	XtSetArg(args[n], XtNbitmap, (XtArgVal)pm); n++;
	w =  XtCreateManagedWidget("workersButton", commandWidgetClass,
			box, args, n);

	n = 0;
	XtSetArg(args[n], XtNleft, XtChainLeft); n++;
	XtSetArg(args[n], XtNright, XtChainLeft); n++;
	XtSetArg(args[n], XtNtop, XtChainTop); n++;
	XtSetArg(args[n], XtNbottom, XtChainTop); n++;
	callback[0].callback = (XtCallbackProc)redo_cb;
	callback[0].closure = (caddr_t)0;
	XtSetArg(args[n], XtNcallback, callback); n++;
	XtSetArg(args[n], XtNfromHoriz, w); n++;
	pm = XCreatePixmapFromBitmapData(xDisp, xRootW,
			redo_bits, redo_width, redo_height, 1, 0, 1);
	XtSetArg(args[n], XtNbitmap, (XtArgVal)pm); n++;
	w =  XtCreateManagedWidget("recalcButton", commandWidgetClass,
			box, args, n);

	n = 0;
	XtSetArg(args[n], XtNleft, XtChainLeft); n++;
	XtSetArg(args[n], XtNright, XtChainLeft); n++;
	XtSetArg(args[n], XtNtop, XtChainTop); n++;
	XtSetArg(args[n], XtNbottom, XtChainTop); n++;
	callback[0].callback = (XtCallbackProc)zout_cb;
	callback[0].closure = (caddr_t)0;
	XtSetArg(args[n], XtNcallback, callback); n++;
	XtSetArg(args[n], XtNfromHoriz, w); n++;
	pm = XCreatePixmapFromBitmapData(xDisp, xRootW,
			zout_bits, zout_width, zout_height, 1, 0, 1);
	XtSetArg(args[n], XtNbitmap, (XtArgVal)pm); n++;
	w =  XtCreateManagedWidget("zoomoutButton", commandWidgetClass,
			box, args, n);

	n = 0;
	XtSetArg(args[n], XtNleft, XtChainLeft); n++;
	XtSetArg(args[n], XtNright, XtChainLeft); n++;
	XtSetArg(args[n], XtNtop, XtChainTop); n++;
	XtSetArg(args[n], XtNbottom, XtChainTop); n++;
	callback[0].callback = (XtCallbackProc)bars_cb;
	callback[0].closure = (caddr_t)0;
	XtSetArg(args[n], XtNcallback, callback); n++;
	XtSetArg(args[n], XtNfromHoriz, w); n++;
	XtSetArg(args[n], XtNstate, dobars); n++;
	pm = XCreatePixmapFromBitmapData(xDisp, xRootW,
			bars_bits, bars_width, bars_height, 1, 0, 1);
	XtSetArg(args[n], XtNbitmap, (XtArgVal)pm); n++;
	w =  XtCreateManagedWidget("barsButton", toggleWidgetClass,
			box, args, n);

	imCan.cn_re1 = -2.0;
	imCan.cn_im1 = -2.0;
	imCan.cn_re2 = 2.0;
	imCan.cn_im2 = 2.0;
/*
	n = 0;
	XtSetArg(args[n], XtNleft, XtChainLeft); n++;
	XtSetArg(args[n], XtNright, XtChainRight); n++;
	XtSetArg(args[n], XtNtop, XtChainTop); n++;
	XtSetArg(args[n], XtNbottom, XtChainTop); n++;
	XtSetArg(args[n], XtNfromVert, w); n++;
	sprintf(buf, "(%.2e,%.2e) (%.2e,%.2e)", 
			imCan.cn_re1, imCan.cn_im1, imCan.cn_re2, imCan.cn_im2);
	XtSetArg(args[n], XtNlabel, buf); n++;
	w =  XtCreateManagedWidget("coords", labelWidgetClass,
			box, args, n);
*/

	imCan.cn_wd = 256;
	imCan.cn_ht = 256;
	imCan.cn_x1 = 0;
	imCan.cn_y1 = 0;
	imCan.cn_x2 = 256;
	imCan.cn_y2 = 256;
	imCan.cn_zoom = 1;
	imCan.cn_ox = 0;
	imCan.cn_oy = 0;
	imCan.cn_dat = TALLOC(imCan.cn_wd * imCan.cn_ht, u_char);
	splat_out(imCan.cn_dat, imCan.cn_wd, imCan.cn_ht, 1);
	cre_xim(&imCan);
	repaint_region(&imCan, 0, 0, imCan.cn_wd - 1, imCan.cn_ht - 1);

	n = 0;
	XtSetArg(args[n], XtNtop, XtChainTop); n++;
	XtSetArg(args[n], XtNright, XtChainRight); n++;
	XtSetArg(args[n], XtNwidth, imCan.cn_wd); n++;
	XtSetArg(args[n], XtNheight, imCan.cn_ht); n++;
	XtSetArg(args[n], XtNfromVert, w); n++;
	imCan.cn_wgt = XtCreateManagedWidget("canvas", widgetClass,
		box, args, n);

	XtRealizeWidget(topLevel);

	imCan.cn_win = XtWindow(imCan.cn_wgt);
	XDefineCursor(xDisp, imCan.cn_win, crossCr);

	XtAddEventHandler(imCan.cn_wgt, StructureNotifyMask|ExposureMask,
		False, canvas_ev, 0);
}


splat_out(ba, wd, ht, dir)
	u_char *ba;
	int wd, ht;
	int dir;
{
	int x = wd;
	int o = 0;
	int n = wd * ht;

	dir = dir ? 7 : 1;
	while (--n >= 0) {
		*ba++ = x & 7 ? 0 : 255;
		if (--x <= o) {
			o = (o + dir) & 7;
			x = wd + o;
		}
	}
}


void
canvas_ev(wgt, cli, xev, rem)
	Widget wgt;
	XtPointer cli;
	XEvent *xev;
	Boolean *rem;
{
	int wd, ht;				/* size of new canvas */
	double re1, im1, re2, im2;
	int ox, oy;				/* offset of new image in old */
	u_char *dat;			/* new canvas image */
	u_char *b1, *b2;		/* for copying image data */
	int w, h;

	switch (xev->type) {

	case ConfigureNotify:
		wd = xev->xconfigure.width;
		ht = xev->xconfigure.height;

		ox = (imCan.cn_wd - wd) / 2;
		re1 = imCan.cn_re1
				+ ((imCan.cn_re2 - imCan.cn_re1) * ox) / imCan.cn_wd;
		re2 = imCan.cn_re1
				+ ((imCan.cn_re2 - imCan.cn_re1) * (ox + wd)) / imCan.cn_wd;
		oy = (imCan.cn_ht - ht) / 2;
		im1 = imCan.cn_im1
				+ ((imCan.cn_re2 - imCan.cn_re1) * oy) / imCan.cn_wd;
		im2 = imCan.cn_im1
				+ ((imCan.cn_re2 - imCan.cn_re1) * (oy + ht)) / imCan.cn_wd;
		imCan.cn_re1 = re1;
		imCan.cn_re2 = re2;
		imCan.cn_im1 = im1;
		imCan.cn_im2 = im2;
/*
	printf("%dx%d  %e,%e  %e,%e\n", wd, ht, re1, im1, re2, im2);
	printf("%e : %e\n", (re2 - re1) / wd, (im2 - im1) / ht);
*/

		dat = TALLOC(wd * ht, u_char);
		splat_out(dat, wd, ht, 0);

		w = min(imCan.cn_wd, wd);
		h = min(imCan.cn_ht, ht);
		b1 = imCan.cn_dat + max(0, ox) + max(0, oy) * imCan.cn_wd;
		b2 = dat + max(0, -ox) + max(0, -oy) * wd;
		while (h-- > 0) {
			BCOPY(b1, b2, w);
			b1 += imCan.cn_wd;
			b2 += wd;
		}
		free(imCan.cn_dat);

		imCan.cn_dat = dat;
		imCan.cn_wd = wd;
		imCan.cn_ht = ht;
		imCan.cn_x1 = 0;
		imCan.cn_y1 = 0;
		imCan.cn_x2 = 0;
		imCan.cn_y2 = 0;
		cre_xim(&imCan);
		repaint_region(&imCan, 0, 0, imCan.cn_wd - 1, imCan.cn_ht - 1);
		refresh_region(&imCan, 0, 0, imCan.cn_wd - 1, imCan.cn_ht - 1);
		break;

	case Expose:
		{
			static int once = 1;

			if (once) {
				redo_cb((Widget)0, (caddr_t)0, (caddr_t)0);
				once = 0;
			}
		}
		rubbox(imCan.cn_win, imCan.cn_x1, imCan.cn_y1,
				imCan.cn_x2, imCan.cn_y2);
		refresh_region(&imCan, xev->xexpose.x, xev->xexpose.y,
				xev->xexpose.x + xev->xexpose.width - 1,
				xev->xexpose.y + xev->xexpose.height - 1);
		rubbox(imCan.cn_win, imCan.cn_x1, imCan.cn_y1,
				imCan.cn_x2, imCan.cn_y2);
		break;

	default:
		break;
	}
}


/*	pick()
*
*	s - start
*	a - adjust
*	e - end
*	m - modify
*/

void
pick(wgt, xev, par, nump)
	Widget wgt;
	XEvent *xev;
	String *par;
	int *nump;
{
	Window w = XtWindow(wgt);
	int wd = imCan.cn_wd;
	int ht = imCan.cn_ht;
	int x1 = imCan.cn_x1;
	int y1 = imCan.cn_y1;
	int x2 = imCan.cn_x2;
	int y2 = imCan.cn_y2;
	int x, y;

	if (*nump == 1 && get_event_xy(xev, &x, &y)) {
		switch (par[0][0]) {

		case 's':
			if (x1 != -1)
				rubbox(w, x1, y1, x2, y2);
			x2 = x1 = x;
			y2 = y1 = y;
			rubbox(w, x1, y1, x2, y2);
			break;

		case 'a':
			if (x1 != -1) {
				rubbox(w, x1, y1, x2, y2);
				x2 = (abs(y - y1) * wd) / ht;
				x2 = x - x1 > 0 ? x1 + x2 : x1 - x2;
				y2 = (abs(x - x1) * ht) / wd;
				y2 = y - y1 > 0 ? y1 + y2 : y1 - y2;
				if (abs(x2 - x1) > abs(x - x1))
					y2 = y;
				else
					x2 = x;
				rubbox(w, x1, y1, x2, y2);
			}
			break;

		case 'e':
			if (x1 != -1) {
				x2 = (abs(y - y1) * wd) / ht;
				x2 = x - x1 > 0 ? x1 + x2 : x1 - x2;
				y2 = (abs(x - x1) * ht) / wd;
				y2 = y - y1 > 0 ? y1 + y2 : y1 - y2;
				if (abs(x2 - x1) > abs(x - x1))
					y2 = y;
				else
					x2 = x;
/*
	printf("%d, %d -> %d, %d\n", x1, y1, x2, y2);
*/
			}
			break;

		case 'm':
			if (x1 != -1) {
				rubbox(w, x1, y1, x2, y2);
				if (abs(x - x1) < abs(x - x2))
					x1 = x2;
				if (abs(y - y1) < abs(y - y2))
					y1 = y2;
				x2 = (abs(y - y1) * wd) / ht;
				x2 = x - x1 > 0 ? x1 + x2 : x1 - x2;
				y2 = (abs(x - x1) * ht) / wd;
				y2 = y - y1 > 0 ? y1 + y2 : y1 - y2;
				if (abs(x2 - x1) > abs(x - x1))
					y2 = y;
				else
					x2 = x;
				rubbox(w, x1, y1, x2, y2);
			}
			break;

		default:
			break;
		}
		imCan.cn_x1 = x1;
		imCan.cn_y1 = y1;
		imCan.cn_x2 = x2;
		imCan.cn_y2 = y2;
	}
}


int
get_event_xy(xev, xp, yp)
	XEvent *xev;
	int *xp, *yp;
{
	switch (xev->type) {
	case KeyPress:
	case KeyRelease:
		*xp = xev->xkey.x;
		*yp = xev->xkey.y;
		return 1;
		break;

	case ButtonPress:
	case ButtonRelease:
		*xp = xev->xbutton.x;
		*yp = xev->xbutton.y;
		return 1;
		break;

	case MotionNotify:
		*xp = xev->xmotion.x;
		*yp = xev->xmotion.y;
		return 1;
		break;

	case EnterNotify:
	case LeaveNotify:
		*xp = xev->xcrossing.x;
		*yp = xev->xcrossing.y;
		return 1;
		break;

	default:
		return 0;
	}
}


/*	rubbox()
*
*	Draw a rubberband box XOR in a window.  Call again to undraw.
*/

rubbox(w, x1, y1, x2, y2)
	Window w;
	int x1, x2, y1, y2;
{
	register int x, y, h, v;

	if (x1 < x2)
		{ x = x1; h = x2 - x1; }
	else
		{ x = x2; h = x1 - x2; }
	if (y1 < y2)
		{ y = y1; v = y2 - y1; }
	else
		{ y = y2; v = y1 - y2; }

	XDrawRectangle(xDisp, w, rubGc, x, y, h, v);
}


/*	sw4()
*
*	Byteswap a 32-bit integer.
*/

IBIT32
sw4(x)
	IBIT32 x;
{
	u_char *cp = (u_char*)&x;
	u_char c;

	c = cp[0];
	cp[0] = cp[3];
	cp[3] = c;
	c = cp[1];
	cp[1] = cp[2];
	cp[2] = c;
	return x;
}


/*	repaint_region()
*
*	Replot XImage from its underlying image.
*/

repaint_region(cnp, x1, y1, x2, y2)
	struct canvas *cnp;
	int x1, y1, x2, y2;				/* image coordinates */
{
	char *ximbuf;					/* ximage data */
	u_char *ribuf = cnp->cn_dat;	/* src image data */
	int sx, sy;						/* source x, y counter */
	int dx, dy;						/* dest x, y counter */
	int xa, ya;
	unsigned char *sa;
	char *da;
	IBIT32 pixv;
	IBIT32 pixr, pixg, pixb;
	int lbs;
	int lbm;

	if (x1 < cnp->cn_ox)
		x1 = cnp->cn_ox;
	if (y1 < cnp->cn_oy)
		y1 = cnp->cn_oy;
	if (++x2 > cnp->cn_wd)
		x2 = cnp->cn_wd;
	if (++y2 > cnp->cn_ht)
		y2 = cnp->cn_ht;

	ximbuf = cnp->cn_xim->data;
	if (!isMono) {
		if (isCmap) {	/* display is colormapped */
			lbs = 8 - ffs(NCMV) + 1;	/* shift make byte into cmap depth */
			lbm = 0xff >> lbs;			/* mask significant */
			sy = y1;
			dy = (y1 - cnp->cn_oy) * cnp->cn_zoom;
			ya = 0;
			while (sy < y2 && dy < cnp->cn_ht) {
				sx = x1;
				dx = (x1 - cnp->cn_ox) * cnp->cn_zoom;
				xa = 0;
				sa = ribuf + sy * cnp->cn_wd + sx;
				da = ximbuf + dy * cnp->cn_xim->bytes_per_line + dx;
				while (sx < x2 && dx < cnp->cn_wd) {
					*da = cmapInd[lbm & (*sa >> lbs)];
					da++;
					dx++;
					if (++xa >= cnp->cn_zoom) {
						xa = 0;
						sx++;
						sa++;
					}
				}
				dy++;
				if (++ya >= cnp->cn_zoom) {
					ya = 0;
					sy++;
				}
			}

		} else {	/* display is true color */

			lbm = 0xff;

			sy = y1;
			dy = (y1 - cnp->cn_oy) * cnp->cn_zoom;
			ya = 0;
			while (sy < y2 && dy < cnp->cn_ht) {
				sx = x1;
				dx = (x1 - cnp->cn_ox) * cnp->cn_zoom;
				xa = 0;
				sa = ribuf + sy * cnp->cn_wd + sx;
				da = ximbuf + dy * cnp->cn_xim->bytes_per_line + dx * sizeof(IBIT32);
				while (sx < x2 && dx < cnp->cn_wd) {
					pixv = lbm & *sa;
					pixr = fclutr[pixv];
					pixg = fclutg[pixv];
					pixb = fclutb[pixv];
					pixv = (redMask & (pixr << redShift))
							| (greenMask & (pixg << greenShift))
							| (blueMask & (pixb << blueShift));
					if (revByte)
						*((IBIT32*)da) = sw4(pixv);
					else
						*((IBIT32*)da) = pixv;
		
					da += sizeof(IBIT32);
					dx++;
					if (++xa >= cnp->cn_zoom) {
						xa = 0;
						sx++;
						sa++;
					}
				}
				dy++;
				if (++ya >= cnp->cn_zoom) {
					ya = 0;
					sy++;
				}
			}
		}

	} else {	/* binary display  XXX region not done yet */
		u_char pxa;
		u_char msks[8];

		for (lbs = 0; lbs < 8; lbs++)
			if (bimbo == LSBFirst)
				msks[lbs] = 1 << lbs;
			else
				msks[7 - lbs] = 1 << lbs;
		sy = cnp->cn_oy;
		dy = 0;
		ya = 0;
		while (sy < cnp->cn_ht && dy < cnp->cn_ht) {
			sx = cnp->cn_ox;
			dx = 0;
			xa = 0;
			sa = ribuf + sy * cnp->cn_wd + sx;
			da = ximbuf + dy * cnp->cn_xim->bytes_per_line;
			pxa = 0;
			while (sx < cnp->cn_wd && dx < cnp->cn_wd) {
				if (*sa > ditclass[dx & 7][dy & 7])
					pxa |= msks[dx & 7];
				if (!(++dx & 7)) {
					*da++ = pxa;
					pxa = 0;
				}
				if (++xa >= cnp->cn_zoom) {
					xa = 0;
					sx++;
					sa++;
				}
			}
			if (dx & 7)
				*da++ = pxa;
			dy++;
			if (++ya >= cnp->cn_zoom) {
				ya = 0;
				sy++;
			}
		}
	}
}


/*	cre_xim()
*
*	Create (or change) XImage for a canvas.
*/

cre_xim(cnp)
	struct canvas *cnp;
{
	char *ximbuf;
	int wd = cnp->cn_wd;
	int ht = cnp->cn_ht;
	int nb;

	if (cnp->cn_xim)
		XDestroyImage(cnp->cn_xim);

	if (!isMono) {
		nb = wd * ht;
		if (!isCmap) {
			ximbuf = TALLOC(nb * xBypp, char);
			cnp->cn_xim = XCreateImage(xDisp, defVis, nPlanes, ZPixmap, 0,
				ximbuf, wd, ht, xBpp, wd * xBypp);

		} else {
			ximbuf = TALLOC(nb, char);

			cnp->cn_xim = XCreateImage(xDisp, defVis, nPlanes, ZPixmap, 0,
				ximbuf, wd, ht, 8, wd);
		}

	} else {
		nb = byteswide(wd) * ht;
		ximbuf = TALLOC(nb, char);

		cnp->cn_xim = XCreateImage(xDisp, defVis, 1, XYBitmap, 0,
			ximbuf, wd, ht, 8, byteswide(wd));
	}
}


/*	refresh_region()
*
*	Refresh a canvas window.
*/

refresh_region(cnp, x1, y1, x2, y2)
	struct canvas *cnp;
	int x1, y1, x2, y2;
{
	XPutImage(xDisp, cnp->cn_win, canGc, cnp->cn_xim,
			x1, y1, x1, y1, x2 - x1 + 1, y2 - y1 + 1);
}


/*	start_recalc()
*
*	Start calculation by sending off as many tiles as possible.
*/

start_recalc()
{
	double im1 = imCan.cn_im1;
	double im2 = imCan.cn_im2 - im1;
	int ht = imCan.cn_ht;
	int wd = imCan.cn_wd;
	double reim[4];					/* tile corner coords */
	int wdht[2];					/* tile wd, ht */
	int y1, y2;

	njobs = ht / TILEHEIGHT + 1;
	jobstogo = njobs;
	jobnext = njobs;

	reim[0] = imCan.cn_re1;
	reim[2] = imCan.cn_re2;
	wdht[0] = wd;

	while (servfree >= 0 && jobnext) {
		y2 = (jobnext-- * ht) / njobs;
		y1 = (jobnext * ht) / njobs;
		reim[1] = im1 + (y1 * im2) / ht;
		reim[3] = im1 + (y2 * im2) / ht;
		wdht[1] = y2 - y1;
		servjobs[servfree] = jobnext;
/*
		printf("sent job %d to serv %d: %dx%d %f,%f/%f,%f\n",
				jobnext, servfree,
				wdht[0], wdht[1], reim[0], reim[1], reim[2], reim[3]);
		pvm_initsend(PvmDataDefault);
		pvm_pkdouble(reim, 4, 1);
		pvm_pkint(wdht, 2, 1);
*/
/*
		pvm_packf("%+ %4lf %2d", PvmDataDefault, reim, wdht);
*/
		pvm_packf("%+ %2lx %2d", PvmDataDefault, reim, wdht);
/*
		umbuf_dump(pvm_getsbuf());
*/
		if (pvm_send(servtids[servfree], 1)) {
			pvm_exit();
			exit(1);
		}

		if (dobars)
			label_row(y1, y2, servfree);
/*
			label_row(y1, y2, (servtids[servfree] & hostmask) >> hostshift);
*/
		servfree = servflist[servfree];
	}
	while (pvm_nrecv(-1, -1) > 0)
		claim_tile();
	if (jobstogo) {
		if (pvmsocket == -1) {
			int n, *fds;

			n = pvm_getfds(&fds);
			while (n-- > 0) {
				XtAppAddInput(context, fds[n], (XtPointer)XtInputReadMask, pvm_cb, (XtPointer)0);
/*
				fprintf(stderr, "%d\n", fds[n]);
*/
			}
			pvmsocket = 0;
		}
	}
	else
		redoing = 0;
}


claim_tile()
{
	int tid;		/* id of sender */
	int s;			/* server number */
	int wd = imCan.cn_wd;
	int ht = imCan.cn_ht;
	double im1 = imCan.cn_im1;
	double im2 = imCan.cn_im2 - im1;
	double reim[4];					/* tile corner coords */
	int wdht[2];					/* tile wd, ht */
	int y1, y2;						/* tile start, end rows */
	int h;							/* height of tile */

	pvm_bufinfo(pvm_getrbuf(), (int*)0, (int*)0, &tid);

	for (s = nserv; s-- > 0; )
		if (tid == servtids[s])
			break;
	if (s < 0) {
		printf("bogus message?\n");
		return;
	}
	y1 = (servjobs[s] * ht) / njobs;
	y2 = ((servjobs[s] + 1) * ht) / njobs;
	h = y2 - y1;
/*
	pvm_upkbyte((char*)imCan.cn_dat + y1 * wd, wd * h, 1);
*/
	pvm_unpackf("%*c", wd * h, (char*)imCan.cn_dat + y1 * wd);

	if (dobars)
		label_row(y1, y2, s);
/*
		label_row(y1, y2, (tid & hostmask) >> hostshift);
*/

	repaint_region(&imCan, 0, y1, wd - 1, y2 - 1);
	refresh_region(&imCan, 0, y1, wd - 1, y2 - 1);

	jobstogo--;

	/* put work server back on free list */

	servflist[s] = servfree;
	servfree = s;

	/* assign more work if available */

	if (jobnext) {
		reim[0] = imCan.cn_re1;
		reim[2] = imCan.cn_re2;
		wdht[0] = imCan.cn_wd;

		y2 = (jobnext-- * ht) / njobs;
		y1 = (jobnext * ht) / njobs;
		reim[1] = im1 + (y1 * im2) / ht;
		reim[3] = im1 + (y2 * im2) / ht;
		wdht[1] = y2 - y1;
		servjobs[servfree] = jobnext;
/*
		printf("sent job %d to serv %d: %dx%d %f,%f/%f,%f\n",
				jobnext, servfree,
				wdht[0], wdht[1], reim[0], reim[1], reim[2], reim[3]);
		pvm_initsend(PvmDataDefault);
		pvm_pkdouble(reim, 4, 1);
		pvm_pkint(wdht, 2, 1);
*/
/*
		pvm_packf("%+ %4lf %2d", PvmDataDefault, reim, wdht);
*/
		pvm_packf("%+ %2lx %2d", PvmDataDefault, reim, wdht);

		if (pvm_send(servtids[servfree], 1)) {
			pvm_exit();
			exit(1);
		}
		if (dobars)
			label_row(y1, y2, servfree);
/*
			label_row(y1, y2, (servtids[servfree] & hostmask) >> hostshift);
*/
		servfree = servflist[servfree];
	}
}


label_row(y1, y2, n)
	int y1, y2;
	int n;
{
	int wd = imCan.cn_wd;
	int h = y2 - y1;
	int y, x;
	u_char *ba;
	int c = isMono ? 128 : 240;

	ba = imCan.cn_dat + y1 * wd;
	for (y = h; y-- > 0; ) {
		BZERO(ba, 21);
		ba += wd;
	}
	ba = imCan.cn_dat + y1 * wd;
	for (y = h - 2; y-- > 0; ) {
		ba += wd;
		for (x = 0; x < 20; x++)
			if (x & 3)
				ba[x] = (n & (1 << x / 4)) ? 255 : c;
	}
	repaint_region(&imCan, 0, y1, 20, y2 - 1);
	refresh_region(&imCan, 0, y1, 20, y2 - 1);
}


