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

#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Xaw/Command.h>
#include <X11/Xaw/Toggle.h>
#include <X11/Xaw/Viewport.h>
#include <X11/Xaw/Box.h>
#include <X11/Xaw/Form.h>
#include <X11/Xaw/Scrollbar.h>
#include <X11/Shell.h>
#include <X11/cursorfont.h>
#include <stdio.h>
#ifndef NOSTDHDRS
#include <stdlib.h>
#include <unistd.h>
#endif
#include "Paint.h"
#include "xpaint.h"
#include "palette.h"
#include "menu.h"
#include "misc.h"
#include "cutCopyPaste.h"

#define PADDING	4
#define BW	1

typedef struct LocalInfo_s {
	int	curX, curY;
	int	offX, offY, baseX, baseY;
	Widget	cursor;		/* The Box widget */
	Widget	subpaint;	/* the paint widget inside the box */
	Widget	view;		/* the widget in the popup window */
	Widget	paint;		/* The source of this zoom */
	Widget	shell;

	int		zoom;		/* paint widget zoom value */
	Position	spX, spY;
	Dimension	spBW;		/* subpaint, x, y and borderwidth */
	struct LocalInfo_s	*next;
} LocalInfo;

static LocalInfo	*head = NULL;

/*
**  When the popup window is gone, free the storage
*/
static void destroyCallback(Widget w, XtPointer larg, void *junk2)
{
	LocalInfo	*l = (LocalInfo *)larg;
	LocalInfo	*c = head, **p = &head;

	while (c != NULL && c != l) {
		p = &c->next;
		c = c->next;
	}

	if (c != NULL) 
		*p = l->next;

	XtFree((XtPointer)l);
}

/*
**  Done or WM-Close pressed, destroy widgets
*/
static void doneCallback(Widget w, XtPointer larg, void *junk2)
{
	LocalInfo	*l = (LocalInfo *)larg;

	/*
	**  Destroy both the cursor and the popup window
	*/
        XtDestroyWidget(l->cursor);
        XtDestroyWidget(GetShell(w));
}

/*
**  Move the cursor and the view window to the position
**   specified by x,y
*/
static void moveCursor(LocalInfo *l, int x, int y, int w, int h)
{
	int	dw, dh;
	int	rx, ry;

	if (w == -1 || h == -1) {
		int		z;
		Dimension	wt, ht;

		XtVaGetValues(l->view, XtNzoom, &z,
				       XtNwidth, &wt,
				       XtNheight, &ht,
				       NULL),

		w = (wt + z - 1) / z;
		h = (ht + z - 1) / z;
	}

	XtVaGetValues(l->paint, XtNdrawWidth, &dw, XtNdrawHeight, &dh, NULL);
	if (x < 0) x = 0;
	if (y < 0) y = 0;
	if (x + w > dw) x = dw - w;
	if (y + h > dh) y = dh - h;
	if (x < 0 || y < 0) {
		int	z;

		XtVaGetValues(l->view, XtNzoom, &z, NULL);

		/* XXX zoom bigger than actual */
		return;
	}

	rx = x / l->zoom;
	ry = y / l->zoom;
	x  = rx * l->zoom;
	y  = ry * l->zoom;

	XtVaSetValues(l->view, XtNzoomX, rx, XtNzoomY, ry, NULL);
	XtVaSetValues(l->cursor, XtNx, x - l->spX - l->spBW - 1, 
				 XtNy, y - l->spY - l->spBW - 1, NULL);
	XtVaSetValues(l->subpaint, XtNzoomX, rx, XtNzoomY, ry, NULL);
}

/*
**  Given the popup dimensions, set the "cursor" view to the correct size
*/
static void resizeCursor(LocalInfo *l)
{
	Dimension	width, height;
	int		zoom;
	int		pw,ph;
	int		dw,dh;
	int		zx,zy;

	XtVaGetValues(l->view, XtNwidth, &width, 
			       XtNheight, &height,
			       XtNzoom, &zoom,
			       NULL);
	XtVaGetValues(l->paint, XtNdrawWidth, &dw, 
				XtNdrawHeight, &dh, 
				XtNzoom, &l->zoom, 
				NULL);
	XtVaGetValues(l->subpaint, XtNzoomX, &zx, 
				   XtNzoomY, &zy, 
				   NULL);
	
	pw = (width + zoom - 1) / zoom;
	ph = (height + zoom - 1) / zoom;
	if ((pw + zx > dw) || (ph + zy > dh)) {
		int	nx,ny;

		nx = (pw + zx > dw) ? dw - pw : zx;
		ny = (ph + zy > dh) ? dh - ph : zy;

		/*
		**  If the new x or y value off the screen, set it back.
		**    and resize view
		*/
		if (nx < 0 || ny < 0) {
			if (nx < 0) {
				pw += nx;
				nx  = 0;
			}
			if (ny < 0) {
				ph += ny;
				ny  = 0;
			}
			/* XXX -- this really should be SetValues(width,height)
			**        but that doesn't work..?/
			*/
			XtResizeWidget(l->view, pw * zoom, ph * zoom, 1);
		}
		moveCursor(l, nx, ny, pw, ph);
	}

	pw *= l->zoom;
	ph *= l->zoom;

	XtVaSetValues(l->cursor, XtNwidth,  pw + 2 * (PADDING + BW),
				 XtNheight, ph + 2 * (PADDING + BW),
				 NULL);
	XtVaSetValues(l->subpaint, XtNwidth,  pw,
				   XtNheight, ph,
				   NULL);
}

/*
**  Button down in the cursor window
*/
static void press(Widget w, LocalInfo *l, XButtonEvent *event, Boolean *flg)
{
	Position	x, y;

	XtVaGetValues(w, XtNx, &x, XtNy, &y, NULL);

	l->offX = event->x;
	l->offY = event->y;
	l->baseX = event->x_root - x;
	l->baseY = event->y_root - y;
	XtVaGetValues(l->paint, XtNzoom, &l->zoom, NULL);
}
static void motion(Widget w, LocalInfo *l, XMotionEvent *event, Boolean *flg)
{
	int		nx, ny;
	int		px, py;

	/*
	**  Compress motion events.
	*/
	while (XCheckTypedWindowEvent(XtDisplay(w), XtWindow(w), MotionNotify, (XEvent*)event));

        nx = event->x_root - l->baseX;
	ny = event->y_root - l->baseY;

	px = nx + l->spX + l->spBW + 1;
	py = ny + l->spY + l->spBW + 1;

	moveCursor(l, px, py, -1, -1);
}

/*
**  If the paint view size changes, update the parent paint window cursor size
*/
static void sizeChanged(Widget w, LocalInfo *l, XtPointer junk)
{
	resizeCursor(l);
}

/*
**  The parent box widget changed size, resize to fit.
*/
static void boxChanged(Widget w, LocalInfo *l, XConfigureEvent *event, Boolean *flg)
{
	Dimension	width, height;
	Dimension	hpad, vpad, bw;
	int		zoom;

	XtVaGetValues(l->view, XtNzoom, &zoom, XtNborderWidth, &bw,
			       NULL);
	XtVaGetValues(XtParent(l->view), XtNwidth,  &width,
					 XtNheight, &height,
				         XtNhSpace, &hpad,
				         XtNvSpace, &vpad,
					 NULL);
	width  -= (hpad + bw) * 2 ;
	height -= (vpad + bw) * 2;

	/* XXX -- this really should be SetValues(width,height)
	**        but that doesn't work..?/
	*/
	XtResizeWidget(l->view, width, height, 1);

	resizeCursor(l);
}

/*
**  One of the zoom perctange buttons pressed
*/
static void buttonCallback(Widget w, XtPointer lArg, void *junk2)
{
	LocalInfo	*l  = (LocalInfo *)lArg;
	char		*lbl;
	int		nz;
	int		cx, cy, zx, zy, z, nw, nh;
	int		x, y;
	Dimension	width, height;
	Boolean		state;

	XtVaGetValues(w, XtNstate, &state, XtNlabel, &lbl, NULL);

	if (state == False)
		return;

	nz = 0;
	sscanf(lbl, "%*d:%d", &nz);
	if (nz == 0)
		return;

	XtVaGetValues(l->view, XtNzoomX, &zx, 
			       XtNzoomY, &zy, 
			       XtNzoom, &z,
			       XtNwidth,  &width, 
			       XtNheight, &height, 
			       NULL);

	cx = zx + ((width + z - 1) / z) / 2;
	cy = zy + ((height + z - 1) / z) / 2;
	nw = (width + nz - 1) / nz;
	nh = (height + nz - 1) / nz;

	XtVaSetValues(l->view, XtNzoom, nz, NULL);
	/*  center on image center */
	x = cx - nw / 2;
	y = cy - nh / 2;

	moveCursor(l, x, y, width / nz, height / nz);
}

/*
**  Construct the fatbits popup, and cursor
**
*/
void FatbitsEdit(Widget paint)
{
	Widget		shell, form, fat;
	Widget		button, box;
	int		i, zoom;
	Colormap	cmap;
	Position	x, y, lx, ly;
	Dimension	width = 48, height = 48;
	LocalInfo	*l;
	static char	*zoomList[] = { 
				"zoomButton1",
				"zoomButton2",
				"zoomButton3",
				"zoomButton4",
				"zoomButton5",
			};
	XtTranslations          trans = XtParseTranslationTable("<BtnDown>,<BtnUp>: set() notify()");
	Widget			first = None;

	for (l = head; l != NULL && l->paint != paint; l = l->next);

	if (l != NULL) {
		XMapRaised(XtDisplay(l->shell), XtWindow(l->shell));
		return;
	}

	l = XtNew(LocalInfo);

	l->paint = paint;
	l->next  = head;
	head     = l;

        XtVaGetValues(paint, XtNcolormap, &cmap, 
			     XtNzoom, &l->zoom, 
			     XtNdownX, &lx, 
			     XtNdownY, &ly, 
			     NULL);

        shell = XtVaCreatePopupShell("fatbits", topLevelShellWidgetClass, 
				GetShell(paint), XtNcolormap, cmap,
                                NULL);
	l->shell = shell;
	PaletteAddUser(PaletteFind(shell, cmap), shell);
	form  = XtVaCreateManagedWidget("form", formWidgetClass, shell, NULL);
	
	box = XtVaCreateManagedWidget("fatBox", boxWidgetClass, form,
				XtNbackgroundPixmap, GetBackgroundPixmap(form),
				NULL);
	XtAddEventHandler(box, StructureNotifyMask, False, (XtEventHandler)boxChanged, (XtPointer)l);


	fat = XtVaCreateManagedWidget("paint", paintWidgetClass, box,
				XtNpaint, paint,
				XtNbottom, XtChainBottom,
				NULL);
	l->view = fat;
	XtVaGetValues(fat, XtNzoom, &zoom, NULL);
	XtVaSetValues(fat, XtNwidth, width * zoom, 
			   XtNheight, height * zoom, 
			   NULL);

	button = XtVaCreateManagedWidget("done", commandWidgetClass, form,
				XtNfromVert, box,
				XtNtop, XtChainBottom,
				XtNbottom, XtChainBottom,
				XtNleft, XtChainLeft,
				XtNright, XtChainLeft,
				NULL);

	XtAddCallback(button, XtNcallback, doneCallback, (XtPointer)l);

	ccpAddStdPopup(fat);

	first = None;
	for (i = 0; i < XtNumber(zoomList); i++) {
		button = XtVaCreateManagedWidget(zoomList[i], 
						toggleWidgetClass, form,
						XtNfromHoriz, button,
						XtNfromVert, box,
						XtNtop, XtChainBottom,
						XtNbottom, XtChainBottom,
						XtNleft, XtChainRight,
						XtNright, XtChainRight,
						XtNradioGroup, first,
						XtNtranslations, trans,
						NULL);
		first = button;
		XtAddCallback(button, XtNcallback, buttonCallback, (XtPointer)l);

		if (i == XtNumber(zoomList) / 2) {
			XtVaSetValues(button, XtNstate, True, NULL);
		}
	}

	XtAddCallback(shell, XtNdestroyCallback, destroyCallback, (XtPointer)l);
	AddDestroyCallback(shell, (void (*)(Widget, void *, XEvent *))doneCallback, (XtPointer)l);

        XtPopup(shell, XtGrabNone);

#if 0
	XtVaGetValues(fat, XtNwidth, &width, XtNheight, &height, NULL);
#endif

	lx -= width / 2;
	ly -= height / 2;
	if (lx < 0) lx = 0;
	if (ly < 0) ly = 0;

	x = lx - (PADDING + 2);
	y = ly - (PADDING + 2);

	box = XtVaCreateManagedWidget("fatBox", boxWidgetClass, paint,
				XtNx, x, 
				XtNy, y,
				XtNwidth, width + 2 * (PADDING + 1),
				XtNheight, height + 2 * (PADDING + 1),
				XtNborderWidth, 1,
				XtNbackgroundPixmap, GetBackgroundPixmap(paint),
				NULL);

	l->cursor = box;

	XtAddEventHandler(box, ButtonPressMask, False, (XtEventHandler)press, (XtPointer)l);
	XtAddEventHandler(box, ButtonMotionMask, False, (XtEventHandler)motion, (XtPointer)l);

	l->subpaint = XtVaCreateManagedWidget("fatPaint", paintWidgetClass, box,
				XtNpaint, paint,
				XtNzoom, PwZoomParent,
				XtNx, PADDING,
				XtNy, PADDING,
				XtNborderWidth, 1,
				XtNwidth, width,
				XtNheight, height,
				XtVaTypedArg, XtNcursor, XtRString, "fleur", sizeof(Cursor),
				NULL);

	XtAddCallback(l->view, XtNsizeChanged, (XtCallbackProc)sizeChanged, (XtPointer)l);

	XtVaGetValues(l->subpaint, XtNx, &l->spX, 
				   XtNy, &l->spY,
				   XtNborderWidth, &l->spBW,
				   NULL);

	XtVaSetValues(l->subpaint, XtNzoomX, lx, XtNzoomY, ly, NULL);
	XtVaSetValues(l->view, XtNzoomX, lx, XtNzoomY, ly, NULL);

	/*
	**  Set the current operator
	*/
	GraphicAdd(fat);
	StateAddParent(shell, paint);
}
