/*
 * gbut.c
 *
 * Generic button code.
 * Meant to be reusable.
 */

#ifdef  X11_DISP

#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include "gbut.h"
#include <string.h>

static Display *gdpy;

static gbut_info  **butlist;
static int  maxbuts, bpressed, kpressed, but_in, twitched;
static Window  last_mwin;
static int  last_mx = -1, last_my = -1, badwin = 0;

static gbut_info  deadbut = {0, 0,0,0,0, 0, NULL, NULL, NULL};

static int  mousein(Window win, int x, int y, int reason);


void  gbut_init(Display *dpy, Window win)  {
	gdpy = dpy;
	last_mwin = win;
	maxbuts = 0;
	bpressed = GBUT_NOBUT;
	kpressed = GBUT_NOBUT;
	but_in = GBUT_NOBUT;
	twitched = GBUT_NOBUT;
	butlist = NULL;
}


void  gbut_badwindow(Window win)  {
	badwin = win;
}


void  gbut_create(int butnum, gbut_info *info)  {
	if (info->win == badwin)
		badwin = 0;
	if (butnum >= maxbuts)  {
		gbut_info  **newbutlist;
		int  newmaxbuts, i;

		newmaxbuts = butnum + 1;
		newbutlist = (gbut_info **)malloc(newmaxbuts * sizeof(gbut_info *));
		if (newbutlist == NULL)  {
			fprintf(stderr, "Error: Could not allocate %d buttons.\n",
							newmaxbuts);
			exit(1);
		}
		if (butlist != NULL)  {
			bcopy(butlist, newbutlist, maxbuts * sizeof(gbut_info *));
			free(butlist);
		}
		butlist = newbutlist;
		for (i = maxbuts;  i < newmaxbuts;  ++i)
			butlist[i] = &deadbut;
		maxbuts = newmaxbuts;
	}
	butlist[butnum] = info;
	gbut_draw(butnum);
	gbut_mmove(last_mwin, last_mx, last_my);
}


void  gbut_destroy(int butnum)  {
	butlist[butnum] = &deadbut;
	if (but_in == butnum)
		but_in = GBUT_NOBUT;
	if (bpressed == butnum)
		bpressed = GBUT_NOBUT;
	if (kpressed == butnum)
		kpressed = GBUT_NOBUT;
	if (twitched == butnum)
		twitched = GBUT_NOBUT;
}


void  gbut_setflags(int butnum, unsigned newflags)  {
	unsigned  oldflags = butlist[butnum]->flags;

	newflags = ((oldflags & ~(newflags >> GBUT_MAXBITS)) | newflags) &
		(GBUT_PRESSABLE|GBUT_DRAWABLE|GBUT_TWITCHABLE);
	if (but_in == butnum)
		but_in = GBUT_NOBUT;
	if (mousein(last_mwin, last_mx, last_my, GBUT_CT_MOVE) == butnum)  {
		but_in = butnum;
		if (newflags & GBUT_TWITCHABLE)
			newflags |= GBUT_TWITCHED;
		if ((bpressed == butnum) && (newflags & GBUT_PRESSABLE))
			newflags |= GBUT_PRESSED;
	} else  {
		if (bpressed == butnum)
			bpressed = GBUT_NOBUT;
		if (kpressed == butnum)
			kpressed = GBUT_NOBUT;
		if (twitched == butnum)
			twitched = GBUT_NOBUT;
	}
	if ((kpressed == butnum) && !(newflags & GBUT_PRESSABLE))
		kpressed = GBUT_NOBUT;
	if ((bpressed == butnum) && !(newflags & GBUT_PRESSABLE))
		bpressed = GBUT_NOBUT;
	if ((twitched == butnum) && !(newflags & GBUT_TWITCHABLE))
		twitched = GBUT_NOBUT;
	if (newflags != oldflags)  {
		butlist[butnum]->flags = newflags;
		gbut_draw(butnum);
	}
}

		
void  gbut_draw(int butnum)  {
	if (butlist[butnum]->flags & GBUT_DRAWABLE)
		butlist[butnum]->drawfunc(butnum, butlist[butnum]);
}


void  gbut_redraw(Window win, int x, int y, int w, int h)  {
	int  i;

	for (i = 0;  i < maxbuts;  ++i)  {
		if ((win == butlist[i]->win) &&
				(x < butlist[i]->x + butlist[i]->w) &&
				(x+w >= butlist[i]->x) &&
				(y < butlist[i]->y + butlist[i]->h) &&
				(y+h >= butlist[i]->y))  {
			gbut_draw(i);
		}
	}
}


int  gbut_mmove(Window win, int x, int y)  {
	int  i, flags, butnum;

	last_mwin = win;
	last_mx = x;
	last_my = y;
	butnum = mousein(win, x, y, GBUT_CT_MOVE);
	if (but_in == butnum)
		return(butnum);
	but_in = butnum;
	if (twitched != GBUT_NOBUT)  {
		butlist[twitched]->flags &= ~(GBUT_PRESSED | GBUT_TWITCHED);
		gbut_draw(twitched);
		twitched = GBUT_NOBUT;
	}
	if ((bpressed != GBUT_NOBUT) && (bpressed != butnum) &&
			(butlist[bpressed]->flags & GBUT_PRESSED))  {
		butlist[bpressed]->flags &= ~GBUT_PRESSED;
		gbut_draw(bpressed);
	}
	if (butnum != GBUT_NOBUT)  {
		flags = butlist[butnum]->flags;
		if (flags & GBUT_TWITCHABLE)  {
			twitched = butnum;
			flags |= GBUT_TWITCHED;
		}
		if (bpressed == butnum)
			flags |= GBUT_PRESSED;
		if (flags != butlist[butnum]->flags)  {
			butlist[butnum]->flags = flags;
			gbut_draw(butnum);
		}
	}
	return(butnum);
}


int  gbut_mpress(Window win, int x, int y)  {
	int  butnum, flags;

	last_mwin = win;
	last_mx = x;
	last_my = y;
	if (kpressed != GBUT_NOBUT)  {
		butlist[kpressed]->flags &= ~GBUT_PRESSED;
		gbut_draw(kpressed);
		kpressed = GBUT_NOBUT;
	}
	if (bpressed != GBUT_NOBUT)  {
		butlist[bpressed]->flags &= ~GBUT_PRESSED;
		gbut_draw(bpressed);
		bpressed = GBUT_NOBUT;
	}
	butnum = mousein(win, x, y, GBUT_CT_PRESS);
	but_in = butnum;
	if (butnum != twitched)  {
		if (twitched != GBUT_NOBUT)  {
			butlist[twitched]->flags &= ~GBUT_TWITCHED;
			gbut_draw(twitched);
		}
		twitched = butnum;
	}
	if (butnum != GBUT_NOBUT)  {
		flags = butlist[butnum]->flags;
		if (flags & GBUT_PRESSABLE)  {
			flags |= GBUT_PRESSED;
			bpressed = butnum;
		}
		if (flags & GBUT_TWITCHABLE)  {
			flags |= GBUT_TWITCHABLE;
			twitched = butnum;
		}
		if (flags != butlist[butnum]->flags)  {
			butlist[butnum]->flags = flags;
			gbut_draw(butnum);
		}
	}
	return(butnum);
}


int  gbut_mrelease(Window win, int x, int y)  {
	int  butnum, old_bpr;

	last_mwin = win;
	last_mx = x;
	last_my = y;
	if (kpressed != GBUT_NOBUT)  {
		butlist[kpressed]->flags &= ~GBUT_PRESSED;
		gbut_draw(kpressed);
		kpressed = GBUT_NOBUT;
	}
	if ((old_bpr = bpressed) != GBUT_NOBUT)  {
		butlist[bpressed]->flags &= ~GBUT_PRESSED;
		gbut_draw(bpressed);
		bpressed = GBUT_NOBUT;
	}
	butnum = mousein(win, x, y, GBUT_CT_RELEASE);
	but_in = butnum;
	if (butnum != twitched)  {
		if (twitched != GBUT_NOBUT)  {
			butlist[twitched]->flags &= ~GBUT_TWITCHED;
			gbut_draw(twitched);
		}
		twitched = butnum;
		if ((butnum != GBUT_NOBUT) &&
				(butlist[butnum]->flags & GBUT_TWITCHABLE))  {
			butlist[butnum]->flags |= GBUT_TWITCHED;
			gbut_draw(butnum);
		}
	}
	if (butnum == old_bpr)
		return(butnum);
	else
		return(GBUT_NOBUT);
}


int  gbut_kpress(Window win, char *keystr)  {
	int  i, fset;
	char  sstr[100] = ":";

	if (bpressed != GBUT_NOBUT)  {
		butlist[bpressed]->flags &= ~GBUT_PRESSED;
		gbut_draw(bpressed);
		bpressed = GBUT_NOBUT;
	}
	if (kpressed != GBUT_NOBUT)  {
		butlist[kpressed]->flags &= ~GBUT_PRESSED;
		gbut_draw(kpressed);
		kpressed = GBUT_NOBUT;
	}
	strcpy(sstr+1, keystr);
	strcat(sstr+1, ":");
	for (i = 0;  i < maxbuts;  ++i)  {
		if ((butlist[i]->win == win) && (butlist[i]->flags & GBUT_PRESSABLE) &&
				strstr(butlist[i]->keystr, sstr))
			kpressed = i;
	}
	if (kpressed != GBUT_NOBUT)  {
		butlist[kpressed]->flags |= GBUT_PRESSED;
		gbut_draw(kpressed);
	}
	return(kpressed);
}
	

int  gbut_krelease(Window win, char *keystr)  {
	int  res;
	char  sstr[100] = ":";

	if ((res = kpressed) == GBUT_NOBUT)
		return(GBUT_NOBUT);
	else  {
		butlist[kpressed]->flags &= ~GBUT_PRESSED;
		gbut_draw(kpressed);
		kpressed = GBUT_NOBUT;
	}
	strcpy(sstr+1, keystr);
	strcat(sstr+1, ":");
	if (strstr(butlist[res]->keystr, sstr))
		return(res);
	return(GBUT_NOBUT);
}


int  gbut_true(int butnum, gbut_info *info,
							 int checktype, int x, int y)  {
	return(1);
}


/* mousein(win, x, y, reason) will return the button number that the mouse is
 *   presently inside of.  If the mouse is not in any button GBUT_NOBUT will
 *   be returned.
 * The checkfunc will be called only if the mouse is already inside the
 *   (x,y,w,h) boundaries of the button.  "reason" is one of GBUT_CT_PRESS,
 *   GBUT_CT_RELEASE, or GBUT_CT_MOVE, depending on the reason for the button
 *   to be checked.
 * Non-pressable buttons will never be returned.
 *
 * NOTE: This does a linear search through the buttons.  YUCK!  SLOW!  I'd
 *   like to recode this to do a three-dimensional binary search (the
 *   dimensions being window, x, and y) but that would take some seriously
 *   heavy-duty coding and I have a million other things to do first.
 */
static int  mousein(Window win, int x, int y, int reason)  {
	int  i, fset, butnum = GBUT_NOBUT, checkr;
	gbut_info  *info;

	for (i = 0;  i < maxbuts;  ++i)  {
		info = butlist[i];
		if ((info->flags & GBUT_PRESSABLE) &&
				(win == info->win) &&
				(x >= info->x) && (x < info->x + info->w) &&
				(y >= info->y) && (y < info->y + info->h))  {
			if ((i == bpressed) && (reason == GBUT_CT_MOVE))
				checkr = info->checkfunc(i, info, GBUT_CT_PMOVE, x, y);
			else
				checkr = info->checkfunc(i, info, reason, x, y);
			if (checkr)  {
				if (win != badwin)
					gbut_inbutton(win);
				return(i);
			}
		}
	}
	if (win != badwin)
		gbut_outbutton(win);
	return(GBUT_NOBUT);
}


#endif  /* X11_DISP */
