/* +-------------------------------------------------------------------+ */
/* | 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/Shell.h>
#include <X11/Xaw/Box.h>
#include <X11/Xaw/Form.h>
#include <X11/Xaw/Command.h>
#include <X11/Xaw/Toggle.h>
#include "xpaint.h"
#include "misc.h"
#include "Paint.h"

#include "bitmaps/paintA.xbm"
#include "bitmaps/paintB.xbm"
#include "bitmaps/paintC.xbm"
#include "bitmaps/paintD.xbm"
#include "bitmaps/paintE.xbm"
#include "bitmaps/paintF.xbm"
#include "bitmaps/paintG.xbm"
#include "bitmaps/paintH.xbm"
#include "bitmaps/paintI.xbm"
#include "bitmaps/paintJ.xbm"
#include "bitmaps/paintK.xbm"
#include "bitmaps/paintL.xbm"
#include "bitmaps/paintM.xbm"
#include "bitmaps/paintN.xbm"
#include "bitmaps/paintO.xbm"
#include "bitmaps/paintP.xbm"
#include "bitmaps/paintQ.xbm"
#include "bitmaps/paintR.xbm"
#include "bitmaps/paintS.xbm"
#include "bitmaps/paintT.xbm"

#define BRUSH(name)	 \
	(char *)CONCAT(name,_bits), CONCAT(name,_width), CONCAT(name,_height)

typedef struct {
	Pixmap	pixmap;
	Cursor	cursor;
	char	*bits;
	int	width, height;
} BrushItem;

static BrushItem	brushList[] = {
	{ None, None, BRUSH(paintA) },
	{ None, None, BRUSH(paintB) },
	{ None, None, BRUSH(paintC) },
	{ None, None, BRUSH(paintD) },
	{ None, None, BRUSH(paintE) },
	{ None, None, BRUSH(paintF) },
	{ None, None, BRUSH(paintG) },
	{ None, None, BRUSH(paintH) },
	{ None, None, BRUSH(paintI) },
	{ None, None, BRUSH(paintJ) },
	{ None, None, BRUSH(paintK) },
	{ None, None, BRUSH(paintL) },
	{ None, None, BRUSH(paintM) },
	{ None, None, BRUSH(paintN) },
	{ None, None, BRUSH(paintO) },
	{ None, None, BRUSH(paintP) },
	{ None, None, BRUSH(paintQ) },
	{ None, None, BRUSH(paintR) },
	{ None, None, BRUSH(paintS) },
	{ None, None, BRUSH(paintT) },
};

#define default_bits	paintA_bits
#define default_width	paintA_width
#define default_height	paintA_height

typedef struct {
	Boolean	isErase, useSecond;
	Pixmap	pixmap;
	int	width, height;
} LocalInfo;

static BrushItem	*currentBrush = NULL;
static Boolean		eraseMode = True;

static void 	draw(Widget w, OpInfo *info, LocalInfo *l, int x, int y)
{
	XRectangle	undo;
	int		sx = x - l->width / 2;
	int		sy = y - l->height / 2;
	GC		gc;

	if (l->isErase)
		gc = info->base_gc;
	else 
		gc = l->useSecond ? info->second_gc : info->first_gc;

	XSetClipOrigin(XtDisplay(w), gc, sx, sy);

	if (l->isErase && eraseMode && info->base != None) {
		XCopyArea(XtDisplay(w), info->base, info->drawable,
				gc, sx, sy, l->width, l->height, sx, sy);
	} else {
		XFillRectangle(XtDisplay(w), info->drawable, 
				gc, sx, sy, l->width, l->height);
	}

	if (info->surface == opPixmap) {
		XYtoRECT(sx, sy, sx + l->width, sy + l->height, &undo);

		UndoGrow(w, sx, sy);
		UndoGrow(w, sx + l->width, sy + l->height);
		PwUpdate(w, &undo, False);
	}
}

static void	press(Widget w, LocalInfo *l, XButtonEvent *event, OpInfo *info) 
{
	/*
	**  Check to make sure all buttons are up, before doing this
	*/
	if ((event->state & (Button1Mask|Button2Mask|Button3Mask|Button4Mask|Button5Mask)) != 0)
		return;

	if (info->surface == opWindow && info->isFat)
		return;

	l->useSecond = event->button == Button2;
	l->width     = currentBrush->width;
	l->height    = currentBrush->height;

	XSetClipMask(XtDisplay(w), info->first_gc, l->pixmap);
	XSetClipMask(XtDisplay(w), info->second_gc, l->pixmap);
	XSetClipMask(XtDisplay(w), info->base_gc, l->pixmap);
	
	UndoStart(w, info);

	draw(w, info, l, event->x, event->y);
}

static void	motion(Widget w, LocalInfo *l, XMotionEvent *event, OpInfo *info) 
{
	if (info->surface == opWindow && info->isFat)
		return;

	draw(w, info, l, event->x, event->y);
}

static void	release(Widget w, LocalInfo *l, XButtonEvent *event, OpInfo *info) 
{
	int	mask;
	/*
	**  Check to make sure all buttons are up, before doing this
	*/
	mask = Button1Mask|Button2Mask|Button3Mask|Button4Mask|Button5Mask;
	switch (event->button) {
	case Button1:	mask ^= Button1Mask; break;
	case Button2:	mask ^= Button2Mask; break;
	case Button3:	mask ^= Button3Mask; break;
	case Button4:	mask ^= Button4Mask; break;
	case Button5:	mask ^= Button5Mask; break;
	}
	if ((event->state & mask) != 0)
		return;

	XSetClipMask(XtDisplay(w), info->first_gc, None);
	XSetClipMask(XtDisplay(w), info->second_gc, None);
	XSetClipMask(XtDisplay(w), info->base_gc, None);
}

static void	setPixmap(Widget w, void *brushArg)
{
	BrushItem	*brush = (BrushItem *)brushArg;
	LocalInfo	*l = (LocalInfo*)GraphicGetData(w);

	l->pixmap = brush->pixmap;
}

static void	setCursor(Widget w, void *brushArg)
{
	static Boolean	inited = False;
	static XColor	xcols[2];
	BrushItem	*brush = (BrushItem *)brushArg;
	Display		*dpy = XtDisplay(w);

	if (!inited) {
		Colormap	map;
		Screen		*screen = XtScreen(w);

		inited = True;
		xcols[0].pixel = WhitePixelOfScreen(screen);
		xcols[1].pixel = BlackPixelOfScreen(screen);

		XtVaGetValues(w, XtNcolormap, &map, NULL);

		XQueryColors(dpy, map, xcols, XtNumber(xcols));
	}
	if (brush->cursor == None) {
		Pixmap	mask;
		XImage	*src, *msk;
		GC	gc;
		Boolean	flg;
		int	x, y;

		src = XGetImage(dpy, brush->pixmap, 0, 0, brush->width,
					brush->height, AllPlanes, ZPixmap);
		msk = NewXImage(dpy, NULL, 1, brush->width, brush->height);
		for (y = 0; y < brush->height; y++) {
			for (x = 0; x < brush->width; x++) {
				Pixel	p = XGetPixel(src, x, y);

				flg = (Boolean)p;
			
				if (!flg && x > 0)
					flg = XGetPixel(src, x - 1, y);
				if (!flg && x < brush->width - 1)
					flg = XGetPixel(src, x + 1, y);
				if (!flg && y > 0)
					flg = XGetPixel(src, x, y - 1);
				if (!flg && y < brush->height - 1)
					flg = XGetPixel(src, x, y + 1);
				
				XPutPixel(msk, x, y, flg);
			}
		}
		mask = XCreatePixmap(dpy, brush->pixmap, brush->width, brush->height, 1);
		gc = XCreateGC(dpy, mask, 0, 0);
		XPutImage(dpy, mask, gc, msk, 0, 0, 0, 0, brush->width, brush->height);
		XDestroyImage(src);
		XDestroyImage(msk);
		XFreeGC(dpy, gc);

		brush->cursor = XCreatePixmapCursor(dpy, brush->pixmap, mask, 
			 	&xcols[1], &xcols[0], 
				brush->width / 2, brush->height / 2);
		XFreePixmap(dpy, mask);
	}
	XtVaSetValues(w, XtNcursor, brush->cursor, NULL);
}

/*
**  Those public functions
*/
Boolean	EraseGetMode()
{
	return eraseMode;
}
void EraseSetMode(Boolean  mode)
{
	eraseMode = mode;
}
void *BrushAdd(Widget w)
{
	LocalInfo	*l = XtNew(LocalInfo);

	XtVaSetValues(w, XtNcompress, False, NULL);

	l->isErase = False;
	l->pixmap  = currentBrush->pixmap;

	OpAddEventHandler(w, opWindow|opPixmap, ButtonPressMask, 
			FALSE, (OpEventProc)press, (XtPointer)l);
	OpAddEventHandler(w, opWindow|opPixmap, ButtonMotionMask, 
			FALSE, (OpEventProc)motion, (XtPointer)l);
	OpAddEventHandler(w, opWindow|opPixmap, ButtonReleaseMask, 
			FALSE, (OpEventProc)release, (XtPointer)l);

	setCursor(w, (void*)currentBrush);

	return l;
}
void BrushRemove(Widget w, LocalInfo *l)
{
	OpRemoveEventHandler(w, opWindow|opPixmap, ButtonPressMask, 
			FALSE, (OpEventProc)press, (XtPointer)l);
	OpRemoveEventHandler(w, opWindow|opPixmap, ButtonMotionMask, 
			FALSE, (OpEventProc)motion, (XtPointer)l);
	OpRemoveEventHandler(w, opWindow|opPixmap, ButtonReleaseMask, 
			FALSE, (OpEventProc)release, (XtPointer)l);

	XtFree((XtPointer)l);
}
void *EraseAdd(Widget w)
{
	LocalInfo	*l = XtNew(LocalInfo);

	XtVaSetValues(w, XtNcompress, False, NULL);

	l->isErase = True;
	l->pixmap  = currentBrush->pixmap;

	OpAddEventHandler(w, opWindow|opPixmap, ButtonPressMask, 
			FALSE, (OpEventProc)press, (XtPointer)l);
	OpAddEventHandler(w, opWindow|opPixmap, ButtonMotionMask, 
			FALSE, (OpEventProc)motion, (XtPointer)l);
	OpAddEventHandler(w, opWindow|opPixmap, ButtonReleaseMask, 
			FALSE, (OpEventProc)release, (XtPointer)l);

	setCursor(w, (void*)currentBrush);

	return l;
}
void EraseRemove(Widget w, LocalInfo *l)
{
	OpRemoveEventHandler(w, opWindow|opPixmap, ButtonPressMask, 
			FALSE, (OpEventProc)press, (XtPointer)l);
	OpRemoveEventHandler(w, opWindow|opPixmap, ButtonMotionMask, 
			FALSE, (OpEventProc)motion, (XtPointer)l);
	OpRemoveEventHandler(w, opWindow|opPixmap, ButtonReleaseMask, 
			FALSE, (OpEventProc)release, (XtPointer)l);
		
	XtFree((XtPointer)l);
}

/*
**  Initializer to create a default brush
*/

void	BrushInit(Widget toplevel)
{
	currentBrush = &brushList[0];
	currentBrush->pixmap = XCreatePixmapFromBitmapData(
		XtDisplay(toplevel), 
		RootWindowOfScreen(XtScreen(toplevel)),
		currentBrush->bits,
		currentBrush->width,
		currentBrush->height,
		False, True, 1);
}

/*
**  The brush selection dialog
*/

static void	closePopup(Widget button, Widget shell)
{
	XtPopdown(shell);
}

static void 	selectBrush(Widget shell, BrushItem *nc)
{
	currentBrush = nc;

	if (((int)CurrentOp[0] == (int)BrushAdd) ||
	    ((int)CurrentOp[0] == (int)EraseAdd)) {
		GraphicAll(setCursor, (void*)currentBrush);
		GraphicAll(setPixmap, (void*)currentBrush);
	}
}

static Widget	createDialog(Widget w)
{
	Widget		shell, form, box, icon, firstIcon = 0, close;
	GC		gc, igc;
	XGCValues	values;
	int		i;
	Pixel		fg, bg;
	Pixmap		pix;

	shell = XtVaCreatePopupShell("brush",
			topLevelShellWidgetClass, w,
			NULL);

	form = XtVaCreateManagedWidget(NULL,
			formWidgetClass, shell,
			NULL);

	box  = XtVaCreateManagedWidget("box",
			boxWidgetClass, form,
			NULL);

	values.foreground    = WhitePixelOfScreen(XtScreen(w));
	values.background    = BlackPixelOfScreen(XtScreen(w));

	gc = XCreateGC(XtDisplay(w), 
			RootWindowOfScreen(XtScreen(w)),
			GCForeground|GCBackground, &values);

	values.background    = WhitePixelOfScreen(XtScreen(w));
	values.foreground    = BlackPixelOfScreen(XtScreen(w));
	igc = XCreateGC(XtDisplay(w), 
			RootWindowOfScreen(XtScreen(w)),
			GCForeground|GCBackground, &values);

	for (i = 0; i < XtNumber(brushList); i++) {
		if (brushList[i].pixmap == None)
			brushList[i].pixmap = XCreatePixmapFromBitmapData(
				XtDisplay(box), 
				RootWindowOfScreen(XtScreen(box)),
				brushList[i].bits,
				brushList[i].width,
				brushList[i].height,
				False, True, 1);

		icon = XtVaCreateManagedWidget("icon",
				toggleWidgetClass, box,
				XtNradioGroup, firstIcon,
				NULL);

		pix = XCreatePixmap(XtDisplay(box), 
					RootWindowOfScreen(XtScreen(box)),
					brushList[i].width,
					brushList[i].height,
					DefaultDepthOfScreen(XtScreen(box)));
	
		XtVaGetValues(icon, XtNforeground, &fg,
				    XtNbackground, &bg,
				    NULL);

		/*
		**  Clear then draw the clipped rectangle in
		*/
		XSetClipMask(XtDisplay(w), gc, None);
		XSetForeground(XtDisplay(w), gc, bg);
		XFillRectangle(XtDisplay(w), pix, gc, 0, 0,
				brushList[i].width, brushList[i].height);
		XSetClipMask(XtDisplay(w), gc, brushList[i].pixmap);
		XSetForeground(XtDisplay(w), gc, fg);
		XFillRectangle(XtDisplay(w), pix, gc, 0, 0,
				brushList[i].width, brushList[i].height);

		XtVaSetValues(icon, XtNbitmap, pix, NULL);

		if (firstIcon == NULL) {
			XtVaSetValues(icon, XtNstate, True, NULL);
			firstIcon = icon;
		}

		XtAddCallback(icon, XtNcallback, 
				(XtCallbackProc)selectBrush, (XtPointer)&brushList[i]);
	}

	close  = XtVaCreateManagedWidget("close",
			commandWidgetClass, form,
			XtNfromVert, box,
			XtNtop, XtChainBottom,
			NULL);

	XtAddCallback(close, XtNcallback, (XtCallbackProc)closePopup, (XtPointer)shell);

	XFreeGC(XtDisplay(w), gc);
	XFreeGC(XtDisplay(w), igc);

	AddDestroyCallback(shell, (void (*)(Widget, void *, XEvent *))closePopup, shell);

	return shell;
}

void	BrushSelect(Widget w)
{
	static Widget	popup = NULL;

	if (popup == NULL) 
		popup = createDialog(GetToplevel(w));
	
	XtPopup(popup, XtGrabNone);
	XMapRaised(XtDisplay(popup), XtWindow(popup));
}
