/*
 * Copyright (c) 2002 Alban G. Hertroys
 *
 * libDockapp example - Usage of action rectangles
 *
 * Some clickable and draggable areas for you to play
 * with.
 *
 * There's a bit much in here...
 */

/* The cursor font - stamdard cursor glyphs. */
#include <X11/cursorfont.h>

/* Required because we don't use a pixmap for the shape (using a DASetShape
 * variation). Instead we use the XLib call "XShapeCombineRegion".
 * Window shapes are an extension (since X11R5).
 */
#include <X11/extensions/shape.h>

#include <dockapp.h>

/* already includes Xlib, Xresources, XPM, stdlib and stdio */

/*
 * Type definitions
 */

/* I like to keep my graphic contexts (GCs) together */
struct Colors {
	GC white;       /* foreground color from X-resource, or default */
	GC black;       /* background color, idem */
	GC lightGray;   /* Some GC's we'll have to define for colors */
	GC darkGray;
	GC slider;      /* draw-color when not highlighted,
			 * dark-color when highlighted
			 */
	GC sliderLight; /* draw-color when highlighted */
	GC sliderDark;  /* dark-color when not highlighted */
};


/*
 * Global variables
 */
Pixmap pixmap;          /* pixmap pixmap */
DARect *buttonDown = NULL;
struct Colors *colors;  /* our colors */
DAActionRect **actionRects;
float sliderPos = 0.7;
int mouseIn = 0;
Cursor pointer;

/*
 * Prototypes for local functions
 */
struct Colors *setGCs(Drawable d);
unsigned long adjustColor(unsigned long color, signed int adjustment);

/* drawing routines */
void createBtn(DARect rect);
void drawRaisedFrame(DARect rect);
void drawSunkenFrame(DARect rect);
void createSquare(DARect rect);
void drawSquare(DARect rect, int button);
void createSlider(DARect rect);
void drawSlider(DARect rect);
void setPointerColor(GC);

/* utility functions */
DAActionRect setRectAction(DARect rect, DARectCallback action);


/* event handlers functions */
void destroy(void);
void buttonPress(int button, int state, int x, int y);
void buttonRelease(int button, int state, int x, int y);
void mouseMove(int x, int y);
void mouseEnter(void);
void mouseLeave(void);

/* what to do for a specific event for every 'item' in the dockapp */
/*	Button that can be pressed "down" and jumps back "up" again */
void btnDown(int x, int y, DARect rect, void *data);
void btnUp(int x, int y, DARect rect, void *data);
void btnLeave(int x, int y, DARect rect, void *data);

/*	Square that tells which button was pressed (number) */
void squareDown(int x, int y, DARect rect, void *data);

/*	A draggable slider that highlights when the mouse is over it */
void sliderDown(int x, int y, DARect rect, void *data);
void sliderUp(int x, int y, DARect rect, void *data);
void sliderMove(int x, int y, DARect rect, void *data);
void sliderEnter(int x, int y, DARect rect, void *data);
void sliderLeave(int x, int y, DARect rect, void *data);


/*
 * M A I N
 */

int
main(int argc, char **argv)
{
	/* define the event handlers for the events */
	DACallbacks eventCallbacks = {
		destroy, /* destroy */
		buttonPress, /* buttonPress */
		buttonRelease, /* buttonRelease */
		mouseMove, /* motion (mouse) */
		mouseEnter, /* mouse enters window */
		mouseLeave, /* mouse leaves window */
		NULL    /* timeout */
	};

	/* define regions (x, y, width, height) that need event-handling */
	Region clipRegion = XCreateRegion();

	DARect btn = {0, 0, 16, 16},
	       square = {0, 25, 22, 22},
	       slider = {24, 0, 23, 48};

	/* define what to do if an event occurs in a rectangle */
	DAActionRect *buttonPressRects, *buttonReleaseRects,
	*mouseMoveRects, *mouseEnterRects, *mouseLeaveRects;

	buttonPressRects = malloc(3 * sizeof(DAActionRect));
	buttonPressRects[0] = setRectAction(btn, btnDown);
	buttonPressRects[1] = setRectAction(square, squareDown);
	buttonPressRects[2] = setRectAction(slider, sliderDown);

	buttonReleaseRects = malloc(2 * sizeof(DAActionRect));
	buttonReleaseRects[0] = setRectAction(btn, btnUp);
	buttonReleaseRects[1] = setRectAction(slider, sliderUp);

	mouseMoveRects = malloc(sizeof(DAActionRect));
	mouseMoveRects[0] = setRectAction(slider, sliderMove);

	mouseEnterRects = malloc(sizeof(DAActionRect));
	mouseEnterRects[0] = setRectAction(slider, sliderEnter);

	mouseLeaveRects = malloc(2 * sizeof(DAActionRect));
	mouseLeaveRects[0] = setRectAction(btn, btnLeave);
	mouseLeaveRects[1] = setRectAction(slider, sliderLeave);


	/* XXX: make action rectangles available outside main()
	 * ...libDockapp should be able to do this... (reminder)
	 */
	actionRects = malloc(6 * sizeof(DAActionRect *));
	actionRects[0] = buttonPressRects;
	actionRects[1] = buttonReleaseRects;
	actionRects[2] = mouseMoveRects;
	actionRects[3] = mouseEnterRects;
	actionRects[4] = mouseLeaveRects;
	actionRects[5] = NULL;

	/* provide standard command-line options */
	DAParseArguments(
		argc, argv, /* Where the options come from */
		NULL, 0, /* Our list with options - none as you can see */
		"This is the help text for the rectangle example of how to "
		"use libDockapp.\n",
		"Rectangle example version 1.0");

	/* Tell libdockapp what version we expect it to be, so that you can use
	 * older programs with newer versions of libdockapp with less risc for
	 * compatibility problems.
	 */
	DASetExpectedVersion(20030126);

	/* Initialize a dockapp */
	DAInitialize(
		"",             /* Use default display */
		"daRectangleExample",   /* WM_CLASS hint; don't use chars in [.?*: ] */
		48, 48,         /* geometry of dockapp internals */
		argc, argv      /* (needed internally) */
		);

	/* Create a pixmap to draw on, and to display */
	pixmap = DAMakePixmap(); /* size == dockapp geometry (48,48) */

	colors = setGCs(pixmap);

	XFillRectangle(DADisplay, pixmap, DAClearGC, 0, 0, 48, 48);
	XClearWindow(DADisplay, DAWindow);

	/* Make a "Region" from the shapes we have */
	XUnionRectWithRegion(&btn, clipRegion, clipRegion);
	XUnionRectWithRegion(&square, clipRegion, clipRegion);
	XUnionRectWithRegion(&slider, clipRegion, clipRegion);

	/* Make this region a window shape mask */
	XShapeCombineRegion(DADisplay, DAWindow, ShapeBounding,
			    0, 0, clipRegion, ShapeSet);

	/* We don't need the region anymore (it is copied by XShapeCombineRegion).
	 * XXX: That's not certain, it is not documented. XSetRegion does so,
	 * though, so it is a likely assumption that it does copy.
	 */
	XDestroyRegion(clipRegion);

	/* The cursor we want to use.
	 * Specify 'None' for the default,
	 * or one from X11/cursorfont.h
	 */
	pointer = XCreateFontCursor(DADisplay, XC_based_arrow_up);
	XDefineCursor(DADisplay, DAWindow, pointer);

	/* a square with an image that changes when clicked (A button). */
	createBtn(btn);

	/* a square that shows the number of the mouse-button pressed on click. */
	createSquare(square);

	/* a slider a using two dashed line GC's. */
	createSlider(slider);

	/* Tell libdockapp this is the pixmap that we want to show */
	DASetPixmap(pixmap);

	/* Process events every 100ms */
	DASetTimeout(100);

	/* set event callbacks */
	DASetCallbacks(&eventCallbacks);

	/* Display the pixmap we said it to show */
	DAShow();

	/* Process events and keep the dockapp running */
	DAEventLoop();

	return 0;
}


/* Create our GC's to draw colored lines and such */
struct Colors *
setGCs(Drawable d)
{
	struct Colors *colors;
	XGCValues gcv;
	unsigned long origColor;
	char dashList[2] = {3, 1};

	colors = malloc(sizeof(struct Colors));
	if (colors == NULL)
		return NULL;

	/* Get the GC-values of the default GC */
	XGetGCValues(DADisplay, DAGC,
		     GCForeground | GCBackground | GCGraphicsExposures, &gcv);

	origColor = gcv.foreground;

	/* don't send expose events */
	gcv.graphics_exposures = False;

	/* GC for white color */
	gcv.foreground = WhitePixel(DADisplay, DefaultScreen(DADisplay));
	colors->white = XCreateGC(DADisplay, d,
				  GCForeground | GCGraphicsExposures, &gcv);

	/* GC for dark blue color */
#if 1
	gcv.foreground = DAGetColor("navy");
#else
	gcv.foreground = 0;
#endif
	colors->black = XCreateGC(DADisplay, d,
				  GCForeground | GCGraphicsExposures, &gcv);

	/* GC for light borders */
	gcv.foreground = DAGetColor("lightGray");
	colors->lightGray = XCreateGC(DADisplay, d,
				      GCForeground | GCGraphicsExposures, &gcv);

	/* GC for dark borders (note re-use of gcv-values) */
	gcv.foreground = DAGetColor("#222222");
	colors->darkGray =  XCreateGC(DADisplay, d,
				      GCForeground | GCGraphicsExposures, &gcv);

	/* GC for the un-/highlighted colors and dashed line of the slider */
	gcv.foreground = origColor;
	gcv.line_width = 9;
	gcv.line_style = LineOnOffDash;

	colors->slider = XCreateGC(DADisplay, d,
				   GCForeground | GCBackground | GCGraphicsExposures |
				   GCLineWidth | GCLineStyle, &gcv);

	XSetDashes(DADisplay, colors->slider, 1, dashList, 2);

	/* light slider GC */
	gcv.foreground = adjustColor(origColor, +0x40);

	colors->sliderLight = XCreateGC(DADisplay, d,
					GCForeground | GCBackground | GCGraphicsExposures |
					GCLineWidth | GCLineStyle, &gcv);

	XSetDashes(DADisplay, colors->sliderLight, 1, dashList, 2);

	/* dark slider GC */
	gcv.foreground = adjustColor(origColor, -0x40);

	colors->sliderDark = XCreateGC(DADisplay, d,
				       GCForeground | GCBackground | GCGraphicsExposures |
				       GCLineWidth | GCLineStyle, &gcv);

	XSetDashes(DADisplay, colors->sliderDark, 1, dashList, 2);

	return colors;
}


/* Make a (GC) color lighter or darker */
unsigned long
adjustColor(unsigned long color, signed int adjustment)
{
	signed long r, g, b;

	r = color >> 16;
	g = (color - (r << 16)) >> 8;
	b = (color - (g << 8) - (r << 16));

	r += adjustment;
	g += adjustment;
	b += adjustment;

	if (r > 0xff)
		r = 0xff;

	if (g > 0xff)
		g = 0xff;

	if (b > 0xff)
		b = 0xff;

	if (r < 0)
		r = 0;

	if (g < 0)
		g = 0;

	if (b < 0)
		b = 0;

	return ((unsigned short)r << 16) +
	       ((unsigned short)g << 8) +
	       (unsigned short)b;
}


void
setPointerColor(GC color)
{
	XGCValues gcv;
	XColor fcolor, bcolor;

	XGetGCValues(DADisplay, color, GCForeground, &gcv);

	fcolor.pixel = gcv.foreground;
	fcolor.flags = DoRed | DoGreen | DoBlue;

	bcolor.red = 0;
	bcolor.green = 0;
	bcolor.blue = 0;

	XRecolorCursor(DADisplay, pointer, &fcolor, &bcolor);
}


/* event handlers functions */
void destroy(void)
{
}

void buttonPress(int button, int state, int x, int y)
{
	int *data = malloc(sizeof(int *));

	*data = button;

	DAProcessActionRects(x, y, actionRects[0], 3, (void *)data);

	free(data);
}

void buttonRelease(int button, int state, int x, int y)
{
	DAProcessActionRects(x, y, actionRects[1], 2, NULL);

}

void
mouseMove(int x, int y)
{
	DAProcessActionRects(x, y, actionRects[2], 1, NULL);
}

void
mouseEnter(void)
{
	mouseIn = 1;

	drawSlider(actionRects[3][0].rect);
}


void
mouseLeave(void)
{
	mouseIn = 0;

	/* mouse pointer left the dockapp window */
	DAProcessActionRects(0, 0, actionRects[4], 2, NULL);

	/* if the button is still depressed, make it go up again. */
/* TODO: Use data in actionRects[4] here instead of below check */
	if (buttonDown != NULL)
		btnUp(0, 0, *buttonDown, NULL);

	drawSlider(actionRects[4][1].rect);
}

/* what to do for a specific event for every 'item' in the dockapp */
/*	Button that can be pressed "down" and jumps back "up" again */
void
btnDown(int x, int y, DARect rect, void *data)
{
	buttonDown = &rect;
	drawSunkenFrame(rect);
}


void
btnUp(int x, int y, DARect rect, void *data)
{
	buttonDown = NULL;
	drawRaisedFrame(rect);
}


void
btnLeave(int x, int y, DARect rect, void *data)
{
	if (buttonDown == NULL)
		return;

	drawRaisedFrame(rect);
}


/*	Square that tells which button was pressed (number) */
void
squareDown(int x, int y, DARect rect, void *data)
{
	int button;

	if (data) {
		int *tmp = (int *)data;

		button = *tmp;
	} else
		button = 0;

	drawSquare(rect, button);
}


/*	A draggable slider that highlights when the mouse is over it */
void
sliderDown(int x, int y, DARect rect, void *data)
{
	buttonDown = &rect;
	setPointerColor(colors->sliderDark);
}


void
sliderUp(int x, int y, DARect rect, void *data)
{
	buttonDown = NULL;
	setPointerColor(colors->black);
}


void
sliderMove(int x, int y, DARect rect, void *data)
{
	if (buttonDown == NULL /* ||
				  rect.x != buttonDown->x ||
				  rect.y != buttonDown->y ||
				  rect.width != buttonDown->width ||
				  rect.height != buttonDown->height */)
		return;

	sliderPos = (float)(rect.height - y) / (float)rect.height;
	if (sliderPos > 1.0)
		sliderPos = 1.0;

	if (sliderPos < 0.0)
		sliderPos = 0.0;

	drawSlider(rect);
}

void sliderEnter(int x, int y, DARect rect, void *data)
{
}
void sliderLeave(int x, int y, DARect rect, void *data)
{
}



/*
 * Drawing functions
 */
void
createBtn(DARect rect)
{
	/* fill square excluding borders */
	XFillRectangle(DADisplay, pixmap, colors->lightGray,
		       rect.x + 1, rect.y + 1, rect.width - 2, rect.height - 2);

	drawRaisedFrame(rect);
}


void
drawRaisedFrame(DARect rect)
{
	/* left border */
	XDrawLine(DADisplay, pixmap, colors->white,
		  rect.x, rect.y, rect.x, rect.y + rect.height - 2);
	/* top border */
	XDrawLine(DADisplay, pixmap, colors->white,
		  rect.x + 1, rect.y, rect.width - 1, rect.y);
	/* bottom border */
	XDrawLine(DADisplay, pixmap, colors->darkGray,
		  rect.x,                 rect.y + rect.height - 1,
		  rect.x + rect.width - 1, rect.y + rect.height - 1);
	/* right border */
	XDrawLine(DADisplay, pixmap, colors->darkGray,
		  rect.x + rect.width - 1, rect.y + 1,
		  rect.x + rect.width - 1, rect.y + rect.height - 2);

	DASetPixmap(pixmap);
}


void
drawSunkenFrame(DARect rect)
{
	/* left border */
	XDrawLine(DADisplay, pixmap, colors->darkGray,
		  rect.x, rect.y, rect.x, rect.y + rect.height - 2);
	/* top border */
	XDrawLine(DADisplay, pixmap, colors->darkGray,
		  rect.x + 1, rect.y, rect.width - 1, rect.y);
	/* bottom border */
	XDrawLine(DADisplay, pixmap, colors->white,
		  rect.x,                 rect.y + rect.height - 1,
		  rect.x + rect.width - 1, rect.y + rect.height - 1);
	/* right border */
	XDrawLine(DADisplay, pixmap, colors->white,
		  rect.x + rect.width - 1, rect.y + 1,
		  rect.x + rect.width - 1, rect.y + rect.height - 2);

	DASetPixmap(pixmap);
}


void
createSquare(DARect rect)
{
	/* fill square excluding borders */
	XFillRectangle(DADisplay, pixmap, colors->black,
		       rect.x + 1, rect.y + 1, rect.width - 2, rect.height - 2);

	XDrawRectangle(DADisplay, pixmap, colors->lightGray,
		       rect.x, rect.y, rect.width - 1, rect.height - 1);

	drawSquare(rect, 0);
}


void
drawSquare(DARect rect, int button)
{
	char label[3];

	XFillRectangle(DADisplay, pixmap, colors->black,
		       rect.x + 1, rect.y + 1, rect.width - 2, rect.height - 2);

	snprintf(label, 3, "%2d", button);
	XDrawString(DADisplay, pixmap, colors->white,
		    rect.x + 3, rect.y + rect.height - 5, label, 2);

	DASetPixmap(pixmap);
}


void
createSlider(DARect rect)
{
	/* fill square excluding borders */
	XFillRectangle(DADisplay, pixmap, colors->black,
		       rect.x + 1, rect.y + 1, rect.width - 2, rect.height - 2);

	drawSunkenFrame(rect);

	drawSlider(rect);
}


void
drawSlider(DARect rect)
{
	GC highColor, lowColor; /* determine colors to use */

	if (mouseIn) {
		highColor = colors->sliderLight;
		lowColor = colors->slider;
	} else {
		highColor = colors->slider;
		lowColor = colors->sliderDark;
	}

	/* Draw two lines from bottom to sliderPos fraction of height */
	if (sliderPos < 1.0) {
		XDrawLine(DADisplay, pixmap, highColor,
			  rect.x + 6, rect.y + rect.height - 2,
			  rect.x + 6, rect.y + (1.0 - sliderPos) * (rect.height - 2));

		XDrawLine(DADisplay, pixmap, highColor,
			  rect.x + 17, rect.y + rect.height - 2,
			  rect.x + 17, rect.y + (1.0 - sliderPos) * (rect.height - 2));
	}

	if (sliderPos > 0.0) {
		XDrawLine(DADisplay, pixmap, lowColor,
			  rect.x + 6, rect.y + 1,
			  rect.x + 6, rect.y + (1.0 - sliderPos) * (rect.height - 2));

		XDrawLine(DADisplay, pixmap, lowColor,
			  rect.x + 17, rect.y + 1,
			  rect.x + 17, rect.y + (1.0 - sliderPos) * (rect.height - 2));
	}

	DASetPixmap(pixmap);
}


DAActionRect
setRectAction(DARect rect, DARectCallback action)
{
	DAActionRect ar;

	ar.rect     = rect;
	ar.action   = action;

	return ar;
}