/***********************************************************************
 *   Code is based on wmppp, wmload, wmtime, wmcp, and asbutton
 *   Author: Edward H. Flora <ehflora@ksu.edu>
 *   Ver 0 Rel 6.1    Jan 23, 2005
 *
 *   Contributors:
 *              Christian 'Greek0' Aichinger <Greek0@gmx.net>
 *                  Did some code cleanup and fixed several memory leaks.
 *              Ralf Horstmann <ralf.horstmann@gmx.de>
 *                  Added ability to load pixmaps at startup,
 *                  without having to re-compile
 *              Michael Cohrs <camico@users.sourceforge.net>
 *                  Added Tool Tips, and updated graphics
 *              Bruno Essmann <essmann@users.sourceforge.net>)
 *                  Creator of wmpager
 *              Casey Harkins <charkins@cs.wisc.edu>
 *                  Bug fix reading config file path - 3/6/99
 *                  Added button-presses, and other - denoted by *charkins*
 *              Ben Cohen <buddog@aztec.asu.edu>
 *                  original author of wmcp (et al.)
 *              Thomas Nemeth <tnemeth@multimania.com>
 *                  contributor to wmcp
 *              Michael Henderson <mghenderson@lanl.gov>
 *                  Application ideas, suggestions
 *              Ryan ?? <pancake@mindspring.com>
 *                  Modified wmbutton to asbutton.
 *                  Note: asbutton is a seperate program, not associated
 *                        with wmbutton (just as wmbutton is not associated
 *                        with wmcp)
 *              Jon Bruno
 *                  Web Page Development
 *    The contributors listed above are not necessarily involved with the
 *    development of wmbutton.  I'm listing them here partially as thanks for
 *    helping out, catching bugs in the code, etc.
 ***********************************************************************/
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>

#include "wmbutton.h"

#include "backdrop.xpm"           /* background graphic */
#include "buttons.xpm"            /* graphic of 9 buttons */
#include "mask.xbm"               /* Border Graphic */

/*************** Function Prototypes ***********************************/
void redraw(void);
void getPixmaps(void);
int  whichButton(int x, int y);   // determine which button has been pressed
void SetWmHints();
void SetClassHints();

/***********************************************************************
 * 		Globals..    OK.. there's too many globals.
 *                           Feel free and fix it, if you'd like.
 ***********************************************************************/
Display *display;
int screen;
Window rootwin, win, iconwin;
GC gc;
int depth;
Pixel bg_pixel, fg_pixel;

struct Config_t Config;

typedef struct _XpmIcon {
	Pixmap pixmap;
	Pixmap mask;
	XpmAttributes attributes;
} XpmIcon;

typedef struct _button_region {
	int x, y;
	int i, j;
} ButtonArea;

ButtonArea button_region[9];

XpmIcon template, visible, buttons;

int border = 0;
int button_pressed = -1;	/* button to be drawn pressed *charkins*/

char *app_name = "wmbutton";

/***********************************************************************
 * 		Main
 ***********************************************************************/
int main(int argc, char **argv)
{
	XEvent report;
	XGCValues xgcValues;
	XTextProperty app_name_atom;
	XSizeHints xsizehints;
	Pixmap pixmask;

	int dummy = 0;
	int N = 1;	/* Button number pressed to goto app # */

	/* Added for Tool Tip Support */
	long nTooltipShowDelay = TOOLTIP_SHOW_DELAY;
	long nTooltipReshowDelay = TOOLTIP_RESHOW_DELAY;
	long nTooltipTimer = -1;
	long nTooltipHideTimer = -1;
	long nNow;
	int nTooltipButton = 0, nTooltipX = 0, nTooltipY = 0;

	/* Parse Command Line Arguments */
	parseargs(argc, argv);

	/* Open Display */
	if ((display = XOpenDisplay(Config.Display_str)) == NULL)
		err_mess(FAILDISP, Config.Display_str);

	screen  = DefaultScreen(display);
	rootwin = RootWindow(display, screen);
	depth   = DefaultDepth(display, screen);

	bg_pixel = WhitePixel(display, screen);
	fg_pixel = BlackPixel(display, screen);

	xsizehints.flags  = USSize | USPosition;
	xsizehints.width  = 64;
	xsizehints.height = 64;

	/* Parse Geometry string and fill in sizehints fields */
	XWMGeometry(display, screen,
		    Config.Geometry_str,
		    NULL,
		    border,
		    &xsizehints,
		    &xsizehints.x,
		    &xsizehints.y,
		    &xsizehints.width,
		    &xsizehints.height,
		    &dummy);

	if ((win = XCreateSimpleWindow(display,
				       rootwin,
				       xsizehints.x,
				       xsizehints.y,
				       xsizehints.width,
				       xsizehints.height,
				       border,
				       fg_pixel, bg_pixel)) == 0)
		err_mess(FAILSWIN, NULL);

	if ((iconwin = XCreateSimpleWindow(display,
					   win,
					   xsizehints.x,
					   xsizehints.y,
					   xsizehints.width,
					   xsizehints.height,
					   border,
					   fg_pixel, bg_pixel)) == 0)

		err_mess(FAILICON, NULL);

	/* Set up shaped windows */
	/* Gives the appicon a border so you can grab and move it. */
	if ((pixmask = XCreateBitmapFromData(display,
					     win,
					     mask_bits,
					     mask_width,
					     mask_height)) == 0)
		err_mess(FAILXPM, NULL);

	XShapeCombineMask(display, win, ShapeBounding, 0, 0, pixmask, ShapeSet);
	XShapeCombineMask(display, iconwin, ShapeBounding, 0, 0, pixmask, ShapeSet);

	/* Convert in pixmaps from .xpm includes. */
	getPixmaps();

	/* Interclient Communication stuff */
	/* Appicons don't work with out this stuff */
	SetWmHints();
	SetClassHints();

	XSetWMNormalHints(display, win, &xsizehints);

	/* Tell window manager what the title bar name is. We never see */
	/* this anyways in the WithdrawnState      */
	if (XStringListToTextProperty(&app_name, 1, &app_name_atom) == 0)
		err_mess(FAILWNAM, app_name);

	XSetWMName(display, win, &app_name_atom);

	/* Create Graphic Context */
	if ((gc = XCreateGC(display, win, (GCForeground | GCBackground),
			    &xgcValues)) == NULL)
		err_mess(FAILGC, NULL);

	/* XEvent Masks. We want both window to process X events */
	XSelectInput(display, win,
		     ExposureMask |
		     ButtonPressMask |
		     ButtonReleaseMask | /* added ButtonReleaseMask *charkins*/
		     PointerMotionMask |
		     StructureNotifyMask |
		     LeaveWindowMask);

	XSelectInput(display, iconwin,
		     ExposureMask |
		     ButtonPressMask |
		     ButtonReleaseMask | /* added ButtonReleaseMask *charkins*/
		     PointerMotionMask |
		     StructureNotifyMask |
		     LeaveWindowMask);

	/* Store the 'state' of the application for restarting */
	XSetCommand(display, win, argv, argc);

	/* Window won't ever show up until it is mapped.. then drawn after a 	*/
	/* ConfigureNotify */
	XMapWindow(display, win);

	/* Initialize Tooltip Support */
	initTime();
	initTooltip();

	/* X Event Loop */
	while (1) {
		while (XPending(display) || nTooltipTimer == -1) {
			XNextEvent(display, &report);

			switch (report.type) {
			case Expose:
				if (report.xexpose.count != 0)
					break;

				if (Config.Verbose)
					fprintf(stdout, "Event: Expose\n");

				redraw();
				break;

			case ConfigureNotify:
				if (Config.Verbose)
					fprintf(stdout, "Event: ConfigureNotify\n");

				redraw();
				break;

			case MotionNotify:
				if (hasTooltipSupport()) {
					if (!hasTooltip()) {
						nTooltipTimer = currentTimeMillis();
						nTooltipX = report.xbutton.x;
						nTooltipY = report.xbutton.y;
						nTooltipButton = whichButton(report.xbutton.x, report.xbutton.y);
					} else {
						int nButton = whichButton(report.xbutton.x, report.xbutton.y);
						if (nButton != nTooltipButton) {
							hideTooltip();
							nTooltipTimer = -1;
							nTooltipX = report.xbutton.x;
							nTooltipY = report.xbutton.y;
							nTooltipButton = nButton;
							showTooltip(nTooltipButton, nTooltipX, nTooltipY);
						}
					}
				}
				break;

			case LeaveNotify:
				if (Config.Verbose)
					fprintf(stdout, "Event: LeaveNotify\n");

				if (hasTooltip()) {
					hideTooltip();
					nTooltipHideTimer = currentTimeMillis();
				}
				nTooltipTimer = -1;
				break;

			case ButtonPress:	/* draw button pressed, don't launch *charkins*/
				if (hasTooltip()) {
					hideTooltip();
					nTooltipHideTimer = currentTimeMillis();
				}

				switch (report.xbutton.button) {
				case Button1:
					N = whichButton(report.xbutton.x, report.xbutton.y);
					if ((N >= 0) && (N <= NUMB_OF_APPS)) {
						button_pressed = N + LMASK;
						redraw();
					}

					if (Config.Verbose)
						fprintf(stdout, "Button 1:x=%d y=%d N=%d\n",
							report.xbutton.x, report.xbutton.y, N+LMASK);
					break;

				case Button2:
					if (!Config.mmouse) {
						N = whichButton(report.xbutton.x, report.xbutton.y);
						if ((N >= 0) && (N <= NUMB_OF_APPS)) {
							button_pressed = N + MMASK;
							redraw();
						}

						if (Config.Verbose)
							fprintf(stdout, "Button 2:x=%d y=%d N=%d\n",
								report.xbutton.x, report.xbutton.y, N+MMASK);
					}
					break;

				case Button3:
					N = whichButton(report.xbutton.x, report.xbutton.y);
					if ((N >= 0) && (N <= NUMB_OF_APPS)) {
						button_pressed = N + RMASK;
						redraw();
					}

					if (Config.Verbose)
						fprintf(stdout, "Button 3:x=%d y=%d N=%d\n",
							report.xbutton.x, report.xbutton.y, N+RMASK);
					break;
				}
				break;

			case ButtonRelease:   /* launch app here if still over button *charkins*/
				switch (report.xbutton.button) {
				case Button1:
					N = whichButton(report.xbutton.x, report.xbutton.y);
					if ((N >= 0) && (N <= NUMB_OF_APPS) && (N == button_pressed))
						RunAppN(N + LMASK);

					button_pressed = -1;
					redraw();

					if (Config.Verbose)
						fprintf(stdout, "Button 1:x=%d y=%d N=%d\n",
							report.xbutton.x, report.xbutton.y, N+LMASK);
					break;

				case Button2:
					if (!Config.mmouse) {
						N = whichButton(report.xbutton.x, report.xbutton.y);
						if ((N >= 0) && (N <= NUMB_OF_APPS) && (N == button_pressed))
							RunAppN(N + MMASK);

						button_pressed = -1;
						redraw();

						if (Config.Verbose)
							fprintf(stdout, "Button 2:x=%d y=%d N=%d\n",
								report.xbutton.x, report.xbutton.y, N+MMASK);
					}
					break;

				case Button3:
					N = whichButton(report.xbutton.x, report.xbutton.y);
					if ((N >= 0) && (N <= NUMB_OF_APPS) && (N == button_pressed))
						RunAppN(N + RMASK);

					button_pressed = -1;
					redraw();

					if (Config.Verbose)
						fprintf(stdout, "Button 3:x=%d y=%d N=%d\n",
							report.xbutton.x, report.xbutton.y, N+RMASK);
					break;
				}
				break;

			case DestroyNotify:
				if (Config.Verbose)
					fprintf(stdout, "Bye\n");

				destroyTooltip();
				XFreeGC(display, gc);
				XDestroyWindow(display, win);
				XDestroyWindow(display, iconwin);
				XCloseDisplay(display);
				exit(0);
				break;
			}
		}

		usleep(50000);
		nNow = currentTimeMillis();
		if (nTooltipTimer != -1 &&
		    ((nNow > nTooltipTimer + nTooltipShowDelay) ||
		     (nNow < nTooltipHideTimer + nTooltipReshowDelay))) {
			showTooltip(nTooltipButton, nTooltipX, nTooltipY);
			nTooltipTimer = -1;
		}
	}

	return (0);
}
/***********************************************************************/

/***********************************************************************
 *   redraw
 *
 *	 Map the button region coordinates.
 *
 *   Draw the appropriate number of buttons on the 'visible' Pixmap
 *   using data from the 'buttons' pixmap.
 *
 *   Then, copy the 'visible' pixmap to the two windows ( the withdrawn
 *   main window and the icon window which is the main window's icon image.)
 ***********************************************************************/
void redraw() {
	int n, i, j, dest_x, dest_y, space, offset, bsize = 18;

	if (Config.Verbose)
		fprintf(stdout, "In Redraw()\n");

	space = 0;
	offset = 5;
	XCopyArea(display, template.pixmap, visible.pixmap, gc, 0, 0,
		  template.attributes.width, template.attributes.height, 0, 0);

	for (j = 0; j < 3; j++) {
		for (i = 0; i < 3; i++) {
			n = i + j * 3;
			dest_x = i * (bsize + space) + offset + space;
			dest_y = j * (bsize + space) + offset + space;

			/* Define button mouse coords */
			button_region[n].x = dest_x;
			button_region[n].y = dest_y;
			button_region[n].i = dest_x + bsize - 1;
			button_region[n].j = dest_y + bsize - 1;

			/* Copy button images for valid apps */
			if ((n + 1) <= NUMB_OF_APPS)
				XCopyArea(display, buttons.pixmap, visible.pixmap, gc,
					  i * bsize, j * bsize, bsize, bsize, dest_x, dest_y);
		}
	}

	if (button_pressed > 0) {	/* draw pressed button *charkins*/
		if (button_pressed > RMASK)
			button_pressed -= RMASK;
		else if (button_pressed > MMASK)
			button_pressed -= MMASK;
		else if (button_pressed > LMASK)
			button_pressed -= LMASK;

		i = (button_pressed - 1) % 3;	/* get col of button */
		j = (button_pressed - 1) / 3;	/* get row of button */
		dest_x = i * (bsize + space) + offset + space;
		dest_y = j * (bsize + space) + offset + space;
		XSetForeground(display, gc, bg_pixel);
		XDrawLine(display, visible.pixmap, gc,
			  dest_x + 1, dest_y + bsize - 1,
			  dest_x + bsize - 1, dest_y + bsize - 1);
		XDrawLine(display, visible.pixmap, gc,
			  dest_x + bsize - 1, dest_y + bsize - 1,
			  dest_x + bsize - 1, dest_y + 1);
		XSetForeground(display, gc, fg_pixel);
		XDrawLine(display, visible.pixmap, gc,
			  dest_x, dest_y, dest_x + bsize - 2, dest_y);
		XDrawLine(display, visible.pixmap, gc,
			  dest_x, dest_y, dest_x, dest_y + bsize - 2);
	} /*charkins*/

	flush_expose(win);
	XCopyArea(display, visible.pixmap, win, gc, 0, 0,
		  visible.attributes.width, visible.attributes.height, 0, 0);
	flush_expose(iconwin);
	XCopyArea(display, visible.pixmap, iconwin, gc, 0, 0,
		  visible.attributes.width, visible.attributes.height, 0, 0);
}
/***********************************************************************/

/***********************************************************************
 *  whichButton
 *
 *  Return the button that at the x,y coordinates. The button need not
 *  be visible ( drawn ). Return -1 if no button match.
 ***********************************************************************/
int whichButton(int x, int y)
{
	int index;

	for (index = 0; index < NUMB_OF_APPS; index++) {
		if (x >= button_region[index].x &&
		    x <= button_region[index].i &&
		    y >= button_region[index].y &&
		    y <= button_region[index].j)
			return(index + 1);
	}
	return -1;
}
/***********************************************************************/


/***********************************************************************
 *   getPixmaps
 *
 *   Load XPM data into X Pixmaps.
 *
 *   Pixmap template contains the untouched window backdrop image.
 *   Pixmap visible is the template pixmap with buttons drawn on it.
 *          -- what is seen by the user.
 *   Pixmap buttons holds the images for individual buttons that are
 *          later copied onto Pixmap visible.
 ***********************************************************************/
void getPixmaps()
{
	int loaded = 0;
	template.attributes.valuemask |= (XpmReturnPixels | XpmReturnExtensions);
	visible.attributes.valuemask  |= (XpmReturnPixels | XpmReturnExtensions);
	buttons.attributes.valuemask  |= (XpmReturnPixels | XpmReturnExtensions);

	if (Config.Verbose)
		fprintf(stdout, "In getPixmaps\n");

	/* Template Pixmap. Never Drawn To. */
	if (XpmCreatePixmapFromData(display, rootwin, backdrop_xpm,
				&template.pixmap, &template.mask,
				&template.attributes) != XpmSuccess)
		err_mess(FAILTMPL, NULL);

	/* Visible Pixmap. Copied from template Pixmap and then drawn to. */
	if (XpmCreatePixmapFromData(display, rootwin, backdrop_xpm,
				&visible.pixmap, &visible.mask,
				&visible.attributes) != XpmSuccess)
		err_mess(FAILVIS, NULL);

	/* Button Pixmap.  */
	if (access(Config.buttonfile, R_OK) == 0) {
		/* load buttons from file */
		if (XpmReadFileToPixmap(display, rootwin, Config.buttonfile,
					&buttons.pixmap, &buttons.mask,
					&buttons.attributes) != XpmSuccess)
			err_mess(FAILBUT, NULL);
		else
			loaded = 1;
	}

	if (!loaded) {
		/* Use Builtin Button Pixmap.  */
		if (Config.Verbose)
			fprintf(stdout, "Using builtin buttons pixmap\n");

		if (XpmCreatePixmapFromData(display, rootwin, buttons_xpm,
					    &buttons.pixmap, &buttons.mask,
					    &buttons.attributes) != XpmSuccess)
			err_mess(FAILBUT, NULL);
	}

	if (Config.Verbose)
		fprintf(stdout, "Leaving getPixmaps\n");

}
/*********************************************************************/

void SetWmHints()
{
	XWMHints *xwmhints;

	xwmhints = XAllocWMHints();
	xwmhints->flags = WindowGroupHint | IconWindowHint | StateHint;
	xwmhints->icon_window = iconwin;
	xwmhints->window_group = win;
	xwmhints->initial_state = WithdrawnState;
	XSetWMHints(display, win, xwmhints);
	XFree(xwmhints);
	xwmhints = NULL;
}

void SetClassHints()
{
	XClassHint xclasshint;

	xclasshint.res_name = "wmbutton";
	xclasshint.res_class = "Wmbutton";
	XSetClassHint(display, win, &xclasshint);
}
