/* 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 "libs/fvwmlib.h"
#include "fvwm.h"
#include "externs.h"
#include "cursor.h"
#include "functions.h"
#include "bindings.h"
#include "misc.h"
#include "screen.h"
#include "cursor.h"
#include "menus.h"

static Cursor cursors[CRS_MAX];
static const unsigned int default_cursors[CRS_MAX] =
{
  None,
  XC_top_left_corner,      /* CRS_POSITION */
  XC_top_left_arrow,       /* CRS_TITLE */
  XC_top_left_arrow,       /* CRS_DEFAULT */
  XC_hand2,                /* CRS_SYS */
  XC_fleur,                /* CRS_MOVE */
  XC_sizing,               /* CRS_RESIZE */
  XC_watch,                /* CRS_WAIT */
  XC_top_left_arrow,       /* CRS_MENU */
  XC_crosshair,            /* CRS_SELECT */
  XC_pirate,               /* CRS_DESTROY */
  XC_top_side,             /* CRS_TOP */
  XC_right_side,           /* CRS_RIGHT */
  XC_bottom_side,          /* CRS_BOTTOM */
  XC_left_side,            /* CRS_LEFT */
  XC_top_left_corner,      /* CRS_TOP_LEFT */
  XC_top_right_corner,     /* CRS_TOP_RIGHT */
  XC_bottom_left_corner,   /* CRS_BOTTOM_LEFT */
  XC_bottom_right_corner,  /* CRS_BOTTOM_RIGHT */
  XC_top_side,             /* CRS_TOP_EDGE */
  XC_right_side,           /* CRS_RIGHT_EDGE */
  XC_bottom_side,          /* CRS_BOTTOM_EDGE */
  XC_left_side,            /* CRS_LEFT_EDGE */
  XC_left_ptr,             /* CRS_ROOT */
  XC_plus                  /* CRS_STROKE */
};

/***********************************************************************
 *
 *  Procedure:
 *	CreateCursors - Loads fvwm cursors
 *
 ***********************************************************************/
Cursor *CreateCursors(Display *dpy)
{
  int i;
  /* define cursors */
  cursors[0] = None;
  for (i = 1; i < CRS_MAX; i++)
  {
    cursors[i] = XCreateFontCursor(dpy, default_cursors[i]);
  }
  return cursors;
}


static void SafeDefineCursor(Window w, Cursor cursor)
{
  if (w)
    XDefineCursor(dpy,w,cursor);
}

void CMD_CursorStyle(F_CMD_ARGS)
{
  char *cname=NULL, *newcursor=NULL;
  char *errpos = NULL;
#ifdef XPM
  char *path = NULL;
#endif
  char *fore = NULL, *back = NULL;
  XColor colors[2];
  int index,nc,i,my_nc;
  FvwmWindow *fw;

  action = GetNextToken(action,&cname);
  action = GetNextToken(action,&newcursor);

  if (!cname)
  {
    fvwm_msg(ERR,"CursorStyle","Bad cursor style");
    if (cname)
      free(cname);
    return;
  }
  if (StrEquals("POSITION",cname)) index = CRS_POSITION;
  else if (StrEquals("DEFAULT",cname)) index = CRS_DEFAULT;
  else if (StrEquals("SYS",cname)) index = CRS_SYS;
  else if (StrEquals("TITLE",cname)) index = CRS_TITLE;
  else if (StrEquals("MOVE",cname)) index = CRS_MOVE;
  else if (StrEquals("RESIZE",cname)) index = CRS_RESIZE;
  else if (StrEquals("MENU",cname)) index = CRS_MENU;
  else if (StrEquals("WAIT",cname)) index = CRS_WAIT;
  else if (StrEquals("SELECT",cname)) index = CRS_SELECT;
  else if (StrEquals("DESTROY",cname)) index = CRS_DESTROY;
  else if (StrEquals("LEFT",cname)) index = CRS_LEFT;
  else if (StrEquals("RIGHT",cname)) index = CRS_RIGHT;
  else if (StrEquals("TOP",cname)) index = CRS_TOP;
  else if (StrEquals("BOTTOM",cname)) index = CRS_BOTTOM;
  else if (StrEquals("TOP_LEFT",cname)) index = CRS_TOP_LEFT;
  else if (StrEquals("TOP_RIGHT",cname)) index = CRS_TOP_RIGHT;
  else if (StrEquals("BOTTOM_LEFT",cname)) index = CRS_BOTTOM_LEFT;
  else if (StrEquals("BOTTOM_RIGHT",cname)) index = CRS_BOTTOM_RIGHT;
  else if (StrEquals("LEFT_EDGE",cname)) index = CRS_LEFT_EDGE;
  else if (StrEquals("RIGHT_EDGE",cname)) index = CRS_RIGHT_EDGE;
  else if (StrEquals("TOP_EDGE",cname)) index = CRS_TOP_EDGE;
  else if (StrEquals("BOTTOM_EDGE",cname)) index = CRS_BOTTOM_EDGE;
  else if (StrEquals("ROOT",cname)) index = CRS_ROOT;
  else if (StrEquals("STROKE",cname)) index = CRS_STROKE;
  else
  {
    fvwm_msg(ERR,"CursorStyle","Unknown cursor name %s",cname);
    free(cname);
    free(newcursor);
    return;
  }
  free(cname);

  /* check if the cursor is given by X11 name */
  if (newcursor)
    my_nc = fvwmCursorNameToIndex(newcursor);
  else
    my_nc = default_cursors[index];

  if (my_nc == -1)
  {
    nc = strtol(newcursor, &errpos, 10);
    if (errpos && *errpos == '\0')
      my_nc = 0;
  }
  else
    nc = my_nc;

  if (my_nc > -1)
    {
      /* newcursor was a number or the name of a X11 cursor */
      if ((nc < 0) || (nc >= XC_num_glyphs) || ((nc % 2) != 0))
	{
	  fvwm_msg(ERR, "CursorStyle", "Bad cursor number %s", newcursor);
	  free(newcursor);
	  return;
	}
      free(newcursor);

      /* replace the cursor defn */
      if (Scr.FvwmCursors[index])
	XFreeCursor(dpy,Scr.FvwmCursors[index]);
      Scr.FvwmCursors[index] = XCreateFontCursor(dpy,nc);
    }
  else
    {
      /* newcursor was not a number neither a X11 cursor name */
      if (StrEquals("none", newcursor) || StrEquals("tiny", newcursor))
      {
	static Pixmap nocursor = None;
	XColor nccol;

	if (nocursor == None)
	{
	  if (Scr.FvwmCursors[index])
	    XFreeCursor (dpy, Scr.FvwmCursors[index]);
	  XSetForeground(dpy, Scr.MonoGC, (tolower(*newcursor) == 'n') ? 0 : 1);
	  XFillRectangle(dpy, Scr.ScratchMonoPixmap, Scr.MonoGC, 0, 0, 1, 1);
	  Scr.FvwmCursors[index] =
	    XCreatePixmapCursor(
	      dpy, Scr.ScratchMonoPixmap, Scr.ScratchMonoPixmap, &nccol, &nccol,
	      0, 0);
	  free (newcursor);
	}
      }
      else
      {
#ifdef XPM
	XpmAttributes xpm_attributes;
	Pixmap source, mask;
	unsigned int x;
	unsigned int y;

	path = findImageFile (newcursor, NULL, R_OK);
	if (!path)
	{
	  fvwm_msg (ERR, "CursorStyle", "Cursor %s not found", newcursor);
	  free (newcursor);
	  return;
	}

	xpm_attributes.depth = 1; /* we need source to be a bitmap */
	xpm_attributes.valuemask = XpmSize | XpmDepth | XpmHotspot;
	if (XpmReadFileToPixmap (dpy, Scr.Root, path, &source, &mask,
				 &xpm_attributes) != XpmSuccess)
	{
	  fvwm_msg (ERR, "CursorStyle", "Error reading cursor xpm %s",
		    newcursor);
	  free (newcursor);
	  return;
	}

	colors[0].pixel = GetColor(DEFAULT_CURSOR_FORE_COLOR);
	colors[1].pixel = GetColor(DEFAULT_CURSOR_BACK_COLOR);
	XQueryColors (dpy, Pcmap, colors, 2);

	if (Scr.FvwmCursors[index])
	  XFreeCursor (dpy, Scr.FvwmCursors[index]);
	x = xpm_attributes.x_hotspot;
	if (x >= xpm_attributes.width)
	  x = xpm_attributes.width / 2;
	y = xpm_attributes.y_hotspot;
	if (y >= xpm_attributes.height)
	  y = xpm_attributes.height / 2;
	Scr.FvwmCursors[index] =
	  XCreatePixmapCursor(
	    dpy, source, mask, &(colors[0]), &(colors[1]), x, y);

	free (newcursor);
	free (path);
#else /* ! XPM */
	fvwm_msg(ERR, "CursorStyle", "Bad cursor name or number %s", newcursor);
	free (newcursor);
	return;
#endif
      }
    }

  /* look for optional color arguments */
  action = GetNextToken(action, &fore);
  action = GetNextToken(action, &back);
  if (fore && back)
  {
    colors[0].pixel = GetColor(fore);
    colors[1].pixel = GetColor(back);
    XQueryColors (dpy, Pcmap, colors, 2);
    XRecolorCursor (dpy, Scr.FvwmCursors[index], &(colors[0]), &(colors[1]));
  }
  if (fore)
    free(fore);
  if (back)
    free(back);

  /* redefine all the windows using cursors */
  fw = Scr.FvwmRoot.next;
  while(fw)
  {
    for (i=0;i<4;i++)
    {
      SafeDefineCursor(fw->corners[i],Scr.FvwmCursors[CRS_TOP_LEFT+i]);
      SafeDefineCursor(fw->sides[i],Scr.FvwmCursors[CRS_TOP+i]);
    }
    for (i = 0 ; i / 2 < Scr.nr_left_buttons; i += 2)
    {
      SafeDefineCursor(fw->button_w[i],Scr.FvwmCursors[CRS_SYS]);
    }
    for (i = 1 ; i / 2 < Scr.nr_right_buttons; i += 2)
    {
      SafeDefineCursor(fw->button_w[i],
		       Scr.FvwmCursors[CRS_SYS]);
    }
    SafeDefineCursor(fw->title_w, Scr.FvwmCursors[CRS_TITLE]);
    if (index == CRS_DEFAULT)
    {
      SafeDefineCursor(fw->frame, Scr.FvwmCursors[CRS_DEFAULT]);
      SafeDefineCursor(fw->Parent, Scr.FvwmCursors[CRS_DEFAULT]);
      SafeDefineCursor(fw->decor_w, Scr.FvwmCursors[CRS_DEFAULT]);
      if (IS_ICONIFIED(fw))
      {
	if (!HAS_NO_ICON_TITLE(fw))
	  SafeDefineCursor(fw->icon_w, Scr.FvwmCursors[CRS_DEFAULT]);
	if (fw->icon_pixmap_w != None)
	  SafeDefineCursor(fw->icon_pixmap_w, Scr.FvwmCursors[CRS_DEFAULT]);
      }
    }
    fw = fw->next;
  }

  /* Do the menus for good measure */
  SetMenuCursor(Scr.FvwmCursors[CRS_MENU]);

  SafeDefineCursor(Scr.PanFrameTop.win, Scr.FvwmCursors[CRS_TOP_EDGE]);
  SafeDefineCursor(Scr.PanFrameBottom.win, Scr.FvwmCursors[CRS_BOTTOM_EDGE]);
  SafeDefineCursor(Scr.PanFrameLeft.win, Scr.FvwmCursors[CRS_LEFT_EDGE]);
  SafeDefineCursor(Scr.PanFrameRight.win, Scr.FvwmCursors[CRS_RIGHT_EDGE]);
  /* migo (04/Nov/1999): don't annoy users which use xsetroot */
  if (index == CRS_ROOT)
    SafeDefineCursor(Scr.Root, Scr.FvwmCursors[CRS_ROOT]);
}

/***********************************************************************
 *
 *  builtin function: (set)BusyCursor
 *  Defines in which cases fvwm "grab" the cursor during execution of
 *  certain functions.
 *
 ***********************************************************************/
void CMD_BusyCursor(F_CMD_ARGS)
{
  char *option = NULL;
  char *optstring = NULL;
  char *args = NULL;
  int flag = -1;
  char *optlist[] = {
    "read", "wait", "modulesynchronous", "dynamicmenu", "*", NULL
  };

  while (action  && *action != '\0')
  {
    action = GetQuotedString(action, &optstring, ",", NULL, NULL, NULL);
    if (!optstring)
      break;

    args = GetNextToken(optstring, &option);
    if (!option)
    {
      free(optstring);
      break;
    }

    flag = ParseToggleArgument(args, NULL, -1, True);
    free(optstring);
    if (flag == -1)
    {
      fvwm_msg(ERR, "BusyCursor", "error in boolean specification");
      free(option);
      break;
    }

    switch (GetTokenIndex(option, optlist, 0, NULL))
    {
    case 0: /* read */
      if (flag)
        Scr.BusyCursor |= BUSY_READ;
      else
        Scr.BusyCursor &= ~BUSY_READ;
      break;

    case 1: /* wait */
      if (flag)
        Scr.BusyCursor |= BUSY_WAIT;
      else
        Scr.BusyCursor &= ~BUSY_WAIT;
      break;

    case 2: /* modulesynchronous */
      if (flag)
        Scr.BusyCursor |= BUSY_MODULESYNCHRONOUS;
      else
        Scr.BusyCursor &= ~BUSY_MODULESYNCHRONOUS;
      break;

    case 3: /* dynamicmenu */
      if (flag)
        Scr.BusyCursor |= BUSY_DYNAMICMENU;
      else
        Scr.BusyCursor &= ~BUSY_DYNAMICMENU;
      break;

    case 4: /* "*" */
      if (flag)
        Scr.BusyCursor |= BUSY_ALL;
      else
        Scr.BusyCursor &= ~(BUSY_ALL);
      break;

    default:
      fvwm_msg(ERR, "BusyCursor", "unknown context '%s'", option);
      break;
    }
    free(option);
  }
}


syntax highlighted by Code2HTML, v. 0.9.1