/* -*-c-*- */
/* FvwmTaskBar Module for Fvwm.
 *
 *  Copyright 1994,  Mike Finger (mfinger@mermaid.micro.umn.edu or
 *                               Mike_Finger@atk.com)
 *  Copyright 1995,  Pekka Pietik{inen (pp@netppl.fi)
 *
 * The functions in this source file that are the original work of Mike Finger.
 * This source file has been modified for use with fvwm95look by
 * Pekka Pietik{inen, David Barth, Hector Peraza, etc, etc...
 *
 * No guarantees or warantees or anything are provided or implied in any way
 * whatsoever. Use this program at your own risk. Permission to use this
 * program for any purpose is given, as long as the copyright is kept intact.
 *
 */

/* This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "config.h"

#include <stdio.h>
#include <X11/Xlib.h>

#include "libs/fvwmlib.h"
#include "libs/Colorset.h"
#include "libs/Flocale.h"
#include "libs/Picture.h"
#include "libs/PictureGraphics.h"
#include "libs/Rectangles.h"

#include "ButtonArray.h"
#include "Mallocs.h"

extern FlocaleFont *FButtonFont, *FSelButtonFont;
extern Display *dpy;
extern Window win;
extern GC shadow, hilite, graph, whitegc, blackgc, checkered, icongraph,
	  iconhilite, iconshadow, focusgraph, focushilite, focusshadow;
extern GC iconbackgraph;
extern GC focusbackgraph;
extern FlocaleWinString *FwinString;
extern int pad;
extern int button_width;
extern int iconcolorset;
extern int focuscolorset;
extern int colorset;
extern int NoBrightFocus;
extern int ThreeDfvwm;
extern char *FocusBackColor;
extern Button *StartButton;

int w3p;         /* width of the string "..." in pixels */

extern int NRows, RowHeight;

#define MIN_BUTTON_SIZE 32 /*....*/

/*
 * Button handling functions and procedures
 */

/* Draws a 3D rectangle */
void Draw3dRect(
	Window wn, int x, int y, int w, int h, XRectangle *bounding,
	int state, Bool iconified)
{
	colorset_t *cset = &Colorset[0];  /* initialize to avoid warning */
	int d = 1;
	XRectangle r,inter;
	Bool draw;

	if (ThreeDfvwm)
	{
		d = 2;
	}
	if (bounding)
	{
		r.x = bounding->x;
		r.y = bounding->y;
		r.width = bounding->width;
		r.height = bounding->height;
	}
	else
	{
		r.x = x;
		r.y = y;
		r.width = w;
		r.height = h;
	}
	XClearArea (dpy, wn, r.x, r.y, r.width, r.height, False);
	if (iconified)
	{
		draw = frect_get_intersection(
			x + d, y + d, w - 3, h - 3,
			r.x, r.y, r.width, r.height, &inter);
		if (iconcolorset >= 0)
		{
			cset = &Colorset[iconcolorset];
		}
		if (iconcolorset >= 0 && (cset->pixmap || cset->shape_mask)
		    && draw)
		{
			/* we have a colorset background */
			SetClippedRectangleBackground(
				dpy, win, x + d, y + d, w - 3, h - 3, &inter,
				cset, Pdepth, icongraph);
		}
		else if (draw)
		{
			XFillRectangle (
				dpy, wn, iconbackgraph,
				inter.x, inter.y, inter.width, inter.height);
		}
		XDrawLine (dpy, wn, iconhilite, x, y, x+w-2, y);
		XDrawLine (dpy, wn, iconhilite, x, y, x, y+h-2);
		if (ThreeDfvwm)
		{
			XDrawLine (dpy, wn, iconhilite, x, y+1, x+w-2, y+1);
			XDrawLine (dpy, wn, iconhilite, x+1, y, x+1, y+h-2);
		}
		XDrawLine (dpy, wn, iconshadow,  x+1, y+h-2, x+w-2, y+h-2);
		XDrawLine (dpy, wn, iconshadow,  x+w-2, y+h-2, x+w-2, y+1);
		XDrawLine (dpy, wn, blackgc, x, y+h-1, x+w-1, y+h-1);
		XDrawLine (dpy, wn, blackgc, x+w-1, y+h-1, x+w-1, y);
		return;
	}

	switch (state)
	{
	case BUTTON_UP:
		XDrawLine (dpy, wn, hilite, x, y, x+w-2, y);
		XDrawLine (dpy, wn, hilite, x, y, x, y+h-2);
		if (ThreeDfvwm)
		{
			XDrawLine (dpy, wn, hilite, x, y+1, x+w-2, y+1);
			XDrawLine (dpy, wn, hilite, x+1, y, x+1, y+h-2);
		}
		XDrawLine (dpy, wn, shadow,  x+1, y+h-2, x+w-2, y+h-2);
		XDrawLine (dpy, wn, shadow,  x+w-2, y+h-2, x+w-2, y+1);
		XDrawLine (dpy, wn, blackgc, x, y+h-1, x+w-1, y+h-1);
		XDrawLine (dpy, wn, blackgc, x+w-1, y+h-1, x+w-1, y);
		break;
	case BUTTON_BRIGHT:
		draw = frect_get_intersection(
				x + 2, y + 2, w - 4 + (2 - d), h - 4 + (2 - d),
				r.x, r.y, r.width, r.height, &inter);
		if (focuscolorset >= 0)
			cset = &Colorset[focuscolorset];
		if (focuscolorset >= 0 && (cset->pixmap || cset->shape_mask) &&
		    draw)
		{
			/* we have a colorset background */
			SetClippedRectangleBackground(
				dpy, win, x + 2, y + 2, w - 4 + (2 - d),
				h - 4 + (2 - d), &inter, cset, Pdepth,
				focusgraph);
		}
		else if (draw)
		{
			XFillRectangle (
				dpy, wn, focusbackgraph, inter.x, inter.y,
				inter.width, inter.height);
		}
		if (!NoBrightFocus)
			XFillRectangle (
				dpy, wn, checkered, inter.x, inter.y,
				inter.width, inter.height);
		XDrawLine (dpy, wn, blackgc, x, y, x+w-2, y);
		XDrawLine (dpy, wn, blackgc, x, y, x, y+h-2);

		XDrawLine (dpy, wn, focusshadow, x, y+1, x+w-2, y+1);
		XDrawLine (dpy, wn, focusshadow, x+1, y, x+1, y+h-2);
		if (ThreeDfvwm)
		{
			XDrawLine (
				dpy, wn, focushilite,  x+1, y+h-2, x+w-2, y+h-2);
			XDrawLine (
				dpy, wn, focushilite,  x+w-2, y+h-2, x+w-2, y+1);
		}
		XDrawLine (dpy, wn, focushilite, x, y+h-1, x+w-1, y+h-1);
		XDrawLine (dpy, wn, focushilite, x+w-1, y+h-1, x+w-1, y);
		break;
	case BUTTON_DOWN:
		XDrawLine (dpy, wn, blackgc, x, y, x+w-2, y);
		XDrawLine (dpy, wn, blackgc, x, y, x, y+h-2);

		XDrawLine (dpy, wn, shadow, x, y+1, x+w-2, y+1);
		XDrawLine (dpy, wn, shadow, x+1, y, x+1, y+h-2);
		if (ThreeDfvwm)
		{
			XDrawLine (dpy, wn, hilite,  x+1, y+h-2, x+w-2, y+h-2);
			XDrawLine (dpy, wn, hilite,  x+w-2, y+h-2, x+w-2, y+1);
		}
		XDrawLine (dpy, wn, hilite, x, y+h-1, x+w-1, y+h-1);
		XDrawLine (dpy, wn, hilite, x+w-1, y+h-1, x+w-1, y);
		break;
	}
}


/* -------------------------------------------------------------------------
   ButtonNew - Allocates and fills a new button structure
   ------------------------------------------------------------------------- */
Button *ButtonNew(const char *title, FvwmPicture *p, int state, int count)
{
  Button *new;

  if (title == NULL)
    return NULL;
  new = (Button *)safemalloc(sizeof(Button));
  new->title = safemalloc(strlen(title)+1);
  strcpy(new->title, title);
  if (p != NULL) {
    new->p.picture = p->picture;
    new->p.mask    = p->mask;
    new->p.alpha   = p->alpha;
    new->p.width   = p->width;
    new->p.height  = p->height;
    new->p.depth   = p->depth;
  } else {
    new->p.picture = 0;
  }

  new->state = state;
  new->count = count;
  new->next  = NULL;
  new->needsupdate = 1;
  new->iconified = 0;

  return new;
}

/* -------------------------------------------------------------------------
   ButtonDraw - Draws the specified button
   ------------------------------------------------------------------------- */
void ButtonDraw(Button *button, int x, int y, int w, int h, XEvent *evp)
{
	char *t3p = "...\0";
	int state, x3p, newx;
	int search_len;
	FlocaleFont *Ffont;
	XGCValues gcv;
	unsigned long gcm;
	GC *drawgc;
	int cs;
	XRectangle rect,inter;
	Region t_region;

	if (w <= 0)
	{
		w = 1;
	}
	if (button == NULL)
	{
		return;
	}
	if (evp)
	{
		if (!frect_get_intersection(
			x, y, w, h,
			evp->xexpose.x, evp->xexpose.y,
			evp->xexpose.width, evp->xexpose.height,
			&rect))
		{
			return;
		}
	}
	else
	{
		rect.x = x;
		rect.y = y;
		rect.width = w;
		rect.height = h;
	}
	button->needsupdate = 0;
	state = button->state;
	Draw3dRect(
		win, x, y, w, h, &rect, state, !!(button->iconified));
	if (button->iconified)
	{
		drawgc = &icongraph;
		cs = iconcolorset;
	}
	else if (state == BUTTON_BRIGHT)
	{
		drawgc = &focusgraph;
		cs = focuscolorset;
	}
	else
	{
		drawgc = &graph;
		cs = colorset;
	}

	if (state != BUTTON_UP) { x++; y++; }

	if (state == BUTTON_BRIGHT || button == StartButton)
	{
		Ffont = FSelButtonFont;
	}
	else
	{
		Ffont = FButtonFont;
	}

	if (Ffont->font != NULL)
	{
		gcm = GCFont;
		gcv.font = Ffont->font->fid;
		XChangeGC(dpy, *drawgc, gcm, &gcv);
	}

	newx = 4;
	w3p = FlocaleTextWidth(Ffont, t3p, 3);

	if (button->p.picture != 0)
	{
		/* clip pixmap to fit inside button */
		int pheight = min(button->p.height, h-2);
		int offset = (button->p.height > h) ?
			0 : ((h - button->p.height) >> 1);
		int pwidth = min(button->p.width, w-5);
		FvwmRenderAttributes fra;

		fra.mask = FRAM_DEST_IS_A_WINDOW;
		if (cs >= 0)
		{
			fra.mask |= FRAM_HAVE_ICON_CSET;
			fra.colorset = &Colorset[cs];
		}
		if (frect_get_intersection(
			rect.x, rect.y, rect.width, rect.height,
			x+3, y+offset, pwidth, pheight, &inter))
		{
			PGraphicsRenderPicture(
				dpy, win, &(button->p), &fra, win,
				*drawgc, None, None,
				(inter.x > (x+3))? inter.x - (x+3):0,
				(inter.y > (y+offset))? inter.y - (y+offset):0,
				inter.width, inter.height,
				inter.x /*x+3*/, inter.y /*y+offset*/,
				inter.width, inter.height, False);
		}
		newx += pwidth+2;
	}

	if (button->title == NULL)
	{
		return;
	}

	t_region = XCreateRegion();
	XUnionRectWithRegion (&rect, t_region, t_region);
	/* see if we have the place for "...", if not for "..", if not for "."
	 * if not return */
	if ((newx + w3p + 2) >= w)
	{
		t3p = "..\0";
		w3p = FlocaleTextWidth(Ffont, t3p, 2);
		if ((newx + w3p + 2) >= w)
		{
			t3p = ".\0";
			w3p = FlocaleTextWidth(Ffont, t3p, 1);
			if ((newx + w3p + 2) >= w)
			{
				XDestroyRegion(t_region);
				return;
			}
		}
	}

	search_len = strlen(button->title);
	FwinString->win = win;
	FwinString->y = y + Ffont->ascent + (RowHeight - Ffont->height)/2;
	FwinString->gc = *drawgc;
	FwinString->flags.has_colorset = False;
	FwinString->flags.has_clip_region = True;
	FwinString->clip_region = t_region;
	if (cs >= 0)
	{
		FwinString->colorset = &Colorset[cs];
		FwinString->flags.has_colorset = True;
	}
	button->truncate = False;

	if (FlocaleTextWidth(Ffont, button->title, search_len) > w-newx-3)
	{
		x3p = 0;
		while (search_len >= 0
		       && ((x3p = newx + FlocaleTextWidth(
					      Ffont, button->title, search_len))
			   > w-w3p-3))
		{
			search_len--;
		}
		FwinString->str = t3p;
		FwinString->x = x + x3p;
		FlocaleDrawString(dpy, Ffont, FwinString, 0);
		button->truncate = True;
	}

	/* Only print as much of the title as will fit.  */
	if (search_len)
	{
		FwinString->str = button->title;
		FwinString->x = x + newx;
		FwinString->len = search_len;
		FlocaleDrawString(dpy, Ffont, FwinString, FWS_HAVE_LENGTH);
	}
	FwinString->flags.has_clip_region = False;
	XDestroyRegion(t_region);
}


/* -------------------------------------------------------------------------
   ButtonUpdate - Change the name/state of a button
   ------------------------------------------------------------------------- */
int ButtonUpdate(Button *button, const char *title, int state)
{
  if (button == NULL) return -1;

  if ((title != NULL) && (button->title != title)) {
    button->title = (char *)saferealloc(button->title,strlen(title)+1);
    strcpy(button->title,title);
    button->needsupdate = 1;
  }

  if (state != DONT_CARE) {
    if (state != button->state) {
      button->state = state;
      button->needsupdate = 1;
    }
  }

  return 1;
}

/* -------------------------------------------------------------------------
   ButtonDelete - Free space allocated to a button
   ------------------------------------------------------------------------- */
void ButtonDelete(Button *ptr)
{
  if (ptr != NULL) {
    if (ptr->title != NULL) free(ptr->title);
    free(ptr);
  }
}

/* -------------------------------------------------------------------------
   ButtonName - Return the name of the button
   ------------------------------------------------------------------------- */
char *ButtonName(Button *button)
{
  if (button == NULL) return NULL;
  else return button->title;
}


/*
 *  ButtonArray handling functions and procedures
 */

/* -------------------------------------------------------------------------
   InitArray - Initialize the arrary of buttons
   ------------------------------------------------------------------------- */
void InitArray(ButtonArray *array, int x, int y, int w, int h, int tw)
{
   array->count = 0;
   array->head = array->tail = NULL;
   array->x  = x;
   array->y  = y;
   array->w  = w;
   array->h  = h;
   array->tw = tw;
}

/* -------------------------------------------------------------------------
   UpdateArray - Update the array specifics.  x,y, width, height
   ------------------------------------------------------------------------- */
void UpdateArray(ButtonArray *array,int x,int y,int w, int h, int tw)
{
   Button *temp;

   if (x != -1)
     array->x = x;
   if (y != -1)
     array->y = y;
   if (w != -1)
     array->w = w;
   if (h != -1)
     array->h = h;
   if (tw != -1)
     array->tw = tw;
   for(temp=array->head; temp!=NULL; temp=temp->next)
     temp->needsupdate = 1;
}

/* -------------------------------------------------------------------------
   AddButton - Allocate space for and add the button to the list
   ------------------------------------------------------------------------- */
void AddButton(ButtonArray *array, const char *title, FvwmPicture *p, int state,
	       int count, int iconified)
{
  Button *new, *temp;

  new = ButtonNew(title, p, state, count);
  if (new == NULL)
    return;
  if (iconified)
    new->iconified = 1;
  if (array->head == NULL)
    array->head = new;
  else
  {
    for (temp = array->head; temp->next; temp = temp->next)
      ;
    temp->next = new;
  }
  array->count++;

  ArrangeButtonArray(array);
}

/* -------------------------------------------------------------------------
   ArrangeButtonArray - Rearrange the button size (called from AddButton,
			  RemoveButton, AdjustWindow)
   ------------------------------------------------------------------------- */

void ArrangeButtonArray (ButtonArray *array)
{
  int tw;
  Button *temp;

  if (!array->count)
    tw = array->w;
  else if (NRows == 1)
    tw = array->w / array->count;
  else
    tw = array->w / ((array->count / NRows)+1);

  if (tw > button_width)
    tw = button_width;
  if (tw < MIN_BUTTON_SIZE)
    tw = MIN_BUTTON_SIZE;

  if (tw != array->tw) /* update needed */
  {
    array->tw = tw;
    for(temp = array->head; temp != NULL; temp = temp->next)
      temp->needsupdate = 1;
  }
}

/* -------------------------------------------------------------------------
   UpdateButton - Change the name/state of a button
   ------------------------------------------------------------------------- */
int UpdateButton(ButtonArray *array, int butnum, const char *title, int state)
{
  Button *temp;

  for (temp = array->head; temp; temp = temp->next)
    if (temp->count == butnum)
      break;

  return ButtonUpdate(temp, title, state);
}

/* -------------------------------------------------------------------------
   UpdateButtonPicture - Change the picture of a button
   ------------------------------------------------------------------------- */
int UpdateButtonPicture(ButtonArray *array, int butnum, FvwmPicture *p)
{
  Button *temp;

  for (temp=array->head; temp; temp=temp->next)
    if (temp->count == butnum)
      break;

  if (temp == NULL) return -1;
  if (temp->p.picture != p->picture || temp->p.mask != p->mask) {
    temp->p.picture = p->picture;
    temp->p.mask    = p->mask;
    temp->p.alpha   = p->alpha;
    temp->p.width   = p->width;
    temp->p.height  = p->height;
    temp->p.depth   = p->depth;
    temp->needsupdate = 1;
  }
  return 1;
}

/* -------------------------------------------------------------------------
   RemoveButton - Delete a button from the list
   ------------------------------------------------------------------------- */
void RemoveButton(ButtonArray *array, int butnum)
{
  Button *temp, *to_die;

  if (array->head == NULL)
    return;

  if (array->head->count == butnum)
  {
    to_die = array->head;
    array->head = array->head->next;
  }
  else
  {
    for (temp = array->head, to_die = array->head->next; to_die != NULL;
	 to_die = to_die->next, temp = temp->next)
    {
      if (to_die->count == butnum)
	break;
    }
    if (!to_die)
      return;
    temp->next = to_die->next;
  }

  ButtonDelete(to_die);
  if (array->count > 0)
    array->count--;
  for (temp = array->head; temp; temp = temp->next)
    temp->needsupdate = 1;

  ArrangeButtonArray(array);
}

/* -------------------------------------------------------------------------
   find_n - Find the nth button in the list (Use internally)
   ------------------------------------------------------------------------- */
Button *find_n(ButtonArray *array, int n)
{
  Button *temp;

  temp = array->head;
  if (n < 0)
    return NULL;
  for ( ; temp && n != temp->count; temp=temp->next)
    ;
  return temp;
}

/* -------------------------------------------------------------------------
   FreeAllButtons - Free the whole array of buttons
   ------------------------------------------------------------------------- */
void FreeAllButtons(ButtonArray *array)
{
  Button *temp, *temp2;

  for(temp=array->head; temp!=NULL; ) {
    temp2 = temp;
    temp  = temp->next;
    ButtonDelete(temp2);
  }
  array->count = 0;
  array->head = NULL;
}

/* ------------------------------------------------------------------------
   DrawButtonArray - Draw the whole array (all=1), or only those that need.
   ------------------------------------------------------------------------ */
void DrawButtonArray(ButtonArray *array, int all, XEvent *evp)
{
	Button *temp;
	int x, y, n;
	static Bool exposed = False;

	if (!exposed && evp)
	{
		exposed = True;
	}
	if (!exposed)
	{
		return;
	}
	x = 0;
	y = array->y;
	n = 1;
	for(temp=array->head; temp!=NULL; temp=temp->next)
	{
		if ((x + array->tw > array->w) && (n < NRows))
		{
			x = 0;
			y += RowHeight+2;
			++n;
		}
		if (temp->needsupdate || all)
			ButtonDraw(
				temp, x + array->x, y, array->tw-pad, array->h,
				(temp->needsupdate)? NULL:evp);
		x += array->tw;
	}
}

/* -------------------------------------------------------------------------
   RadioButton - Enable button i and verify all others are disabled
   ------------------------------------------------------------------------- */
void RadioButton(ButtonArray *array, int butnum, int state)
{
  Button *button;

  for(button=array->head; button!=NULL; button=button->next) {
    if (button->count == butnum) {
      button->state = state;
      button->needsupdate = 1;
    } else {
      if (button->state != BUTTON_UP) {
	button->state = BUTTON_UP;
	button->needsupdate = 1;
      }
    }
  }
}
/* -------------------------------------------------------------------------

   ------------------------------------------------------------------------- */
Bool CheckRootTransparentButtons(ButtonArray *array)
{
	Button *button;
	Bool r = 0;

	if (!CSET_IS_TRANSPARENT_ROOT(iconcolorset) &&
	    !CSET_IS_TRANSPARENT_ROOT(focuscolorset))
	{
		return r;
	}
	for(button=array->head; button!=NULL; button=button->next)
	{
		if ((button->iconified &&
		     CSET_IS_TRANSPARENT_ROOT(iconcolorset)) ||
		    (button->state == BUTTON_BRIGHT &&
		     CSET_IS_TRANSPARENT_ROOT(focuscolorset)))
		{
			r = True;
			button->needsupdate = 1;
		}
	}
	return r;
}

/* -------------------------------------------------------------------------
   WhichButton - Based on x,y which button was pressed
   ------------------------------------------------------------------------- */
int WhichButton(ButtonArray *array, int xp, int yp)
{
  int   junkx, junky, junkz;
  char *junkt;

  return LocateButton(array, xp, yp, &junkx, &junky, &junkt, &junkz);
}

int LocateButton(ButtonArray *array, int xp,  int yp, int *xb, int *yb,
		 char **name, int *trunc)
{
  Button *temp;
  int num, x, y, n;

  if (xp < array->x || xp > array->x+array->w) return -1;

  x = 0;
  y = array->y;
  n = 1;
  for(temp=array->head, num=0; temp!=NULL; temp=temp->next, num++) {
    if((x + array->tw > array->w) && (n < NRows))
      { x = 0; y += RowHeight+2; ++n; }
    if( xp >= x+array->x && xp <= x+array->x+array->tw-3 &&
	yp >= y && yp <= y+array->h) break;
    x += array->tw;
  }

  if (num<0 || num>array->count-1) num = -1;

  *xb = x+array->x;
  *yb = y;
  if (temp) {
    *name  = temp->title;
    *trunc = temp->truncate;
  } else {
    *name = NULL;
  }

  return (num == -1) ? num : temp->count;
}

/* -------------------------------------------------------------------------
    ButtonCoordinates - Compute the coordinates of a button (animation)
   ------------------------------------------------------------------------- */
extern int  StartAndLaunchButtonsWidth;
void ButtonCoordinates(ButtonArray *array, int numbut, int *xc, int *yc)
{
	Button *temp;
	int x = 0;
	int y = 0;
	int r = 0;

	for(temp=array->head; temp->count != numbut; temp=temp->next)
	{
		if((x + 2*array->tw > array->w) && (r < NRows))
		{
			x = 0;
			y += RowHeight+2;
			++r;
		}
		else
		{
			x += array->tw;
		}
	}

	*xc = x+ StartAndLaunchButtonsWidth+3;
	*yc = y;
}

void ButtonDimensions(ButtonArray *array, int *width, int *height)
{
	*width = array->tw;
	*height = RowHeight+2;
}